@oxygen-agent/cli 1.948.1 → 1.982.3

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.
Files changed (54) hide show
  1. package/README.md +1 -1
  2. package/dist/admin-primary-providers-render.js +9 -1
  3. package/dist/cli-values.d.ts +14 -0
  4. package/dist/cli-values.js +26 -0
  5. package/dist/command-manifest.js +6 -0
  6. package/dist/functions-commands.js +13 -5
  7. package/dist/help.js +1 -0
  8. package/dist/index.js +1171 -240
  9. package/dist/knowledge-repository-commands.d.ts +6 -0
  10. package/dist/knowledge-repository-commands.js +198 -0
  11. package/dist/skills.js +20 -0
  12. package/dist/ugc-commands.js +122 -8
  13. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +2 -0
  14. package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +8 -0
  15. package/node_modules/@oxygen/shared/dist/capability-discovery.js +99 -15
  16. package/node_modules/@oxygen/shared/dist/copilot-errors.js +3 -0
  17. package/node_modules/@oxygen/shared/dist/copilot-journeys.d.ts +19 -1
  18. package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.d.ts +19 -0
  19. package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.js +26 -0
  20. package/node_modules/@oxygen/shared/dist/copilot-journeys.js +8 -41
  21. package/node_modules/@oxygen/shared/dist/inbox-avatar-url.d.ts +28 -0
  22. package/node_modules/@oxygen/shared/dist/inbox-avatar-url.js +57 -0
  23. package/node_modules/@oxygen/shared/dist/index.d.ts +4 -0
  24. package/node_modules/@oxygen/shared/dist/index.js +4 -0
  25. package/node_modules/@oxygen/shared/dist/knowledge-bases.d.ts +74 -0
  26. package/node_modules/@oxygen/shared/dist/knowledge-bases.js +456 -0
  27. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +17 -38
  28. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +14 -39
  29. package/node_modules/@oxygen/shared/dist/knowledge-repository.d.ts +22 -0
  30. package/node_modules/@oxygen/shared/dist/knowledge-repository.js +121 -0
  31. package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.d.ts +20 -0
  32. package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.js +155 -0
  33. package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +10 -0
  34. package/node_modules/@oxygen/shared/dist/mailbox-import.js +53 -0
  35. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +8 -0
  36. package/node_modules/@oxygen/shared/dist/plan-limits.js +8 -0
  37. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
  38. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
  39. package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +24 -0
  40. package/node_modules/@oxygen/shared/dist/product-analytics-events.js +24 -0
  41. package/node_modules/@oxygen/shared/dist/recipes.d.ts +6 -0
  42. package/node_modules/@oxygen/shared/dist/recipes.js +23 -0
  43. package/node_modules/@oxygen/shared/dist/sequences.d.ts +126 -2
  44. package/node_modules/@oxygen/shared/dist/sequences.js +280 -4
  45. package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.d.ts +2 -0
  46. package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.js +24 -0
  47. package/node_modules/@oxygen/shared/dist/ugc.d.ts +8 -0
  48. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +8 -1
  49. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
  50. package/node_modules/@oxygen/shared/dist/version.js +3 -1
  51. package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +6 -2
  52. package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +15 -4
  53. package/node_modules/@oxygen/shared/package.json +15 -0
  54. package/package.json +2 -1
