@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.
- package/README.md +1 -1
- package/dist/admin-primary-providers-render.js +9 -1
- package/dist/cli-values.d.ts +14 -0
- package/dist/cli-values.js +26 -0
- package/dist/command-manifest.js +6 -0
- package/dist/functions-commands.js +13 -5
- package/dist/help.js +1 -0
- package/dist/index.js +1171 -240
- package/dist/knowledge-repository-commands.d.ts +6 -0
- package/dist/knowledge-repository-commands.js +198 -0
- package/dist/skills.js +20 -0
- package/dist/ugc-commands.js +122 -8
- package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +99 -15
- package/node_modules/@oxygen/shared/dist/copilot-errors.js +3 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.d.ts +19 -1
- package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.d.ts +19 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.js +26 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.js +8 -41
- package/node_modules/@oxygen/shared/dist/inbox-avatar-url.d.ts +28 -0
- package/node_modules/@oxygen/shared/dist/inbox-avatar-url.js +57 -0
- package/node_modules/@oxygen/shared/dist/index.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/index.js +4 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bases.d.ts +74 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bases.js +456 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +17 -38
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +14 -39
- package/node_modules/@oxygen/shared/dist/knowledge-repository.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/knowledge-repository.js +121 -0
- package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.d.ts +20 -0
- package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.js +155 -0
- package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/mailbox-import.js +53 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.js +8 -0
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
- package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.js +24 -0
- package/node_modules/@oxygen/shared/dist/recipes.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/recipes.js +23 -0
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +126 -2
- package/node_modules/@oxygen/shared/dist/sequences.js +280 -4
- package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.js +24 -0
- package/node_modules/@oxygen/shared/dist/ugc.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +8 -1
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +3 -1
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +6 -2
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +15 -4
- package/node_modules/@oxygen/shared/package.json +15 -0
- 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,
|
package/dist/ugc-commands.js
CHANGED
|
@@ -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
|
-
|
|
259
|
-
program_id
|
|
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
|
|
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: "
|
|
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";
|