Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.2.0] - 2026-07-19

### Added

- `ccal version` (and `--version` / `-v`) prints the installed version.
- `ccal update` checks npm for a newer release and self-updates via
`npm install -g` when one is available.

## [1.1.0] - 2026-07-19

### Added
Expand Down Expand Up @@ -35,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
and common install locations.
- Permissive CORS for browser and webview clients.

[Unreleased]: https://github.com/rousan/ccal/compare/v1.1.0...HEAD
[Unreleased]: https://github.com/rousan/ccal/compare/v1.2.0...HEAD
[1.2.0]: https://github.com/rousan/ccal/releases/tag/v1.2.0
[1.1.0]: https://github.com/rousan/ccal/releases/tag/v1.1.0
[1.0.0]: https://github.com/rousan/ccal/releases/tag/v1.0.0
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ provider with base URL `http://127.0.0.1:8787/v1`, any API key, and pick one of
one response out — claude's streaming JSON output is translated back into
OpenAI chunks (assistant text plus compact one-line notes for any tool calls).

## Commands

```
ccal serve [options] Start the OpenAI-compatible server
ccal update Update ccal to the latest published version
ccal version Print the installed version (also: --version, -v)
```

## Configuration

```
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rousan/ccal",
"version": "1.1.0",
"version": "1.2.0",
"description": "Claude Code Adapter for LLMs — an OpenAI-compatible HTTP server that proxies to the local `claude` CLI.",
"type": "module",
"license": "MIT",
Expand Down
77 changes: 72 additions & 5 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,63 @@
// We keep argument parsing deliberately small and dependency-free — the surface
// is tiny, so a hand-rolled parser is clearer than pulling in a CLI framework.

import { spawnSync } from "node:child_process";
import { createRequire } from "node:module";

import { serve } from "@hono/node-server";

import { createServer } from "./server.js";
import { checkClaude } from "./claude-binary.js";

// The installed version, read from the package manifest (one dir up from dist/).
const VERSION = (
createRequire(import.meta.url)("../package.json") as { version: string }
).version;

/** GitHub release notes URL for a version, shown after a successful update. */
const releaseUrl = (v: string) =>
`https://github.com/rousan/ccal/releases/tag/v${v}`;

/** Latest published version on npm, or null if the registry is unreachable. */
async function latestVersion(): Promise<string | null> {
try {
const res = await fetch("https://registry.npmjs.org/@rousan%2fccal/latest", {
headers: { accept: "application/json" },
});
if (!res.ok) return null;
const body = (await res.json()) as { version?: string };
return body.version ?? null;
} catch {
return null;
}
}

/** Compare npm's latest against the running version and self-update if newer. */
async function runUpdate(): Promise<void> {
console.log(`Installed: ccal ${VERSION}`);
const latest = await latestVersion();
if (!latest) {
fail("Couldn't reach the npm registry to check for updates.");
}
if (latest === VERSION) {
console.log("ccal is already up to date.");
return;
}
console.log(`Updating ccal ${VERSION} -> ${latest}...`);
const result = spawnSync("npm", ["install", "-g", `@rousan/ccal@${latest}`], {
stdio: "inherit",
});
if (result.status === 0) {
console.log(`Updated to ccal ${latest}.`);
console.log(`Release notes: ${releaseUrl(latest)}`);
} else {
fail(
"Automatic update failed. Update manually with:\n" +
" npm install -g @rousan/ccal@latest",
);
}
}

// Parsed options for the `serve` subcommand.
interface ServeOptions {
port: number;
Expand Down Expand Up @@ -95,9 +147,11 @@ function printHelp(): void {
"An OpenAI-compatible HTTP server that proxies to the local `claude` CLI.",
"",
"Usage:",
" ccal serve [options]",
" ccal serve [options] Start the OpenAI-compatible server",
" ccal update Update ccal to the latest published version",
" ccal version Print the installed version",
"",
"Options:",
"Serve options:",
" --port <n> Port to bind (default: 8787)",
" --host <addr> Host to bind (default: 127.0.0.1)",
" --cwd <dir> Working directory for the claude process",
Expand All @@ -110,7 +164,7 @@ function printHelp(): void {
);
}

function main(): void {
async function main(): Promise<void> {
const argv = process.argv.slice(2);

if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
Expand All @@ -119,8 +173,21 @@ function main(): void {
}

const command = argv[0];

if (command === "version" || command === "--version" || command === "-v") {
console.log(`ccal ${VERSION}`);
return;
}

if (command === "update") {
await runUpdate();
return;
}

if (command !== "serve") {
fail(`Unknown command: ${command}. Try 'ccal serve'.`);
fail(
`Unknown command: ${command}. Try 'ccal serve', 'ccal update', or 'ccal version'.`,
);
}

// Allow `ccal serve --help` to print usage rather than start the server.
Expand Down Expand Up @@ -156,4 +223,4 @@ function main(): void {
});
}

main();
main().catch((err) => fail(err instanceof Error ? err.message : String(err)));
Loading