@@ -0,0 +1,6 @@
1
+ import { Command } from "commander";
2
+ type Handle = (command: string, options: {
3
+ json?: boolean;
4
+ }, action: () => Promise<unknown>) => Promise<void>;
5
+ export declare function registerKnowledgeRepositoryCommands(program: Command, handle: Handle): void;
6
+ export {};
@@ -0,0 +1,198 @@
1
+ import { readFile, writeFile, stat } from "node:fs/promises";
2
+ import { createHash } from "node:crypto";
3
+ import { resolve } from "node:path";
4
+ import { requestOxygen } from "./http-client.js";
5
+ const post = (path, body) => {
6
+ if (Buffer.byteLength(JSON.stringify(body)) > 2_000_000)
7
+ throw new Error("This inline request exceeds 2 MB. Add larger attachments through the connected GitHub repository.");
8
+ return requestOxygen(`/api/cli/knowledge/${path}`, { method: "POST", body });
9
+ };
10
+ export function registerKnowledgeRepositoryCommands(program, handle) {
11
+ const knowledge = program.commands.find((c) => c.name() === "knowledge");
12
+ if (!knowledge)
13
+ throw new Error("Knowledge commands must be registered first.");
14
+ const repos = knowledge
15
+ .command("repositories")
16
+ .description("Import an existing GitHub or Obsidian repository into Knowledge, or keep it automatically in sync. Start with integrations connect github; web setup: /knowledge/repositories; docs: /docs/knowledge/knowledge-graph.");
17
+ repos
18
+ .command("list")
19
+ .description("Read connected repositories, import progress, sync freshness, pending changes, and errors. 0 credits.")
20
+ .option("--json", "Print a JSON envelope.")
21
+ .action((o) => handle("knowledge repositories list", o, () => post("repositories/list", {})));
22
+ for (const operation of ["preview", "connect"]) {
23
+ const cmd = repos
24
+ .command(operation)
25
+ .description(operation === "preview"
26
+ ? "Preview branch/folder contents, exclusions and the future sync policy. Does not activate sync. 0 credits."
27
+ : "Approve the exact preview and start importing. two_way authorizes future automatic GitHub writes to the selected branch. 0 Oxygen credits.")
28
+ .requiredOption("--connection-id <id>", "GitHub connection ID from integrations list.")
29
+ .requiredOption("--repository-id <id>", "Repository ID from integrations github repositories.")
30
+ .requiredOption("--branch <name>", "Connected branch; unmerged feature branches are separate.")
31
+ .option("--root-path <path>", "Repository subfolder; omit for the entire repository.", "")
32
+ .option("--mode <mode>", "two_way or import (one-time copy).", "two_way")
33
+ .option("--json", "Print a JSON envelope.");
34
+ if (operation === "connect")
35
+ cmd
36
+ .requiredOption("--preview-token <token>", "Exact token returned by repositories preview.")
37
+ .option("--approved", "Approve the displayed import and future sync policy.");
38
+ cmd.action((o) => handle(`knowledge repositories ${operation}`, o, () => post(`repositories/${operation}`, {
39
+ connectionId: o.connectionId,
40
+ repositoryId: Number(o.repositoryId),
41
+ branch: o.branch,
42
+ rootPath: o.rootPath,
43
+ mode: o.mode,
44
+ ...(operation === "connect"
45
+ ? { previewToken: o.previewToken, approved: o.approved === true }
46
+ : {}),
47
+ })));
48
+ }
49
+ for (const action of ["sync", "pause", "resume", "disconnect"]) {
50
+ const description = {
51
+ sync: "Request an immediate sync attempt. Saved changes remain safe if GitHub is unavailable.",
52
+ pause: "Pause automatic sync; keep all imported files and history.",
53
+ resume: "Resume the previously approved sync policy.",
54
+ disconnect: "Disconnect this repository; keep its files and history in Knowledge.",
55
+ }[action];
56
+ repos
57
+ .command(action)
58
+ .description(description)
59
+ .requiredOption("--id <id>", "Connected repository mount ID.")
60
+ .option("--json", "Print a JSON envelope.")
61
+ .action((o) => handle(`knowledge repositories ${action}`, o, () => post("repositories/action", { id: o.id, action })));
62
+ }
63
+ repos
64
+ .command("conflicts")
65
+ .description("Read conflicts; add --path to inspect preserved base, Oxygen, and GitHub versions.")
66
+ .requiredOption("--mount-id <id>", "Connected repository mount ID.")
67
+ .option("--path <path>", "Exact file path.")
68
+ .option("--json", "Print a JSON envelope.")
69
+ .action((o) => handle("knowledge repositories conflicts", o, () => post("repositories/conflicts", {
70
+ mountId: o.mountId,
71
+ ...(o.path ? { path: o.path } : {}),
72
+ })));
73
+ repos
74
+ .command("resolve")
75
+ .description("Apply a reviewed conflict decision. Canonical copy remains proposal-gated; GitHub publishes under the saved sync policy.")
76
+ .requiredOption("--mount-id <id>", "Connected repository mount ID.")
77
+ .requiredOption("--path <path>", "Exact file path.")
78
+ .requiredOption("--local-revision <revision|null>", "Reviewed Oxygen revision or null.")
79
+ .requiredOption("--remote-sha <sha|null>", "Reviewed GitHub blob SHA or null.")
80
+ .requiredOption("--decision <choice>", "oxygen, github, or merged.")
81
+ .option("--file <path>", "Complete edited UTF-8 file for merged.")
82
+ .option("--approved", "Approve this exact decision.")
83
+ .option("--json", "Print a JSON envelope.")
84
+ .action((o) => handle("knowledge repositories resolve", o, async () => post("repositories/resolve", {
85
+ mountId: o.mountId,
86
+ path: o.path,
87
+ expectedLocalRevision: o.localRevision === "null" ? null : Number(o.localRevision),
88
+ expectedRemoteSha: o.remoteSha === "null" ? null : o.remoteSha,
89
+ decision: o.decision,
90
+ ...(o.file ? { text: await readFile(String(o.file), "utf8") } : {}),
91
+ approved: o.approved === true,
92
+ })));
93
+ const files = knowledge
94
+ .command("files")
95
+ .description("Original repository paths and immutable file revisions, including attachments.");
96
+ files
97
+ .command("list")
98
+ .requiredOption("--mount-id <id>", "Connected repository ID.")
99
+ .option("--cursor <cursor>", "Continue the previous page.")
100
+ .option("--limit <number>", "Maximum files, up to 500.", "100")
101
+ .option("--json", "Print a JSON envelope.")
102
+ .action((o) => handle("knowledge files list", o, () => post("files/list", {
103
+ mountId: o.mountId,
104
+ limit: Number(o.limit),
105
+ ...(o.cursor ? { cursor: o.cursor } : {}),
106
+ })));
107
+ files
108
+ .command("get")
109
+ .description("Read original file content, properties and download link at an optional immutable revision.")
110
+ .requiredOption("--id <id>", "File ID from files list.")
111
+ .option("--revision <number>", "Specific saved file revision.")
112
+ .option("--json", "Print a JSON envelope.")
113
+ .action((o) => handle("knowledge files get", o, () => post("files/get", {
114
+ id: o.id,
115
+ ...(o.revision ? { revision: Number(o.revision) } : {}),
116
+ })));
117
+ files
118
+ .command("upsert")
119
+ .description("Create or replace an original file, or update frontmatter properties. Canonical copy still requires a proposal.")
120
+ .requiredOption("--mount-id <id>", "Connected repository ID.")
121
+ .option("--id <id>", "Existing file ID.")
122
+ .option("--path <path>", "Exact repository-relative path; required for new files.")
123
+ .requiredOption("--expected-revision <number>", "Reviewed file revision; 0 creates a new file.")
124
+ .option("--file <path>", "Complete local file, including any frontmatter or binary bytes.")
125
+ .option("--properties-json <json>", "Frontmatter properties to change; mutually exclusive with --file.")
126
+ .option("--json", "Print a JSON envelope.")
127
+ .action((o) => handle("knowledge files upsert", o, async () => {
128
+ if (Boolean(o.file) === Boolean(o.propertiesJson))
129
+ throw new Error("Provide exactly one of --file or --properties-json.");
130
+ if (o.file && (await stat(String(o.file))).size > 1_500_000)
131
+ throw new Error("Inline file uploads must fit a 2 MB JSON request (including base64). Add larger attachments through the connected GitHub repository.");
132
+ const patch = o.propertiesJson
133
+ ? JSON.parse(String(o.propertiesJson))
134
+ : undefined;
135
+ if (patch !== undefined &&
136
+ (!patch || typeof patch !== "object" || Array.isArray(patch)))
137
+ throw new Error("--properties-json must contain an object.");
138
+ return post("files/upsert", {
139
+ mountId: o.mountId,
140
+ ...(o.id ? { id: o.id } : {}),
141
+ ...(o.path ? { path: o.path } : {}),
142
+ expectedFileRevision: Number(o.expectedRevision),
143
+ ...(o.file
144
+ ? {
145
+ bytesBase64: (await readFile(String(o.file))).toString("base64"),
146
+ }
147
+ : { propertyPatch: patch }),
148
+ });
149
+ }));
150
+ files
151
+ .command("archive")
152
+ .description("Archive a file with revision checking. A connected two-way repository publishes the deletion; history remains.")
153
+ .requiredOption("--id <id>", "File ID.")
154
+ .requiredOption("--expected-revision <number>", "Reviewed file revision.")
155
+ .option("--json", "Print a JSON envelope.")
156
+ .action((o) => handle("knowledge files archive", o, () => post("files/archive", {
157
+ id: o.id,
158
+ expectedFileRevision: Number(o.expectedRevision),
159
+ })));
160
+ files
161
+ .command("download")
162
+ .description("Download exact original bytes with digest verification; refuses to overwrite an existing local file.")
163
+ .requiredOption("--id <id>", "File ID.")
164
+ .requiredOption("--out <path>", "New local destination filename.")
165
+ .option("--revision <number>", "Specific immutable revision.")
166
+ .option("--json", "Print a JSON envelope.")
167
+ .action((o) => handle("knowledge files download", o, async () => {
168
+ const query = new URLSearchParams({
169
+ id: String(o.id),
170
+ ...(o.revision ? { revision: String(o.revision) } : {}),
171
+ });
172
+ const data = await requestOxygen(`/api/cli/knowledge/files/get?${query}`);
173
+ const bytes = await requestOxygen(`/api/cli/knowledge/files/download?${query}`, { binaryMaxBytes: Math.max(1, data.file.sizeBytes) });
174
+ if (bytes.length !== data.file.sizeBytes ||
175
+ createHash("sha256").update(bytes).digest("hex") !== data.file.sha256)
176
+ throw new Error("Downloaded bytes do not match the saved file revision.");
177
+ await writeFile(String(o.out), bytes, { flag: "wx", mode: 0o600 });
178
+ return { path: resolve(String(o.out)), ...data.file };
179
+ }));
180
+ const integrations = program.commands.find((c) => c.name() === "integrations");
181
+ if (integrations) {
182
+ const github = integrations
183
+ .command("github")
184
+ .description("Choose repositories authorized through the GitHub App. Connect first with integrations connect github. The Oxygen GitHub App requests Contents read and write plus Metadata read on the repositories you select, and push events; it requests no account or organization permissions. integrations list reports GitHub availability for this deployment.");
185
+ github
186
+ .command("repositories")
187
+ .requiredOption("--connection-id <id>", "GitHub connection ID.")
188
+ .option("--json", "Print a JSON envelope.")
189
+ .action((o) => handle("integrations github repositories", o, () => requestOxygen(`/api/cli/integrations/github/repositories?connection_id=${encodeURIComponent(String(o.connectionId))}`)));
190
+ github
191
+ .command("branches")
192
+ .requiredOption("--connection-id <id>", "GitHub connection ID.")
193
+ .requiredOption("--repository-id <id>", "Immutable GitHub repository ID.")
194
+ .option("--page <number>", "Page to read.", "1")
195
+ .option("--json", "Print a JSON envelope.")
196
+ .action((o) => handle("integrations github branches", o, () => requestOxygen(`/api/cli/integrations/github/branches?${new URLSearchParams({ connection_id: String(o.connectionId), repository_id: String(o.repositoryId), page: String(o.page) })}`)));
197
+ }
198
+ }
package/dist/skills.js CHANGED
@@ -66,6 +66,17 @@ export async function resolveSkillsInstallSource(options) {
66
66
  return publicSource(`access_unavailable:${code}`);
67
67
  }
