Skip to content
Draft
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
10 changes: 9 additions & 1 deletion src/cli/dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ const commandRunners: Record<string, CommandRunner> = {
},
config,
port: live.port,
}, ["mcode", "pi", "raycast"]));
}, ["mcode", "pi", "raycast", "commandcode"]));
} catch (error) {
console.warn(`Client integrations were not refreshed: ${error instanceof Error ? error.message : String(error)}`);
}
Expand Down Expand Up @@ -829,6 +829,14 @@ const commandRunners: Record<string, CommandRunner> = {
const { handleZcodeCommand } = await import("./integrations");
return await handleZcodeCommand(deps.args.slice(1));
},
commandcode: async deps => {
const { handleCommandcodeCommand } = await import("./integrations");
return await handleCommandcodeCommand(deps.args.slice(1));
},
cmd: async deps => {
const { handleCommandcodeCommand } = await import("./integrations");
return await handleCommandcodeCommand(deps.args.slice(1));
},
help: async () => {
printUsage();
return 0;
Expand Down
4 changes: 3 additions & 1 deletion src/cli/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Usage:
ocx memory [--json] Alias of ocx observe memory
ocx api-key <sub> Alias of ocx access key
ocx access <sub> External API keys and endpoint information
ocx export --client <id> Print a client config wired to the running proxy (13 clients)
ocx export --client <id> Print a client config wired to the running proxy (14 clients)
ocx integration client <sub> Enable, disable, inspect or roll back a client integration
ocx grok <sub> Grok Build model selection and apply
ocx system <sub> Runtime settings, startup, sync, OpenCodex updates, and Codex CLI inspection
Expand All @@ -89,6 +89,8 @@ Usage:
ocx mcode [args...] Launch MiniMax Code through its managed provider
ocx mmx text <sub> [args] Launch MiniMax CLI text through the proxy
ocx zcode [sub] Connect ZCode to the proxy (managed provider)
ocx commandcode [sub] Connect Command Code CLI to the proxy (managed provider)
ocx cmd [sub] Alias of ocx commandcode
ocx help [command] Show help
ocx --version | -v Print version

Expand Down
21 changes: 21 additions & 0 deletions src/cli/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,24 @@ export async function handleZcodeCommand(argv: string[], deps: RuntimeApiDeps =
}
return code;
}

const COMMANDCODE_USAGE = "Usage: ocx commandcode [status|show|list|enable|disable|history|journal|restore] [--json]";

