@bitkyc08/opencodex 2.7.41 → 2.7.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/gui/dist/assets/index-Bl_VBGoI.js +65 -0
- package/gui/dist/assets/index-DfVGuN88.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/base.ts +6 -0
- package/src/adapters/kiro-constants.ts +6 -2
- package/src/adapters/kiro-retry.ts +175 -10
- package/src/adapters/kiro.ts +172 -85
- package/src/adapters/mimo-free.ts +1 -0
- package/src/adapters/openai-chat.ts +30 -4
- package/src/adapters/openai-responses.ts +90 -12
- package/src/bridge.ts +91 -43
- package/src/claude/desktop-3p-paths.ts +84 -0
- package/src/claude/desktop-3p.ts +29 -2
- package/src/cli/access.ts +108 -0
- package/src/cli/account-auth.ts +223 -0
- package/src/cli/account.ts +9 -1
- package/src/cli/agent.ts +184 -0
- package/src/cli/combo.ts +119 -0
- package/src/cli/config-command.ts +145 -0
- package/src/cli/debug.ts +20 -8
- package/src/cli/doctor.ts +45 -8
- package/src/cli/help.ts +65 -13
- package/src/cli/index.ts +108 -7
- package/src/cli/integrations.ts +142 -0
- package/src/cli/models-runtime.ts +212 -0
- package/src/cli/models.ts +9 -10
- package/src/cli/observe.ts +117 -0
- package/src/cli/provider-runtime.ts +152 -0
- package/src/cli/provider.ts +23 -1
- package/src/cli/runtime-api.ts +325 -0
- package/src/cli/star-prompt.ts +3 -3
- package/src/cli/status.ts +17 -0
- package/src/cli/system-command.ts +112 -0
- package/src/codex/auth-api.ts +3 -2
- package/src/codex/catalog/aggregation.ts +113 -18
- package/src/codex/catalog/provider-fetch.ts +24 -13
- package/src/codex/catalog/sync.ts +20 -8
- package/src/codex/catalog.ts +2 -1
- package/src/codex/refresh.ts +10 -3
- package/src/codex/routing.ts +21 -32
- package/src/codex/sync.ts +17 -0
- package/src/config.ts +48 -0
- package/src/generated/jawcode-model-metadata.ts +2 -1
- package/src/grok/inject.ts +184 -4
- package/src/grok/status.ts +33 -0
- package/src/lib/retry-after.ts +55 -0
- package/src/lib/windows-elevation.ts +627 -0
- package/src/providers/openai-sidecar.ts +46 -2
- package/src/providers/registry.ts +52 -0
- package/src/server/auth-cors.ts +6 -0
- package/src/server/chat-completions.ts +6 -1
- package/src/server/claude-messages.ts +20 -1
- package/src/server/images.ts +14 -7
- package/src/server/management/agent-settings-routes.ts +10 -4
- package/src/server/management/combo-routes.ts +0 -1
- package/src/server/management/config-routes.ts +0 -1
- package/src/server/management/logs-usage-routes.ts +94 -0
- package/src/server/management/model-routes.ts +0 -1
- package/src/server/management/oauth-account-routes.ts +0 -1
- package/src/server/management/provider-routes.ts +0 -1
- package/src/server/management/shared.ts +0 -1
- package/src/server/management/system-routes.ts +27 -15
- package/src/server/management-api.ts +0 -1
- package/src/server/memory-watchdog.ts +54 -10
- package/src/server/request-log-conversation.ts +168 -0
- package/src/server/request-log.ts +122 -2
- package/src/server/responses/core.ts +76 -13
- package/src/server/responses/passthrough-error.ts +38 -13
- package/src/server/startup-action-control.ts +266 -15
- package/src/service.ts +512 -3
- package/src/storage/cleanup.ts +1538 -0
- package/src/storage/scanner.ts +4 -1
- package/src/types.ts +16 -0
- package/src/update/job.ts +229 -25
- package/src/usage/log.ts +39 -0
- package/src/web-search/loop.ts +8 -1
- package/gui/dist/assets/index-B2J4t3te.css +0 -1
- package/gui/dist/assets/index-BmvM6wRb.js +0 -65
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliUsageError,
|
|
3
|
+
printData,
|
|
4
|
+
rejectArgs,
|
|
5
|
+
runCliAction,
|
|
6
|
+
runtimeRequest,
|
|
7
|
+
takeFlag,
|
|
8
|
+
takeOption,
|
|
9
|
+
type RuntimeApiDeps,
|
|
10
|
+
} from "./runtime-api";
|
|
11
|
+
|
|
12
|
+
const USAGE = `Usage:
|
|
13
|
+
ocx access key [list] [--json]
|
|
14
|
+
ocx access key create [name] [--json]
|
|
15
|
+
ocx access key remove <id> --yes [--json]
|
|
16
|
+
ocx access endpoints [--json]
|
|
17
|
+
ocx access models [--json]
|
|
18
|
+
ocx access test <model> [--protocol <chat|responses|messages>] [--json]`;
|
|
19
|
+
|
|
20
|
+
async function key(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
21
|
+
const args = [...argv];
|
|
22
|
+
const action = (args.shift() ?? "list").toLowerCase();
|
|
23
|
+
const wantsJson = takeFlag(args, "--json");
|
|
24
|
+
if (action === "list") {
|
|
25
|
+
rejectArgs(args, USAGE);
|
|
26
|
+
const result = await runtimeRequest<Record<string, unknown>>("/api/keys", {}, deps);
|
|
27
|
+
const keys = Array.isArray(result.keys) ? result.keys as Array<Record<string, unknown>> : [];
|
|
28
|
+
printData(result, wantsJson, keys.length
|
|
29
|
+
? keys.map(entry => `${String(entry.id)} ${String(entry.name)} ${String(entry.prefix ?? "")}`)
|
|
30
|
+
: ["No API access keys configured."]);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (action === "create") {
|
|
34
|
+
const name = args.shift() ?? "default";
|
|
35
|
+
rejectArgs(args, USAGE);
|
|
36
|
+
const result = await runtimeRequest<Record<string, unknown>>("/api/keys", {
|
|
37
|
+
method: "POST",
|
|
38
|
+
body: JSON.stringify({ name }),
|
|
39
|
+
}, deps);
|
|
40
|
+
// The plaintext key is returned once. Keep text output explicit so callers know to store it.
|
|
41
|
+
printData(result, wantsJson, [
|
|
42
|
+
`Created API key ${String(result.name ?? name)} (${String(result.id ?? "")}).`,
|
|
43
|
+
`Key (shown once): ${String(result.key ?? "")}`,
|
|
44
|
+
]);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (action === "remove" || action === "delete") {
|
|
48
|
+
const id = args.shift();
|
|
49
|
+
const yes = takeFlag(args, "--yes");
|
|
50
|
+
if (!id) throw new CliUsageError("key id is required", USAGE);
|
|
51
|
+
if (!yes) throw new CliUsageError("remove requires --yes", USAGE);
|
|
52
|
+
rejectArgs(args, USAGE);
|
|
53
|
+
const result = await runtimeRequest("/api/keys", { method: "DELETE", body: JSON.stringify({ id }) }, deps);
|
|
54
|
+
printData(result, wantsJson, [`Removed API key ${id}.`]);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
throw new CliUsageError(`unknown key command ${action}`, USAGE);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function endpoints(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
61
|
+
const args = [...argv];
|
|
62
|
+
const wantsJson = takeFlag(args, "--json");
|
|
63
|
+
rejectArgs(args, USAGE);
|
|
64
|
+
const result = await runtimeRequest<Record<string, unknown>>("/api/keys", {}, deps);
|
|
65
|
+
const view = Object.fromEntries(Object.entries(result).filter(([key]) => key.endsWith("Endpoint") || key === "baseUrl" || key === "endpoint"));
|
|
66
|
+
printData(view, wantsJson, Object.entries(view).map(([name, value]) => `${name}: ${String(value)}`));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function models(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
70
|
+
const args = [...argv];
|
|
71
|
+
const wantsJson = takeFlag(args, "--json");
|
|
72
|
+
rejectArgs(args, USAGE);
|
|
73
|
+
const result = await runtimeRequest<Record<string, unknown>>("/v1/models", {}, deps);
|
|
74
|
+
const rows = Array.isArray(result.data) ? result.data as Array<Record<string, unknown>> : [];
|
|
75
|
+
printData(result, wantsJson, rows.map(row => `${String(row.id)} ${String(row.owned_by ?? "")}`.trimEnd()));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function testModel(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
79
|
+
const args = [...argv];
|
|
80
|
+
const model = args.shift();
|
|
81
|
+
const wantsJson = takeFlag(args, "--json");
|
|
82
|
+
const protocol = takeOption(args, "--protocol") ?? "chat";
|
|
83
|
+
if (!model) throw new CliUsageError("model is required", USAGE);
|
|
84
|
+
if (!(["chat", "responses", "messages"] as const).includes(protocol as "chat" | "responses" | "messages")) {
|
|
85
|
+
throw new CliUsageError("--protocol must be chat, responses, or messages", USAGE);
|
|
86
|
+
}
|
|
87
|
+
rejectArgs(args, USAGE);
|
|
88
|
+
const request = protocol === "responses"
|
|
89
|
+
? { path: "/v1/responses", body: { model, input: "Reply with OK.", max_output_tokens: 16 } }
|
|
90
|
+
: protocol === "messages"
|
|
91
|
+
? { path: "/v1/messages", body: { model, messages: [{ role: "user", content: "Reply with OK." }], max_tokens: 16 } }
|
|
92
|
+
: { path: "/v1/chat/completions", body: { model, messages: [{ role: "user", content: "Reply with OK." }], max_tokens: 16, stream: false } };
|
|
93
|
+
const result = await runtimeRequest(request.path, { method: "POST", body: JSON.stringify(request.body) }, deps);
|
|
94
|
+
printData(result, wantsJson, [`${model}: ${protocol} request succeeded.`]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function handleAccessCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
|
|
98
|
+
return runCliAction(async () => {
|
|
99
|
+
const [sub = "key", ...rest] = argv;
|
|
100
|
+
if (sub === "key" || sub === "keys") await key(rest, deps);
|
|
101
|
+
else if (sub === "endpoints") await endpoints(rest, deps);
|
|
102
|
+
else if (sub === "models") await models(rest, deps);
|
|
103
|
+
else if (sub === "test") await testModel(rest, deps);
|
|
104
|
+
else throw new CliUsageError(`unknown access command ${sub}`, USAGE);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const ACCESS_USAGE = USAGE;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliUsageError,
|
|
3
|
+
printData,
|
|
4
|
+
readSecretLine,
|
|
5
|
+
rejectArgs,
|
|
6
|
+
runCliAction,
|
|
7
|
+
runtimeRequest,
|
|
8
|
+
takeFlag,
|
|
9
|
+
takeOption,
|
|
10
|
+
takeOptionWithSyntax,
|
|
11
|
+
type CliStdin,
|
|
12
|
+
type RuntimeApiDeps,
|
|
13
|
+
} from "./runtime-api";
|
|
14
|
+
|
|
15
|
+
const USAGE = `Usage:
|
|
16
|
+
ocx account login <provider> [--id <account-id>] [--reauth] [--code -] [--no-wait] [--json]
|
|
17
|
+
ocx account code <provider> [--flow <flow-id>] [--json] (reads the code from stdin)
|
|
18
|
+
ocx account cancel <provider> [--flow <flow-id>] [--json]
|
|
19
|
+
ocx account reset-credits <account-id|main> [--consume --yes] [--json]
|
|
20
|
+
|
|
21
|
+
The redirect URL or authorization code is a short-lived credential. Pipe it in
|
|
22
|
+
rather than passing it as an argument, where it lands in shell history and is
|
|
23
|
+
visible to anyone who can run ps:
|
|
24
|
+
pbpaste | ocx account code <provider> --flow <flow-id>
|
|
25
|
+
ocx account login <provider> --code - (same, for the login flow)`;
|
|
26
|
+
|
|
27
|
+
const CODEX_NAMES = new Set(["openai", "codex", "chatgpt"]);
|
|
28
|
+
|
|
29
|
+
interface LoginStart {
|
|
30
|
+
url?: string;
|
|
31
|
+
flowId?: string;
|
|
32
|
+
instructions?: string;
|
|
33
|
+
deviceCode?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** `-` means "read it from stdin", the documented way to pass a code silently. */
|
|
37
|
+
const STDIN_SENTINEL = "-";
|
|
38
|
+
|
|
39
|
+
const ARGV_WARNING =
|
|
40
|
+
"warning: the authorization code was passed as a command-line argument, so it is now in your shell history and was visible in the process list while this ran. Pipe it on stdin instead, or pass `-` to read from stdin.";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the code, preferring stdin.
|
|
44
|
+
*
|
|
45
|
+
* Never logs the value itself — the warning names the exposure, not the
|
|
46
|
+
* credential. What this closes is shell history, `ps`, and this program's own
|
|
47
|
+
* output; a code pasted at an interactive prompt is still visible on the
|
|
48
|
+
* terminal, exactly as it is in the existing interactive login.
|
|
49
|
+
*/
|
|
50
|
+
async function resolveCode(
|
|
51
|
+
supplied: { value: string; inline: boolean } | undefined,
|
|
52
|
+
deps: RuntimeApiDeps,
|
|
53
|
+
required: boolean,
|
|
54
|
+
): Promise<string | undefined> {
|
|
55
|
+
if (supplied && supplied.value !== STDIN_SENTINEL) {
|
|
56
|
+
console.error(ARGV_WARNING);
|
|
57
|
+
return supplied.value;
|
|
58
|
+
}
|
|
59
|
+
if (!supplied && !required) return undefined;
|
|
60
|
+
const input: CliStdin = deps.stdinImpl ?? process.stdin;
|
|
61
|
+
if (input.isTTY) console.error("Paste the redirect URL or authorization code, then press Enter:");
|
|
62
|
+
return await readSecretLine(deps, "authorization code");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function login(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
66
|
+
const args = [...argv];
|
|
67
|
+
const provider = args.shift()?.trim().toLowerCase();
|
|
68
|
+
const wantsJson = takeFlag(args, "--json");
|
|
69
|
+
const noWait = takeFlag(args, "--no-wait");
|
|
70
|
+
const reauth = takeFlag(args, "--reauth");
|
|
71
|
+
const id = takeOption(args, "--id");
|
|
72
|
+
const suppliedCode = takeOptionWithSyntax(args, "--code");
|
|
73
|
+
if (!provider) throw new CliUsageError("provider is required", USAGE);
|
|
74
|
+
rejectArgs(args, USAGE);
|
|
75
|
+
// Only resolve when --code was actually given: a plain `ocx account login`
|
|
76
|
+
// opens the browser flow and polls, and must not block on stdin.
|
|
77
|
+
const code = await resolveCode(suppliedCode, deps, false);
|
|
78
|
+
|
|
79
|
+
if (CODEX_NAMES.has(provider)) {
|
|
80
|
+
const start = await runtimeRequest<LoginStart>("/api/codex-auth/login", {
|
|
81
|
+
method: "POST",
|
|
82
|
+
body: JSON.stringify({ ...(id ? { id } : {}), ...(reauth ? { reauth: true } : {}) }),
|
|
83
|
+
}, deps);
|
|
84
|
+
if (!wantsJson) {
|
|
85
|
+
if (start.url) console.log(`Open this URL to sign in:\n${start.url}`);
|
|
86
|
+
if (start.instructions) console.log(start.instructions);
|
|
87
|
+
if (start.flowId) console.log(`Flow: ${start.flowId}`);
|
|
88
|
+
}
|
|
89
|
+
if (code && start.flowId) {
|
|
90
|
+
await runtimeRequest("/api/codex-auth/login/code", {
|
|
91
|
+
method: "POST",
|
|
92
|
+
body: JSON.stringify({ flowId: start.flowId, input: code }),
|
|
93
|
+
}, deps);
|
|
94
|
+
}
|
|
95
|
+
if (noWait) {
|
|
96
|
+
if (wantsJson) printData(start, true);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (!start.flowId) throw new CliUsageError("login did not return a flow id");
|
|
100
|
+
for (let attempt = 0; attempt < 150; attempt++) {
|
|
101
|
+
await Bun.sleep(2_000);
|
|
102
|
+
const state = await runtimeRequest<Record<string, unknown>>(
|
|
103
|
+
`/api/codex-auth/login-status?flowId=${encodeURIComponent(start.flowId)}${id ? `&accountId=${encodeURIComponent(id)}` : ""}${reauth ? "&reauth=1" : ""}`,
|
|
104
|
+
{}, deps,
|
|
105
|
+
);
|
|
106
|
+
if (state.status === "done") {
|
|
107
|
+
printData(state, wantsJson, [`Logged in${state.email ? ` as ${String(state.email)}` : ""}.`]);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (state.status === "error" || state.status === "expired") {
|
|
111
|
+
throw new CliUsageError(String(state.error ?? `login ${state.status}`));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
throw new CliUsageError("login timed out");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (id && !reauth) throw new CliUsageError("--id is only valid with --reauth for provider OAuth accounts", USAGE);
|
|
118
|
+
const start = await runtimeRequest<LoginStart>("/api/oauth/login", {
|
|
119
|
+
method: "POST",
|
|
120
|
+
body: JSON.stringify({ provider, addAccount: !reauth, ...(reauth && id ? { accountId: id, reauth: true } : {}) }),
|
|
121
|
+
}, deps);
|
|
122
|
+
if (!wantsJson) {
|
|
123
|
+
if (start.url) console.log(`Open this URL to sign in:\n${start.url}`);
|
|
124
|
+
if (start.instructions) console.log(start.instructions);
|
|
125
|
+
if (start.deviceCode) console.log(`Device code: ${start.deviceCode}`);
|
|
126
|
+
}
|
|
127
|
+
if (code) {
|
|
128
|
+
await runtimeRequest("/api/oauth/login/code", {
|
|
129
|
+
method: "POST",
|
|
130
|
+
body: JSON.stringify({ provider, input: code }),
|
|
131
|
+
}, deps);
|
|
132
|
+
}
|
|
133
|
+
if (noWait) {
|
|
134
|
+
if (wantsJson) printData(start, true);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
for (let attempt = 0; attempt < 100; attempt++) {
|
|
138
|
+
await Bun.sleep(2_000);
|
|
139
|
+
const state = await runtimeRequest<Record<string, unknown>>(`/api/oauth/status?provider=${encodeURIComponent(provider)}`, {}, deps);
|
|
140
|
+
if (state.loggedIn === true) {
|
|
141
|
+
printData(state, wantsJson, [`Logged in to ${provider}.`]);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (state.error) throw new CliUsageError(String(state.error));
|
|
145
|
+
}
|
|
146
|
+
throw new CliUsageError("login timed out");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function code(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
150
|
+
const args = [...argv];
|
|
151
|
+
const provider = args.shift()?.trim().toLowerCase();
|
|
152
|
+
// Flags first. Taking the positional before parsing them made
|
|
153
|
+
// `ocx account code openai --flow f1` read `--flow` as the code and then
|
|
154
|
+
// reject `f1` as an unexpected argument.
|
|
155
|
+
const wantsJson = takeFlag(args, "--json");
|
|
156
|
+
const flowId = takeOption(args, "--flow");
|
|
157
|
+
const suppliedCode = takeOptionWithSyntax(args, "--code");
|
|
158
|
+
// An unknown flag is not a credential. Without this guard `--nope` becomes
|
|
159
|
+
// the positional code and the real complaint ("unexpected argument") is
|
|
160
|
+
// replaced by a confusing one about how the code was passed.
|
|
161
|
+
const positional = args[0]?.startsWith("--") ? undefined : args.shift();
|
|
162
|
+
if (!provider) throw new CliUsageError("provider is required", USAGE);
|
|
163
|
+
// A second positional here is most likely the code, split by an unquoted
|
|
164
|
+
// space or a stray shell expansion. Naming it back would put it on stderr.
|
|
165
|
+
rejectArgs(args, USAGE, { redactValues: true });
|
|
166
|
+
if (suppliedCode && positional !== undefined) {
|
|
167
|
+
throw new CliUsageError("pass the code either positionally or with --code, not both", USAGE);
|
|
168
|
+
}
|
|
169
|
+
const input = await resolveCode(
|
|
170
|
+
suppliedCode ?? (positional === undefined ? undefined : { value: positional, inline: false }),
|
|
171
|
+
deps,
|
|
172
|
+
true,
|
|
173
|
+
);
|
|
174
|
+
if (!input) throw new CliUsageError("provider and redirect/code are required", USAGE);
|
|
175
|
+
const path = CODEX_NAMES.has(provider) ? "/api/codex-auth/login/code" : "/api/oauth/login/code";
|
|
176
|
+
if (CODEX_NAMES.has(provider) && !flowId) throw new CliUsageError("Codex login code requires --flow <flow-id>", USAGE);
|
|
177
|
+
const body = CODEX_NAMES.has(provider) ? { flowId, input } : { provider, input };
|
|
178
|
+
const result = await runtimeRequest(path, { method: "POST", body: JSON.stringify(body) }, deps);
|
|
179
|
+
printData(result, wantsJson, ["Login code submitted."]);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function cancel(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
183
|
+
const args = [...argv];
|
|
184
|
+
const provider = args.shift()?.trim().toLowerCase();
|
|
185
|
+
const wantsJson = takeFlag(args, "--json");
|
|
186
|
+
const flowId = takeOption(args, "--flow");
|
|
187
|
+
if (!provider) throw new CliUsageError("provider is required", USAGE);
|
|
188
|
+
rejectArgs(args, USAGE);
|
|
189
|
+
const codex = CODEX_NAMES.has(provider);
|
|
190
|
+
const result = await runtimeRequest(codex ? "/api/codex-auth/login/cancel" : "/api/oauth/login/cancel", {
|
|
191
|
+
method: "POST",
|
|
192
|
+
body: JSON.stringify(codex ? { flowId } : { provider }),
|
|
193
|
+
}, deps);
|
|
194
|
+
printData(result, wantsJson, [`Cancelled ${provider} login.`]);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function resetCredits(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
198
|
+
const args = [...argv];
|
|
199
|
+
const rawId = args.shift()?.trim();
|
|
200
|
+
const wantsJson = takeFlag(args, "--json");
|
|
201
|
+
const consume = takeFlag(args, "--consume");
|
|
202
|
+
const yes = takeFlag(args, "--yes");
|
|
203
|
+
if (!rawId) throw new CliUsageError("account id is required", USAGE);
|
|
204
|
+
if (consume && !yes) throw new CliUsageError("consuming a reset credit requires --yes", USAGE);
|
|
205
|
+
rejectArgs(args, USAGE);
|
|
206
|
+
const accountId = rawId === "main" ? "__main__" : rawId;
|
|
207
|
+
const result = consume
|
|
208
|
+
? await runtimeRequest("/api/codex-auth/reset-credits/consume", { method: "POST", body: JSON.stringify({ accountId }) }, deps)
|
|
209
|
+
: await runtimeRequest(`/api/codex-auth/reset-credits?accountId=${encodeURIComponent(accountId)}`, {}, deps);
|
|
210
|
+
printData(result, wantsJson);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export async function handleAccountAuthCommand(sub: string, argv: string[], deps: RuntimeApiDeps = {}): Promise<number | null> {
|
|
214
|
+
let action: (() => Promise<void>) | undefined;
|
|
215
|
+
if (sub === "login" || sub === "reauth") action = () => login(sub === "reauth" ? [...argv, "--reauth"] : argv, deps);
|
|
216
|
+
else if (sub === "code") action = () => code(argv, deps);
|
|
217
|
+
else if (sub === "cancel") action = () => cancel(argv, deps);
|
|
218
|
+
else if (sub === "reset-credits") action = () => resetCredits(argv, deps);
|
|
219
|
+
if (!action) return null;
|
|
220
|
+
return runCliAction(action);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export const ACCOUNT_AUTH_USAGE = USAGE;
|
package/src/cli/account.ts
CHANGED
|
@@ -25,6 +25,10 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
25
25
|
ocx account remove <provider> <account-or-key-id|main> --yes [--json]
|
|
26
26
|
ocx account clear-cooldown <provider> <account-id|main> [--json]
|
|
27
27
|
ocx account add-key <provider> [--label <label>] [--json]
|
|
28
|
+
ocx account login <provider> [--id <account-id>] [--reauth] [--code -] [--no-wait] [--json]
|
|
29
|
+
ocx account code <provider> [--flow <flow-id>] [--json] (reads the code from stdin)
|
|
30
|
+
ocx account cancel <provider> [--flow <flow-id>] [--json]
|
|
31
|
+
ocx account reset-credits <account-id|main> [--consume --yes] [--json]
|
|
28
32
|
|
|
29
33
|
List and switch provider accounts and API-key pools (masked output only).
|
|
30
34
|
'main' selects the Codex App login for the openai account pool.`;
|
|
@@ -57,7 +61,7 @@ function displayId(id: string): string {
|
|
|
57
61
|
|
|
58
62
|
function statusText(row: AccountRow): string {
|
|
59
63
|
const parts: string[] = [];
|
|
60
|
-
if (row.active) parts.push(row.type === "codex" ? "
|
|
64
|
+
if (row.active) parts.push(row.type === "codex" ? "selected" : "active");
|
|
61
65
|
if (row.needsReauth) parts.push("needs-reauth");
|
|
62
66
|
return parts.join(" ");
|
|
63
67
|
}
|
|
@@ -260,6 +264,10 @@ export async function cmdAccount(args: string[], deps: AccountDeps = {}): Promis
|
|
|
260
264
|
if (sub === "remove") return await cmdRemove(rest, deps);
|
|
261
265
|
if (sub === "clear-cooldown") return await cmdClearCooldown(rest, deps);
|
|
262
266
|
if (sub === "add-key") return await cmdAddKey(rest, deps);
|
|
267
|
+
if (["login", "reauth", "code", "cancel", "reset-credits"].includes(sub ?? "")) {
|
|
268
|
+
const { handleAccountAuthCommand } = await import("./account-auth");
|
|
269
|
+
return await handleAccountAuthCommand(sub!, rest, deps) ?? 1;
|
|
270
|
+
}
|
|
263
271
|
console.error(ACCOUNT_USAGE);
|
|
264
272
|
return 1;
|
|
265
273
|
} catch (err) {
|
package/src/cli/agent.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliUsageError,
|
|
3
|
+
csv,
|
|
4
|
+
printData,
|
|
5
|
+
rejectArgs,
|
|
6
|
+
runCliAction,
|
|
7
|
+
runtimeRequest,
|
|
8
|
+
summaryLines,
|
|
9
|
+
takeBooleanOption,
|
|
10
|
+
takeFlag,
|
|
11
|
+
takeIntegerOption,
|
|
12
|
+
takeOption,
|
|
13
|
+
type RuntimeApiDeps,
|
|
14
|
+
} from "./runtime-api";
|
|
15
|
+
|
|
16
|
+
const USAGE = `Usage:
|
|
17
|
+
ocx agent [status] [--json]
|
|
18
|
+
ocx agent injection <status|set> [--model <id|->] [--effort <level|->]
|
|
19
|
+
[--prompt <text|->] [--guidance <on|off>] [--json]
|
|
20
|
+
ocx agent effort <status|set> [--main <level|->] [--subagent <level|->] [--json]
|
|
21
|
+
ocx agent subagents <status|set|clear> [model,model...] [--json]
|
|
22
|
+
ocx agent fallback <status|set|clear> [model,model...] [--poll-ms <5000-600000>] [--json]
|
|
23
|
+
ocx agent sidecar <status|web|vision> [--model <id|->] [--backend <openai|anthropic|->]
|
|
24
|
+
[--reasoning <level>] [--max-descriptions <n>] [--json]`;
|
|
25
|
+
|
|
26
|
+
function clearable(value: string | undefined): string | null | undefined {
|
|
27
|
+
return value === "-" ? null : value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function status(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
31
|
+
const args = [...argv];
|
|
32
|
+
const wantsJson = takeFlag(args, "--json");
|
|
33
|
+
rejectArgs(args, USAGE);
|
|
34
|
+
const [v2, injection, caps, subagents, fallback, sidecars] = await Promise.all([
|
|
35
|
+
runtimeRequest("/api/v2", {}, deps),
|
|
36
|
+
runtimeRequest("/api/injection-model", {}, deps),
|
|
37
|
+
runtimeRequest("/api/effort-caps", {}, deps),
|
|
38
|
+
runtimeRequest("/api/subagent-models", {}, deps),
|
|
39
|
+
runtimeRequest("/api/subagent-model-fallback", {}, deps),
|
|
40
|
+
runtimeRequest("/api/sidecar-settings", {}, deps),
|
|
41
|
+
]);
|
|
42
|
+
const result = { v2, injection, caps, subagents, fallback, sidecars };
|
|
43
|
+
printData(result, wantsJson, summaryLines(result));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function injection(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
47
|
+
const args = [...argv];
|
|
48
|
+
const action = (args.shift() ?? "status").toLowerCase();
|
|
49
|
+
const wantsJson = takeFlag(args, "--json");
|
|
50
|
+
if (action === "status") {
|
|
51
|
+
rejectArgs(args, USAGE);
|
|
52
|
+
const result = await runtimeRequest("/api/injection-model", {}, deps);
|
|
53
|
+
printData(result, wantsJson, summaryLines(result));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (action !== "set") throw new CliUsageError(`unknown injection action ${action}`, USAGE);
|
|
57
|
+
const model = clearable(takeOption(args, "--model"));
|
|
58
|
+
const effort = clearable(takeOption(args, "--effort"));
|
|
59
|
+
const prompt = clearable(takeOption(args, "--prompt"));
|
|
60
|
+
const guidance = takeBooleanOption(args, "--guidance");
|
|
61
|
+
rejectArgs(args, USAGE);
|
|
62
|
+
const body: Record<string, unknown> = {};
|
|
63
|
+
if (model !== undefined) body.model = model;
|
|
64
|
+
if (effort !== undefined) body.effort = effort;
|
|
65
|
+
if (prompt !== undefined) body.prompt = prompt;
|
|
66
|
+
if (guidance !== undefined) body.multiAgentGuidanceEnabled = guidance;
|
|
67
|
+
if (Object.keys(body).length === 0) throw new CliUsageError("at least one injection option is required", USAGE);
|
|
68
|
+
const result = await runtimeRequest("/api/injection-model", { method: "PUT", body: JSON.stringify(body) }, deps);
|
|
69
|
+
printData(result, wantsJson, ["Agent injection settings updated."]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function effort(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
73
|
+
const args = [...argv];
|
|
74
|
+
const action = (args.shift() ?? "status").toLowerCase();
|
|
75
|
+
const wantsJson = takeFlag(args, "--json");
|
|
76
|
+
if (action === "status") {
|
|
77
|
+
rejectArgs(args, USAGE);
|
|
78
|
+
const result = await runtimeRequest("/api/effort-caps", {}, deps);
|
|
79
|
+
printData(result, wantsJson, summaryLines(result));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (action !== "set") throw new CliUsageError(`unknown effort action ${action}`, USAGE);
|
|
83
|
+
const main = clearable(takeOption(args, "--main"));
|
|
84
|
+
const subagent = clearable(takeOption(args, "--subagent"));
|
|
85
|
+
rejectArgs(args, USAGE);
|
|
86
|
+
const body: Record<string, unknown> = {};
|
|
87
|
+
if (main !== undefined) body.effortCap = main;
|
|
88
|
+
if (subagent !== undefined) body.subagentEffortCap = subagent;
|
|
89
|
+
if (Object.keys(body).length === 0) throw new CliUsageError("--main and/or --subagent is required", USAGE);
|
|
90
|
+
const result = await runtimeRequest("/api/effort-caps", { method: "PUT", body: JSON.stringify(body) }, deps);
|
|
91
|
+
printData(result, wantsJson, ["Agent effort caps updated."]);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function subagents(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
95
|
+
const args = [...argv];
|
|
96
|
+
const action = (args.shift() ?? "status").toLowerCase();
|
|
97
|
+
const wantsJson = takeFlag(args, "--json");
|
|
98
|
+
if (action === "status") {
|
|
99
|
+
rejectArgs(args, USAGE);
|
|
100
|
+
const result = await runtimeRequest("/api/subagent-models", {}, deps);
|
|
101
|
+
printData(result, wantsJson, summaryLines(result));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
let models: string[];
|
|
105
|
+
if (action === "clear") models = [];
|
|
106
|
+
else if (action === "set") {
|
|
107
|
+
const raw = args.shift();
|
|
108
|
+
if (!raw) throw new CliUsageError("comma-separated subagent models are required", USAGE);
|
|
109
|
+
models = csv(raw) ?? [];
|
|
110
|
+
} else throw new CliUsageError(`unknown subagents action ${action}`, USAGE);
|
|
111
|
+
rejectArgs(args, USAGE);
|
|
112
|
+
if (models.length > 5) throw new CliUsageError("at most 5 subagent models are allowed", USAGE);
|
|
113
|
+
const result = await runtimeRequest("/api/subagent-models", { method: "PUT", body: JSON.stringify({ models }) }, deps);
|
|
114
|
+
printData(result, wantsJson, [`Subagent roster: ${models.join(", ") || "cleared"}`]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function fallback(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
118
|
+
const args = [...argv];
|
|
119
|
+
const action = (args.shift() ?? "status").toLowerCase();
|
|
120
|
+
const wantsJson = takeFlag(args, "--json");
|
|
121
|
+
if (action === "status") {
|
|
122
|
+
rejectArgs(args, USAGE);
|
|
123
|
+
const result = await runtimeRequest("/api/subagent-model-fallback", {}, deps);
|
|
124
|
+
printData(result, wantsJson, summaryLines(result));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const body: Record<string, unknown> = {};
|
|
128
|
+
if (action === "clear") body.models = [];
|
|
129
|
+
else if (action === "set") {
|
|
130
|
+
const raw = args[0] && !args[0].startsWith("--") ? args.shift() : undefined;
|
|
131
|
+
if (raw) body.models = csv(raw) ?? [];
|
|
132
|
+
} else throw new CliUsageError(`unknown fallback action ${action}`, USAGE);
|
|
133
|
+
const pollMs = takeIntegerOption(args, "--poll-ms", { min: 5_000 });
|
|
134
|
+
if (pollMs !== undefined) {
|
|
135
|
+
if (pollMs > 600_000) throw new CliUsageError("--poll-ms must be <= 600000", USAGE);
|
|
136
|
+
body.pollMs = pollMs;
|
|
137
|
+
}
|
|
138
|
+
rejectArgs(args, USAGE);
|
|
139
|
+
if (Object.keys(body).length === 0) throw new CliUsageError("models and/or --poll-ms is required", USAGE);
|
|
140
|
+
const result = await runtimeRequest("/api/subagent-model-fallback", { method: "PUT", body: JSON.stringify(body) }, deps);
|
|
141
|
+
printData(result, wantsJson, ["Subagent fallback settings updated."]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function sidecar(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
145
|
+
const args = [...argv];
|
|
146
|
+
const section = (args.shift() ?? "status").toLowerCase();
|
|
147
|
+
const wantsJson = takeFlag(args, "--json");
|
|
148
|
+
if (section === "status") {
|
|
149
|
+
rejectArgs(args, USAGE);
|
|
150
|
+
const result = await runtimeRequest("/api/sidecar-settings", {}, deps);
|
|
151
|
+
printData(result, wantsJson, summaryLines(result));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (section !== "web" && section !== "vision") throw new CliUsageError("sidecar must be web, vision, or status", USAGE);
|
|
155
|
+
const model = takeOption(args, "--model");
|
|
156
|
+
const backend = takeOption(args, "--backend");
|
|
157
|
+
const reasoning = takeOption(args, "--reasoning");
|
|
158
|
+
const maxDescriptionsPerTurn = takeIntegerOption(args, "--max-descriptions", { min: 1 });
|
|
159
|
+
rejectArgs(args, USAGE);
|
|
160
|
+
const settings: Record<string, unknown> = {};
|
|
161
|
+
if (model !== undefined) settings.model = model === "-" ? "" : model;
|
|
162
|
+
if (backend !== undefined) settings.backend = backend === "-" ? null : backend;
|
|
163
|
+
if (reasoning !== undefined) settings.reasoning = reasoning;
|
|
164
|
+
if (maxDescriptionsPerTurn !== undefined) settings.maxDescriptionsPerTurn = maxDescriptionsPerTurn;
|
|
165
|
+
if (Object.keys(settings).length === 0) throw new CliUsageError("at least one sidecar option is required", USAGE);
|
|
166
|
+
const body = section === "web" ? { webSearch: settings } : { vision: settings };
|
|
167
|
+
const result = await runtimeRequest("/api/sidecar-settings", { method: "PUT", body: JSON.stringify(body) }, deps);
|
|
168
|
+
printData(result, wantsJson, [`${section} sidecar settings updated.`]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function handleAgentCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
|
|
172
|
+
return runCliAction(async () => {
|
|
173
|
+
const [sub = "status", ...rest] = argv;
|
|
174
|
+
if (sub === "status") await status(rest, deps);
|
|
175
|
+
else if (sub === "injection" || sub === "guidance") await injection(rest, deps);
|
|
176
|
+
else if (sub === "effort") await effort(rest, deps);
|
|
177
|
+
else if (sub === "subagents" || sub === "roster") await subagents(rest, deps);
|
|
178
|
+
else if (sub === "fallback") await fallback(rest, deps);
|
|
179
|
+
else if (sub === "sidecar") await sidecar(rest, deps);
|
|
180
|
+
else throw new CliUsageError(`unknown agent command ${sub}`, USAGE);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const AGENT_USAGE = USAGE;
|
package/src/cli/combo.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliUsageError,
|
|
3
|
+
printData,
|
|
4
|
+
rejectArgs,
|
|
5
|
+
runCliAction,
|
|
6
|
+
runtimeRequest,
|
|
7
|
+
takeFlag,
|
|
8
|
+
takeIntegerOption,
|
|
9
|
+
takeOption,
|
|
10
|
+
type RuntimeApiDeps,
|
|
11
|
+
} from "./runtime-api";
|
|
12
|
+
|
|
13
|
+
const USAGE = `Usage:
|
|
14
|
+
ocx combo [list] [--json]
|
|
15
|
+
ocx combo show <id> [--json]
|
|
16
|
+
ocx combo set <id> --targets <provider/model[:weight],...>
|
|
17
|
+
[--strategy <failover|round-robin>] [--sticky <1-100>]
|
|
18
|
+
[--effort <low|medium|high|xhigh|max|ultra|->] [--alias <name|->]
|
|
19
|
+
[--rename-from <id>] [--json]
|
|
20
|
+
ocx combo remove <id> --yes [--json]`;
|
|
21
|
+
|
|
22
|
+
type ComboRow = Record<string, unknown> & { id?: string; model?: string };
|
|
23
|
+
|
|
24
|
+
function parseTargets(value: string): Array<{ provider: string; model: string; weight?: number }> {
|
|
25
|
+
const targets = value.split(",").map(part => part.trim()).filter(Boolean).map(part => {
|
|
26
|
+
const colon = part.lastIndexOf(":");
|
|
27
|
+
let selector = part;
|
|
28
|
+
let weight: number | undefined;
|
|
29
|
+
if (colon > part.indexOf("/")) {
|
|
30
|
+
const maybeWeight = Number(part.slice(colon + 1));
|
|
31
|
+
if (Number.isInteger(maybeWeight)) {
|
|
32
|
+
selector = part.slice(0, colon);
|
|
33
|
+
weight = maybeWeight;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const slash = selector.indexOf("/");
|
|
37
|
+
if (slash <= 0 || slash === selector.length - 1) throw new CliUsageError(`invalid target "${part}"; use provider/model[:weight]`, USAGE);
|
|
38
|
+
const target = { provider: selector.slice(0, slash), model: selector.slice(slash + 1), ...(weight !== undefined ? { weight } : {}) };
|
|
39
|
+
if (weight !== undefined && (weight < 1 || weight > 10_000)) throw new CliUsageError(`target weight must be 1-10000: ${part}`, USAGE);
|
|
40
|
+
return target;
|
|
41
|
+
});
|
|
42
|
+
if (targets.length === 0) throw new CliUsageError("--targets requires at least one provider/model", USAGE);
|
|
43
|
+
return targets;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function list(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
47
|
+
const args = [...argv];
|
|
48
|
+
const wantsJson = takeFlag(args, "--json");
|
|
49
|
+
rejectArgs(args, USAGE);
|
|
50
|
+
const result = await runtimeRequest<{ combos?: ComboRow[] }>("/api/combos", {}, deps);
|
|
51
|
+
const rows = result.combos ?? [];
|
|
52
|
+
printData(result, wantsJson, rows.length ? rows.map(row => `${String(row.id)} ${String(row.model ?? `combo/${row.id}`)}`) : ["No combos configured."]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function show(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
56
|
+
const args = [...argv];
|
|
57
|
+
const id = args.shift();
|
|
58
|
+
const wantsJson = takeFlag(args, "--json");
|
|
59
|
+
if (!id) throw new CliUsageError("combo id is required", USAGE);
|
|
60
|
+
rejectArgs(args, USAGE);
|
|
61
|
+
const result = await runtimeRequest<{ combos?: ComboRow[] }>("/api/combos", {}, deps);
|
|
62
|
+
const combo = (result.combos ?? []).find(row => row.id === id);
|
|
63
|
+
if (!combo) throw new CliUsageError(`unknown combo ${id}`);
|
|
64
|
+
printData(combo, wantsJson);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function set(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
68
|
+
const args = [...argv];
|
|
69
|
+
const id = args.shift()?.trim();
|
|
70
|
+
const wantsJson = takeFlag(args, "--json");
|
|
71
|
+
if (!id) throw new CliUsageError("combo id is required", USAGE);
|
|
72
|
+
const targetsRaw = takeOption(args, "--targets");
|
|
73
|
+
if (!targetsRaw) throw new CliUsageError("--targets is required", USAGE);
|
|
74
|
+
const strategy = takeOption(args, "--strategy") ?? "failover";
|
|
75
|
+
if (strategy !== "failover" && strategy !== "round-robin") throw new CliUsageError("--strategy must be failover or round-robin", USAGE);
|
|
76
|
+
const stickyLimit = takeIntegerOption(args, "--sticky", { min: 1 }) ?? 1;
|
|
77
|
+
if (stickyLimit > 100) throw new CliUsageError("--sticky must be <= 100", USAGE);
|
|
78
|
+
const effort = takeOption(args, "--effort");
|
|
79
|
+
const alias = takeOption(args, "--alias");
|
|
80
|
+
const renameFrom = takeOption(args, "--rename-from");
|
|
81
|
+
rejectArgs(args, USAGE);
|
|
82
|
+
const combo: Record<string, unknown> = {
|
|
83
|
+
strategy,
|
|
84
|
+
stickyLimit,
|
|
85
|
+
targets: parseTargets(targetsRaw),
|
|
86
|
+
};
|
|
87
|
+
if (effort !== undefined) combo.defaultEffort = effort === "-" ? null : effort;
|
|
88
|
+
if (alias !== undefined) combo.alias = alias === "-" ? "" : alias;
|
|
89
|
+
const result = await runtimeRequest("/api/combos", {
|
|
90
|
+
method: "PUT",
|
|
91
|
+
body: JSON.stringify({ id, combo, ...(renameFrom ? { renameFrom } : {}) }),
|
|
92
|
+
}, deps);
|
|
93
|
+
printData(result, wantsJson, [`Saved combo ${id}.`]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function remove(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
97
|
+
const args = [...argv];
|
|
98
|
+
const id = args.shift()?.trim();
|
|
99
|
+
const wantsJson = takeFlag(args, "--json");
|
|
100
|
+
const yes = takeFlag(args, "--yes");
|
|
101
|
+
if (!id) throw new CliUsageError("combo id is required", USAGE);
|
|
102
|
+
if (!yes) throw new CliUsageError("remove requires --yes", USAGE);
|
|
103
|
+
rejectArgs(args, USAGE);
|
|
104
|
+
const result = await runtimeRequest(`/api/combos?id=${encodeURIComponent(id)}`, { method: "DELETE" }, deps);
|
|
105
|
+
printData(result, wantsJson, [`Removed combo ${id}.`]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function handleComboCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
|
|
109
|
+
return runCliAction(async () => {
|
|
110
|
+
const [sub = "list", ...rest] = argv;
|
|
111
|
+
if (sub === "list") await list(rest, deps);
|
|
112
|
+
else if (sub === "show") await show(rest, deps);
|
|
113
|
+
else if (sub === "set" || sub === "create" || sub === "update") await set(rest, deps);
|
|
114
|
+
else if (sub === "remove" || sub === "delete") await remove(rest, deps);
|
|
115
|
+
else throw new CliUsageError(`unknown combo command ${sub}`, USAGE);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const COMBO_USAGE = USAGE;
|