68
68
  }
69
+ function requireSkillContentAccess(index, source) {
70
+ if (!index.authentication_required)
71
+ return;
72
+ throw new OxygenError("skills_authentication_required", "Sign in to download Oxygen agent instructions.", {
73
+ details: {
74
+ next_step: "Run `oxygen login`, then retry `oxygen skills install` or `oxygen skills get`.",
75
+ ...(source.fallback_reason ? { access_fallback: source.fallback_reason } : {}),
76
+ },
77
+ exitCode: 1,
78
+ });
79
+ }
69
80
  export async function listAgentSkills(options, runtime = {}) {
70
81
  const apiUrl = normalizeApiUrl(readOption(options.apiUrl) ?? defaultApiUrl());
71
82
  const source = await resolveSkillsInstallSource({ apiUrl, ...runtime });
@@ -137,6 +148,7 @@ export async function getAgentSkill(skillName, options, runtime = {}) {
137
148
  exitCode: 1,
138
149
  });
139
150
  }
151
+ requireSkillContentAccess(index, source);
140
152
  const normalized = skillName.trim();
141
153
  const skill = index.skills.find((entry) => entry.name === normalized);
142
154
  if (!skill) {
@@ -203,6 +215,12 @@ options, runtime = {}) {
203
215
  ...(runtime.credentials !== undefined ? { credentials: runtime.credentials } : {}),
204
216
  ...(runtime.fetch ? { fetch: runtime.fetch } : {}),
205
217
  });
