@oxygen-agent/cli 1.1011.326 → 1.1011.440
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/command-manifest.js +15 -4
- package/dist/connectors-commands.d.ts +4 -1
- package/dist/connectors-commands.js +84 -10
- package/dist/contract-flags.d.ts +2 -0
- package/dist/contract-flags.js +26 -11
- package/dist/crm-support-flags.d.ts +11 -0
- package/dist/crm-support-flags.js +60 -0
- package/dist/customer-operation-commands.d.ts +5 -0
- package/dist/customer-operation-commands.js +101 -0
- package/dist/database-query-flags.d.ts +2 -0
- package/dist/database-query-flags.js +6 -0
- package/dist/help.d.ts +3 -1
- package/dist/help.js +43 -0
- package/dist/http-client.d.ts +2 -0
- package/dist/http-client.js +41 -1
- package/dist/index.js +640 -234
- package/dist/knowledge-repository-commands.js +12 -14
- package/dist/mailbox-ramp-notice.d.ts +11 -0
- package/dist/mailbox-ramp-notice.js +53 -0
- package/dist/settings-commands.d.ts +11 -0
- package/dist/settings-commands.js +73 -0
- package/dist/skills.d.ts +5 -4
- package/dist/skills.js +106 -79
- package/dist/tool-catalog-detail-command.d.ts +7 -0
- package/dist/tool-catalog-detail-command.js +19 -0
- package/dist/tool-catalog-search-window.d.ts +6 -0
- package/dist/tool-catalog-search-window.js +12 -0
- package/dist/transcript.js +1 -0
- package/node_modules/@oxygen/shared/dist/bounded-read-result.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/bounded-read-result.js +25 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +47 -1
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +625 -39
- package/node_modules/@oxygen/shared/dist/connection-approval.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/connection-approval.js +2 -0
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +110 -0
- package/node_modules/@oxygen/shared/dist/contracts/connectors.d.ts +32 -6
- package/node_modules/@oxygen/shared/dist/contracts/connectors.js +56 -4
- package/node_modules/@oxygen/shared/dist/contracts/crm-attributes.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-attributes.js +39 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-enrichment-read.d.ts +21 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-enrichment-read.js +15 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-support.d.ts +117 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-support.js +43 -0
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +131 -0
- package/node_modules/@oxygen/shared/dist/contracts/dashboard-tabs.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/contracts/dashboard-tabs.js +2 -0
- package/node_modules/@oxygen/shared/dist/contracts/database.d.ts +30 -0
- package/node_modules/@oxygen/shared/dist/contracts/database.js +29 -0
- package/node_modules/@oxygen/shared/dist/contracts/integration-connect.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/contracts/integration-connect.js +7 -0
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-content-reads.d.ts +43 -0
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-content-reads.js +20 -0
- package/node_modules/@oxygen/shared/dist/contracts/linkedin-invites-list.d.ts +12 -0
- package/node_modules/@oxygen/shared/dist/contracts/linkedin-invites-list.js +8 -0
- package/node_modules/@oxygen/shared/dist/contracts/operations.d.ts +48 -2
- package/node_modules/@oxygen/shared/dist/contracts/operations.js +68 -2
- package/node_modules/@oxygen/shared/dist/contracts/table-grid-query.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-grid-query.js +6 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-query-grid.d.ts +21 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-query-grid.js +27 -0
- package/node_modules/@oxygen/shared/dist/contracts/tool-catalog-detail.d.ts +19 -0
- package/node_modules/@oxygen/shared/dist/contracts/tool-catalog-detail.js +10 -0
- package/node_modules/@oxygen/shared/dist/contracts/tool-catalog-search-window.d.ts +12 -0
- package/node_modules/@oxygen/shared/dist/contracts/tool-catalog-search-window.js +7 -0
- package/node_modules/@oxygen/shared/dist/contracts/workspace-pages.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/contracts/workspace-pages.js +49 -0
- package/node_modules/@oxygen/shared/dist/decision-ledger-parser.d.ts +32 -0
- package/node_modules/@oxygen/shared/dist/decision-ledger-parser.js +303 -0
- package/node_modules/@oxygen/shared/dist/decision-ledger.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/decision-ledger.generated.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/decision-ledger.generated.js +717 -0
- package/node_modules/@oxygen/shared/dist/decision-ledger.js +20 -0
- package/node_modules/@oxygen/shared/dist/email-campaign-ramp.d.ts +104 -0
- package/node_modules/@oxygen/shared/dist/email-campaign-ramp.js +250 -0
- package/node_modules/@oxygen/shared/dist/mcp-event-contract.d.ts +10 -7
- package/node_modules/@oxygen/shared/dist/mcp-event-contract.js +11 -7
- package/node_modules/@oxygen/shared/dist/mcp-table-widget.d.ts +50 -0
- package/node_modules/@oxygen/shared/dist/mcp-table-widget.js +24 -0
- package/node_modules/@oxygen/shared/dist/plan-band.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/plan-band.js +12 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +20 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.js +41 -0
- package/node_modules/@oxygen/shared/dist/provider-input-value.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/provider-input-value.js +10 -0
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +33 -4
- package/node_modules/@oxygen/shared/dist/sequences.js +48 -13
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +17 -0
- 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/package.json +8 -6
- package/package.json +1 -1
- package/node_modules/@oxygen/shared/dist/copilot-skills.d.ts +0 -15
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +0 -31
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +0 -41
- package/node_modules/@oxygen/shared/dist/copilot-skills.js +0 -6
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile, writeFile, stat } from "node:fs/promises";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
4
5
|
import { requestOxygen } from "./http-client.js";
|
|
5
6
|
const post = (path, body) => {
|
|
6
7
|
if (Buffer.byteLength(JSON.stringify(body)) > 2_000_000)
|
|
@@ -99,21 +100,14 @@ export function registerKnowledgeRepositoryCommands(program, handle) {
|
|
|
99
100
|
.option("--cursor <cursor>", "Continue the previous page.")
|
|
100
101
|
.option("--limit <number>", "Maximum files, up to 500.", "100")
|
|
101
102
|
.option("--json", "Print a JSON envelope.")
|
|
102
|
-
.action((o) => handle("knowledge files list", o, () =>
|
|
103
|
-
mountId: o.mountId,
|
|
104
|
-
limit: Number(o.limit),
|
|
105
|
-
...(o.cursor ? { cursor: o.cursor } : {}),
|
|
106
|
-
})));
|
|
103
|
+
.action((o) => handle("knowledge files list", o, () => requestOxygen(`/api/cli/knowledge/files/list?${new URLSearchParams({ mountId: String(o.mountId), limit: String(o.limit), ...(o.cursor ? { cursor: String(o.cursor) } : {}) })}`)));
|
|
107
104
|
files
|
|
108
105
|
.command("get")
|
|
109
106
|
.description("Read original file content, properties and download link at an optional immutable revision.")
|
|
110
107
|
.requiredOption("--id <id>", "File ID from files list.")
|
|
111
108
|
.option("--revision <number>", "Specific saved file revision.")
|
|
112
109
|
.option("--json", "Print a JSON envelope.")
|
|
113
|
-
.action((o) => handle("knowledge files get", o, () =>
|
|
114
|
-
id: o.id,
|
|
115
|
-
...(o.revision ? { revision: Number(o.revision) } : {}),
|
|
116
|
-
})));
|
|
110
|
+
.action((o) => handle("knowledge files get", o, () => requestOxygen(`/api/cli/knowledge/files/get?${new URLSearchParams({ id: String(o.id), ...(o.revision ? { revision: String(o.revision) } : {}) })}`)));
|
|
117
111
|
files
|
|
118
112
|
.command("upsert")
|
|
119
113
|
.description("Create or replace an original file, or update frontmatter properties. Canonical copy still requires a proposal.")
|
|
@@ -122,11 +116,15 @@ export function registerKnowledgeRepositoryCommands(program, handle) {
|
|
|
122
116
|
.option("--path <path>", "Exact repository-relative path; required for new files.")
|
|
123
117
|
.requiredOption("--expected-revision <number>", "Reviewed file revision; 0 creates a new file.")
|
|
124
118
|
.option("--file <path>", "Complete local file, including any frontmatter or binary bytes.")
|
|
125
|
-
.option("--
|
|
119
|
+
.option("--text <text>", "Exact UTF-8 content; exclusive with --file and --properties-json.")
|
|
120
|
+
.option("--properties-json <json>", "Frontmatter properties to change; exclusive with --file and --text.")
|
|
126
121
|
.option("--json", "Print a JSON envelope.")
|
|
127
122
|
.action((o) => handle("knowledge files upsert", o, async () => {
|
|
128
|
-
if (
|
|
129
|
-
throw new
|
|
123
|
+
if ([o.file, o.text, o.propertiesJson].filter((value) => value !== undefined).length !== 1)
|
|
124
|
+
throw new OxygenError("invalid_request", "Provide exactly one of --file, --text or --properties-json.", { exitCode: 2 });
|
|
125
|
+
const expectedRevision = Number(o.expectedRevision);
|
|
126
|
+
if (!String(o.expectedRevision ?? "").trim() || !Number.isSafeInteger(expectedRevision) || expectedRevision < 0)
|
|
127
|
+
throw new OxygenError("invalid_request", "--expected-revision must be a nonnegative integer.", { exitCode: 2 });
|
|
130
128
|
if (o.file && (await stat(String(o.file))).size > 1_500_000)
|
|
131
129
|
throw new Error("Inline file uploads must fit a 2 MB JSON request (including base64). Add larger attachments through the connected GitHub repository.");
|
|
132
130
|
const patch = o.propertiesJson
|
|
@@ -139,12 +137,12 @@ export function registerKnowledgeRepositoryCommands(program, handle) {
|
|
|
139
137
|
mountId: o.mountId,
|
|
140
138
|
...(o.id ? { id: o.id } : {}),
|
|
141
139
|
...(o.path ? { path: o.path } : {}),
|
|
142
|
-
expectedFileRevision:
|
|
140
|
+
expectedFileRevision: expectedRevision,
|
|
143
141
|
...(o.file
|
|
144
142
|
? {
|
|
145
143
|
bytesBase64: (await readFile(String(o.file))).toString("base64"),
|
|
146
144
|
}
|
|
147
|
-
: { propertyPatch: patch }),
|
|
145
|
+
: o.text !== undefined ? { text: String(o.text) } : { propertyPatch: patch }),
|
|
148
146
|
});
|
|
149
147
|
}));
|
|
150
148
|
files
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A mailbox's campaign ramp-up as one terminal line, after `mailboxes get`,
|
|
3
|
+
* `mailboxes ramp set` and `mailboxes cap set`. The blind baselines (2026-10-01)
|
|
4
|
+
* had to work out by hand on which sending day a custom ramp reaches the daily
|
|
5
|
+
* cap, and agents read the end of the output, so the schedule, today's limit and
|
|
6
|
+
* that day are restated here from the envelope's `effective_cap`.
|
|
7
|
+
*
|
|
8
|
+
* Returns null unless the envelope carries `effective_cap.ramp_schedule`, so
|
|
9
|
+
* every other command prints nothing.
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatMailboxRampNotice(data: unknown): string | null;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { isRecord } from "./util.js";
|
|
2
|
+
/**
|
|
3
|
+
* A mailbox's campaign ramp-up as one terminal line, after `mailboxes get`,
|
|
4
|
+
* `mailboxes ramp set` and `mailboxes cap set`. The blind baselines (2026-10-01)
|
|
5
|
+
* had to work out by hand on which sending day a custom ramp reaches the daily
|
|
6
|
+
* cap, and agents read the end of the output, so the schedule, today's limit and
|
|
7
|
+
* that day are restated here from the envelope's `effective_cap`.
|
|
8
|
+
*
|
|
9
|
+
* Returns null unless the envelope carries `effective_cap.ramp_schedule`, so
|
|
10
|
+
* every other command prints nothing.
|
|
11
|
+
*/
|
|
12
|
+
export function formatMailboxRampNotice(data) {
|
|
13
|
+
if (!isRecord(data) || !isRecord(data.effective_cap))
|
|
14
|
+
return null;
|
|
15
|
+
const cap = data.effective_cap;
|
|
16
|
+
if (!Array.isArray(cap.ramp_schedule))
|
|
17
|
+
return null;
|
|
18
|
+
const steps = cap.ramp_schedule.flatMap((step) => isRecord(step) && typeof step.per_day === "number" ? [step.per_day] : []);
|
|
19
|
+
const configured = numberOrNull(cap.configured_daily_cap);
|
|
20
|
+
const today = numberOrNull(cap.effective_daily_cap);
|
|
21
|
+
const fullOn = numberOrNull(cap.full_cap_on_sending_day);
|
|
22
|
+
const sendingDays = numberOrNull(cap.sending_days) ?? 0;
|
|
23
|
+
if (configured === null || today === null || steps.length === 0)
|
|
24
|
+
return null;
|
|
25
|
+
const address = isRecord(data.mailbox) && typeof data.mailbox.emailAddress === "string"
|
|
26
|
+
? ` for ${data.mailbox.emailAddress}`
|
|
27
|
+
: "";
|
|
28
|
+
const todayLine = `today up to ${today}.`;
|
|
29
|
+
if (cap.ramp_mode === "off") {
|
|
30
|
+
return `Campaign ramp-up${address}: off, the daily cap of ${configured} applies from the first sending day; ${todayLine}`;
|
|
31
|
+
}
|
|
32
|
+
const mode = cap.ramp_mode === "custom" ? "custom" : "standard";
|
|
33
|
+
const currentDay = sendingDays + 1;
|
|
34
|
+
if (fullOn !== null && currentDay >= fullOn) {
|
|
35
|
+
return `Campaign ramp-up${address} (${mode}) is complete: ${todayLine}`;
|
|
36
|
+
}
|
|
37
|
+
const schedule = `${formatSteps(steps)} a day`;
|
|
38
|
+
const end = fullOn === null
|
|
39
|
+
? `It holds at ${steps[steps.length - 1]} a day and never reaches the daily cap of ${configured}.`
|
|
40
|
+
: `It reaches the daily cap of ${configured} on sending day ${fullOn}.`;
|
|
41
|
+
return `Campaign ramp-up${address} (${mode}): ${schedule}. Today is sending day ${currentDay}: up to ${today}. ${end}`
|
|
42
|
+
+ " A day counts toward the ramp once it has accepted campaign sends (UTC).";
|
|
43
|
+
}
|
|
44
|
+
function numberOrNull(value) {
|
|
45
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
46
|
+
}
|
|
47
|
+
/** "5 → 10 → 20 → 25", shortened to its first three and last two levels past eight. */
|
|
48
|
+
function formatSteps(levels) {
|
|
49
|
+
const shown = levels.length > 8
|
|
50
|
+
? [...levels.slice(0, 3).map(String), "…", ...levels.slice(-2).map(String)]
|
|
51
|
+
: levels.map(String);
|
|
52
|
+
return shown.join(" → ");
|
|
53
|
+
}
|
|
@@ -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 WORKSPACE_PAGES_FLAG_OVERLAY: ContractFlagOverlay;
|
|
8
|
+
/** The POST body for the flags given; --hide, --show or --show-all is required. */
|
|
9
|
+
export declare function buildWorkspacePagesBody(options: Record<string, unknown>): Record<string, unknown>;
|
|
10
|
+
export declare function registerSettingsCommands(program: Command, handle: Handle): void;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// `oxygen settings`: workspace settings every member reads and admins change.
|
|
2
|
+
// Its family today is `settings pages`, the web app's sidebar pages a workspace
|
|
3
|
+
// hides for everyone (Settings → Customise). The set command's flags come from
|
|
4
|
+
// the shared contract that also generates `oxygen_settings_pages_update` and
|
|
5
|
+
// validates the route, so the CLI and MCP cannot disagree about what can be set.
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
8
|
+
import { WORKSPACE_PAGES_GET_OPERATION, WORKSPACE_PAGES_UPDATE_OPERATION } from "@oxygen/shared/contracts/workspace-pages";
|
|
9
|
+
import { addContractFlags, readContractFlags } from "./contract-flags.js";
|
|
10
|
+
import { requestOxygen } from "./http-client.js";
|
|
11
|
+
const list = WORKSPACE_PAGES_GET_OPERATION;
|
|
12
|
+
const update = WORKSPACE_PAGES_UPDATE_OPERATION;
|
|
13
|
+
export const WORKSPACE_PAGES_FLAG_OVERLAY = {
|
|
14
|
+
hide: {
|
|
15
|
+
help: "Pages to hide from the sidebar for everyone, comma-separated, each by its path (/unibox) or name (Unibox) from `settings pages list`. Hiding a section hides its sub-pages.",
|
|
16
|
+
valueName: "<pages>",
|
|
17
|
+
},
|
|
18
|
+
show: {
|
|
19
|
+
help: "Hidden pages to show again, comma-separated, by path or name.",
|
|
20
|
+
valueName: "<pages>",
|
|
21
|
+
},
|
|
22
|
+
show_all: {
|
|
23
|
+
help: "Show every page again. With --hide, only those pages stay hidden.",
|
|
24
|
+
// `--no-show-all` would change nothing, and reads like "hide all".
|
|
25
|
+
noNegate: true,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
function names(value) {
|
|
29
|
+
return Array.isArray(value) && value.length > 0;
|
|
30
|
+
}
|
|
31
|
+
/** The POST body for the flags given; --hide, --show or --show-all is required. */
|
|
32
|
+
export function buildWorkspacePagesBody(options) {
|
|
33
|
+
const body = readContractFlags(options, update.contract, WORKSPACE_PAGES_FLAG_OVERLAY);
|
|
34
|
+
if (!names(body.hide) && !names(body.show) && body.show_all !== true) {
|
|
35
|
+
throw new OxygenError("invalid_request", "Nothing to change: pass --hide <pages> to hide pages for everyone, --show <pages> to show hidden ones again, or --show-all. `settings pages list` names every page.", { exitCode: 1 });
|
|
36
|
+
}
|
|
37
|
+
return body;
|
|
38
|
+
}
|
|
39
|
+
export function registerSettingsCommands(program, handle) {
|
|
40
|
+
const settings = program
|
|
41
|
+
.command("settings")
|
|
42
|
+
.description("Workspace settings: what every member of this workspace sees. "
|
|
43
|
+
+ "`settings pages` is Settings → Customise in the web app: hide or show pages in the sidebar navigation (the left menu) for everyone. "
|
|
44
|
+
+ "`settings pages list` shows each page and whether it is hidden; an admin or owner changes it with `settings pages set --hide <pages>`, `--show <pages>` or `--show-all`. "
|
|
45
|
+
+ "Nothing is deleted: a hidden page stays reachable by URL. Free.");
|
|
46
|
+
const pages = new Command("pages")
|
|
47
|
+
.description("The web app's sidebar pages this workspace hides for every member (Settings → Customise): page visibility in the navigation menu. "
|
|
48
|
+
+ "`list` shows every page with its section and whether it is hidden or visible; `set` hides or shows pages (admins and owners only). "
|
|
49
|
+
+ "Hiding a section hides its sub-pages. Presentation only: nothing is deleted, and a hidden page stays reachable by URL and deep-link. Every response carries its `web_url`.");
|
|
50
|
+
pages.addCommand(new Command(list.cli.path.at(-1) ?? "list")
|
|
51
|
+
.description(list.cli.summary)
|
|
52
|
+
.option("--json", "Print a JSON envelope.")
|
|
53
|
+
.action(async (options) => {
|
|
54
|
+
await handle("settings pages list", options, () => requestOxygen(list.route, { method: list.method }));
|
|
55
|
+
}));
|
|
56
|
+
pages.addCommand(addContractFlags(new Command(update.cli.path.at(-1) ?? "set").description(update.cli.summary), update.contract, WORKSPACE_PAGES_FLAG_OVERLAY)
|
|
57
|
+
.option("--json", "Print a JSON envelope.")
|
|
58
|
+
.addHelpText("after", `
|
|
59
|
+
Examples:
|
|
60
|
+
oxygen settings pages list
|
|
61
|
+
oxygen settings pages set --hide Unibox,Publishing
|
|
62
|
+
oxygen settings pages set --show /unibox
|
|
63
|
+
oxygen settings pages set --show-all
|
|
64
|
+
oxygen settings pages set --show-all --hide Workflows
|
|
65
|
+
`)
|
|
66
|
+
.action(async (options) => {
|
|
67
|
+
await handle("settings pages set", options, () => requestOxygen(update.route, {
|
|
68
|
+
method: update.method,
|
|
69
|
+
body: buildWorkspacePagesBody(options),
|
|
70
|
+
}));
|
|
71
|
+
}));
|
|
72
|
+
settings.addCommand(pages);
|
|
73
|
+
}
|
package/dist/skills.d.ts
CHANGED
|
@@ -18,7 +18,9 @@ export type SkillsListOptions = {
|
|
|
18
18
|
export type SkillsSearchOptions = SkillsListOptions & {
|
|
19
19
|
limit?: string;
|
|
20
20
|
};
|
|
21
|
-
export type SkillsGetOptions = SkillsListOptions
|
|
21
|
+
export type SkillsGetOptions = SkillsListOptions & {
|
|
22
|
+
file?: string;
|
|
23
|
+
};
|
|
22
24
|
export type SkillsDoctorOptions = {
|
|
23
25
|
json?: boolean;
|
|
24
26
|
apiUrl?: string;
|
|
@@ -49,9 +51,8 @@ export type SkillsSourceRuntime = {
|
|
|
49
51
|
};
|
|
50
52
|
/**
|
|
51
53
|
* Resolves where to install skills from. Logged-in CLIs mint a short-lived
|
|
52
|
-
* org-bound access URL
|
|
53
|
-
*
|
|
54
|
-
* installs never regress while older deployments are still in circulation.
|
|
54
|
+
* org-bound access URL. Any failure leaves public metadata discovery usable;
|
|
55
|
+
* downloading instruction bodies always requires the authenticated source.
|
|
55
56
|
*/
|
|
56
57
|
export declare function resolveSkillsInstallSource(options: {
|
|
57
58
|
apiUrl: string;
|
package/dist/skills.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
|
-
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { dirname, join, posix, resolve } from "node:path";
|
|
5
5
|
import { OxygenError, errorMessage, inferUserCapabilityRoute, serializeCapabilityRoute, toFailure, } from "@oxygen/shared";
|
|
6
6
|
import { defaultApiUrl, loadCredentials, normalizeApiUrl } from "./credentials.js";
|
|
7
7
|
import { requestOxygen } from "./http-client.js";
|
|
@@ -22,9 +22,8 @@ const AGENT_DISPLAY_NAMES = {
|
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
24
24
|
* Resolves where to install skills from. Logged-in CLIs mint a short-lived
|
|
25
|
-
* org-bound access URL
|
|
26
|
-
*
|
|
27
|
-
* installs never regress while older deployments are still in circulation.
|
|
25
|
+
* org-bound access URL. Any failure leaves public metadata discovery usable;
|
|
26
|
+
* downloading instruction bodies always requires the authenticated source.
|
|
28
27
|
*/
|
|
29
28
|
export async function resolveSkillsInstallSource(options) {
|
|
30
29
|
const publicSource = (reason) => ({
|
|
@@ -51,7 +50,7 @@ export async function resolveSkillsInstallSource(options) {
|
|
|
51
50
|
credentials,
|
|
52
51
|
...(options.fetch ? { fetch: options.fetch } : {}),
|
|
53
52
|
});
|
|
54
|
-
if (!access
|
|
53
|
+
if (!isValidSkillsAccessResponse(access, options.apiUrl)) {
|
|
55
54
|
return publicSource("invalid_access_response");
|
|
56
55
|
}
|
|
57
56
|
return {
|
|
@@ -66,8 +65,21 @@ export async function resolveSkillsInstallSource(options) {
|
|
|
66
65
|
return publicSource(`access_unavailable:${code}`);
|
|
67
66
|
}
|
|
68
67
|
}
|
|
68
|
+
function isValidSkillsAccessResponse(access, apiUrl) {
|
|
69
|
+
try {
|
|
70
|
+
const base = new URL(access.base_url);
|
|
71
|
+
const api = new URL(apiUrl);
|
|
72
|
+
return base.origin === api.origin && !base.username && !base.password && !base.search && !base.hash
|
|
73
|
+
&& /^\/skills-access\/[^/]+$/.test(base.pathname)
|
|
74
|
+
&& access.index_url === `${access.base_url}/.well-known/skills/index.json`
|
|
75
|
+
&& Number.isFinite(Date.parse(access.expires_at)) && Date.parse(access.expires_at) > Date.now();
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
69
81
|
function requireSkillContentAccess(index, source) {
|
|
70
|
-
if (!index.authentication_required)
|
|
82
|
+
if (source.authenticated && !index.authentication_required)
|
|
71
83
|
return;
|
|
72
84
|
throw new OxygenError("skills_authentication_required", "Sign in to download Oxygen agent instructions.", {
|
|
73
85
|
details: {
|
|
@@ -122,7 +134,7 @@ export async function searchAgentSkills(query, options, runtime = {}) {
|
|
|
122
134
|
.sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name));
|
|
123
135
|
return {
|
|
124
136
|
query: normalized,
|
|
125
|
-
route: serializeCapabilityRoute(route),
|
|
137
|
+
route: serializeCapabilityRoute(route, { visibleSkills: index.skill_names }),
|
|
126
138
|
total_matches: matches.length,
|
|
127
139
|
returned: Math.min(limit, matches.length),
|
|
128
140
|
skills: matches.slice(0, limit).map(({ skill, score, matching_documents }) => ({
|
|
@@ -140,41 +152,27 @@ export async function searchAgentSkills(query, options, runtime = {}) {
|
|
|
140
152
|
}
|
|
141
153
|
export async function getAgentSkill(skillName, options, runtime = {}) {
|
|
142
154
|
const apiUrl = normalizeApiUrl(readOption(options.apiUrl) ?? defaultApiUrl());
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
details: { index_url: source.index_url, error: index.error },
|
|
148
|
-
exitCode: 1,
|
|
155
|
+
const credentials = runtime.credentials !== undefined ? runtime.credentials : await loadCredentials();
|
|
156
|
+
if (!credentials || normalizeApiUrl(credentials.apiUrl) !== apiUrl) {
|
|
157
|
+
throw new OxygenError("skills_authentication_required", "Sign in to download Oxygen agent instructions.", {
|
|
158
|
+
details: { next_step: "Run `oxygen login`, then retry `oxygen skills get`." }, exitCode: 1,
|
|
149
159
|
});
|
|
150
160
|
}
|
|
151
|
-
requireSkillContentAccess(index, source);
|
|
152
161
|
const normalized = skillName.trim();
|
|
153
|
-
const
|
|
154
|
-
if (!
|
|
155
|
-
throw new OxygenError("
|
|
156
|
-
details: {
|
|
162
|
+
const file = readOption(options.file)?.trim() ?? "SKILL.md";
|
|
163
|
+
if (!isSafeSkillName(normalized) || !isSafeSkillFile(file)) {
|
|
164
|
+
throw new OxygenError("skill_file_not_found", `Unknown file in Oxygen skill ${normalized}.`, {
|
|
165
|
+
details: { file },
|
|
157
166
|
exitCode: 2,
|
|
158
167
|
});
|
|
159
168
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
const fetchImpl = runtime.fetch ?? fetch;
|
|
166
|
-
const response = await fetchImpl(skill.entrypoint, { headers: { Accept: "text/markdown" } });
|
|
167
|
-
if (!response.ok) {
|
|
168
|
-
throw new OxygenError("skill_entrypoint_unreachable", `Unable to hydrate Oxygen skill ${normalized}.`, {
|
|
169
|
-
details: { status: response.status },
|
|
170
|
-
exitCode: 1,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
const content = await response.text();
|
|
169
|
+
const params = new URLSearchParams({ name: normalized, file });
|
|
170
|
+
const skill = await requestOxygen(`/api/cli/skills/get?${params}`, {
|
|
171
|
+
method: "GET", credentials, ...(runtime.fetch ? { fetch: runtime.fetch } : {}),
|
|
172
|
+
});
|
|
174
173
|
return {
|
|
175
174
|
...skill,
|
|
176
|
-
|
|
177
|
-
install: `oxygen skills install --skill ${skill.name} --json`,
|
|
175
|
+
install: `oxygen skills install --skill ${normalized} --json`,
|
|
178
176
|
};
|
|
179
177
|
}
|
|
180
178
|
export async function doctorAgentSkills(options, runtime = {}) {
|
|
@@ -215,12 +213,10 @@ options, runtime = {}) {
|
|
|
215
213
|
...(runtime.credentials !== undefined ? { credentials: runtime.credentials } : {}),
|
|
216
214
|
...(runtime.fetch ? { fetch: runtime.fetch } : {}),
|
|
217
215
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
requireSkillContentAccess(await inspectSkillIndex(source.index_url, runtime.fetch), source);
|
|
223
|
-
}
|
|
216
|
+
// The client also fails closed against older servers whose catalog predates
|
|
217
|
+
// authentication_required. Metadata can never authorize a content download.
|
|
218
|
+
if (!source.authenticated)
|
|
219
|
+
requireSkillContentAccess(emptySkillIndexInspection({ status: null, error: { code: "skills_authentication_required", message: "Sign in to download skills." } }), source);
|
|
224
220
|
const agents = readWords(options.agents ?? DEFAULT_SKILL_AGENTS);
|
|
225
221
|
const skill = readOption(options.skill) ?? "*";
|
|
226
222
|
const env = runtime.env ?? process.env;
|
|
@@ -236,9 +232,8 @@ options, runtime = {}) {
|
|
|
236
232
|
.join(", ")}\n`);
|
|
237
233
|
// A specific shortcut skill can declare prerequisite skills that its SKILL.md
|
|
238
234
|
// invokes (e.g. oxygen-clay-migration → oxygen-gtm, whose recipes it lives in).
|
|
239
|
-
// Installing it alone would leave those missing, so
|
|
240
|
-
//
|
|
241
|
-
// and skipped for "*", which already installs everything.
|
|
235
|
+
// Installing it alone would leave those missing, so resolve its full graph
|
|
236
|
+
// before writing files. "*" already installs every visible skill.
|
|
242
237
|
const dependencySkills = skill === "*"
|
|
243
238
|
? []
|
|
244
239
|
: await resolveSkillInstallDependencies(skill, source.index_url, runtime.fetch);
|
|
@@ -247,18 +242,15 @@ options, runtime = {}) {
|
|
|
247
242
|
// reach the installer's argv — any local process can read a child's
|
|
248
243
|
// arguments. Stage the requested skill content to a private temp dir (the
|
|
249
244
|
// tokenized URLs stay inside this process on the fetches) and hand the
|
|
250
|
-
// installer that local path instead.
|
|
251
|
-
// passed straight through as before.
|
|
245
|
+
// installer that local path instead.
|
|
252
246
|
let stagingDir = null;
|
|
253
247
|
const outputs = [];
|
|
254
248
|
const skippedTargets = [];
|
|
255
249
|
try {
|
|
256
250
|
let installSource = source.install_url;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
installSource = stagingDir;
|
|
261
|
-
}
|
|
251
|
+
stagingDir = mkdtempSync(join(tmpdir(), "oxygen-skills-"));
|
|
252
|
+
await stageAuthenticatedSkills(stagingDir, source, targets, runtime.fetch ?? fetch);
|
|
253
|
+
installSource = stagingDir;
|
|
262
254
|
for (const target of targets) {
|
|
263
255
|
const args = installerAddArgs(installSource, agents, target);
|
|
264
256
|
if (!options.project)
|
|
@@ -393,6 +385,10 @@ async function stageAuthenticatedSkills(dir, source, targets, fetchImpl) {
|
|
|
393
385
|
const wanted = targets.includes("*")
|
|
394
386
|
? entries
|
|
395
387
|
: entries.filter((entry) => targets.includes(entry.name));
|
|
388
|
+
const missing = targets.includes("*") ? [] : targets.filter((target) => !wanted.some((entry) => entry.name === target));
|
|
389
|
+
if (wanted.length === 0 || missing.length > 0) {
|
|
390
|
+
throw stagingFailure("select_skills", { missing_skills: missing });
|
|
391
|
+
}
|
|
396
392
|
// `<base>/.well-known/skills/index.json` → `<base>/.well-known/skills`; the
|
|
397
393
|
// installer reads a skill's files from `<root>/<name>/<file>`.
|
|
398
394
|
const skillsRoot = source.index_url.replace(/\/index\.json$/i, "");
|
|
@@ -412,7 +408,7 @@ async function stageAuthenticatedSkills(dir, source, targets, fetchImpl) {
|
|
|
412
408
|
const content = await fetchStagedSkillFile(`${skillsRoot}/${entry.name}/${file}`, fetchImpl);
|
|
413
409
|
const destination = join(dir, entry.name, ...file.split("/"));
|
|
414
410
|
mkdirSync(dirname(destination), { recursive: true });
|
|
415
|
-
writeFileSync(destination, content);
|
|
411
|
+
writeFileSync(destination, localizeSkillLinks(content, skillsRoot, entry.name, file));
|
|
416
412
|
}
|
|
417
413
|
catch (error) {
|
|
418
414
|
failure ??= error;
|
|
@@ -430,7 +426,7 @@ const STAGED_FILE_RETRY_DELAYS_MS = [250, 1_000];
|
|
|
430
426
|
async function fetchStagedSkillEntries(indexUrl, fetchImpl) {
|
|
431
427
|
let response;
|
|
432
428
|
try {
|
|
433
|
-
response = await fetchImpl(indexUrl, { headers: { Accept: "application/json" } });
|
|
429
|
+
response = await fetchImpl(indexUrl, { headers: { Accept: "application/json" }, redirect: "error" });
|
|
434
430
|
}
|
|
435
431
|
catch (error) {
|
|
436
432
|
throw stagingFailure("fetch_index", { index_url: indexUrl, message: errorMessage(error) });
|
|
@@ -448,14 +444,21 @@ async function fetchStagedSkillEntries(indexUrl, fetchImpl) {
|
|
|
448
444
|
if (!isRecord(parsed) || !Array.isArray(parsed.skills)) {
|
|
449
445
|
throw stagingFailure("parse_index", { index_url: indexUrl, message: "skills array missing" });
|
|
450
446
|
}
|
|
447
|
+
if (parsed.authentication_required === true) {
|
|
448
|
+
throw stagingFailure("fetch_index", { message: "Authenticated skill content is unavailable." });
|
|
449
|
+
}
|
|
451
450
|
const entries = [];
|
|
452
451
|
for (const entry of parsed.skills) {
|
|
453
|
-
if (!isRecord(entry) || typeof entry.name !== "string" || !entry.name
|
|
454
|
-
|
|
452
|
+
if (!isRecord(entry) || typeof entry.name !== "string" || !isSafeSkillName(entry.name)) {
|
|
453
|
+
throw stagingFailure("parse_index", { message: "Invalid skill name." });
|
|
454
|
+
}
|
|
455
455
|
const files = Array.isArray(entry.files)
|
|
456
456
|
? entry.files.filter((file) => typeof file === "string" && file.trim().length > 0)
|
|
457
457
|
: [];
|
|
458
|
-
entries.
|
|
458
|
+
if (!files.includes("SKILL.md") || files.some((file) => !isSafeSkillFile(file)) || new Set(files).size !== files.length || entries.some((candidate) => candidate.name === entry.name)) {
|
|
459
|
+
throw stagingFailure("parse_index", { message: "Invalid skill files.", skill: entry.name });
|
|
460
|
+
}
|
|
461
|
+
entries.push({ name: entry.name, files });
|
|
459
462
|
}
|
|
460
463
|
return entries;
|
|
461
464
|
}
|
|
@@ -464,7 +467,7 @@ async function fetchStagedSkillFile(url, fetchImpl) {
|
|
|
464
467
|
for (let attempt = 0;; attempt += 1) {
|
|
465
468
|
let details;
|
|
466
469
|
try {
|
|
467
|
-
const response = await fetchImpl(url, { signal: AbortSignal.timeout(STAGED_FILE_TIMEOUT_MS) });
|
|
470
|
+
const response = await fetchImpl(url, { signal: AbortSignal.timeout(STAGED_FILE_TIMEOUT_MS), redirect: "error" });
|
|
468
471
|
if (response.ok)
|
|
469
472
|
return await response.text();
|
|
470
473
|
if (response.status !== 429 && response.status < 500) {
|
|
@@ -483,6 +486,28 @@ async function fetchStagedSkillFile(url, fetchImpl) {
|
|
|
483
486
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
484
487
|
}
|
|
485
488
|
}
|
|
489
|
+
function isSafeSkillName(name) {
|
|
490
|
+
return name.length <= 64 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name);
|
|
491
|
+
}
|
|
492
|
+
function isSafeSkillFile(file) {
|
|
493
|
+
return /^[a-zA-Z0-9_.-]+(?:\/[a-zA-Z0-9_.-]+)*$/.test(file)
|
|
494
|
+
&& file.split("/").every((part) => part !== "." && part !== "..");
|
|
495
|
+
}
|
|
496
|
+
/** Installed references survive token expiry and stay within installed skills. */
|
|
497
|
+
function localizeSkillLinks(content, skillsRoot, skill, file) {
|
|
498
|
+
if (!file.endsWith(".md"))
|
|
499
|
+
return content;
|
|
500
|
+
return content.replace(/\]\((https?:\/\/[^)\s]+)\)/g, (match, url) => {
|
|
501
|
+
if (!url.startsWith(`${skillsRoot}/`))
|
|
502
|
+
return match;
|
|
503
|
+
const target = url.slice(skillsRoot.length + 1);
|
|
504
|
+
const [name, ...parts] = target.split("/");
|
|
505
|
+
const relativeFile = parts.join("/").split("#")[0];
|
|
506
|
+
if (!name || !isSafeSkillName(name) || !isSafeSkillFile(relativeFile))
|
|
507
|
+
return match;
|
|
508
|
+
return `](${posix.relative(posix.dirname(`${skill}/${file}`), target)})`;
|
|
509
|
+
});
|
|
510
|
+
}
|
|
486
511
|
// Mirror the installer-failure error so a staging failure surfaces identically
|
|
487
512
|
// (code, message, exitCode) to the caller — never as a silent URL fallback.
|
|
488
513
|
function stagingFailure(stage, details) {
|
|
@@ -491,31 +516,33 @@ function stagingFailure(stage, details) {
|
|
|
491
516
|
exitCode: 1,
|
|
492
517
|
});
|
|
493
518
|
}
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
// dependencies so a dependency lookup can never block or fail an install.
|
|
519
|
+
// A dependency is part of the selected skill's operable contract. Resolve the
|
|
520
|
+
// full graph before installation; missing dependencies and cycles fail closed.
|
|
497
521
|
async function resolveSkillInstallDependencies(skill, indexUrl, fetchImpl) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
522
|
+
const index = await inspectSkillIndex(indexUrl, fetchImpl ?? fetch);
|
|
523
|
+
if (!index.reachable)
|
|
524
|
+
throw stagingFailure("fetch_index", { message: "Unable to resolve skill dependencies." });
|
|
525
|
+
const resolved = [];
|
|
526
|
+
const visited = new Set();
|
|
527
|
+
const visiting = new Set();
|
|
528
|
+
const visit = (name) => {
|
|
529
|
+
if (visiting.has(name))
|
|
530
|
+
throw stagingFailure("skill_dependencies", { message: "Circular skill dependency.", skill: name });
|
|
531
|
+
if (visited.has(name))
|
|
532
|
+
return;
|
|
533
|
+
const entry = index.skills.find((candidate) => candidate.name === name);
|
|
503
534
|
if (!entry)
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
catch {
|
|
517
|
-
return [];
|
|
518
|
-
}
|
|
535
|
+
throw stagingFailure("select_skills", { missing_skills: [name] });
|
|
536
|
+
visiting.add(name);
|
|
537
|
+
for (const dependency of entry.dependencies)
|
|
538
|
+
visit(dependency);
|
|
539
|
+
visiting.delete(name);
|
|
540
|
+
visited.add(name);
|
|
541
|
+
if (name !== skill)
|
|
542
|
+
resolved.push(name);
|
|
543
|
+
};
|
|
544
|
+
visit(skill);
|
|
545
|
+
return resolved;
|
|
519
546
|
}
|
|
520
547
|
// Adapt the legacy `execFileSync` runtime hook into a spawnSync-shaped return so
|
|
521
548
|
// existing test fixtures (update.test.ts) keep working without a coordinated
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
type Handle = (command: string, options: {
|
|
3
|
+
json?: boolean;
|
|
4
|
+
}, action: () => Promise<unknown>) => Promise<void>;
|
|
5
|
+
/** Legacy no-flag invocation keeps the complete descriptor. */
|
|
6
|
+
export declare function createToolCatalogDetailCommand(handle: Handle): Command;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { toolCatalogDetailQueryContract } from "@oxygen/shared/contracts/tool-catalog-detail";
|
|
3
|
+
import { addContractFlags, readContractFlags } from "./contract-flags.js";
|
|
4
|
+
import { requestOxygen } from "./http-client.js";
|
|
5
|
+
/** Legacy no-flag invocation keeps the complete descriptor. */
|
|
6
|
+
export function createToolCatalogDetailCommand(handle) {
|
|
7
|
+
return addContractFlags(new Command("get")
|
|
8
|
+
.description("Get native tool metadata. Full by default; explicit views report omissions and retain exact schemas. Executable MCP definitions use capabilities schema.")
|
|
9
|
+
.argument("<tool_id>", "Tool id.")
|
|
10
|
+
.option("--json", "Print a JSON envelope."), toolCatalogDetailQueryContract)
|
|
11
|
+
.action(async (toolId, options) => {
|
|
12
|
+
await handle("tools get", options, async () => {
|
|
13
|
+
const query = readContractFlags(options, toolCatalogDetailQueryContract);
|
|
14
|
+
const params = new URLSearchParams(Object.entries(query).map(([key, value]) => [key, Array.isArray(value) ? value.join(",") : String(value)]));
|
|
15
|
+
const suffix = params.size ? `?${params.toString()}` : "";
|
|
16
|
+
return requestOxygen(`/api/cli/tools/${encodeURIComponent(toolId)}${suffix}`);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
export declare function addToolCatalogSearchWindowFlag(command: Command): Command;
|
|
3
|
+
export declare function readToolCatalogSearchWindow(options: Record<string, unknown>): {
|
|
4
|
+
max_chars?: number;
|
|
5
|
+
include_native?: boolean;
|
|
6
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { toolCatalogSearchWindowContract } from "@oxygen/shared/contracts/tool-catalog-search-window";
|
|
2
|
+
import { addContractFlags, readContractFlags } from "./contract-flags.js";
|
|
3
|
+
export function addToolCatalogSearchWindowFlag(command) {
|
|
4
|
+
return addContractFlags(command, toolCatalogSearchWindowContract, { workflow_eligible: { omit: true } });
|
|
5
|
+
}
|
|
6
|
+
export function readToolCatalogSearchWindow(options) {
|
|
7
|
+
const body = readContractFlags(options, toolCatalogSearchWindowContract, { workflow_eligible: { omit: true } });
|
|
8
|
+
return {
|
|
9
|
+
...(typeof body.max_chars === "number" ? { max_chars: body.max_chars } : {}),
|
|
10
|
+
...(typeof body.include_native === "boolean" ? { include_native: body.include_native } : {}),
|
|
11
|
+
};
|
|
12
|
+
}
|
package/dist/transcript.js
CHANGED
|
@@ -129,6 +129,7 @@ const SECRET_PATTERNS = [
|
|
|
129
129
|
[/\bgh[pousr]_[A-Za-z0-9]{20,}/g, "ghx_[REDACTED]"],
|
|
130
130
|
[/\bxox[baprs]-[A-Za-z0-9-]{10,}/g, "xox-[REDACTED]"],
|
|
131
131
|
[/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{6,}/g, "[REDACTED_JWT]"],
|
|
132
|
+
[/\/skills-access\/v1\.[A-Za-z0-9_-]+\.\d+\.[A-Za-z0-9_-]+/g, "/skills-access/[REDACTED_GRANT]"],
|
|
132
133
|
[/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]{16,}/gi, "$1 [REDACTED]"],
|
|
133
134
|
// "secret-ish key": "value" / secret_ish_key=value (JSON + env shapes)
|
|
134
135
|
[/("?(?:api[_-]?key|apikey|secret|password|passwd|token|access[_-]?token|refresh[_-]?token|client[_-]?secret|authorization|auth[_-]?token|private[_-]?key)"?\s*[:=]\s*"?)([^"\s,}]{6,})/gi, "$1[REDACTED]"],
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Complete native receipt sizing for optional bounded JSON reads. Binary
|
|
2
|
+
* windows have their own outer-envelope budget and must not use this helper.
|
|
3
|
+
* The surface is a formatting hint, never authorization or spend authority. */
|
|
4
|
+
export declare function boundedReadResultFits(command: string, data: Record<string, unknown>, maxChars: number | undefined, surface: string | null | undefined, maxAgentResultChars?: number): boolean;
|