Skip to content
Open
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
28 changes: 21 additions & 7 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,14 @@ Commands:
-v, version Show version information
-h, help Show help information

Example:
Options:
--model Override the default model (for 'code' command)
Format: provider,model (e.g., openrouter,anthropic/claude-opus-4)

Examples:
ccr start
ccr code "Write a Hello World"
ccr code --model openrouter,anthropic/claude-sonnet-4 "Explain this code"
ccr ui
`;

Expand Down Expand Up @@ -84,6 +89,19 @@ async function main() {
await showStatus();
break;
case "code":
// Parse --model option from command line arguments
let modelOverride: string | undefined;
const codeArgs: string[] = [];

for (let i = 3; i < process.argv.length; i++) {
if (process.argv[i] === "--model" && process.argv[i + 1]) {
modelOverride = process.argv[i + 1];
i++; // Skip the next argument as it's the model value
} else {
codeArgs.push(process.argv[i]);
}
}

if (!isServiceRunning()) {
console.log("Service not running, starting service...");
const cliPath = join(__dirname, "cli.js");
Expand Down Expand Up @@ -112,19 +130,15 @@ async function main() {
startProcess.unref();

if (await waitForService()) {
// Join all code arguments into a single string to preserve spaces within quotes
const codeArgs = process.argv.slice(3);
executeCodeCommand(codeArgs);
executeCodeCommand(codeArgs, modelOverride);
} else {
console.error(
"Service startup timeout, please manually run `ccr start` to start the service"
);
process.exit(1);
}
} else {
// Join all code arguments into a single string to preserve spaces within quotes
const codeArgs = process.argv.slice(3);
executeCodeCommand(codeArgs);
executeCodeCommand(codeArgs, modelOverride);
}
break;
case "ui":
Expand Down
27 changes: 25 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ async function run(options: RunOptions = {}) {
cleanupPidFile();
process.exit(0);
});
console.log(HOST)

// Use port from environment variable if set (for background process)
const servicePort = process.env.SERVICE_PORT
Expand All @@ -94,11 +93,35 @@ async function run(options: RunOptions = {}) {
},
});
server.addHook("preHandler", apiKeyAuth(config));

// Model override handler
// Intercepts requests to check for model overrides passed via CLI's --model option
server.addHook("preHandler", async (req, reply) => {
if(req.url.startsWith("/v1/messages")) {
router(req, reply, config)
const configWithOverride = { ...config };

// Extract model override from auth token if present
// Format: "test:MODEL:{encoded_model}" where commas are encoded as "___"
const authHeader = req.headers.authorization || '';

if (authHeader.includes(':MODEL:')) {
const parts = authHeader.split(':MODEL:');
if (parts.length === 2) {
// Decode the model string (e.g., "provider___model" → "provider,model")
const modelOverride = parts[1].replace(/___/g, ',');

// Override the default router configuration with the specified model
configWithOverride.Router = {
...config.Router,
default: modelOverride
};
}
}

router(req, reply, configWithOverride)
}
});

server.start();
}

Expand Down
1 change: 1 addition & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const createServer = (config: any): Server => {
}, 1000);
});


// Register static file serving with caching
server.app.register(fastifyStatic, {
root: join(__dirname, "..", "dist"),
Expand Down
14 changes: 13 additions & 1 deletion src/utils/codeCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,27 @@ import {
incrementReferenceCount,
} from "./processCheck";

export async function executeCodeCommand(args: string[] = []) {
export async function executeCodeCommand(args: string[] = [], modelOverride?: string) {
// Set environment variables
const config = await readConfigFile();

const env: Record<string, string> = {
...process.env,
ANTHROPIC_AUTH_TOKEN: "test",
ANTHROPIC_BASE_URL: `http://127.0.0.1:${config.PORT || 3456}`,
API_TIMEOUT_MS: String(config.API_TIMEOUT_MS ?? 600000), // Default to 10 minutes if not set
};

// Model override implementation:
// The CLI client and server run in separate processes, requiring inter-process communication.
// Claude CLI forwards only ANTHROPIC_AUTH_TOKEN and ANTHROPIC_BASE_URL as environment variables.
// We embed the model override in the auth token to pass it reliably to the server.
if (modelOverride) {
// Token format: "test:MODEL:{encoded_model}"
// Commas are encoded as "___" to avoid conflicts (e.g., "provider,model" becomes "provider___model")
const encodedModel = modelOverride.replace(/,/g, '___');
env.ANTHROPIC_AUTH_TOKEN = `test:MODEL:${encodedModel}`;
}

// Non-interactive mode for automation environments
if (config.NON_INTERACTIVE_MODE) {
Expand Down