@yansigit/opencodex 2.36.1-dev.20260829.48 → 2.36.1
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/gui/dist/assets/{ApiKeys-LOOFiZfv.js → ApiKeys-CsmNyf4I.js} +1 -1
- package/gui/dist/assets/{Claude-BwKzpXe3.js → Claude-B4sHsGTD.js} +1 -1
- package/gui/dist/assets/{CodexSet-B5qi9KeE.js → CodexSet-BuAQ4YQn.js} +1 -1
- package/gui/dist/assets/{FileIntegrationPage-B0sUef6W.js → FileIntegrationPage-BC65sEaS.js} +1 -1
- package/gui/dist/assets/{Grok-bsb4n-f4.js → Grok-DNJWlISf.js} +1 -1
- package/gui/dist/assets/{Integrations-CSMwFDvM.js → Integrations-vGJshFZK.js} +2 -2
- package/gui/dist/assets/{IntegrationsOverview-CTORdKJA.js → IntegrationsOverview-DYMkDFHE.js} +1 -1
- package/gui/dist/assets/{Logs-BWYbfELf.js → Logs-Coc7_RPP.js} +1 -1
- package/gui/dist/assets/{Models-CexEtdT1.js → Models-CKVT8Meb.js} +1 -1
- package/gui/dist/assets/{NumberStepper-BapSFQnW.js → NumberStepper-Dug4wR8n.js} +1 -1
- package/gui/dist/assets/{Providers-BwUFeAgA.js → Providers-DyaCHRWL.js} +1 -1
- package/gui/dist/assets/{RestoreDialog-CD8piq90.js → RestoreDialog-BpuvVU2w.js} +1 -1
- package/gui/dist/assets/{Startup-BgX731C2.js → Startup-DAUQqNEN.js} +1 -1
- package/gui/dist/assets/{Storage-Bgq7HphP.js → Storage-DfP95SlJ.js} +1 -1
- package/gui/dist/assets/{Subagents-ChxItbeF.js → Subagents-CMNsZLfk.js} +1 -1
- package/gui/dist/assets/{Usage-BkylzP50.js → Usage-ESFRfBMO.js} +1 -1
- package/gui/dist/assets/{codex-stale-banner-D4gvSWnO.js → codex-stale-banner-W4FhikN7.js} +1 -1
- package/gui/dist/assets/{data-surface-BtO3lwam.js → data-surface-B8PX8rjm.js} +1 -1
- package/gui/dist/assets/{data-surface-pCXChiBf.js → data-surface-CETFXqiA.js} +1 -1
- package/gui/dist/assets/{index-CNopOid3.js → index-DHQHBRhb.js} +3 -3
- package/gui/dist/assets/{model-display-CiUpg8T9.js → model-display-OBTJhQwT.js} +1 -1
- package/gui/dist/assets/{provider-payload-b2jlS-On.js → provider-payload-CJRJUn7w.js} +1 -1
- package/gui/dist/assets/{section-tabs-BP7gEPK6.js → section-tabs-B8sLMiz6.js} +1 -1
- package/gui/dist/assets/shared-DI18-uF7.js +69 -0
- package/gui/dist/index.html +2 -2
- package/package.json +3 -1
- package/src/adapters/base.ts +26 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/cursor-errors.ts +15 -0
- package/src/adapters/cursor/discovery.ts +34 -41
- package/src/adapters/cursor/envelope-echo.ts +128 -0
- package/src/adapters/cursor/request-builder.ts +19 -12
- package/src/adapters/cursor/tool-definitions.ts +2 -1
- package/src/adapters/cursor/tool-result-normalize.ts +23 -31
- package/src/adapters/cursor.ts +21 -2
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +71 -2
- package/src/adapters/google.ts +19 -4
- package/src/adapters/kiro-constants.ts +12 -0
- package/src/adapters/kiro.ts +128 -11
- package/src/adapters/openai-chat.ts +16 -2
- package/src/adapters/openai-responses.ts +15 -2
- package/src/adapters/tool-catalog-nudge.ts +2 -1
- package/src/adapters/xai-web-search.ts +10 -14
- package/src/claude/outbound.ts +14 -3
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +93 -16
- package/src/cli/account-extended.ts +262 -36
- package/src/cli/account-main.ts +12 -12
- package/src/cli/account.ts +40 -10
- package/src/cli/agent.ts +8 -1
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +535 -0
- package/src/cli/claude-desktop.ts +31 -11
- package/src/cli/dispatch.ts +195 -27
- package/src/cli/doctor.ts +100 -1
- package/src/cli/help.ts +11 -2
- package/src/cli/index.ts +19 -3
- package/src/cli/inspect.ts +230 -0
- package/src/cli/observe.ts +11 -3
- package/src/cli/registry.ts +34 -2
- package/src/cli/runtime-api.ts +51 -7
- package/src/cli/status.ts +16 -0
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/usage-report.ts +52 -2
- package/src/cli/version-skew.ts +46 -0
- package/src/codex/account-label.ts +21 -0
- package/src/codex/catalog/provider-fetch.ts +4 -0
- package/src/codex/transition-state.ts +12 -3
- package/src/compatibility/openai-responses.ts +9 -1
- package/src/generated/compatibility-version.json +94 -58
- package/src/integrations/ownership-policy.ts +24 -5
- package/src/integrations/ownership.ts +36 -2
- package/src/integrations/state.ts +40 -7
- package/src/integrations/writer.ts +21 -3
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/errors.ts +25 -1
- package/src/lib/service-secrets.ts +15 -0
- package/src/oauth/store.ts +16 -6
- package/src/providers/label.ts +34 -1
- package/src/responses/turn-termination.ts +107 -0
- package/src/server/management/logs-usage-routes.ts +5 -27
- package/src/server/management/route-registry.ts +317 -0
- package/src/server/proxy-liveness.ts +27 -4
- package/src/server/request-log.ts +37 -17
- package/src/server/responses/core.ts +80 -4
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/service.ts +34 -0
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +79 -33
- package/src/usage/log.ts +32 -6
- package/src/usage/summary.ts +22 -34
- package/gui/dist/assets/shared-BdYGV-yJ.js +0 -69
package/src/cli/account-main.ts
CHANGED
|
@@ -190,8 +190,8 @@ export async function cmdNativeMainAccount(args: string[], deps: AccountDeps): P
|
|
|
190
190
|
if (args.length > 0 || confirmed || rollback) return reject(args);
|
|
191
191
|
const path = sub === "doctor" ? "/api/native-main-profiles/doctor" : "/api/native-main-profiles";
|
|
192
192
|
const result = await apiJson(deps, baseUrl, "GET", path);
|
|
193
|
-
if (result.status === 0) return proxyUnreachable();
|
|
194
|
-
if (result.status !== 200) return apiError(result.json, `failed to ${sub} native profiles
|
|
193
|
+
if (result.status === 0) return proxyUnreachable(result.transportError);
|
|
194
|
+
if (result.status !== 200) return apiError(result.json, `failed to ${sub} native profiles`, result.status);
|
|
195
195
|
if (wantsJson || sub === "doctor") console.log(JSON.stringify(result.json, null, 2));
|
|
196
196
|
else printProfiles(Array.isArray(result.json.profiles) ? result.json.profiles as PublicProfile[] : []);
|
|
197
197
|
return 0;
|
|
@@ -201,8 +201,8 @@ export async function cmdNativeMainAccount(args: string[], deps: AccountDeps): P
|
|
|
201
201
|
const label = args.shift();
|
|
202
202
|
if (!label || args.length > 0 || confirmed || rollback) return reject(args);
|
|
203
203
|
const result = await apiJson(deps, baseUrl, "POST", "/api/native-main-profiles/register", { label });
|
|
204
|
-
if (result.status === 0) return proxyUnreachable();
|
|
205
|
-
if (result.status !== 200) return apiError(result.json, "failed to register the current native login");
|
|
204
|
+
if (result.status === 0) return proxyUnreachable(result.transportError);
|
|
205
|
+
if (result.status !== 200) return apiError(result.json, "failed to register the current native login", result.status);
|
|
206
206
|
if (wantsJson) console.log(JSON.stringify(result.json, null, 2));
|
|
207
207
|
else console.log(`Registered '${label}' for ${effectiveCodexHome(result.json)}.`);
|
|
208
208
|
return 0;
|
|
@@ -212,8 +212,8 @@ export async function cmdNativeMainAccount(args: string[], deps: AccountDeps): P
|
|
|
212
212
|
const label = args.shift();
|
|
213
213
|
if (!label || args.length > 0 || wantsJson || confirmed || rollback) return reject(args);
|
|
214
214
|
const stage = await apiJson(deps, baseUrl, "POST", "/api/native-main-profiles/stage", {});
|
|
215
|
-
if (stage.status === 0) return proxyUnreachable();
|
|
216
|
-
if (stage.status !== 200) return apiError(stage.json, "failed to prepare native login staging");
|
|
215
|
+
if (stage.status === 0) return proxyUnreachable(stage.transportError);
|
|
216
|
+
if (stage.status !== 200) return apiError(stage.json, "failed to prepare native login staging", stage.status);
|
|
217
217
|
const stageId = typeof stage.json.stageId === "string" ? stage.json.stageId : "";
|
|
218
218
|
const writerToken = typeof stage.json.writerToken === "string" ? stage.json.writerToken : "";
|
|
219
219
|
const stagingHome = typeof stage.json.stagingCodexHome === "string" ? stage.json.stagingCodexHome : "";
|
|
@@ -258,8 +258,8 @@ export async function cmdNativeMainAccount(args: string[], deps: AccountDeps): P
|
|
|
258
258
|
if (leaseLost) throw new Error("The native-login staging lease was lost before login completed.");
|
|
259
259
|
if (exitCode !== 0) throw new Error("Official Codex login did not complete successfully.");
|
|
260
260
|
const finish = await apiJson(deps, baseUrl, "POST", "/api/native-main-profiles/stage/finish", { stageId, writerToken, label });
|
|
261
|
-
if (finish.status === 0) return proxyUnreachable();
|
|
262
|
-
if (finish.status !== 200) return apiError(finish.json, "failed to encrypt the staged native login");
|
|
261
|
+
if (finish.status === 0) return proxyUnreachable(finish.transportError);
|
|
262
|
+
if (finish.status !== 200) return apiError(finish.json, "failed to encrypt the staged native login", finish.status);
|
|
263
263
|
finished = true;
|
|
264
264
|
console.log(`Added encrypted native profile '${label}' for ${effectiveCodexHome(finish.json)}.`);
|
|
265
265
|
return 0;
|
|
@@ -283,8 +283,8 @@ export async function cmdNativeMainAccount(args: string[], deps: AccountDeps): P
|
|
|
283
283
|
return reject(args);
|
|
284
284
|
}
|
|
285
285
|
const result = await apiJson(deps, baseUrl, "POST", "/api/native-main-profiles/switch", { target, confirmedStopped: true });
|
|
286
|
-
if (result.status === 0) return proxyUnreachable();
|
|
287
|
-
if (result.status !== 200) return apiError(result.json, "failed to switch the native login");
|
|
286
|
+
if (result.status === 0) return proxyUnreachable(result.transportError);
|
|
287
|
+
if (result.status !== 200) return apiError(result.json, "failed to switch the native login", result.status);
|
|
288
288
|
if (wantsJson) console.log(JSON.stringify(result.json, null, 2));
|
|
289
289
|
else {
|
|
290
290
|
const profile = result.json.activeProfile as PublicProfile | undefined;
|
|
@@ -301,8 +301,8 @@ export async function cmdNativeMainAccount(args: string[], deps: AccountDeps): P
|
|
|
301
301
|
const result = await apiJson(deps, baseUrl, "POST", "/api/native-main-profiles/recover", rollback
|
|
302
302
|
? { rollback: true, confirmedStopped: true }
|
|
303
303
|
: { rollback: false });
|
|
304
|
-
if (result.status === 0) return proxyUnreachable();
|
|
305
|
-
if (result.status !== 200) return apiError(result.json, "failed to recover the native-profile transaction");
|
|
304
|
+
if (result.status === 0) return proxyUnreachable(result.transportError);
|
|
305
|
+
if (result.status !== 200) return apiError(result.json, "failed to recover the native-profile transaction", result.status);
|
|
306
306
|
if (wantsJson) console.log(JSON.stringify(result.json, null, 2));
|
|
307
307
|
else {
|
|
308
308
|
const home = effectiveCodexHome(result.json);
|
package/src/cli/account.ts
CHANGED
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
import { loadConfig } from "../config";
|
|
3
3
|
import { providerCodexAccountMode } from "../providers/registry";
|
|
4
4
|
import type { OcxConfig } from "../types";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
cmdAddKey,
|
|
7
|
+
cmdAlias,
|
|
8
|
+
cmdAutoSwitch,
|
|
9
|
+
cmdClearCooldown,
|
|
10
|
+
cmdImport,
|
|
11
|
+
cmdPause,
|
|
12
|
+
cmdPauseExhausted,
|
|
13
|
+
cmdPriority,
|
|
14
|
+
cmdRefresh,
|
|
15
|
+
cmdRemove,
|
|
16
|
+
cmdSticky,
|
|
17
|
+
cmdStrategy,
|
|
18
|
+
} from "./account-extended";
|
|
6
19
|
import { apiError, apiJson, classifyAccount, fetchRows, proxyUnreachable, resolveBaseUrl, type AccountDeps, type AccountRow, type AccountType, type ApiResult }
|
|
7
20
|
from "./account-api";
|
|
8
21
|
|
|
@@ -23,6 +36,11 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
23
36
|
ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]
|
|
24
37
|
ocx account alias <provider> <account-or-key-id> <display-name|-> [--json]
|
|
25
38
|
ocx account priority <provider> <account-id|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]
|
|
39
|
+
ocx account pause <provider> <account-id|main> [--json]
|
|
40
|
+
ocx account resume <provider> <account-id|main> [--json]
|
|
41
|
+
ocx account pause-exhausted <provider> [--json]
|
|
42
|
+
ocx account strategy <provider> [<quota|round-robin|fill-first>] [--json]
|
|
43
|
+
ocx account sticky <provider> [<1-100>] [--json]
|
|
26
44
|
ocx account remove <provider> <account-or-key-id|main> --yes [--json]
|
|
27
45
|
ocx account clear-cooldown <provider> <account-id|main> [--json]
|
|
28
46
|
ocx account add-key <provider> [--label <label>] [--json]
|
|
@@ -64,6 +82,10 @@ function displayId(id: string): string {
|
|
|
64
82
|
|
|
65
83
|
function statusText(row: AccountRow): string {
|
|
66
84
|
const parts: string[] = [];
|
|
85
|
+
// `paused` leads, and does NOT replace `selected`. A paused-but-selected account is the
|
|
86
|
+
// state an operator most needs named -- requests route to it while the pool believes it is
|
|
87
|
+
// held out -- so printing only one of the two would hide exactly the confusing case (#2703).
|
|
88
|
+
if (row.paused) parts.push("paused");
|
|
67
89
|
if (row.active) parts.push(row.type === "codex" ? "selected" : "active");
|
|
68
90
|
if (row.needsReauth) parts.push("needs-reauth");
|
|
69
91
|
return parts.join(" ");
|
|
@@ -150,8 +172,8 @@ async function cmdList(rest: string[], deps: AccountDeps): Promise<number> {
|
|
|
150
172
|
};
|
|
151
173
|
push("openai", "codex");
|
|
152
174
|
const providersRes = await apiJson(deps, baseUrl, "GET", "/api/oauth/providers");
|
|
153
|
-
if (providersRes.status === 0) return proxyUnreachable();
|
|
154
|
-
if (providersRes.status !== 200) return apiError(providersRes.json, "failed to list OAuth providers");
|
|
175
|
+
if (providersRes.status === 0) return proxyUnreachable(providersRes.transportError);
|
|
176
|
+
if (providersRes.status !== 200) return apiError(providersRes.json, "failed to list OAuth providers", providersRes.status);
|
|
155
177
|
if (Array.isArray(providersRes.json.providers)) {
|
|
156
178
|
for (const p of providersRes.json.providers) {
|
|
157
179
|
if (typeof p === "string") push(p, "live-oauth-list");
|
|
@@ -164,9 +186,10 @@ async function cmdList(rest: string[], deps: AccountDeps): Promise<number> {
|
|
|
164
186
|
const notes: string[] = [];
|
|
165
187
|
for (const t of targets) {
|
|
166
188
|
const r = await fetchRows(deps, baseUrl, t.name, t.type, wantsQuota ? { refresh: refreshQuota } : undefined);
|
|
167
|
-
if (r.networkDown) return proxyUnreachable();
|
|
189
|
+
if (r.networkDown) return proxyUnreachable(r.transportError);
|
|
168
190
|
if (r.errorJson) {
|
|
169
|
-
if (name) return apiError(r.errorJson, `failed to list ${t.name}
|
|
191
|
+
if (name) return apiError(r.errorJson, `failed to list ${t.name}`, r.status);
|
|
192
|
+
|
|
170
193
|
const errorText = typeof r.errorJson.error === "string" ? r.errorJson.error : "";
|
|
171
194
|
const skipUnknownKey = t.type === "api-key"
|
|
172
195
|
&& r.status === 404
|
|
@@ -176,7 +199,7 @@ async function cmdList(rest: string[], deps: AccountDeps): Promise<number> {
|
|
|
176
199
|
&& r.status === 400
|
|
177
200
|
&& errorText.includes("unknown oauth provider");
|
|
178
201
|
if (skipUnknownKey || skipConfigOAuth) continue;
|
|
179
|
-
return apiError(r.errorJson, `failed to list ${t.name}
|
|
202
|
+
return apiError(r.errorJson, `failed to list ${t.name}`, r.status);
|
|
180
203
|
}
|
|
181
204
|
if (r.rows.length === 0) {
|
|
182
205
|
if (showAll) notes.push(`${t.name}: no stored accounts or keys`);
|
|
@@ -222,8 +245,8 @@ async function cmdCurrent(rest: string[], deps: AccountDeps): Promise<number> {
|
|
|
222
245
|
const baseUrl = await resolveBaseUrl(deps);
|
|
223
246
|
if (!baseUrl) return proxyUnreachable();
|
|
224
247
|
const r = await fetchRows(deps, baseUrl, name, c.type);
|
|
225
|
-
if (r.networkDown) return proxyUnreachable();
|
|
226
|
-
if (r.errorJson) return apiError(r.errorJson, `failed to read ${name}
|
|
248
|
+
if (r.networkDown) return proxyUnreachable(r.transportError);
|
|
249
|
+
if (r.errorJson) return apiError(r.errorJson, `failed to read ${name}`, r.status);
|
|
227
250
|
|
|
228
251
|
const activeRow = r.rows.find(row => row.active) ?? null;
|
|
229
252
|
if (wantsJson) {
|
|
@@ -277,8 +300,8 @@ async function cmdUse(rest: string[], deps: AccountDeps): Promise<number> {
|
|
|
277
300
|
activeId = id;
|
|
278
301
|
res = await apiJson(deps, baseUrl, "PUT", "/api/providers/keys/active", { name, id });
|
|
279
302
|
}
|
|
280
|
-
if (res.status === 0) return proxyUnreachable();
|
|
281
|
-
if (res.status !== 200) return apiError(res.json, `failed to switch ${name}
|
|
303
|
+
if (res.status === 0) return proxyUnreachable(res.transportError);
|
|
304
|
+
if (res.status !== 200) return apiError(res.json, `failed to switch ${name}`, res.status);
|
|
282
305
|
|
|
283
306
|
if (wantsJson) console.log(JSON.stringify({ ok: true, provider: name, type: c.type, activeId }, null, 2));
|
|
284
307
|
else console.log(`${name}: active ${c.type === "api-key" ? "key" : "account"} is now ${displayId(activeId)}`);
|
|
@@ -302,6 +325,13 @@ export async function cmdAccount(args: string[], deps: AccountDeps = {}): Promis
|
|
|
302
325
|
if (sub === "auto-switch") return await cmdAutoSwitch(rest, deps);
|
|
303
326
|
if (sub === "alias" || sub === "rename") return await cmdAlias(rest, deps);
|
|
304
327
|
if (sub === "priority") return await cmdPriority(rest, deps);
|
|
328
|
+
// #2702: the server routes existed and only the CLI caller was missing, so these were
|
|
329
|
+
// dashboard-only capabilities.
|
|
330
|
+
if (sub === "pause") return await cmdPause(rest, deps, true);
|
|
331
|
+
if (sub === "resume") return await cmdPause(rest, deps, false);
|
|
332
|
+
if (sub === "pause-exhausted") return await cmdPauseExhausted(rest, deps);
|
|
333
|
+
if (sub === "strategy") return await cmdStrategy(rest, deps);
|
|
334
|
+
if (sub === "sticky") return await cmdSticky(rest, deps);
|
|
305
335
|
if (sub === "remove") return await cmdRemove(rest, deps);
|
|
306
336
|
if (sub === "clear-cooldown") return await cmdClearCooldown(rest, deps);
|
|
307
337
|
if (sub === "add-key") return await cmdAddKey(rest, deps);
|
package/src/cli/agent.ts
CHANGED
|
@@ -32,7 +32,8 @@ const USAGE = `Usage:
|
|
|
32
32
|
ocx agent fallback <status|set|clear> [model,model...] [--poll-ms <5000-600000>] [--json]
|
|
33
33
|
ocx agent sidecar <status|web|vision> [--list] [--model <id|->]
|
|
34
34
|
[--backend web:<openai|anthropic|xai|gemini|exa|-> vision:<openai|anthropic|routed|->]
|
|
35
|
-
[--reasoning <level>] [--max-descriptions <n>] [--json]
|
|
35
|
+
[--reasoning <level>] [--max-descriptions <n>] [--json]
|
|
36
|
+
ocx agent request-user-input [on|off] [--json]`;
|
|
36
37
|
|
|
37
38
|
function clearable(value: string | undefined): string | null | undefined {
|
|
38
39
|
return value === "-" ? null : value;
|
|
@@ -300,6 +301,12 @@ export async function handleAgentCommand(argv: string[], deps: RuntimeApiDeps =
|
|
|
300
301
|
else if (sub === "roles") await roles(rest, deps);
|
|
301
302
|
else if (sub === "fallback") await fallback(rest, deps);
|
|
302
303
|
else if (sub === "sidecar") await sidecar(rest, deps);
|
|
304
|
+
// Lives here rather than as a top-level verb because it is an agent-behavior feature flag:
|
|
305
|
+
// it controls whether default mode may ask the operator a question mid-task.
|
|
306
|
+
else if (sub === "request-user-input") {
|
|
307
|
+
const { requestUserInputAction } = await import("./inspect");
|
|
308
|
+
await requestUserInputAction(rest, deps);
|
|
309
|
+
}
|
|
303
310
|
else throw new CliUsageError(`unknown agent command ${sub}`, USAGE);
|
|
304
311
|
});
|
|
305
312
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ocx capabilities` -- the surface index an agent reads first.
|
|
3
|
+
*
|
|
4
|
+
* The point of this verb is that driving `ocx` programmatically should not require
|
|
5
|
+
* parsing help text. `--json` emits the capability table with the management routes each
|
|
6
|
+
* capability drives; `--route` answers the inverse question.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
CAPABILITIES,
|
|
10
|
+
HEAD_CAPABILITIES,
|
|
11
|
+
capabilitiesForRoute,
|
|
12
|
+
capabilityInvocation,
|
|
13
|
+
type Capability,
|
|
14
|
+
} from "./capabilities";
|
|
15
|
+
import { takeFlag } from "./runtime-api";
|
|
16
|
+
|
|
17
|
+
function takeValueFlag(args: string[], flag: string): string | undefined {
|
|
18
|
+
// Order-independent by construction: scan for the flag anywhere in argv rather than
|
|
19
|
+
// reading a fixed position. Positional flag handling is exactly why
|
|
20
|
+
// `ocx restore back --json` ignored its flag.
|
|
21
|
+
const idx = args.indexOf(flag);
|
|
22
|
+
if (idx === -1) return undefined;
|
|
23
|
+
const value = args[idx + 1];
|
|
24
|
+
args.splice(idx, value === undefined || value.startsWith("-") ? 1 : 2);
|
|
25
|
+
if (value === undefined || value.startsWith("-")) return "";
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function renderHuman(caps: readonly Capability[], includeHead: boolean): void {
|
|
30
|
+
for (const cap of caps) {
|
|
31
|
+
const marker = cap.mutates ? "!" : " ";
|
|
32
|
+
console.log(`${marker} ${capabilityInvocation(cap)}`);
|
|
33
|
+
console.log(` ${cap.summary}`);
|
|
34
|
+
if (cap.routes.length > 0) {
|
|
35
|
+
console.log(` routes: ${cap.routes.map(r => `${r.method} ${r.path}`).join(", ")}`);
|
|
36
|
+
}
|
|
37
|
+
if (cap.flags.length > 0) {
|
|
38
|
+
console.log(` flags: ${cap.flags.map(f => f.name).join(" ")}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (!includeHead) return;
|
|
42
|
+
for (const head of HEAD_CAPABILITIES) {
|
|
43
|
+
console.log(` ocx ${head.invocations[0]}`);
|
|
44
|
+
console.log(` ${head.summary}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function runCapabilities(argv: string[]): Promise<number> {
|
|
49
|
+
const args = [...argv];
|
|
50
|
+
const json = takeFlag(args, "--json");
|
|
51
|
+
const mutatingOnly = takeFlag(args, "--mutating-only");
|
|
52
|
+
const route = takeValueFlag(args, "--route");
|
|
53
|
+
|
|
54
|
+
if (route !== undefined && route.length === 0) {
|
|
55
|
+
console.error("Usage: ocx capabilities --route <path>");
|
|
56
|
+
return 64;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let selected: readonly Capability[] = route === undefined ? CAPABILITIES : capabilitiesForRoute(route);
|
|
60
|
+
if (mutatingOnly) selected = selected.filter(cap => cap.mutates);
|
|
61
|
+
|
|
62
|
+
if (route !== undefined && selected.length === 0) {
|
|
63
|
+
// An unmatched route is a real answer, not an error: the route may be exempt or may
|
|
64
|
+
// not exist. Say which, rather than exiting 0 with silence.
|
|
65
|
+
if (json) {
|
|
66
|
+
console.log(JSON.stringify({ schemaVersion: 1, route, capabilities: [] }, null, 2));
|
|
67
|
+
} else {
|
|
68
|
+
console.error(`No CLI capability drives ${route}.`);
|
|
69
|
+
}
|
|
70
|
+
return 4;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (json) {
|
|
74
|
+
console.log(JSON.stringify({
|
|
75
|
+
schemaVersion: 1,
|
|
76
|
+
...(route === undefined ? {} : { route }),
|
|
77
|
+
capabilities: selected.map(cap => ({
|
|
78
|
+
command: cap.command,
|
|
79
|
+
invocation: capabilityInvocation(cap),
|
|
80
|
+
summary: cap.summary,
|
|
81
|
+
routes: cap.routes,
|
|
82
|
+
flags: cap.flags,
|
|
83
|
+
mutates: cap.mutates,
|
|
84
|
+
json: cap.json,
|
|
85
|
+
...(cap.details ? { details: cap.details } : {}),
|
|
86
|
+
})),
|
|
87
|
+
...(route === undefined && !mutatingOnly ? { headCapabilities: HEAD_CAPABILITIES } : {}),
|
|
88
|
+
}, null, 2));
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
renderHuman(selected, route === undefined && !mutatingOnly);
|
|
93
|
+
return 0;
|
|
94
|
+
}
|