export async function handleCommandcodeCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
const args = [...argv];
const verbIndex = args.findIndex(arg => !arg.startsWith("-"));
const action = (verbIndex === -1 ? "status" : args[verbIndex]).toLowerCase();
const known = ["status", "show", "list", "enable", "disable", "history", "journal", "restore"];
if (!known.includes(action)) {
console.error(`unknown commandcode command ${action}`);
console.error(COMMANDCODE_USAGE);
return 2;
}
const rest = verbIndex === -1 ? args : [...args.slice(0, verbIndex), ...args.slice(verbIndex + 1)];
const forwarded = action === "restore" ? [action, ...rest] : [action, ...rest, "--client", "commandcode"];
const code = await handleClientIntegrationCommand(forwarded, deps);
if (code === 0 && (action === "enable" || action === "disable")) {
console.error("Command Code reads providers on startup.");
}
return code;
}
19 changes: 17 additions & 2 deletions src/cli/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
{ name: "api-key", usage: "ocx api-key <list|create|rotate|remove> ...", summary: "Alias of ocx access key." },
{
name: "export",
usage: "ocx export --client <opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode|prime|aside|raycast> [--json] [--out <path>] [--force]",
summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, Gajae Code, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside, Raycast) wired to the running proxy.",
usage: "ocx export --client <opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode|commandcode|prime|aside|raycast> [--json] [--out <path>] [--force]",
summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, Gajae Code, DeepSeek Harness, MiniMax Code, ZCode, Command Code, Prime Agent, Aside, Raycast) wired to the running proxy.",
details: [
"--json prints the generated document as JSON on stdout; use --out for the client's native format.",
"--out <path> writes the native config there and refuses to replace an existing file without --force.",
Expand Down Expand Up @@ -399,6 +399,21 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
"Select OpenCodex Proxy/<provider>/<model> from ZCode's model picker.",
],
},
{
name: "commandcode",
usage: "ocx commandcode [status|show|list|enable|disable|history|restore] [--json]",
summary: "Connect Command Code CLI to the proxy via its managed provider.",
details: [
"Alias of ocx integration client <sub> --client commandcode.",
"enable writes the managed provider.opencodex block into ~/.commandcode/providers.json; disable removes only that block.",
"Command Code reads providers on startup.",
],
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
{
name: "cmd",
usage: "ocx cmd [status|show|list|enable|disable|history|restore] [--json]",
summary: "Alias of ocx commandcode.",
},
{
name: "restart",
usage: "ocx restart",
Expand Down
33 changes: 33 additions & 0 deletions src/clients/config-export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ import { buildDshClientConfig, summarizeDsh, buildDshContribution } from "./conf
import { buildMcodeClientConfig, summarizeMcode, buildMcodeContribution } from "./config-export/mcode";
import { buildZcodeClientConfig, summarizeZcode, buildZcodeContribution } from "./config-export/zcode";
import { buildRaycastClientConfig, summarizeRaycast, buildRaycastContribution } from "./config-export/raycast";
import {
buildCommandCodeClientConfig,
summarizeCommandCode,
buildCommandCodeContribution,
type CommandCodeGeneratedConfig,
type CommandCodeModelEntry,
type CommandCodeProviderBlock,
} from "./config-export/commandcode";

export type { CommandCodeGeneratedConfig, CommandCodeModelEntry, CommandCodeProviderBlock };
export { buildCommandCodeClientConfig, summarizeCommandCode, buildCommandCodeContribution };



Expand Down Expand Up @@ -438,6 +449,16 @@ export function zcodeConfigPath(env: OpencodeLaunchEnv = process.env, home: stri
return join(zcodeHomeDir(env, home), "v2", "config.json");
}

export function commandCodeHomeDir(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
const override = env.COMMANDCODE_HOME?.trim();
if (override) return absoluteClientPath(override, home, "COMMANDCODE_HOME");
return join(home, ".commandcode");
}

export function commandCodeConfigPath(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
return join(commandCodeHomeDir(env, home), "providers.json");
}

/**
* Prime Agent resolves its agent directory from `PRIME_AGENT_CODING_AGENT_DIR`
* — the brand-derived spelling of the `PI_CODING_AGENT_DIR` that `ompAgentDir`
Expand Down Expand Up @@ -1245,6 +1266,18 @@ export const EXPORT_CLIENTS: Record<ExportClientId, ExportClientSpec> = {
// remote binds refuse — same reasoning as MCode.
loopbackOnly: true,
},
commandcode: {
id: "commandcode",
filename: "providers.json",
destination: env => commandCodeConfigPath(env),
apiKeyEnv: "OPENCODEX_COMMANDCODE_API_KEY",
exportHint: "Command Code reads service token from serviceApiTokenFilePath or loopback placeholder.",
build: buildCommandCodeClientConfig,
format: "json",
summarize: summarizeCommandCode,
buildContribution: buildCommandCodeContribution,
loopbackOnly: true,
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
prime: {
id: "prime",
filename: "prime-models.json",
Expand Down
65 changes: 65 additions & 0 deletions src/clients/config-export/commandcode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Command Code config export.
import { existsSync } from "node:fs";
import type { ExportContext, ManagedContribution } from "./contracts";
import { normalizeExportModels, authoritativeContextWindow, singleFragment } from "./model-metadata";
import { OPENCODE_PROVIDER_ID, LOOPBACK_API_KEY_PLACEHOLDER } from "./constants";
import { serviceApiTokenFilePath } from "../../lib/service-secrets";
import { sanitizeCodexReasoningEfforts } from "../../reasoning-effort";

export interface CommandCodeModelEntry {
reasoningEfforts?: string[];
contextWindow?: number;
}

export interface CommandCodeProviderBlock {
name: "OpenCodex";
api: "openai-completions";
baseURL: string;
apiKey: string;
models: Record<string, CommandCodeModelEntry>;
}

export interface CommandCodeGeneratedConfig {
provider: Record<string, CommandCodeProviderBlock>;
}

export function buildCommandCodeClientConfig(ctx: ExportContext): CommandCodeGeneratedConfig {
const models: Record<string, CommandCodeModelEntry> = {};
for (const model of normalizeExportModels(ctx.models)) {
const entry: CommandCodeModelEntry = {};
const context = authoritativeContextWindow(model.contextWindow);
if (context !== undefined) {
entry.contextWindow = context;
}
const efforts = sanitizeCodexReasoningEfforts(model.reasoningEfforts)
?.filter(effort => ["low", "medium", "high", "xhigh", "max"].includes(effort));
if (efforts && efforts.length > 0) {
entry.reasoningEfforts = efforts;
}
models[model.namespaced] = entry;
}
const tokenPath = serviceApiTokenFilePath();
const apiKey = existsSync(tokenPath) ? `!cat ${tokenPath}` : LOOPBACK_API_KEY_PLACEHOLDER;
return {
provider: {
[OPENCODE_PROVIDER_ID]: {
name: "OpenCodex",
api: "openai-completions",
baseURL: ctx.baseUrl.replace(/\/v1\/?$/, "") + "/v1",
apiKey,
models,
},
},
};
}

export function summarizeCommandCode(document: unknown): { modelCount: number; modelsWithoutLimits: number } {
const models = Object.values((document as CommandCodeGeneratedConfig | undefined)?.provider?.[OPENCODE_PROVIDER_ID]?.models ?? {});
return { modelCount: models.length, modelsWithoutLimits: models.filter(model => model.contextWindow === undefined).length };
}

export function buildCommandCodeContribution(ctx: ExportContext): ManagedContribution {
const doc = buildCommandCodeClientConfig(ctx);
return singleFragment("commandcode", ["provider", OPENCODE_PROVIDER_ID], doc.provider[OPENCODE_PROVIDER_ID]);
}

1 change: 1 addition & 0 deletions src/clients/config-export/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export type ExportClientId =
| "zcode"
| "prime"
| "aside"
| "commandcode"
| "raycast";

export interface ExportClientSpec {
Expand Down
8 changes: 8 additions & 0 deletions src/integrations/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import {
raycastConfigPath,
zcodeConfigPath,
zcodeHomeDir,
commandCodeConfigPath,
commandCodeHomeDir,
type ExportClientId,
} from "../clients/config-export";

Expand Down Expand Up @@ -227,6 +229,12 @@ export const INTEGRATION_CLIENTS: Record<IntegrationClientId, IntegrationClientS
configPath: (env = process.env, home = homedir()) => zcodeConfigPath(env, home),
detectDir: (env = process.env, home = homedir()) => zcodeHomeDir(env, home),
},
commandcode: {
id: "commandcode",
configPath: (env = process.env, home = homedir()) => commandCodeConfigPath(env, home),
detectDir: (env = process.env, home = homedir()) => commandCodeHomeDir(env, home),
writerLock: { suffix: ".lock" },
},
prime: {
id: "prime",
configPath: (env = process.env, home = homedir()) => primeConfigPath(env, home),
Expand Down
99 changes: 99 additions & 0 deletions tests/clients/command-code-client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { describe, expect, test } from "bun:test";
import { join } from "node:path";
import {
ClientPathError,
EXPORT_CLIENTS,
LOOPBACK_API_KEY_PLACEHOLDER,
OPENCODE_PROVIDER_ID,
buildClientConfig,
buildClientConfigText,
buildClientContribution,
commandCodeConfigPath,
commandCodeHomeDir,
type CommandCodeGeneratedConfig,
type ExportContext,
} from "../../src/clients/config-export";
import { INTEGRATION_CLIENTS } from "../../src/integrations/registry";
import type { OcxConfig } from "../../src/types";

const CONFIG = {
port: 10100,
hostname: "127.0.0.1",
defaultProvider: "mock",
providers: { mock: { adapter: "openai-chat", baseUrl: "http://127.0.0.1/v1" } },
} as OcxConfig;

function context(): ExportContext {
return {
baseUrl: "http://127.0.0.1:10100/v1",
config: CONFIG,
models: [
{ namespaced: "anthropic/claude-opus-5", provider: "anthropic", id: "claude-opus-5", contextWindow: 200_000, inputModalities: ["text", "image"] },
{ namespaced: "openai/gpt-5.6-sol", provider: "openai", id: "gpt-5.6-sol", contextWindow: 922_000, reasoningEfforts: ["low", "medium", "high"] },
{ namespaced: "google-antigravity/gemini-3.8-flash", provider: "google-antigravity", id: "gemini-3.8-flash", contextWindow: 1_048_576, reasoningEfforts: ["low", "medium", "high"] },
{ namespaced: "mystery/model", provider: "mystery", id: "model" },
],
};
}

describe("Command Code client config", () => {
test("generates valid JSON with provider.opencodex block", () => {
const built = buildClientConfigText("commandcode", context());
expect(built.format).toBe("json");
expect(JSON.parse(built.text)).toEqual(built.document as never);
});

test("adds only provider.opencodex, wired to the loopback proxy", () => {
const document = buildClientConfig("commandcode", context()) as CommandCodeGeneratedConfig;
expect(Object.keys(document)).toEqual(["provider"]);
expect(Object.keys(document.provider)).toEqual([OPENCODE_PROVIDER_ID]);
const provider = document.provider[OPENCODE_PROVIDER_ID]!;
expect(provider.name).toBe("OpenCodex");
expect(provider.api).toBe("openai-completions");
expect(provider.baseURL).toBe("http://127.0.0.1:10100/v1");
expect(provider.apiKey).toBeDefined();
});

test("emits contextWindow and reasoningEfforts correctly without guessing", () => {
const document = buildClientConfig("commandcode", context()) as CommandCodeGeneratedConfig;
const provider = document.provider[OPENCODE_PROVIDER_ID]!;
const gemini = provider.models["google-antigravity/gemini-3.8-flash"];
expect(gemini).toBeDefined();
expect(gemini?.contextWindow).toBe(1_048_576);
expect(gemini?.reasoningEfforts).toEqual(["low", "medium", "high"]);

const mystery = provider.models["mystery/model"];
expect(mystery).toBeDefined();
expect(mystery?.contextWindow).toBeUndefined();
expect(mystery?.reasoningEfforts).toBeUndefined();
});

test("the contribution owns exactly the provider.opencodex path under its own id", () => {
const contribution = buildClientContribution("commandcode", context());
expect(contribution.clientId).toBe("commandcode");
expect(contribution.fragments.map(f => f.path)).toEqual([["provider", OPENCODE_PROVIDER_ID]]);
});

test("resolves the home directory override and the documented destination", () => {
expect(commandCodeHomeDir({}, "/home/u")).toBe(join("/home/u", ".commandcode"));
expect(commandCodeConfigPath({}, "/home/u")).toBe(join("/home/u", ".commandcode", "providers.json"));
expect(commandCodeConfigPath({ COMMANDCODE_HOME: "/elsewhere" }, "/home/u")).toBe(join("/elsewhere", "providers.json"));
expect(commandCodeConfigPath({ COMMANDCODE_HOME: "~/alt" }, "/home/u")).toBe(join("/home/u", "alt", "providers.json"));
expect(() => commandCodeConfigPath({ COMMANDCODE_HOME: "relative" }, "/home/u")).toThrow(ClientPathError);
});

test("detects installation by the .commandcode directory the override names", () => {
const spec = INTEGRATION_CLIENTS.commandcode;
expect(spec.detectDir({}, "/home/u")).toBe(join("/home/u", ".commandcode"));
expect(spec.detectDir({ COMMANDCODE_HOME: "/elsewhere" } as NodeJS.ProcessEnv, "/home/u")).toBe("/elsewhere");
});

test("ships as a loopback-only integration with proper export metadata", () => {
const spec = EXPORT_CLIENTS.commandcode;
expect(spec.id).toBe("commandcode");
expect(spec.filename).toBe("providers.json");
expect(spec.format).toBe("json");
expect(spec.loopbackOnly).toBe(true);
});
});

Loading