@oxygen-agent/cli 1.1011.98 → 1.1011.322
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 +25 -5
- package/dist/column-run-notices.d.ts +16 -0
- package/dist/column-run-notices.js +57 -0
- package/dist/command-manifest.js +10 -1
- package/dist/connectors-commands.d.ts +11 -0
- package/dist/connectors-commands.js +63 -0
- package/dist/help.d.ts +27 -1
- package/dist/help.js +112 -29
- package/dist/http-client.js +8 -0
- package/dist/index.js +362 -130
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +20 -69
- package/node_modules/@oxygen/shared/dist/byok-connect.d.ts +6 -4
- package/node_modules/@oxygen/shared/dist/byok-connect.js +11 -4
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +131 -1
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +325 -30
- package/node_modules/@oxygen/shared/dist/collab.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/collab.js +12 -0
- package/node_modules/@oxygen/shared/dist/column-decision.js +7 -3
- package/node_modules/@oxygen/shared/dist/column-output-fields.js +45 -3
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.d.ts +20 -13
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.js +29 -13
- package/node_modules/@oxygen/shared/dist/contracts/connectors.d.ts +23 -0
- package/node_modules/@oxygen/shared/dist/contracts/connectors.js +34 -0
- package/node_modules/@oxygen/shared/dist/contracts/operations.d.ts +0 -6
- package/node_modules/@oxygen/shared/dist/contracts/operations.js +8 -1
- package/node_modules/@oxygen/shared/dist/contracts/sender-limits.js +1 -1
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +2 -2
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +2 -2
- package/node_modules/@oxygen/shared/dist/credit-guidance.js +12 -1
- package/node_modules/@oxygen/shared/dist/enrichment-intents.d.ts +37 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.js +79 -44
- package/node_modules/@oxygen/shared/dist/feature-gates.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/feature-gates.js +6 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +57 -8
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +74 -13
- package/node_modules/@oxygen/shared/dist/linkedin-url.d.ts +7 -0
- package/node_modules/@oxygen/shared/dist/linkedin-url.js +9 -0
- package/node_modules/@oxygen/shared/dist/log.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/log.js +15 -0
- package/node_modules/@oxygen/shared/dist/mcp-event-contract.d.ts +597 -0
- package/node_modules/@oxygen/shared/dist/mcp-event-contract.js +713 -0
- package/node_modules/@oxygen/shared/dist/mcp-events-enabled.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/mcp-events-enabled.js +14 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.js +6 -2
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +7 -3
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +22 -1
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +3 -7
- package/node_modules/@oxygen/shared/dist/plan-limits.js +2 -14
- package/node_modules/@oxygen/shared/dist/select-options.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/select-options.js +6 -1
- package/node_modules/@oxygen/shared/dist/sending-schedule.d.ts +39 -0
- package/node_modules/@oxygen/shared/dist/sending-schedule.js +41 -0
- package/node_modules/@oxygen/shared/dist/sequence-lifecycle.d.ts +19 -0
- package/node_modules/@oxygen/shared/dist/sequence-lifecycle.js +20 -0
- package/node_modules/@oxygen/shared/dist/sequence-sender-variables.d.ts +35 -0
- package/node_modules/@oxygen/shared/dist/sequence-sender-variables.js +63 -0
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +13 -7
- package/node_modules/@oxygen/shared/dist/sequences.js +26 -0
- package/node_modules/@oxygen/shared/dist/signup-lead-payload.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/signup-lead-payload.js +5 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-series.js +1 -15
- package/node_modules/@oxygen/shared/dist/standard-webhooks.d.ts +51 -0
- package/node_modules/@oxygen/shared/dist/standard-webhooks.js +124 -0
- package/node_modules/@oxygen/shared/dist/tags.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/tags.js +13 -1
- package/node_modules/@oxygen/shared/dist/ugc.d.ts +11 -7
- package/node_modules/@oxygen/shared/dist/ugc.js +14 -6
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/node_modules/@oxygen/shared/dist/zoned-time.d.ts +39 -0
- package/node_modules/@oxygen/shared/dist/zoned-time.js +81 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -165,6 +165,10 @@ export function renderPrimaryProviderBoard(payload, options = {}) {
|
|
|
165
165
|
((num(summary.fallback_providers) ?? 0) > 0
|
|
166
166
|
? ` · ${count(summary.fallback_providers)} default-chain fallback${(num(summary.fallback_providers) ?? 0) === 1 ? "" : "s"}` +
|
|
167
167
|
((num(summary.fallback_critical) ?? 0) > 0 ? ` (${count(summary.fallback_critical)} critical)` : "")
|
|
168
|
+
: "") +
|
|
169
|
+
((num(summary.outside_chain_providers) ?? 0) > 0
|
|
170
|
+
? ` · ${count(summary.outside_chain_providers)} managed outside every chain` +
|
|
171
|
+
((num(summary.outside_chain_critical) ?? 0) > 0 ? ` (${count(summary.outside_chain_critical)} critical)` : "")
|
|
168
172
|
: ""));
|
|
169
173
|
out(`COGS (managed primaries): 30d ${usd(summary.cost_30d_usd)} · 7d ${usd(summary.cost_7d_usd)}`);
|
|
170
174
|
out(renderGuards(obj(board.guards)));
|
|
@@ -189,17 +193,23 @@ export function renderPrimaryProviderBoard(payload, options = {}) {
|
|
|
189
193
|
}
|
|
190
194
|
// A dry fallback account is invisible until a primary fails, which is
|
|
191
195
|
// exactly when nobody has time to notice it — so it renders here too.
|
|
192
|
-
const fallbacks =
|
|
193
|
-
.map((row) => obj(row))
|
|
194
|
-
.filter((row) => row !== null)
|
|
195
|
-
.sort((a, b) => postureRank(str(obj(a.posture)?.level)) - postureRank(str(obj(b.posture)?.level)) ||
|
|
196
|
-
(str(a.provider) ?? "").localeCompare(str(b.provider) ?? ""));
|
|
196
|
+
const fallbacks = rowsWorstFirst(board.default_chain_fallbacks);
|
|
197
197
|
if (fallbacks.length > 0) {
|
|
198
198
|
out("");
|
|
199
199
|
out("Default-chain fallbacks (not primary anywhere — a routed chain reaches them when its primary fails):");
|
|
200
200
|
for (const row of fallbacks)
|
|
201
201
|
out(renderProvider(row, now, binary));
|
|
202
202
|
}
|
|
203
|
+
// Managed data providers no routed chain reaches. Direct tool calls and product
|
|
204
|
+
// features still spend OXYGEN's key there, and until this group existed a dead
|
|
205
|
+
// key or a dry wallet on one of them showed on no surface at all.
|
|
206
|
+
const outsideChains = rowsWorstFirst(board.managed_outside_chains);
|
|
207
|
+
if (outsideChains.length > 0) {
|
|
208
|
+
out("");
|
|
209
|
+
out("Managed outside every chain (OXYGEN holds the key; direct tool calls and product features reach them, no routed chain does):");
|
|
210
|
+
for (const row of outsideChains)
|
|
211
|
+
out(renderProvider(row, now, binary));
|
|
212
|
+
}
|
|
203
213
|
const intents = arr(board.intents_without_primary)
|
|
204
214
|
.map((row) => obj(row))
|
|
205
215
|
.filter((row) => row !== null);
|
|
@@ -234,6 +244,14 @@ export function renderPrimaryProviderBoard(payload, options = {}) {
|
|
|
234
244
|
}
|
|
235
245
|
return lines.join("\n");
|
|
236
246
|
}
|
|
247
|
+
/** Provider rows from one board group, worst posture first, then by name. */
|
|
248
|
+
function rowsWorstFirst(value) {
|
|
249
|
+
return arr(value)
|
|
250
|
+
.map((row) => obj(row))
|
|
251
|
+
.filter((row) => row !== null)
|
|
252
|
+
.sort((a, b) => postureRank(str(obj(a.posture)?.level)) - postureRank(str(obj(b.posture)?.level)) ||
|
|
253
|
+
(str(a.provider) ?? "").localeCompare(str(b.provider) ?? ""));
|
|
254
|
+
}
|
|
237
255
|
const INTENT_KIND_COPY = {
|
|
238
256
|
fallback_only: "fallback only — paid routed steps exist but none is seeded primary (the real gap):",
|
|
239
257
|
preflight_only: "preflight only — every step is a free sizing/count probe (by design):",
|
|
@@ -285,6 +303,8 @@ function renderProvider(row, now, binary) {
|
|
|
285
303
|
const healthStatus = str(health.latest_status) ?? (probe === "none" ? "unmonitored" : "no data");
|
|
286
304
|
const healthDetail = [
|
|
287
305
|
`${probe} probe`,
|
|
306
|
+
// No zero-credit read exists for this vendor; the reasons below say why.
|
|
307
|
+
str(health.unwatchable) ? "structurally unwatchable" : null,
|
|
288
308
|
ago(health.last_checked_at, now),
|
|
289
309
|
num(health.uptime_pct_96h) === null ? null : `${pct(health.uptime_pct_96h)} up 96h`,
|
|
290
310
|
(num(health.degraded_pct_96h) ?? 0) > 0 ? `${pct(health.degraded_pct_96h)} degraded` : null,
|
|
@@ -9,6 +9,16 @@
|
|
|
9
9
|
* live run, or a preview the server could not produce).
|
|
10
10
|
*/
|
|
11
11
|
export declare function formatAiPromptPreviewNotice(data: unknown): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* `columns run --dry-run` and `enrich-column preview` carry
|
|
14
|
+
* `provider_availability.lanes`: whether Oxygen's managed account behind each
|
|
15
|
+
* lane can take a call right now (OXP-1.14.12). On 2026-09-27 a BounceBan
|
|
16
|
+
* column's dry run printed nothing while that account was benched and the
|
|
17
|
+
* approved run then deferred forever. One line for the verdict (with the fix
|
|
18
|
+
* when no lane can run), then one line per lane that cannot run. `--json`
|
|
19
|
+
* readers get the fields; nothing is printed while every lane can run.
|
|
20
|
+
*/
|
|
21
|
+
export declare function formatProviderLaneNotices(availability: unknown): string[];
|
|
12
22
|
/**
|
|
13
23
|
* Advisory reference findings from `columns add` / `update` / `rename`.
|
|
14
24
|
*
|
|
@@ -19,3 +29,9 @@ export declare function formatAiPromptPreviewNotice(data: unknown): string | nul
|
|
|
19
29
|
* sibling's reference. Printed to stderr so `--json` piping stays clean.
|
|
20
30
|
*/
|
|
21
31
|
export declare function formatColumnReferenceNotices(data: unknown): string[];
|
|
32
|
+
/**
|
|
33
|
+
* The inputs `columns add --capability` mapped, in `--input` syntax, or the
|
|
34
|
+
* warning and command for one it could not find. The envelope carries both for
|
|
35
|
+
* `--json` readers; this is the line a human sees on stderr.
|
|
36
|
+
*/
|
|
37
|
+
export declare function formatEnrichmentInputMappingNotice(data: unknown): string[];
|
|
@@ -30,11 +30,41 @@ export function formatAiPromptPreviewNotice(data) {
|
|
|
30
30
|
const truncation = preview.truncated === true
|
|
31
31
|
? ` — truncated to ${shown.length} of ${chars} chars`
|
|
32
32
|
: "";
|
|
33
|
+
const state = isRecord(preview.state) && isRecord(preview.state.row)
|
|
34
|
+
? JSON.stringify(preview.state.row, null, 2)
|
|
35
|
+
: null;
|
|
33
36
|
return [
|
|
34
37
|
`prompt preview — row ${rowId} (${source}), before grounding${truncation}:`,
|
|
35
38
|
shown,
|
|
39
|
+
...(state ? [`row values sent separately in classification state:\n${state.slice(0, 4000)}${state.length > 4000 ? "… (truncated)" : ""}`] : []),
|
|
36
40
|
].join("\n");
|
|
37
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* `columns run --dry-run` and `enrich-column preview` carry
|
|
44
|
+
* `provider_availability.lanes`: whether Oxygen's managed account behind each
|
|
45
|
+
* lane can take a call right now (OXP-1.14.12). On 2026-09-27 a BounceBan
|
|
46
|
+
* column's dry run printed nothing while that account was benched and the
|
|
47
|
+
* approved run then deferred forever. One line for the verdict (with the fix
|
|
48
|
+
* when no lane can run), then one line per lane that cannot run. `--json`
|
|
49
|
+
* readers get the fields; nothing is printed while every lane can run.
|
|
50
|
+
*/
|
|
51
|
+
export function formatProviderLaneNotices(availability) {
|
|
52
|
+
if (!isRecord(availability) || !Array.isArray(availability.lanes))
|
|
53
|
+
return [];
|
|
54
|
+
const lines = [];
|
|
55
|
+
for (const lane of availability.lanes) {
|
|
56
|
+
if (!isRecord(lane) || (lane.availability !== "degraded" && lane.availability !== "unknown"))
|
|
57
|
+
continue;
|
|
58
|
+
const state = lane.availability === "unknown" ? "availability unknown" : "unavailable";
|
|
59
|
+
const detail = readRecordString(lane, "message") ?? readRecordString(lane, "next_action");
|
|
60
|
+
lines.push(`! ${readRecordString(lane, "tool_id") ?? "a lane"} ${state}${detail ? `: ${detail}` : ""}`);
|
|
61
|
+
}
|
|
62
|
+
const message = readRecordString(availability, "message");
|
|
63
|
+
if (lines.length === 0 || !message)
|
|
64
|
+
return lines;
|
|
65
|
+
const fix = availability.will_produce_results === false ? readRecordString(availability, "next_action") : null;
|
|
66
|
+
return [`! ${message}${fix ? ` ${fix}` : ""}`, ...lines];
|
|
67
|
+
}
|
|
38
68
|
/**
|
|
39
69
|
* Advisory reference findings from `columns add` / `update` / `rename`.
|
|
40
70
|
*
|
|
@@ -57,3 +87,30 @@ export function formatColumnReferenceNotices(data) {
|
|
|
57
87
|
}
|
|
58
88
|
return notices;
|
|
59
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* The inputs `columns add --capability` mapped, in `--input` syntax, or the
|
|
92
|
+
* warning and command for one it could not find. The envelope carries both for
|
|
93
|
+
* `--json` readers; this is the line a human sees on stderr.
|
|
94
|
+
*/
|
|
95
|
+
export function formatEnrichmentInputMappingNotice(data) {
|
|
96
|
+
if (!isRecord(data) || !isRecord(data.input_mapping))
|
|
97
|
+
return [];
|
|
98
|
+
const notices = [];
|
|
99
|
+
const mapped = isRecord(data.input_mapping.mapped) ? Object.entries(data.input_mapping.mapped) : [];
|
|
100
|
+
if (mapped.length > 0) {
|
|
101
|
+
const origin = data.input_mapping.source === "auto_detected" ? "Inputs mapped from this table" : "Inputs";
|
|
102
|
+
notices.push(`${origin}: ${mapped.map(([input, reads]) => `${input}=${String(reads)}`).join(", ")}.`);
|
|
103
|
+
}
|
|
104
|
+
const gap = Array.isArray(data.warnings)
|
|
105
|
+
? data.warnings.find((entry) => isRecord(entry) && entry.code === "enrichment_inputs_unmapped")
|
|
106
|
+
: undefined;
|
|
107
|
+
if (isRecord(gap)) {
|
|
108
|
+
const message = readRecordString(gap, "message");
|
|
109
|
+
if (message)
|
|
110
|
+
notices.push(`warning: ${message}`);
|
|
111
|
+
const nextAction = readRecordString(data, "next_action");
|
|
112
|
+
if (nextAction)
|
|
113
|
+
notices.push(nextAction);
|
|
114
|
+
}
|
|
115
|
+
return notices;
|
|
116
|
+
}
|
package/dist/command-manifest.js
CHANGED
|
@@ -25,8 +25,9 @@ const COMMAND_SEARCH_STOP_WORDS = new Set([
|
|
|
25
25
|
// leaf in the tree using that verb, so the default (mutates:false) would have
|
|
26
26
|
// advertised the most consequential collaboration write as a read.
|
|
27
27
|
const MUTATING_VERBS = new Set([
|
|
28
|
+
"forget-whatsapp-chat",
|
|
28
29
|
"start-whatsapp",
|
|
29
|
-
"
|
|
30
|
+
"revoke-invite", "confirm",
|
|
30
31
|
// Exact leaf: `ugc creators set-target` writes one creator's posting deal.
|
|
31
32
|
"set-target",
|
|
32
33
|
"infrastructure-exit",
|
|
@@ -116,6 +117,7 @@ const MUTATING_COMMANDS = new Set([
|
|
|
116
117
|
// zero-credit. Keep those exceptions exact so discovery never invents spend.
|
|
117
118
|
const ZERO_CREDIT_APPROVAL_COMMANDS = new Set([
|
|
118
119
|
"inbox delete-message",
|
|
120
|
+
"inbox forget-whatsapp-chat",
|
|
119
121
|
"tables watcher preview",
|
|
120
122
|
"knowledge repositories connect",
|
|
121
123
|
"knowledge repositories resolve",
|
|
@@ -129,6 +131,12 @@ const ZERO_CREDIT_APPROVAL_COMMANDS = new Set([
|
|
|
129
131
|
"ugc sponsorship cancel",
|
|
130
132
|
// A Function binding stores a cap for future runs; creating it executes nothing.
|
|
131
133
|
"functions bind",
|
|
134
|
+
// Same shape: a draft Sequence stores its LinkedIn --max-credits ceiling for a
|
|
135
|
+
// later approved `sequences start`, which carries its own cap. Creating or
|
|
136
|
+
// editing one spends nothing; the manifest said otherwise while MCP called
|
|
137
|
+
// the same tools free (blind user eval, 2026-09-29).
|
|
138
|
+
"sequences create",
|
|
139
|
+
"sequences update",
|
|
132
140
|
// Starting an attended session itself is free. This legacy compatibility
|
|
133
141
|
// flag neither authorizes nor caps later inference; only `copilot send` can
|
|
134
142
|
// trigger the billed model loop.
|
|
@@ -166,6 +174,7 @@ const READ_ONLY_COMMANDS = new Set([
|
|
|
166
174
|
// calling the command without its confirmation flag writes anything.
|
|
167
175
|
const PREVIEW_BY_DEFAULT_COMMANDS = new Set([
|
|
168
176
|
"inbox delete-message",
|
|
177
|
+
"inbox forget-whatsapp-chat",
|
|
169
178
|
"tables watcher preview",
|
|
170
179
|
"ugc creators invite",
|
|
171
180
|
"ugc creators send-invite",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { type ContractFlagOverlay } from "./contract-flags.js";
|
|
3
|
+
type JsonOptions = {
|
|
4
|
+
json?: boolean;
|
|
5
|
+
};
|
|
6
|
+
type Handle = (command: string, options: JsonOptions, action: () => Promise<unknown>) => Promise<void>;
|
|
7
|
+
export declare const CONNECTORS_FLAG_OVERLAY: ContractFlagOverlay;
|
|
8
|
+
/** The PATCH body for the flags given; one of the two workspace flags is required. */
|
|
9
|
+
export declare function buildConnectorUpdateBody(options: Record<string, unknown>): Record<string, unknown>;
|
|
10
|
+
export declare function registerConnectorsCommands(program: Command, handle: Handle): void;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// `oxygen connectors`: the apps a person connected to OXYGEN with OAuth
|
|
2
|
+
// (ChatGPT, Claude, Codex…) and the workspace each one works in without asking.
|
|
3
|
+
// The set command's flags come from the shared contract that also generates
|
|
4
|
+
// `oxygen_connector_update` and validates the route, so the CLI and MCP cannot
|
|
5
|
+
// disagree about what can be set.
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
8
|
+
import { CONNECTOR_UPDATE_OPERATION } from "@oxygen/shared/contracts/connectors";
|
|
9
|
+
import { operationPath } from "@oxygen/shared/contracts/operations";
|
|
10
|
+
import { addContractFlags, readContractFlags } from "./contract-flags.js";
|
|
11
|
+
import { requestOxygen } from "./http-client.js";
|
|
12
|
+
const update = CONNECTOR_UPDATE_OPERATION;
|
|
13
|
+
export const CONNECTORS_FLAG_OVERLAY = {
|
|
14
|
+
default_workspace: {
|
|
15
|
+
help: "The workspace this app should work in without asking: its id, slug or exact name from `orgs list`. You must be a member.",
|
|
16
|
+
valueName: "<workspace>",
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
/** The PATCH body for the flags given; one of the two workspace flags is required. */
|
|
20
|
+
export function buildConnectorUpdateBody(options) {
|
|
21
|
+
const body = readContractFlags(options, update.contract, CONNECTORS_FLAG_OVERLAY);
|
|
22
|
+
if (!("default_workspace" in body)) {
|
|
23
|
+
throw new OxygenError("invalid_request", "Pass --default-workspace <workspace> to set where the app works, or --clear-default-workspace to have it ask each time.", { exitCode: 1 });
|
|
24
|
+
}
|
|
25
|
+
return body;
|
|
26
|
+
}
|
|
27
|
+
export function registerConnectorsCommands(program, handle) {
|
|
28
|
+
const connectors = program
|
|
29
|
+
.command("connectors")
|
|
30
|
+
.description("Connected apps: ChatGPT, Claude, Codex and any other app you connected to OXYGEN with OAuth, and the default workspace each one works in without asking. "
|
|
31
|
+
+ "`connectors list` shows them; `connectors set <app> --default-workspace <workspace>` changes that app only. "
|
|
32
|
+
+ "Picking a workspace inside an app changes the same setting, and the latest choice wins. This CLI keeps its own workspace (`orgs use`). "
|
|
33
|
+
+ "Web: https://oxygen-agent.com/settings/connected-apps.");
|
|
34
|
+
connectors.addCommand(new Command("list")
|
|
35
|
+
.description("List your connected apps with each one's default workspace, when it was last used, and the workspaces you can choose from. Read-only, 0 credits.")
|
|
36
|
+
.option("--json", "Print a JSON envelope.")
|
|
37
|
+
.action(async (options) => {
|
|
38
|
+
await handle("connectors list", options, () => requestOxygen("/api/cli/connectors"));
|
|
39
|
+
}));
|
|
40
|
+
connectors.addCommand(new Command("get")
|
|
41
|
+
.description("Show one connected app and its default workspace. Read-only, 0 credits.")
|
|
42
|
+
.argument("<connection>", update.cli.argument?.description ?? "The app's id or name.")
|
|
43
|
+
.option("--json", "Print a JSON envelope.")
|
|
44
|
+
.action(async (connection, options) => {
|
|
45
|
+
await handle("connectors get", options, () => requestOxygen(`/api/cli/connectors/${encodeURIComponent(connection)}`));
|
|
46
|
+
}));
|
|
47
|
+
connectors.addCommand(addContractFlags(new Command(update.cli.path.at(-1) ?? "set")
|
|
48
|
+
.description(update.cli.summary)
|
|
49
|
+
.argument(`<${update.cli.argument?.name ?? "connection"}>`, update.cli.argument?.description), update.contract, CONNECTORS_FLAG_OVERLAY)
|
|
50
|
+
.option("--json", "Print a JSON envelope.")
|
|
51
|
+
.addHelpText("after", `
|
|
52
|
+
Examples:
|
|
53
|
+
oxygen connectors list
|
|
54
|
+
oxygen connectors set ChatGPT --default-workspace "Acme GTM"
|
|
55
|
+
oxygen connectors set <id> --clear-default-workspace
|
|
56
|
+
`)
|
|
57
|
+
.action(async (connection, options) => {
|
|
58
|
+
await handle("connectors set", options, () => requestOxygen(operationPath(update, { connection }), {
|
|
59
|
+
method: update.method,
|
|
60
|
+
body: buildConnectorUpdateBody(options),
|
|
61
|
+
}));
|
|
62
|
+
}));
|
|
63
|
+
}
|
package/dist/help.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
1
|
+
import type { Command, CommanderError } from "commander";
|
|
2
|
+
import { type CliFailure } from "@oxygen/shared/cli-result";
|
|
2
3
|
export declare function applyOxygenHelp(program: Command, binaryName: string): void;
|
|
3
4
|
/**
|
|
4
5
|
* Turn commander's own edit-distance suggester on for EVERY command.
|
|
@@ -28,4 +29,29 @@ export declare function enableCommandSuggestions(command: Command): void;
|
|
|
28
29
|
* all, or when the flag is not a long option.
|
|
29
30
|
*/
|
|
30
31
|
export declare function unknownOptionHint(program: Command, argv: readonly string[], message: string): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Drop the help flag when it follows an operand that names no subcommand.
|
|
34
|
+
*
|
|
35
|
+
* Commander answers `--help` before it checks the operand, so `oxygen sequence
|
|
36
|
+
* --help` printed the ROOT help and exited 0: a typo'd command read as success,
|
|
37
|
+
* and the agent took the root help for the group it meant (blind user eval,
|
|
38
|
+
* 2026-09-29). Without the flag the same argv fails as the unknown command it
|
|
39
|
+
* is, with commander's suggestion, our hint and exit 2. Only a group that takes
|
|
40
|
+
* no positional argument qualifies, so `sequences get <id> --help` still helps.
|
|
41
|
+
*/
|
|
42
|
+
export declare function withoutHelpForUnknownCommand(program: Command, argv: readonly string[]): string[];
|
|
31
43
|
export declare function unknownCommandHint(program: Command, argv: readonly string[]): string | null;
|
|
44
|
+
/**
|
|
45
|
+
* The failure envelope and exit code for a commander usage error, or null when
|
|
46
|
+
* the error is not one (`--help` and `--version` exit 0). Commander refuses
|
|
47
|
+
* before any action runs, so the envelope every handler prints never covered
|
|
48
|
+
* these: `ugc creators accept` without `--linkedin-url` gave `--json` callers an
|
|
49
|
+
* empty stdout and exit 1 (UGC E2E pass, 2026-09-29). `hint` is the first hint
|
|
50
|
+
* already written to stderr; it becomes `details.next_step`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function commanderUsageFailure(program: Command, argv: readonly string[], error: CommanderError, hint: string | null): {
|
|
53
|
+
envelope: CliFailure;
|
|
54
|
+
exitCode: number;
|
|
55
|
+
} | null;
|
|
56
|
+
/** `--json` anywhere before a `--` terminator, where commander still reads options. */
|
|
57
|
+
export declare function argvRequestsJson(argv: readonly string[]): boolean;
|
package/dist/help.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CLI_EXIT_CODE_TABLE } from "@oxygen/shared";
|
|
2
|
+
import { exitCodeForErrorCode, failure } from "@oxygen/shared/cli-result";
|
|
2
3
|
// Grouped `--help` for the ~70 top-level command groups: commander's default
|
|
3
4
|
// registration-order wall (login, auth, profiles, ... before any GTM noun) is
|
|
4
5
|
// the first thing every fresh agent reads, so order it by the OXYGEN OS layer
|
|
@@ -225,7 +226,7 @@ export function unknownOptionHint(program, argv, message) {
|
|
|
225
226
|
const flag = /unknown option '(--[^']+)'/.exec(message)?.[1];
|
|
226
227
|
if (!flag)
|
|
227
228
|
return null;
|
|
228
|
-
const
|
|
229
|
+
const { command, path } = resolveCommandPath(program, argv);
|
|
229
230
|
const longs = new Set();
|
|
230
231
|
for (let current = command; current; current = current.parent) {
|
|
231
232
|
for (const option of current.options)
|
|
@@ -245,45 +246,127 @@ export function unknownOptionHint(program, argv, message) {
|
|
|
245
246
|
}
|
|
246
247
|
return `Hint: \`${path}\` has no \`${bare}\`. ${helpLine}`;
|
|
247
248
|
}
|
|
248
|
-
/**
|
|
249
|
+
/**
|
|
250
|
+
* Walk the command path in argv and return the deepest command, its printable
|
|
251
|
+
* path, and the first token that is no subcommand of it (`next`). Root options
|
|
252
|
+
* and their values (`--org <id>`, `--profile <name>`) are stepped over, since
|
|
253
|
+
* agents routinely pass them first; any other option ends the walk, because
|
|
254
|
+
* options below the root are commander's business.
|
|
255
|
+
*/
|
|
249
256
|
function resolveCommandPath(program, argv) {
|
|
250
|
-
let
|
|
257
|
+
let command = program;
|
|
251
258
|
let path = program.name();
|
|
252
|
-
for (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
259
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
260
|
+
const token = argv[index];
|
|
261
|
+
if (token.startsWith("-")) {
|
|
262
|
+
const flag = token.split("=")[0];
|
|
263
|
+
const option = program.options.find((candidate) => candidate.long === flag || candidate.short === flag);
|
|
264
|
+
if (!option)
|
|
265
|
+
break;
|
|
266
|
+
const value = argv[index + 1];
|
|
267
|
+
if (!token.includes("=") && (option.required || (option.optional && value !== undefined && !value.startsWith("-"))))
|
|
268
|
+
index += 1;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const child = command.commands.find((candidate) => candidate.name() === token || candidate.aliases().includes(token));
|
|
256
272
|
if (!child)
|
|
257
|
-
|
|
258
|
-
|
|
273
|
+
return { command, path, next: token };
|
|
274
|
+
command = child;
|
|
259
275
|
path = `${path} ${token}`;
|
|
260
276
|
}
|
|
261
|
-
return
|
|
277
|
+
return { command, path };
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Drop the help flag when it follows an operand that names no subcommand.
|
|
281
|
+
*
|
|
282
|
+
* Commander answers `--help` before it checks the operand, so `oxygen sequence
|
|
283
|
+
* --help` printed the ROOT help and exited 0: a typo'd command read as success,
|
|
284
|
+
* and the agent took the root help for the group it meant (blind user eval,
|
|
285
|
+
* 2026-09-29). Without the flag the same argv fails as the unknown command it
|
|
286
|
+
* is, with commander's suggestion, our hint and exit 2. Only a group that takes
|
|
287
|
+
* no positional argument qualifies, so `sequences get <id> --help` still helps.
|
|
288
|
+
*/
|
|
289
|
+
export function withoutHelpForUnknownCommand(program, argv) {
|
|
290
|
+
const args = argv.slice(2);
|
|
291
|
+
const end = args.indexOf("--");
|
|
292
|
+
const scanned = end < 0 ? args : args.slice(0, end);
|
|
293
|
+
const isHelp = (token) => token === "--help" || token === "-h";
|
|
294
|
+
if (!scanned.some(isHelp))
|
|
295
|
+
return [...argv];
|
|
296
|
+
const { command, next } = resolveCommandPath(program, args);
|
|
297
|
+
if (next === undefined || command.commands.length === 0 || command.registeredArguments.length > 0)
|
|
298
|
+
return [...argv];
|
|
299
|
+
return [...argv.slice(0, 2), ...args.filter((token, index) => !(index < scanned.length && isHelp(token)))];
|
|
262
300
|
}
|
|
263
301
|
export function unknownCommandHint(program, argv) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
302
|
+
const { command, path, next } = resolveCommandPath(program, argv);
|
|
303
|
+
if (next === undefined || command.commands.length === 0)
|
|
304
|
+
return null;
|
|
305
|
+
return buildUnknownCommandHint(command, path, next);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Commander's refusals, each as the `missing_*` / `invalid_*` code the exit
|
|
309
|
+
* table classes as 2 (usage). `commander.help` is a command group run without
|
|
310
|
+
* a subcommand (help on stderr); `commander.error` is a command's own
|
|
311
|
+
* `command.error(...)`.
|
|
312
|
+
*/
|
|
313
|
+
const COMMANDER_USAGE_CODES = {
|
|
314
|
+
"commander.missingMandatoryOptionValue": "missing_required_option",
|
|
315
|
+
"commander.optionMissingArgument": "missing_option_value",
|
|
316
|
+
"commander.missingArgument": "missing_argument",
|
|
317
|
+
"commander.help": "missing_subcommand",
|
|
318
|
+
"commander.unknownOption": "invalid_option",
|
|
319
|
+
"commander.unknownCommand": "invalid_command",
|
|
320
|
+
"commander.excessArguments": "invalid_arguments",
|
|
321
|
+
"commander.invalidArgument": "invalid_argument",
|
|
322
|
+
"commander.conflictingOption": "invalid_option_combination",
|
|
323
|
+
"commander.error": "invalid_request",
|
|
324
|
+
};
|
|
325
|
+
/**
|
|
326
|
+
* The failure envelope and exit code for a commander usage error, or null when
|
|
327
|
+
* the error is not one (`--help` and `--version` exit 0). Commander refuses
|
|
328
|
+
* before any action runs, so the envelope every handler prints never covered
|
|
329
|
+
* these: `ugc creators accept` without `--linkedin-url` gave `--json` callers an
|
|
330
|
+
* empty stdout and exit 1 (UGC E2E pass, 2026-09-29). `hint` is the first hint
|
|
331
|
+
* already written to stderr; it becomes `details.next_step`.
|
|
332
|
+
*/
|
|
333
|
+
export function commanderUsageFailure(program, argv, error, hint) {
|
|
334
|
+
if (error.exitCode === 0)
|
|
335
|
+
return null;
|
|
336
|
+
const code = COMMANDER_USAGE_CODES[error.code];
|
|
337
|
+
if (!code)
|
|
338
|
+
return null;
|
|
339
|
+
const { path } = resolveCommandPath(program, argv);
|
|
340
|
+
const message = error.code === "commander.help"
|
|
341
|
+
? `\`${path}\` needs a subcommand.`
|
|
342
|
+
: error.message.replace(/^error:\s*/, "");
|
|
343
|
+
const option = /option '(-[^' ]+)/.exec(message)?.[1];
|
|
344
|
+
const helpFor = error.code === "commander.help" ? "subcommands" : "options";
|
|
345
|
+
return {
|
|
346
|
+
envelope: failure(path.split(" ").slice(1).join(" ") || program.name(), {
|
|
347
|
+
code,
|
|
348
|
+
message,
|
|
349
|
+
details: {
|
|
350
|
+
commander_code: error.code,
|
|
351
|
+
...(option ? { option } : {}),
|
|
352
|
+
next_step: hint?.replace(/^Hint:\s*/, "") ?? `Run \`${path} --help\` for its ${helpFor}.`,
|
|
353
|
+
},
|
|
354
|
+
}),
|
|
355
|
+
exitCode: exitCodeForErrorCode(code),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/** `--json` anywhere before a `--` terminator, where commander still reads options. */
|
|
359
|
+
export function argvRequestsJson(argv) {
|
|
360
|
+
const end = argv.indexOf("--");
|
|
361
|
+
return (end < 0 ? argv : argv.slice(0, end)).includes("--json");
|
|
282
362
|
}
|
|
283
363
|
function buildUnknownCommandHint(parent, path, token) {
|
|
284
364
|
const names = parent.commands.map((command) => command.name()).filter((name) => name !== "help");
|
|
285
365
|
const lower = token.toLowerCase();
|
|
286
|
-
|
|
366
|
+
// A read intent on a budget must never point at the mutating `set` command.
|
|
367
|
+
const synonym = path.endsWith(" budget") && lower === "get" && names.includes("list")
|
|
368
|
+
? "list"
|
|
369
|
+
: (COMMAND_SYNONYMS[lower] ?? []).find((candidate) => names.includes(candidate));
|
|
287
370
|
// A unique prefix is the other guess commander cannot make: `tables desc`
|
|
288
371
|
// scores too far from `describe` on edit distance, but it names exactly one
|
|
289
372
|
// command.
|
package/dist/http-client.js
CHANGED
|
@@ -5,6 +5,11 @@ import { defaultApiUrl, loadCredentialsWithEndpoint } from "./credentials.js";
|
|
|
5
5
|
import { assertResolvedApiUrl, cliEndpointDetails, describeCliEndpoint, isProdApiUrl, resolveCliUpdateGuidance, } from "./runtime.js";
|
|
6
6
|
const DEFAULT_REQUEST_TIMEOUT_MS = 120_000;
|
|
7
7
|
const CLI_COMPATIBILITY_CHECK_TIMEOUT_MS = 5_000;
|
|
8
|
+
// A write that fails in transit may already have been applied before the
|
|
9
|
+
// connection dropped or the client gave up, so a blind retry can repeat it.
|
|
10
|
+
// Replay on the Idempotency-Key is per route (most routes ignore the header),
|
|
11
|
+
// so this must not promise that a same-key retry is safe.
|
|
12
|
+
const UNCONFIRMED_WRITE_NEXT_STEP = "This change may already have been applied. Check the current state (for example with the matching list or get command) before you retry.";
|
|
8
13
|
const cliCompatibilityCheckKeys = new Set();
|
|
9
14
|
export async function requestOxygen(// skipcq: JS-R1005
|
|
10
15
|
path, options = {}) {
|
|
@@ -99,6 +104,7 @@ path, options = {}) {
|
|
|
99
104
|
catch (error) {
|
|
100
105
|
if (error instanceof OxygenError)
|
|
101
106
|
throw error;
|
|
107
|
+
const unconfirmedWrite = method === "GET" ? {} : { next_step: UNCONFIRMED_WRITE_NEXT_STEP };
|
|
102
108
|
if (timeout.timedOut() || (timeoutMs !== undefined && isAbortError(error))) {
|
|
103
109
|
throw new OxygenError("network_timeout", "Oxygen API request timed out.", {
|
|
104
110
|
details: {
|
|
@@ -107,6 +113,7 @@ path, options = {}) {
|
|
|
107
113
|
timeout_ms: timeoutMs,
|
|
108
114
|
trace_id: traceId,
|
|
109
115
|
...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),
|
|
116
|
+
...unconfirmedWrite,
|
|
110
117
|
},
|
|
111
118
|
exitCode: 1,
|
|
112
119
|
});
|
|
@@ -116,6 +123,7 @@ path, options = {}) {
|
|
|
116
123
|
api_url: apiUrl,
|
|
117
124
|
reason: error instanceof Error ? error.message : "unknown",
|
|
118
125
|
...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),
|
|
126
|
+
...unconfirmedWrite,
|
|
119
127
|
},
|
|
120
128
|
exitCode: 1,
|
|
121
129
|
});
|