218
+ if (!source.authenticated) {
219
+ // Public discovery remains usable. New servers explicitly mark that their
220
+ // instruction bodies require a signed workspace link; never invoke the
221
+ // installer against that discovery-only catalog.
222
+ requireSkillContentAccess(await inspectSkillIndex(source.index_url, runtime.fetch), source);
223
+ }
206
224
  const agents = readWords(options.agents ?? DEFAULT_SKILL_AGENTS);
207
225
  const skill = readOption(options.skill) ?? "*";
208
226
  const env = runtime.env ?? process.env;
@@ -599,6 +617,7 @@ async function inspectSkillIndex(indexUrl, fetchImpl = fetch) {
599
617
  }
600
618
  return {
601
619
  reachable: true,
620
+ authentication_required: index.authentication_required === true,
602
621
  status: response.status,
603
622
  version: index.version,
604
623
  base_url: index.base_url,
@@ -667,6 +686,7 @@ function parseSkillIndex(value) {
667
686
  })
668
687
  .filter((entry) => entry !== null);
669
688
  return {
689
+ authentication_required: value.authentication_required === true,
670
690
  version: typeof value.version === "string" ? value.version : null,
671
691
  base_url: typeof value.base_url === "string" ? value.base_url : null,
672
692
  skills,
@@ -27,10 +27,11 @@ function parseField(value, field) {
27
27
  export function registerUgcCommands(program, handle) {
28
28
  const ugc = program
29
29
  .command("ugc")
30
- .description("Manage UGC programs and independent creator participations: briefs, voice, posts, approvals, sponsorship and bounded amplification. Requires UGC availability.");
30
+ .description("Manage UGC programs and independent creator participations: briefs, voice, posts, approvals, sponsorship and bounded amplification. Requires UGC availability. Start with memberships list in a creator workspace. See the oxygen-ugc skill; JSON reads return web_url for inspection.");
31
31
  const groupDescriptions = {
32
32
  programs: "Programs you operate or joined. Lifecycle: create, update, duplicate (settings + briefs), archive (stops future work, keeps shared history, reversible with restore) and delete (permanent; only while no creator has ever joined).",
33
33
  creators: "Creator participations: invite, revoke an invite, accept in the creator workspace, connect a sender, change approval authority, or revoke.",
34
+ memberships: "Active programs this creator workspace has joined. Read these before choosing a program for a personal post.",
34
35
  briefs: "Program briefs with ordered questions for creators.",
35
36
  posts: "Enrolled program posts: enroll existing posts, draft from briefs, edit, schedule, and record creator or brand approval.",
36
37
  analytics: "Stored daily program and creator performance.",
@@ -38,6 +39,7 @@ export function registerUgcCommands(program, handle) {
38
39
  voice: "Creator voice profiles and imports.",
39
40
  sponsorship: "Seat and credit sponsorship per participation.",
40
41
  amplification: "Bounded, approval-gated amplification of enrolled posts.",
42
+ "peer-amplification": "Creator-owned consent, sender, voice, caps and immutable receipts for engaging with peer posts in one program.",
41
43
  calendar: "Bounded enrolled-post calendar reads.",
42
44
  };
43
45
  const groups = new Map();
@@ -97,6 +99,11 @@ export function registerUgcCommands(program, handle) {
97
99
  query.set("participation_id", String(options.participationId));
98
100
  return requestOxygen(`/api/cli/ugc?${query}`);
99
101
  }));
102
+ group("memberships")
103
+ .command("list")
104
+ .description("Read active program memberships for the creator workspace. Free, stored and provider-free.")
105
+ .option("--json", "Print a JSON envelope.")
106
+ .action((options) => handle("ugc memberships list", options, () => requestOxygen("/api/cli/ugc?view=memberships")));
100
107
  group("briefs")
101
108
  .command("list")
102
109
  .description("Read one program's UGC briefs. Read-only; requires --program-id.")
@@ -117,6 +124,8 @@ export function registerUgcCommands(program, handle) {
117
124
  .description("Read UGC posts in the active workspace. Read-only.")
118
125
  .requiredOption("--program-id <id>", "Program ID.")
119
126
  .option("--participation-id <id>", "Creator participation ID.")
127
+ .option("--post-id <id>", "Read one canonical post ID.")
128
+ .option("--enrollment-version <n>", "Read one exact enrollment version after a post was removed and re-added.")
120
129
  .option("--cursor <cursor>", "Opaque next_cursor from the previous page.")
121
130
  .option("--limit <count>", "Posts per page, 1–500.")
122
131
  .option("--json", "Print a JSON envelope.")
@@ -126,6 +135,10 @@ export function registerUgcCommands(program, handle) {
126
135
  query.set("program_id", String(options.programId));
127
136
  if (options.participationId)
128
137
  query.set("participation_id", String(options.participationId));
138
+ if (options.postId)
139
+ query.set("post_id", String(options.postId));
140
+ if (options.enrollmentVersion)
141
+ query.set("enrollment_version", String(options.enrollmentVersion));
129
142
  if (options.cursor)
130
143
  query.set("cursor", String(options.cursor));
131
144
  if (options.limit)
@@ -232,6 +245,12 @@ export function registerUgcCommands(program, handle) {
232
245
  description: "Read retained public comments, metrics, and publication receipts for one enrolled post. Stored data only, 0 credits.",
233
246
  fields: [
234
247
  ["post_id", "Enrolled source post UUID.", true],
248
+ [
249
+ "enrollment_version",
250
+ "Exact enrollment_version returned by posts list; required to distinguish remove/re-enroll history.",
251
+ true,
252
+ ],
253
+ ["participation_id", "Exact creator participation ID.", false],
235
254
  ["cursor", "next_comments_cursor from the previous page.", false],
236
255
  ["metric_cursor", "next_metrics_cursor from the previous page.", false],
237
256
  ["action_cursor", "next_actions_cursor from the previous page.", false],
@@ -244,20 +263,30 @@ export function registerUgcCommands(program, handle) {
244
263
  description: "Read this program's current amplification authority and immutable action history. Master workspace only; 0 credits, no provider call.",
245
264
  fields: [["cursor", "next_cursor from the previous action page.", false]],
246
265
  },
266
+ {
267
+ group: "peer-amplification",
268
+ command: "get",
269
+ view: "peer_amplification",
270
+ description: "Read this creator's own peer-engagement policy, host gate, readiness and immutable receipts. Creator workspace only; free and provider-free.",
271
+ fields: [
272
+ ["participation_id", "Exact creator participation ID.", true],
273
+ ["cursor", "next_cursor from the previous action page.", false],
274
+ ],
275
+ programRequired: false,
276
+ },
247
277
  ]) {
248
278
  const command = group(spec.group)
249
279
  .command(spec.command)
250
280
  .description(spec.description)
251
- .requiredOption("--program-id <id>", "Program ID.")
252
281
  .option("--limit <count>", "Rows per page, up to 100.")
253
282
  .option("--json", "Print a JSON envelope.");
283
+ command["programRequired" in spec && spec.programRequired === false ? "option" : "requiredOption"]("--program-id <id>", "Program ID.");
254
284
  for (const [name, description, required] of spec.fields)
255
285
  command[required ? "requiredOption" : "option"](`--${name.replaceAll("_", "-")} <value>`, description);
256
286
  command.action((options) => handle(`ugc ${spec.group} ${spec.command}`, options, () => {
257
- const query = new URLSearchParams({
258
- view: spec.view,
259
- program_id: String(options.programId),
260
- });
287
+ const query = new URLSearchParams({ view: spec.view });
288
+ if (options.programId)
289
+ query.set("program_id", String(options.programId));
261
290
  for (const [name] of spec.fields) {
262
291
  const key = name.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
263
292
  if (options[key] !== undefined)
@@ -347,6 +376,12 @@ export function registerUgcCommands(program, handle) {
347
376
  description: "Require a separate brand approval before publishing.",
348
377
  required: false,
349
378
  },
379
+ {
380
+ name: "peer_engagement_enabled",
381
+ kind: "boolean",
382
+ description: "Host permission for creators in this program to opt into peer engagement; enabling requires a signed-in master admin.",
383
+ required: false,
384
+ },
350
385
  {
351
386
  name: "status",
352
387
  kind: "string",
@@ -536,7 +571,7 @@ export function registerUgcCommands(program, handle) {
536
571
  {
537
572
  action: "participation_update",
538
573
  command: "creators update",
539
- description: "Connect an existing LinkedIn sender, change approval authority, or revoke participation.",
574
+ description: "Connect an existing LinkedIn sender, change approval authority, or leave the entire program. Revocation stops future sharing and program actions; your posts stay in your workspace and the organizer retains previously shared history. This workspace cannot currently rejoin the same program. To remove one post while staying, use ugc posts remove.",
540
575
  fields: [
541
576
  {
542
577
  name: "participation_id",
@@ -559,7 +594,7 @@ export function registerUgcCommands(program, handle) {
559
594
  {
560
595
  name: "status",
561
596
  kind: "string",
562
- description: "Program status, or revoked for a participation.",
597
+ description: "Use revoked to leave the entire program. This participation cannot be restored.",
563
598
  required: false,
564
599
  },
565
600
  ],
@@ -663,6 +698,37 @@ export function registerUgcCommands(program, handle) {
663
698
  },
664
699
  ],
665
700
  },
701
+ {
702
+ action: "post_remove",
703
+ command: "posts remove",
704
+ description: "Remove one creator-owned post from a program before publication while retaining immutable enrollment history. The personal draft remains in Publishing.",
705
+ fields: [
706
+ {
707
+ name: "program_id",
708
+ kind: "string",
709
+ description: "Program ID.",
710
+ required: true,
711
+ },
712
+ {
713
+ name: "post_id",
714
+ kind: "string",
715
+ description: "Canonical personal post ID.",
716
+ required: true,
717
+ },
718
+ {
719
+ name: "enrollment_id",
720
+ kind: "string",
721
+ description: "Exact active enrollment ID returned by posts list.",
722
+ required: true,
723
+ },
724
+ {
725
+ name: "source_revision",
726
+ kind: "string",
727
+ description: "Exact current content fingerprint returned by posts list.",
728
+ required: true,
729
+ },
730
+ ],
731
+ },
666
732
  {
667
733
  action: "draft_create",
668
734
  command: "posts draft",
@@ -723,6 +789,12 @@ export function registerUgcCommands(program, handle) {
723
789
  description: "Canonical post ID.",
724
790
  required: true,
725
791
  },
792
+ {
793
+ name: "enrollment_version",
794
+ kind: "number",
795
+ description: "Exact enrollment version returned by posts list; always send it so a removed/re-enrolled post cannot reuse an old edit.",
796
+ required: true,
797
+ },
726
798
  {
727
799
  name: "title",
728
800
  kind: "string",
@@ -760,6 +832,12 @@ export function registerUgcCommands(program, handle) {
760
832
  description: "Canonical post ID.",
761
833
  required: true,
762
834
  },
835
+ {
836
+ name: "enrollment_version",
837
+ kind: "number",
838
+ description: "Exact enrollment version returned by posts list; always send it so approval binds the current enrollment.",
839
+ required: true,
840
+ },
763
841
  {
764
842
  name: "source_revision",
765
843
  kind: "string",
@@ -791,6 +869,12 @@ export function registerUgcCommands(program, handle) {
791
869
  description: "Canonical post ID.",
792
870
  required: true,
793
871
  },
872
+ {
873
+ name: "enrollment_version",
874
+ kind: "number",
875
+ description: "Exact enrollment version returned by posts list; always send it so brand approval binds the current enrollment.",
876
+ required: true,
877
+ },
794
878
  {
795
879
  name: "source_revision",
796
880
  kind: "string",
@@ -1046,6 +1130,36 @@ export function registerUgcCommands(program, handle) {
1046
1130
  },
1047
1131
  ],
1048
1132
  },
1133
+ {
1134
+ action: "peer_amplification_save",
1135
+ command: "peer-amplification save",
1136
+ description: "Preview or save this creator workspace's own peer-engagement consent, sender, voice and caps. The creator wallet pays; the host gate must also be enabled.",
1137
+ fields: [
1138
+ { name: "participation_id", kind: "string", description: "Exact creator participation ID.", required: true },
1139
+ { name: "sender_account_id", kind: "string", description: "Creator's matching connected LinkedIn sender ID.", required: true },
1140
+ { name: "voice_page_id", kind: "string", description: "Creator-owned active voice page ID, required for comment or repost.", required: false },
1141
+ { name: "actions", kind: "array", description: "Allowed peer actions: reaction, comment or repost.", required: true },
1142
+ { name: "enabled", kind: "boolean", description: "Enable or disable this creator-owned policy.", required: true },
1143
+ { name: "expected_policy_version", kind: "number", description: "Current policy version from peer-amplification get; omit or use 0 on first save.", required: false },
1144
+ { name: "max_actions_per_post", kind: "number", description: "Hard action cap per peer post.", required: true },
1145
+ { name: "max_credits_per_cycle", kind: "number", description: "Hard creator-wallet credit cap per cycle.", required: true },
1146
+ { name: "approved", kind: "boolean", description: "Confirm this exact creator-owned scope; omit for preview.", required: false },
1147
+ { name: "delay_min_seconds", kind: "number", description: "Minimum delay, 60–3600 seconds.", required: false },
1148
+ { name: "delay_max_seconds", kind: "number", description: "Maximum delay, 60–86400 seconds.", required: false },
1149
+ ],
1150
+ },
1151
+ {
1152
+ action: "peer_amplification_reconcile",
1153
+ command: "peer-amplification reconcile",
1154
+ description: "Preview recording observed evidence for this creator's effect_unknown peer action. Approval records the outcome without a provider call or retry.",
1155
+ fields: [
1156
+ { name: "participation_id", kind: "string", description: "Exact creator participation ID.", required: true },
1157
+ { name: "action_id", kind: "string", description: "Exact effect_unknown peer action ID.", required: true },
1158
+ { name: "outcome", kind: "string", description: "Observed applied or not_applied outcome.", required: true },
1159
+ { name: "evidence", kind: "object", description: "JSON evidence: provider_post_id, evidence_ref and observed_at; applied may require provider_object_id and exact observed_text.", required: true },
1160
+ { name: "approved", kind: "boolean", description: "Confirm this immutable observation; no retry is authorized.", required: false },
1161
+ ],
1162
+ },
1049
1163
  ];
1050
1164
  for (const spec of writes) {
1051
1165
  const [family, leaf] = spec.command.split(" ");
@@ -7,6 +7,8 @@ export type RecipeToolRunOptions = {
7
7
  optional?: boolean;
8
8
  /** Pin this call to one provider account for every replay/scheduled run. */
9
9
  connectionId?: string;
10
+ /** Pin the credential rail instead of automatically selecting a connected key. */
11
+ credentialMode?: "managed" | "user_api_key" | "user_oauth";
10
12
  };
11
13
  export type RecipeToolApi = {
12
14
  run: <T = unknown>(toolId: string, payload?: Record<string, unknown>, options?: RecipeToolRunOptions) => Promise<T>;
@@ -23,6 +23,12 @@ export type CapabilityRouteMatch = {
23
23
  card: CapabilityRouteCard;
24
24
  recommendedTools: string[];
25
25
  recommendedCommands: string[];
26
+ /**
27
+ * The one exact invocation for asks whose answer is a single command with a
28
+ * specific flag the help text alone did not teach. Present only when the
29
+ * intent is that narrow; `recommendedCommands[0]` is the same command.
30
+ */
31
+ exactCommand?: string;
26
32
  };
27
33
  export type PrimitiveRouteMatch = CapabilityRouteMatch & {
28
34
  card: PrimitiveRouteCard;
@@ -34,3 +40,5 @@ export declare function getCapabilityRouteMatch(id: string): CapabilityRouteMatc
34
40
  export declare function inferCapabilityRoute(query: string): CapabilityRouteMatch | null;
35
41
  export declare function inferPrimitiveRoute(query: string): PrimitiveRouteMatch | null;
36
42
  export declare function serializeCapabilityRoute(route: CapabilityRouteMatch | null): Record<string, unknown> | null;
43
+ /** The exact `companies search plan` invocation for the url_recovery intent. */
44
+ export declare const COMPANY_URL_RECOVERY_COMMAND = "oxygen companies search plan --prompt \"Find the websites for these companies: <company names>\" --source-intent url_recovery";