@oxygen-agent/cli 1.948.1 → 1.987.20
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 +1 -1
- package/dist/admin-primary-providers-render.js +9 -1
- package/dist/cli-values.d.ts +14 -0
- package/dist/cli-values.js +26 -0
- package/dist/command-manifest.js +6 -0
- package/dist/functions-commands.js +33 -9
- package/dist/help.d.ts +21 -0
- package/dist/help.js +94 -0
- package/dist/index.js +1493 -297
- package/dist/knowledge-repository-commands.d.ts +6 -0
- package/dist/knowledge-repository-commands.js +198 -0
- package/dist/skills.js +20 -0
- package/dist/ugc-commands.d.ts +3 -6
- package/dist/ugc-commands.js +2 -1086
- package/node_modules/@oxygen/cli-ugc/dist/commands.d.ts +3 -0
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +1178 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +7 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +25 -0
- package/node_modules/@oxygen/cli-ugc/dist/index.d.ts +14 -0
- package/node_modules/@oxygen/cli-ugc/dist/index.js +5 -0
- package/node_modules/@oxygen/cli-ugc/package.json +15 -0
- package/node_modules/@oxygen/formula/dist/expression.js +14 -1
- package/node_modules/@oxygen/formula/dist/formula-functions.js +71 -1
- package/node_modules/@oxygen/formula/dist/index.d.ts +1 -0
- package/node_modules/@oxygen/formula/dist/index.js +1 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +69 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.js +374 -0
- package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +27 -27
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +127 -18
- package/node_modules/@oxygen/shared/dist/column-output-fields.js +12 -4
- package/node_modules/@oxygen/shared/dist/copilot-errors.js +3 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.d.ts +19 -1
- package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.d.ts +19 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.js +26 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.js +8 -41
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.js +43 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +31 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +41 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.js +6 -0
- package/node_modules/@oxygen/shared/dist/inbox-avatar-url.d.ts +28 -0
- package/node_modules/@oxygen/shared/dist/inbox-avatar-url.js +57 -0
- package/node_modules/@oxygen/shared/dist/index.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/index.js +5 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bases.d.ts +74 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bases.js +456 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +32 -38
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +38 -41
- package/node_modules/@oxygen/shared/dist/knowledge-repository.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/knowledge-repository.js +121 -0
- package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.d.ts +20 -0
- package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.js +155 -0
- package/node_modules/@oxygen/shared/dist/langfuse.d.ts +12 -1
- package/node_modules/@oxygen/shared/dist/langfuse.js +48 -8
- package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/mailbox-import.js +53 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.js +8 -0
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
- package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.js +24 -0
- package/node_modules/@oxygen/shared/dist/recipes.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/recipes.js +23 -0
- package/node_modules/@oxygen/shared/dist/research-output-contract.d.ts +33 -1
- package/node_modules/@oxygen/shared/dist/research-output-contract.js +64 -2
- package/node_modules/@oxygen/shared/dist/sequence-crm-events.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequence-hubspot-sync.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +152 -2
- package/node_modules/@oxygen/shared/dist/sequences.js +304 -4
- package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.js +24 -0
- package/node_modules/@oxygen/shared/dist/ugc.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +8 -1
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +3 -1
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +6 -2
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +15 -4
- package/node_modules/@oxygen/shared/package.json +25 -0
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +15 -15
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -274,6 +274,13 @@ function renderProvider(row, now, binary) {
|
|
|
274
274
|
const provider = str(row.provider) ?? "?";
|
|
275
275
|
const level = str(posture.level) ?? "unknown";
|
|
276
276
|
const functions = arr(row.functions).map((fn) => String(fn));
|
|
277
|
+
// A catch-all escalation lane is the only route to its provider and is
|
|
278
|
+
// declared outside the chain's steps; name it so the row does not read as a
|
|
279
|
+
// plain primary for an intent it never leads.
|
|
280
|
+
const escalations = arr(row.primary_for)
|
|
281
|
+
.map((role) => obj(role))
|
|
282
|
+
.filter((role) => role !== null && str(role.role) === "catch_all_escalation")
|
|
283
|
+
.map((role) => str(role.intent) ?? "?");
|
|
277
284
|
const probe = str(health.probe) ?? "none";
|
|
278
285
|
const healthStatus = str(health.latest_status) ?? (probe === "none" ? "unmonitored" : "no data");
|
|
279
286
|
const healthDetail = [
|
|
@@ -284,7 +291,8 @@ function renderProvider(row, now, binary) {
|
|
|
284
291
|
(num(health.auth_error_pct_96h) ?? 0) > 0 ? `${pct(health.auth_error_pct_96h)} auth-error` : null,
|
|
285
292
|
num(health.last_latency_ms) === null ? null : `${count(health.last_latency_ms)} ms`,
|
|
286
293
|
].filter((part) => part !== null);
|
|
287
|
-
lines.push(` - ${provider} [${level}]${functions.length > 0 ? ` ${functions.join("+")}` : ""}
|
|
294
|
+
lines.push(` - ${provider} [${level}]${functions.length > 0 ? ` ${functions.join("+")}` : ""}` +
|
|
295
|
+
`${escalations.length > 0 ? ` (catch-all escalation: ${escalations.join(", ")})` : ""} · ` +
|
|
288
296
|
`key ${row.key_configured === false ? `NOT SET (${str(row.managed_key_env_var) ?? "—"})` : str(row.managed_key_env_var) ?? "customer-supplied"} · ` +
|
|
289
297
|
`health ${healthStatus} (${healthDetail.join(", ")})`);
|
|
290
298
|
const scope = str(traffic.scope) ?? "workspace_attributed";
|
package/dist/cli-values.d.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
/** Parse a `--limit`/`--interval`-style flag into a positive integer, or undefined when absent. */
|
|
2
2
|
export declare function readPositiveInt(value: string | undefined): number | undefined;
|
|
3
|
+
/**
|
|
4
|
+
* Parse an `--offset`-style flag into a NON-NEGATIVE integer, or undefined when absent.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately separate from `readPositiveInt`: for `--limit`/`--interval`, zero is
|
|
7
|
+
* meaningless and rejecting it is correct, but for an offset zero is the natural
|
|
8
|
+
* first page. Sharing one reader made `tables query --offset 0` fail with
|
|
9
|
+
* "Expected a positive integer." — the identity case of the very flag whose help
|
|
10
|
+
* text says "Skip this many rows" (OXY-4360).
|
|
11
|
+
*
|
|
12
|
+
* Callers must test the result with `!== undefined`, never for truthiness: a valid
|
|
13
|
+
* 0 is falsy, which is how the same flag then went on to be dropped from the
|
|
14
|
+
* request body it had just been validated for.
|
|
15
|
+
*/
|
|
16
|
+
export declare function readNonNegativeInt(value: string | undefined): number | undefined;
|
|
3
17
|
/** Read a string field from an unknown record-shaped value, or null when it is missing/non-string. */
|
|
4
18
|
export declare function readRecordString(value: unknown, key: string): string | null;
|
|
5
19
|
/**
|
package/dist/cli-values.js
CHANGED
|
@@ -20,6 +20,32 @@ export function readPositiveInt(value) {
|
|
|
20
20
|
}
|
|
21
21
|
return parsed;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Parse an `--offset`-style flag into a NON-NEGATIVE integer, or undefined when absent.
|
|
25
|
+
*
|
|
26
|
+
* Deliberately separate from `readPositiveInt`: for `--limit`/`--interval`, zero is
|
|
27
|
+
* meaningless and rejecting it is correct, but for an offset zero is the natural
|
|
28
|
+
* first page. Sharing one reader made `tables query --offset 0` fail with
|
|
29
|
+
* "Expected a positive integer." — the identity case of the very flag whose help
|
|
30
|
+
* text says "Skip this many rows" (OXY-4360).
|
|
31
|
+
*
|
|
32
|
+
* Callers must test the result with `!== undefined`, never for truthiness: a valid
|
|
33
|
+
* 0 is falsy, which is how the same flag then went on to be dropped from the
|
|
34
|
+
* request body it had just been validated for.
|
|
35
|
+
*/
|
|
36
|
+
export function readNonNegativeInt(value) {
|
|
37
|
+
const trimmed = value?.trim();
|
|
38
|
+
if (!trimmed)
|
|
39
|
+
return undefined;
|
|
40
|
+
const parsed = Number(trimmed);
|
|
41
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
42
|
+
throw new OxygenError("invalid_number", "Expected a non-negative integer.", {
|
|
43
|
+
details: { value },
|
|
44
|
+
exitCode: 1,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return parsed;
|
|
48
|
+
}
|
|
23
49
|
/** Read a string field from an unknown record-shaped value, or null when it is missing/non-string. */
|
|
24
50
|
export function readRecordString(value, key) {
|
|
25
51
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
package/dist/command-manifest.js
CHANGED
|
@@ -88,12 +88,15 @@ const MUTATING_COMMANDS = new Set([
|
|
|
88
88
|
// zero-credit. Keep those exceptions exact so discovery never invents spend.
|
|
89
89
|
const ZERO_CREDIT_APPROVAL_COMMANDS = new Set([
|
|
90
90
|
"tables watcher preview",
|
|
91
|
+
"knowledge repositories connect",
|
|
92
|
+
"knowledge repositories resolve",
|
|
91
93
|
// Creator invitation emails need exact send authorization but cost 0 credits.
|
|
92
94
|
"ugc creators invite",
|
|
93
95
|
"ugc creators send-invite",
|
|
94
96
|
// These approvals record an observed outcome or stop future renewal;
|
|
95
97
|
// neither operation buys a service or consumes Oxygen credits.
|
|
96
98
|
"ugc amplification reconcile",
|
|
99
|
+
"ugc peer-amplification reconcile",
|
|
97
100
|
"ugc sponsorship cancel",
|
|
98
101
|
// A Function binding stores a cap for future runs; creating it executes nothing.
|
|
99
102
|
"functions bind",
|
|
@@ -138,6 +141,8 @@ const PREVIEW_BY_DEFAULT_COMMANDS = new Set([
|
|
|
138
141
|
"ugc sponsorship preview",
|
|
139
142
|
"ugc sponsorship cancel",
|
|
140
143
|
"ugc amplification reconcile",
|
|
144
|
+
"ugc peer-amplification reconcile",
|
|
145
|
+
"ugc peer-amplification save",
|
|
141
146
|
// Fenced by --approved, not --live: a bare call resolves the sender, sequence,
|
|
142
147
|
// audience and caps and writes nothing. Without this entry discovery would tell
|
|
143
148
|
// an agent that previewing the play arms it, and the preview would go unrun.
|
|
@@ -149,6 +154,7 @@ const PREVIEW_BY_DEFAULT_COMMANDS = new Set([
|
|
|
149
154
|
"support admin reply",
|
|
150
155
|
// A bare call reads the Plain workspace and prints the plan; --apply writes.
|
|
151
156
|
"support admin setup",
|
|
157
|
+
"verify email",
|
|
152
158
|
"workflows webhooks rotate",
|
|
153
159
|
]);
|
|
154
160
|
export function buildCommandManifest(program, binaryName) {
|
|
@@ -3,12 +3,28 @@ import { parseJsonObject } from "./cli-values.js";
|
|
|
3
3
|
import { requestOxygen } from "./http-client.js";
|
|
4
4
|
export function registerFunctionsCommands(program, handle) {
|
|
5
5
|
const functions = program.command("functions")
|
|
6
|
-
.description("
|
|
7
|
-
.addHelpText("after", "\nDraft edits are isolated. Publishing updates future invocations of every caller; queued, in-flight and past runs retain their captured version. Existing caller caps never increase automatically.\nFlow: draft → describe → publish → bind → columns run --dry-run → approved run → runs.\n");
|
|
8
|
-
functions.command("list").description("List the Function library,
|
|
6
|
+
.description("Browse OXYGEN's ready-made Functions (work email, mobile phone, company funding stage, ...), favourite them, bind them to a table, or create, edit and publish your own Functions backed by ordinary Tables.")
|
|
7
|
+
.addHelpText("after", "\nOXYGEN-managed Functions are pre-built and priced: `functions list --managed` shows each one with its per-row credit estimate before you attach it. Attaching (`functions bind`) is free; only `columns run` spends, after a dry run. A managed Function is read-only; `functions duplicate <function>` gives you an editable copy.\nDraft edits are isolated. Publishing updates future invocations of every caller; queued, in-flight and past runs retain their captured version. Existing caller caps never increase automatically.\nFlow: draft → describe → publish → bind → columns run --dry-run → approved run → runs.\nA bound Function executes on its own published execution table and writes named outputs back to the calling table. The call stays visible from the caller: `table-runs list --table <caller> --status all` lists the run, and the binding column's cell holds the outcome (status, outputs, credits, invocation and run ids). Address a Function by id, slug, or the display name shown by `functions list`.\nWorked example with commands: `oxygen skills install --skill oxygen-gtm --project`, then read its recipes/callable-tables.md.\n");
|
|
8
|
+
functions.command("list").description("List the Function library: OXYGEN-managed Functions with their per-row credit estimate, plus your workspace's own drafts and published Functions (up to 500).")
|
|
9
|
+
.option("--managed", "Only OXYGEN-managed Functions (pre-built, priced, read-only; duplicate to customize).")
|
|
10
|
+
.option("--favorites", "Only the Functions you favourited (`functions favorite <function>`).")
|
|
9
11
|
.option("--json", "Print a JSON envelope.")
|
|
10
|
-
.action((options) => handle("functions list", options, () =>
|
|
11
|
-
|
|
12
|
+
.action((options) => handle("functions list", options, () => {
|
|
13
|
+
if (options.managed && options.favorites)
|
|
14
|
+
throw new OxygenError("invalid_request", "Pass --managed or --favorites, not both.", { exitCode: 1 });
|
|
15
|
+
const filter = options.managed ? "managed" : options.favorites ? "favorites" : null;
|
|
16
|
+
return requestOxygen(filter ? `/api/cli/functions?${new URLSearchParams({ filter })}` : "/api/cli/functions");
|
|
17
|
+
}));
|
|
18
|
+
for (const [command, label] of [["favorite", "Favourite a Function so it appears under `functions list --favorites` and the Favorites tab; per user, per workspace."], ["unfavorite", "Remove a Function from your favourites."]]) {
|
|
19
|
+
functions.command(`${command} <function>`).description(label)
|
|
20
|
+
.option("--json", "Print a JSON envelope.")
|
|
21
|
+
.action((ref, options) => handle(`functions ${command}`, options, () => requestOxygen("/api/cli/functions", { method: "POST", body: { operation: command, function: ref } })));
|
|
22
|
+
}
|
|
23
|
+
functions.command("duplicate <function>").description("Copy a Function's steps into a new editable draft of your own (same inputs and outputs, no rows). The way to customize an OXYGEN-managed Function.")
|
|
24
|
+
.option("--name <name>", "Display name for the copy. Defaults to '<source> (copy)'.")
|
|
25
|
+
.option("--json", "Print a JSON envelope.")
|
|
26
|
+
.action((ref, options) => handle("functions duplicate", options, () => requestOxygen("/api/cli/functions", { method: "POST", body: { operation: "duplicate", function: ref, ...(options.name ? { display_name: options.name } : {}) } })));
|
|
27
|
+
functions.command("describe <function>").description("Inspect a Function's inputs, outputs, steps, per-row credit estimate, caps, and whether it is OXYGEN-managed or your own draft/published version.")
|
|
12
28
|
.option("--json", "Print a JSON envelope.")
|
|
13
29
|
.action((ref, options) => handle("functions describe", options, () => requestOxygen(`/api/cli/functions?${new URLSearchParams({ function: ref })}`)));
|
|
14
30
|
functions.command("draft [function]").description("Create a Function draft, or save an existing Function's draft; executes nothing.")
|
|
@@ -21,16 +37,24 @@ export function registerFunctionsCommands(program, handle) {
|
|
|
21
37
|
function: ref,
|
|
22
38
|
...(options.expectedDraftHash ? { expected_draft_hash: options.expectedDraftHash } : {}),
|
|
23
39
|
} })));
|
|
24
|
-
functions.command("publish <function>").description("Publish the inspected draft for every caller's future invocations; preserves captured runs and caller caps.")
|
|
40
|
+
functions.command("publish <function>").description("Publish the inspected draft for every caller's future invocations; preserves captured runs and caller caps. Until the first publish a draft is addressed by its backing table id; publishing mints the Function id that bindings and runs record, and the table id keeps resolving.")
|
|
25
41
|
.requiredOption("--expected-draft-hash <hash>", "function.draft.contentHash returned by draft or describe; rejects concurrent edits.")
|
|
26
42
|
.option("--json", "Print a JSON envelope.")
|
|
27
43
|
.action((ref, options) => handle("functions publish", options, () => requestOxygen("/api/cli/functions", { method: "POST", body: { operation: "publish", function: ref, expected_draft_hash: options.expectedDraftHash } })));
|
|
28
|
-
functions.command("runs <function>").description("Inspect a Function's latest 100 invocations and their captured versions.")
|
|
44
|
+
functions.command("runs <function>").description("Inspect a Function's latest 100 invocations, the table that called each one, and their captured versions. For a call that did not complete, creditsUsed can show the estimate; `table-runs provider-summary <run_id>` reports the credits actually captured.")
|
|
29
45
|
.option("--json", "Print a JSON envelope.")
|
|
30
46
|
.action((ref, options) => handle("functions runs", options, () => requestOxygen(`/api/cli/functions/runs?${new URLSearchParams({ function: ref })}`)));
|
|
31
|
-
functions.command("
|
|
47
|
+
functions.command("rename <function> <name>").description("Rename a Function while preserving its identity, callers and published execution.")
|
|
48
|
+
.option("--json", "Print a JSON envelope.")
|
|
49
|
+
.action((ref, name, options) => handle("functions rename", options, () => requestOxygen("/api/cli/functions", { method: "POST", body: { operation: "rename", function: ref, display_name: name } })));
|
|
50
|
+
functions.command("delete <function>").description("Preview removal from the Function library; preserves backing tables, rows and run history. Connected callers or active runs block deletion.")
|
|
51
|
+
.addHelpText("after", "\nDeletion is final: there is no restore, unlike `tables delete`. To reuse the logic, `tables duplicate` the backing Table and draft a new Function from the copy.\n")
|
|
52
|
+
.option("--yes", "Confirm removal of this Function after reviewing its callers.")
|
|
53
|
+
.option("--json", "Print a JSON envelope.")
|
|
54
|
+
.action((ref, options) => handle("functions delete", options, () => requestOxygen("/api/cli/functions", { method: "POST", body: { operation: "delete", function: ref, approved: options.yes === true } })));
|
|
55
|
+
functions.command("bind <table> <function>").description("Add a Function column with explicit input/output mappings and a caller credit cap. Create each mapped output column on the caller table first with `columns add`; bind rejects an output mapped to a column the caller table does not have.")
|
|
32
56
|
.requiredOption("--inputs-json <json>", "Object mapping Function input keys to caller column keys.")
|
|
33
|
-
.requiredOption("--outputs-json <json>", "Object mapping Function output keys to caller column keys.")
|
|
57
|
+
.requiredOption("--outputs-json <json>", "Object mapping Function output keys to existing caller column keys.")
|
|
34
58
|
.requiredOption("--max-credits-per-item <n>", "Hard credit ceiling for each caller row; future publishes cannot raise it.")
|
|
35
59
|
.option("--overwrite-policy <policy>", "Callback writes: empty_only or overwrite.", "empty_only")
|
|
36
60
|
.option("--key <key>", "Function column key.").option("--label <label>", "Function column label.")
|
package/dist/help.d.ts
CHANGED
|
@@ -1,2 +1,23 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
export declare function applyOxygenHelp(program: Command, binaryName: string): void;
|
|
3
|
+
/**
|
|
4
|
+
* Turn commander's own edit-distance suggester on for EVERY command.
|
|
5
|
+
*
|
|
6
|
+
* It defaults to on, but `copyInheritedSettings` only runs for children created
|
|
7
|
+
* through `.command()`; the hundreds of subcommands this CLI registers with
|
|
8
|
+
* `.addCommand(new Command(...))` carry whatever their own constructor set.
|
|
9
|
+
* Walking the tree makes the setting a property of the whole surface rather
|
|
10
|
+
* than of how each command happened to be built, the same reason
|
|
11
|
+
* `installCommanderExitOverride` recurses.
|
|
12
|
+
*/
|
|
13
|
+
export declare function enableCommandSuggestions(command: Command): void;
|
|
14
|
+
/**
|
|
15
|
+
* The extra line printed after `error: unknown command '<x>'`.
|
|
16
|
+
*
|
|
17
|
+
* Commander's message names what is wrong and nothing a user can do next. This
|
|
18
|
+
* resolves the command they were actually inside, offers the synonym or unique
|
|
19
|
+
* prefix match commander cannot reach, and always names the exact `--help` that
|
|
20
|
+
* lists the real subcommands. Returns null when argv does not describe an
|
|
21
|
+
* unknown subcommand (an unknown OPTION has its own handling).
|
|
22
|
+
*/
|
|
23
|
+
export declare function unknownCommandHint(program: Command, argv: readonly string[]): string | null;
|
package/dist/help.js
CHANGED
|
@@ -139,7 +139,12 @@ export function applyOxygenHelp(program, binaryName) {
|
|
|
139
139
|
` 2. ${binaryName} skills install --json load the skills that teach the GTM loops (automatic after login)`,
|
|
140
140
|
" Add --project to keep the skills inside the current project, including confined agent sessions.",
|
|
141
141
|
` 3. ${binaryName} context resolve --json load workspace context before operating primitives`,
|
|
142
|
+
// A new workspace's first sessions are onboarding, and the consultant guide is
|
|
143
|
+
// a served skill an agent has to be TOLD about: nothing else on a first-touch
|
|
144
|
+
// surface named it (blind eval, 2026-09-16).
|
|
145
|
+
` New or thin workspace? load the onboarding consultant first: ${binaryName} skills get oxygen-onboarding --json`,
|
|
142
146
|
` 4. ${binaryName} capabilities search "<goal>" --json route the goal, then hydrate one exact command`,
|
|
147
|
+
` ${binaryName} commands search "<term>" --json find a command by name or purpose instead of dumping the manifest`,
|
|
143
148
|
` 5. ${binaryName} recipes list --json choose a play from the Recipe catalog for your goal`,
|
|
144
149
|
` Provider inventory: ${binaryName} tools search <brand> --json, then ${binaryName} tools search --provider <provider-id> --all --json (Blitz uses blitzapi).`,
|
|
145
150
|
` Installable systems and recurring monitors: ${binaryName} blueprints list --json.`,
|
|
@@ -154,3 +159,92 @@ export function applyOxygenHelp(program, binaryName) {
|
|
|
154
159
|
`Exit codes: ${exitCodeLine}`,
|
|
155
160
|
].join("\n"));
|
|
156
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Turn commander's own edit-distance suggester on for EVERY command.
|
|
164
|
+
*
|
|
165
|
+
* It defaults to on, but `copyInheritedSettings` only runs for children created
|
|
166
|
+
* through `.command()`; the hundreds of subcommands this CLI registers with
|
|
167
|
+
* `.addCommand(new Command(...))` carry whatever their own constructor set.
|
|
168
|
+
* Walking the tree makes the setting a property of the whole surface rather
|
|
169
|
+
* than of how each command happened to be built, the same reason
|
|
170
|
+
* `installCommanderExitOverride` recurses.
|
|
171
|
+
*/
|
|
172
|
+
export function enableCommandSuggestions(command) {
|
|
173
|
+
command.showSuggestionAfterError(true);
|
|
174
|
+
for (const child of command.commands)
|
|
175
|
+
enableCommandSuggestions(child);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Verbs a user reaches for that this CLI spells differently.
|
|
179
|
+
*
|
|
180
|
+
* Commander's suggester is edit distance only, so it fires for a typo
|
|
181
|
+
* (`describ` → `describe`) and never for a synonym: `get` is seven edits from
|
|
182
|
+
* `describe`, so `oxygen tables get` answered `error: unknown command 'get'`
|
|
183
|
+
* and stopped there with nothing to try next (blind user eval, 2026-09-17).
|
|
184
|
+
* Values are candidate real subcommand names, best first; only names the
|
|
185
|
+
* command actually has are ever offered.
|
|
186
|
+
*/
|
|
187
|
+
const COMMAND_SYNONYMS = {
|
|
188
|
+
get: ["describe", "show", "view", "status", "preview"],
|
|
189
|
+
show: ["describe", "get", "list", "preview"],
|
|
190
|
+
view: ["describe", "preview", "show", "get"],
|
|
191
|
+
info: ["describe", "status", "show"],
|
|
192
|
+
inspect: ["describe", "preview", "show"],
|
|
193
|
+
read: ["describe", "query", "export", "get"],
|
|
194
|
+
ls: ["list"],
|
|
195
|
+
all: ["list"],
|
|
196
|
+
new: ["create", "add"],
|
|
197
|
+
make: ["create", "add"],
|
|
198
|
+
rm: ["delete", "remove", "archive"],
|
|
199
|
+
del: ["delete", "remove", "archive"],
|
|
200
|
+
remove: ["delete", "archive"],
|
|
201
|
+
destroy: ["delete", "archive"],
|
|
202
|
+
edit: ["update", "set", "rename"],
|
|
203
|
+
modify: ["update", "set"],
|
|
204
|
+
exec: ["run", "start", "call"],
|
|
205
|
+
execute: ["run", "start", "call"],
|
|
206
|
+
find: ["search", "query", "list"],
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* The extra line printed after `error: unknown command '<x>'`.
|
|
210
|
+
*
|
|
211
|
+
* Commander's message names what is wrong and nothing a user can do next. This
|
|
212
|
+
* resolves the command they were actually inside, offers the synonym or unique
|
|
213
|
+
* prefix match commander cannot reach, and always names the exact `--help` that
|
|
214
|
+
* lists the real subcommands. Returns null when argv does not describe an
|
|
215
|
+
* unknown subcommand (an unknown OPTION has its own handling).
|
|
216
|
+
*/
|
|
217
|
+
export function unknownCommandHint(program, argv) {
|
|
218
|
+
let current = program;
|
|
219
|
+
let path = program.name();
|
|
220
|
+
for (const token of argv) {
|
|
221
|
+
// Options and their values are commander's business; only the command path
|
|
222
|
+
// is walked here.
|
|
223
|
+
if (token.startsWith("-"))
|
|
224
|
+
break;
|
|
225
|
+
const child = current.commands.find((candidate) => candidate.name() === token || candidate.aliases().includes(token));
|
|
226
|
+
if (child) {
|
|
227
|
+
current = child;
|
|
228
|
+
path = `${path} ${token}`;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (current.commands.length === 0)
|
|
232
|
+
return null;
|
|
233
|
+
return buildUnknownCommandHint(current, path, token);
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
function buildUnknownCommandHint(parent, path, token) {
|
|
238
|
+
const names = parent.commands.map((command) => command.name()).filter((name) => name !== "help");
|
|
239
|
+
const lower = token.toLowerCase();
|
|
240
|
+
const synonym = (COMMAND_SYNONYMS[lower] ?? []).find((candidate) => names.includes(candidate));
|
|
241
|
+
// A unique prefix is the other guess commander cannot make: `tables desc`
|
|
242
|
+
// scores too far from `describe` on edit distance, but it names exactly one
|
|
243
|
+
// command.
|
|
244
|
+
const prefixMatches = names.filter((name) => name.startsWith(lower));
|
|
245
|
+
const suggestion = synonym ?? (prefixMatches.length === 1 ? prefixMatches[0] : null);
|
|
246
|
+
const helpLine = `Run \`${path} --help\` for the ${names.length} subcommands of \`${path}\`.`;
|
|
247
|
+
return suggestion
|
|
248
|
+
? `Hint: \`${path}\` has no \`${token}\`. Did you mean \`${path} ${suggestion}\`? ${helpLine}`
|
|
249
|
+
: `Hint: \`${path}\` has no \`${token}\`. ${helpLine}`;
|
|
250
|
+
}
|