Skip to content
Closed
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
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/ko/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ Windows x64 설치 관측은 [`attest` 명령](/ko/reference/cli/agents/)을 참
`ocx --version`, `ocx -v`, `ocx version`은 스크립트가 읽기 좋은 한 줄짜리 버전을 출력하고 종료합니다.

일반 도움말에는 두 개의 디스패치 대상이 의도적으로 빠져 있습니다. `__refresh-version [preview]`는 분리된 프로세스에서 업데이트 알림 캐시를 새로 고치고, `__gui-update-worker <job-id> [latest|preview] [restart]`는 대시보드 업데이트 작업을 실행합니다. 이들은 구현 세부 사항일 뿐이며 안정적인 사용자 명령이 아닙니다. 대시보드는 worker PID를 기록하고, worker가 죽은 활성 작업은 복구하며, PID가 없는 오래된 활성 기록은 10분 뒤 오래된 것으로 취급하고, 살아 있는 worker를 동시 업데이트로부터 보호합니다.

## Default alias listing

`ocx alias --json`은 `ocx alias list --json`과 같습니다. `--json`은 명시한 alias 작업의 앞이나 뒤에 둘 수 있습니다.
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,7 @@ refreshes the update-notification cache in a detached process, and
implementation details, not stable user-facing commands. The dashboard records the worker PID,
recovers an active job whose worker died, treats older PID-less active records as stale after ten
minutes, and protects a live worker from concurrent updates.

## Default alias listing

`ocx alias --json` is equivalent to `ocx alias list --json`. The output flag can precede or follow an explicit alias action.
1 change: 1 addition & 0 deletions scripts/test-layout/layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
"anthropic-account-pause.test.ts": "adapters/anthropic",
"anthropic-account-threshold.test.ts": "adapters/anthropic",
"anthropic-combo-account-cooldown.test.ts": "adapters/anthropic",
"cli-alias-json.test.ts": "cli",
"cli-anthropic-account-threshold.test.ts": "cli",
"anthropic-model-routes.test.ts": "adapters/anthropic",
"anthropic-agentrouter-language-framing.test.ts": "adapters/anthropic",
Expand Down
3 changes: 2 additions & 1 deletion src/cli/alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ function selector(value: string): { provider: string; model?: string } {
return slash < 0 ? { provider: value } : { provider: value.slice(0, slash), model: value.slice(slash + 1) };
}

/** Manage model aliases, consuming JSON output selection before the default list action. */
export async function handleAliasCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
const args = [...argv];
const action = (args.shift() ?? "list").toLowerCase();
const wantsJson = takeFlag(args, "--json");
const action = (args.shift() ?? "list").toLowerCase();
if (action === "list") {
rejectArgs(args, USAGE);
const result = await runtimeRequest<Record<string, unknown>>("/api/aliases", {}, deps);
Expand Down
2 changes: 1 addition & 1 deletion structure/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ evidence remain late and cannot widen a captured static limit.
Virtual models are the sole model-identity transition: the ordinary and compact paths preserve the
selected public id in diagnostics, rewrite `route.modelId` to the upstream wire id, and atomically
replace `route.staticPolicy` before adapter or capability decisions continue. Model aliases are
resolved before the route result is built, so their policy is already keyed by the native wire id. `src/router.ts` applies bare `blockedModelRedirects` keys after this resolution; ordinary targets substitute one upstream model id on the selected provider/account, including slash-valued targets, while only targets explicitly naming a different configured provider reroute. Qualified source keys take precedence only for those cross-provider targets. A cross-provider redirect sets `RouteResult.credentialDomainRewrite`, which Responses and Chat header scoping treat like combo/policy routes, so a caller credential for the source route is never forwarded.
resolved before the route result is built, so their policy is already keyed by the native wire id. `src/router.ts` applies bare `blockedModelRedirects` keys after this resolution; ordinary targets substitute one upstream model id on the selected provider/account, including slash-valued targets, while only targets explicitly naming a different configured provider reroute. Qualified source keys take precedence only for those cross-provider targets. A cross-provider redirect sets `RouteResult.credentialDomainRewrite`, which Responses and Chat header scoping treat like combo/policy routes, so a caller credential for the source route is never forwarded. `src/cli/alias.ts` consumes the JSON output flag before choosing its default list action. `ocx alias --json` and both flag positions around an explicit action preserve the selected read or mutation.
Live selector hints obey the [credential-scoped cache contract](catalog.md): a selection change
cannot reuse the previous credential's roster to choose an alias target. Passive OAuth observation
neither refreshes credentials nor repairs their storage. Cross-provider redirects require an enabled destination with a resolved key, usable stored OAuth account, or local/keyless auth (never source-caller forward auth), and share one visited set and five-edge budget across alias, provider, combo and policy resolution; every policy redirect must end at an eligible declared candidate, including same-provider substitutions and candidate fallback retries, and pinned account selectors reject provider changes. Decision traces name the final provider/model while policy candidates retain selection evidence and combos retain their route kind and redirect reason.
Expand Down
27 changes: 27 additions & 0 deletions tests/cli/cli-alias-json.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, expect, spyOn, test } from "bun:test";
import { handleAliasCommand } from "../../src/cli/alias";

describe("alias list JSON flags", () => {
test.each([["--json"], ["list", "--json"], ["--json", "list"]].map(args => [args]))("lists aliases for %j", async args => {
const out = spyOn(console, "log").mockImplementation(() => {});
const paths: string[] = [];
const payload = { providers: { local: "Work" }, models: {} };
try {
expect(await handleAliasCommand(args, { baseUrl: "http://cli.test", fetchImpl: async input => {
paths.push(String(input)); return Response.json(payload);
} })).toBe(0);
expect(paths).toEqual(["http://cli.test/api/aliases"]);
expect(JSON.parse(String(out.mock.calls[0]![0]))).toEqual(payload);
} finally { out.mockRestore(); }
});
test("a leading JSON flag does not turn mutation into a list", async () => {
const out = spyOn(console, "log").mockImplementation(() => {});
try {
let method: string | undefined;
expect(await handleAliasCommand(["--json", "set", "local", "Work"], {
baseUrl: "http://cli.test", fetchImpl: async (_input, init) => { method = init?.method; return Response.json({ ok: true }); },
})).toBe(0);
expect(method).toBe("PUT");
} finally { out.mockRestore(); }
});
});
1 change: 1 addition & 0 deletions tests/fixtures/test-layout-expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
"anthropic-account-pause.test.ts": "adapters/anthropic",
"anthropic-account-threshold.test.ts": "adapters/anthropic",
"anthropic-combo-account-cooldown.test.ts": "adapters/anthropic",
"cli-alias-json.test.ts": "cli",
"cli-anthropic-account-threshold.test.ts": "cli",
"anthropic-model-routes.test.ts": "adapters/anthropic",
"anthropic-agentrouter-language-framing.test.ts": "adapters/anthropic",
Expand Down
Loading