@oxygen-agent/cli 1.1014.136 → 1.1014.619
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 +27 -19
- package/dist/cli-output.d.ts +12 -0
- package/dist/cli-output.js +395 -0
- package/dist/cli-paths.d.ts +1 -0
- package/dist/cli-paths.js +3 -0
- package/dist/cli-profile-context.d.ts +3 -0
- package/dist/cli-profile-context.js +13 -0
- package/dist/command-manifest.d.ts +15 -0
- package/dist/command-manifest.js +23 -1
- package/dist/command-support.d.ts +438 -0
- package/dist/command-support.js +1610 -0
- package/dist/commands/action-column.d.ts +3 -0
- package/dist/commands/action-column.js +43 -0
- package/dist/commands/activation.d.ts +3 -0
- package/dist/commands/activation.js +24 -0
- package/dist/commands/admin.d.ts +3 -0
- package/dist/commands/admin.js +347 -0
- package/dist/commands/ads.d.ts +3 -0
- package/dist/commands/ads.js +62 -0
- package/dist/commands/agent.d.ts +3 -0
- package/dist/commands/agent.js +274 -0
- package/dist/commands/api-keys.d.ts +3 -0
- package/dist/commands/api-keys.js +77 -0
- package/dist/commands/approvals.d.ts +3 -0
- package/dist/commands/approvals.js +518 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.js +230 -0
- package/dist/commands/billing.d.ts +3 -0
- package/dist/commands/billing.js +357 -0
- package/dist/commands/blueprints.d.ts +3 -0
- package/dist/commands/blueprints.js +312 -0
- package/dist/commands/budget.d.ts +3 -0
- package/dist/commands/budget.js +55 -0
- package/dist/commands/callables.d.ts +3 -0
- package/dist/commands/callables.js +111 -0
- package/dist/commands/capabilities.d.ts +3 -0
- package/dist/commands/capabilities.js +44 -0
- package/dist/commands/cells.d.ts +3 -0
- package/dist/commands/cells.js +94 -0
- package/dist/commands/columns.d.ts +3 -0
- package/dist/commands/columns.js +1628 -0
- package/dist/commands/commands.d.ts +3 -0
- package/dist/commands/commands.js +45 -0
- package/dist/commands/comments.d.ts +3 -0
- package/dist/commands/comments.js +188 -0
- package/dist/commands/companies.d.ts +3 -0
- package/dist/commands/companies.js +312 -0
- package/dist/commands/connections.d.ts +3 -0
- package/dist/commands/connections.js +39 -0
- package/dist/commands/context.d.ts +3 -0
- package/dist/commands/context.js +170 -0
- package/dist/commands/copilot.d.ts +3 -0
- package/dist/commands/copilot.js +565 -0
- package/dist/commands/crm.d.ts +3 -0
- package/dist/commands/crm.js +1672 -0
- package/dist/commands/custom-integrations.d.ts +3 -0
- package/dist/commands/custom-integrations.js +140 -0
- package/dist/commands/dashboards.d.ts +3 -0
- package/dist/commands/dashboards.js +240 -0
- package/dist/commands/db.d.ts +3 -0
- package/dist/commands/db.js +190 -0
- package/dist/commands/deliverability.d.ts +3 -0
- package/dist/commands/deliverability.js +90 -0
- package/dist/commands/directory.d.ts +3 -0
- package/dist/commands/directory.js +101 -0
- package/dist/commands/domains.d.ts +3 -0
- package/dist/commands/domains.js +604 -0
- package/dist/commands/egress.d.ts +3 -0
- package/dist/commands/egress.js +130 -0
- package/dist/commands/email.d.ts +3 -0
- package/dist/commands/email.js +49 -0
- package/dist/commands/engagement.d.ts +3 -0
- package/dist/commands/engagement.js +189 -0
- package/dist/commands/enrich-column.d.ts +3 -0
- package/dist/commands/enrich-column.js +149 -0
- package/dist/commands/enrichment.d.ts +3 -0
- package/dist/commands/enrichment.js +49 -0
- package/dist/commands/feedback.d.ts +3 -0
- package/dist/commands/feedback.js +96 -0
- package/dist/commands/feeds.d.ts +3 -0
- package/dist/commands/feeds.js +486 -0
- package/dist/commands/find.d.ts +3 -0
- package/dist/commands/find.js +159 -0
- package/dist/commands/followers.d.ts +3 -0
- package/dist/commands/followers.js +39 -0
- package/dist/commands/formulas.d.ts +3 -0
- package/dist/commands/formulas.js +43 -0
- package/dist/commands/home.d.ts +3 -0
- package/dist/commands/home.js +22 -0
- package/dist/commands/inbox.d.ts +3 -0
- package/dist/commands/inbox.js +779 -0
- package/dist/commands/integrations.d.ts +3 -0
- package/dist/commands/integrations.js +290 -0
- package/dist/commands/knowledge.d.ts +3 -0
- package/dist/commands/knowledge.js +874 -0
- package/dist/commands/lead-sourcing.d.ts +3 -0
- package/dist/commands/lead-sourcing.js +52 -0
- package/dist/commands/limits.d.ts +3 -0
- package/dist/commands/limits.js +54 -0
- package/dist/commands/linkedin.d.ts +3 -0
- package/dist/commands/linkedin.js +275 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.js +347 -0
- package/dist/commands/logout.d.ts +3 -0
- package/dist/commands/logout.js +12 -0
- package/dist/commands/mailboxes.d.ts +3 -0
- package/dist/commands/mailboxes.js +1290 -0
- package/dist/commands/managed-inboxes.d.ts +3 -0
- package/dist/commands/managed-inboxes.js +361 -0
- package/dist/commands/messages.d.ts +3 -0
- package/dist/commands/messages.js +78 -0
- package/dist/commands/notetaker.d.ts +3 -0
- package/dist/commands/notetaker.js +79 -0
- package/dist/commands/observability.d.ts +3 -0
- package/dist/commands/observability.js +125 -0
- package/dist/commands/orgs.d.ts +3 -0
- package/dist/commands/orgs.js +237 -0
- package/dist/commands/people.d.ts +3 -0
- package/dist/commands/people.js +353 -0
- package/dist/commands/posts.d.ts +3 -0
- package/dist/commands/posts.js +139 -0
- package/dist/commands/profiles.d.ts +3 -0
- package/dist/commands/profiles.js +250 -0
- package/dist/commands/projects.d.ts +3 -0
- package/dist/commands/projects.js +94 -0
- package/dist/commands/prompts.d.ts +3 -0
- package/dist/commands/prompts.js +84 -0
- package/dist/commands/publishing.d.ts +3 -0
- package/dist/commands/publishing.js +1419 -0
- package/dist/commands/recipes.d.ts +3 -0
- package/dist/commands/recipes.js +120 -0
- package/dist/commands/reviews.d.ts +3 -0
- package/dist/commands/reviews.js +75 -0
- package/dist/commands/rows.d.ts +3 -0
- package/dist/commands/rows.js +27 -0
- package/dist/commands/runs.d.ts +3 -0
- package/dist/commands/runs.js +37 -0
- package/dist/commands/search.d.ts +3 -0
- package/dist/commands/search.js +182 -0
- package/dist/commands/senders.d.ts +3 -0
- package/dist/commands/senders.js +393 -0
- package/dist/commands/sequences.d.ts +3 -0
- package/dist/commands/sequences.js +2322 -0
- package/dist/commands/session.d.ts +3 -0
- package/dist/commands/session.js +109 -0
- package/dist/commands/signals.d.ts +3 -0
- package/dist/commands/signals.js +289 -0
- package/dist/commands/signup-leads.d.ts +3 -0
- package/dist/commands/signup-leads.js +46 -0
- package/dist/commands/skills.d.ts +3 -0
- package/dist/commands/skills.js +50 -0
- package/dist/commands/sourcing.d.ts +3 -0
- package/dist/commands/sourcing.js +27 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.js +51 -0
- package/dist/commands/supabase.d.ts +3 -0
- package/dist/commands/supabase.js +128 -0
- package/dist/commands/support.d.ts +3 -0
- package/dist/commands/support.js +651 -0
- package/dist/commands/suppressions.d.ts +3 -0
- package/dist/commands/suppressions.js +577 -0
- package/dist/commands/table-ingestions.d.ts +3 -0
- package/dist/commands/table-ingestions.js +145 -0
- package/dist/commands/table-runs.d.ts +3 -0
- package/dist/commands/table-runs.js +277 -0
- package/dist/commands/tables.d.ts +3 -0
- package/dist/commands/tables.js +3505 -0
- package/dist/commands/tags.d.ts +3 -0
- package/dist/commands/tags.js +147 -0
- package/dist/commands/tools.d.ts +3 -0
- package/dist/commands/tools.js +246 -0
- package/dist/commands/update.d.ts +3 -0
- package/dist/commands/update.js +106 -0
- package/dist/commands/verify.d.ts +3 -0
- package/dist/commands/verify.js +27 -0
- package/dist/commands/viewers.d.ts +3 -0
- package/dist/commands/viewers.js +72 -0
- package/dist/commands/voice.d.ts +3 -0
- package/dist/commands/voice.js +351 -0
- package/dist/commands/whatsapp.d.ts +3 -0
- package/dist/commands/whatsapp.js +226 -0
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.js +110 -0
- package/dist/commands/worker.d.ts +3 -0
- package/dist/commands/worker.js +60 -0
- package/dist/commands/workflows.d.ts +3 -0
- package/dist/commands/workflows.js +1752 -0
- package/dist/commands/workspace.d.ts +3 -0
- package/dist/commands/workspace.js +94 -0
- package/dist/compact-list-flags.d.ts +7 -0
- package/dist/compact-list-flags.js +12 -0
- package/dist/crm-support-flags.js +14 -5
- package/dist/customer-operation-commands.js +8 -6
- package/dist/diagnostic-uploader.d.ts +3 -1
- package/dist/diagnostic-uploader.js +29 -8
- package/dist/help.d.ts +5 -0
- package/dist/help.js +22 -0
- package/dist/index.d.ts +98 -1
- package/dist/index.js +331 -30719
- package/dist/local-module-loader.d.ts +4 -0
- package/dist/local-module-loader.js +156 -0
- package/dist/visual-commands.js +9 -5
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +2 -2
- package/node_modules/@oxygen/shared/dist/agent-session-schedule.d.ts +36 -0
- package/node_modules/@oxygen/shared/dist/agent-session-schedule.js +69 -0
- package/node_modules/@oxygen/shared/dist/agent-sessions.d.ts +14 -5
- package/node_modules/@oxygen/shared/dist/agent-sessions.js +14 -3
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +175 -14
- package/node_modules/@oxygen/shared/dist/compact-list-result.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/compact-list-result.js +27 -0
- package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.d.ts +12 -1
- package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.js +5 -2
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +4 -2
- package/node_modules/@oxygen/shared/dist/contracts/compact-list-reads.d.ts +57 -0
- package/node_modules/@oxygen/shared/dist/contracts/compact-list-reads.js +23 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-support.d.ts +52 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-support.js +43 -2
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +20 -16
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-content-reads.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-proposal-reads.d.ts +49 -0
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-proposal-reads.js +14 -0
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-sync.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +60 -7
- package/node_modules/@oxygen/shared/dist/contracts/operations.d.ts +9 -1
- package/node_modules/@oxygen/shared/dist/contracts/operations.js +13 -5
- package/node_modules/@oxygen/shared/dist/contracts/publishing-reads.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/teammates.d.ts +16 -0
- package/node_modules/@oxygen/shared/dist/contracts/teammates.js +115 -0
- package/node_modules/@oxygen/shared/dist/cost-estimate-view.js +21 -3
- package/node_modules/@oxygen/shared/dist/cost-estimate.d.ts +31 -1
- package/node_modules/@oxygen/shared/dist/cost-estimate.js +56 -15
- package/node_modules/@oxygen/shared/dist/diagnostic-credentials.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-credentials.js +2 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-lock.js +71 -44
- package/node_modules/@oxygen/shared/dist/diagnostic-spool.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-spool.js +348 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-storage-failure.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-storage-failure.js +42 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-storage.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/diagnostic-storage.js +59 -213
- package/node_modules/@oxygen/shared/dist/diagnostics.js +3 -2
- package/node_modules/@oxygen/shared/dist/email-dsn.d.ts +16 -5
- package/node_modules/@oxygen/shared/dist/email-dsn.js +97 -1
- package/node_modules/@oxygen/shared/dist/emailguard-placement-probe.js +5 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.js +4 -5
- package/node_modules/@oxygen/shared/dist/file-import.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/file-import.js +21 -2
- package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +30 -0
- package/node_modules/@oxygen/shared/dist/hosted-ai.js +20 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +6 -2
- package/node_modules/@oxygen/shared/dist/knowledge-seed-content.d.ts +17 -7
- package/node_modules/@oxygen/shared/dist/knowledge-seed-content.js +81 -17
- package/node_modules/@oxygen/shared/dist/langfuse.js +8 -4
- package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +6 -3
- package/node_modules/@oxygen/shared/dist/mailbox-import.js +9 -9
- package/node_modules/@oxygen/shared/dist/mcp-server-info.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/mcp-server-info.js +10 -0
- package/node_modules/@oxygen/shared/dist/object-storage.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/object-storage.js +14 -0
- package/node_modules/@oxygen/shared/dist/plan-limits-view.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/plan-limits-view.js +17 -1
- 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/provider-selection.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/provider-selection.js +3 -0
- package/node_modules/@oxygen/shared/dist/repricing.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/repricing.js +2 -2
- package/node_modules/@oxygen/shared/dist/schedule-label.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/schedule-label.js +12 -0
- package/node_modules/@oxygen/shared/dist/sequence-failures.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/sequence-failures.js +20 -6
- package/node_modules/@oxygen/shared/dist/teammate-groups.d.ts +55 -0
- package/node_modules/@oxygen/shared/dist/teammate-groups.js +74 -0
- package/node_modules/@oxygen/shared/dist/teammate-messages.d.ts +52 -0
- package/node_modules/@oxygen/shared/dist/teammate-messages.js +88 -0
- package/node_modules/@oxygen/shared/dist/teammate-notes.d.ts +58 -0
- package/node_modules/@oxygen/shared/dist/teammate-notes.js +73 -0
- package/node_modules/@oxygen/shared/dist/teammate-routines.d.ts +95 -0
- package/node_modules/@oxygen/shared/dist/teammate-routines.js +266 -0
- package/node_modules/@oxygen/shared/dist/teammate-tasks.d.ts +56 -0
- package/node_modules/@oxygen/shared/dist/teammate-tasks.js +114 -0
- package/node_modules/@oxygen/shared/dist/teammates.d.ts +78 -0
- package/node_modules/@oxygen/shared/dist/teammates.js +140 -0
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +120 -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/dist/visual-render.d.ts +9 -1
- package/node_modules/@oxygen/shared/dist/visual-render.js +12 -5
- package/node_modules/@oxygen/shared/dist/warmup-metrics.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/warmup-metrics.js +44 -0
- package/package.json +1 -1
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
import { addCompactListReadFlags, appendCompactListReadQuery } from "../compact-list-flags.js";
|
|
2
|
+
import { emitCliFailure } from "../cli-output.js";
|
|
3
|
+
import { parseJsonObject, readPositiveInt } from "../cli-values.js";
|
|
4
|
+
import { KNOWLEDGE_SYNC_DEFAULT_TTL_MINUTES, buildContextResolveBody, isUuid, knowledgeMirrorTargetFor, readCsvOption, readNonNegativeNumber, readPositiveNumber, resolveKnowledgeMirrorTarget, runKnowledgeMirrorSync, runKnowledgeMirrorSyncAll } from "../command-support.js";
|
|
5
|
+
import { addContractFlags, readContractFlags } from "../contract-flags.js";
|
|
6
|
+
import { loadCredentials } from "../credentials.js";
|
|
7
|
+
import { requestOxygen } from "../http-client.js";
|
|
8
|
+
import { acquireMirrorLock, clearConflictFiles, listConflictFiles, listLocalMirrors, localPageSha256, markMirrorStale, mirrorExists, pageFilePath, planMirrorPush, purgeMirror, readMirrorState, releaseMirrorLock, resolveDefaultConfigDir, writeMirrorState } from "../knowledge-mirror.js";
|
|
9
|
+
import { readOption } from "../util.js";
|
|
10
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
11
|
+
import { knowledgeIndexReadContract, knowledgePageReadContract } from "@oxygen/shared/contracts/knowledge-content-reads";
|
|
12
|
+
import { knowledgeProposalWindowContract } from "@oxygen/shared/contracts/knowledge-proposal-reads";
|
|
13
|
+
import { KNOWLEDGE_BOOTSTRAP_MAX_CREDITS } from "@oxygen/shared/knowledge-bootstrap";
|
|
14
|
+
import { KNOWLEDGE_PAGE_TYPES } from "@oxygen/shared/knowledge-constants";
|
|
15
|
+
import { parseKnowledgePageMarkdown } from "@oxygen/shared/knowledge-markdown";
|
|
16
|
+
import { Command } from "commander";
|
|
17
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
+
// The wiki index counts the whole wiki even when the page list is a window, so
|
|
19
|
+
// name both numbers: "200 of 512 pages" is honest, "200 pages" is not.
|
|
20
|
+
function writeKnowledgeIndexCapNotice(data) {
|
|
21
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
22
|
+
return;
|
|
23
|
+
const index = data.index;
|
|
24
|
+
if (!index || typeof index !== "object" || Array.isArray(index))
|
|
25
|
+
return;
|
|
26
|
+
const block = index;
|
|
27
|
+
if (block.capped !== true)
|
|
28
|
+
return;
|
|
29
|
+
const counts = block.counts;
|
|
30
|
+
const returned = typeof counts?.returned === "number" ? counts.returned : null;
|
|
31
|
+
const total = typeof counts?.total === "number" ? counts.total : null;
|
|
32
|
+
const shown = returned === null
|
|
33
|
+
? "a window of this wiki's pages"
|
|
34
|
+
: `${returned}${total === null ? "" : ` of ${total}`} page(s)`;
|
|
35
|
+
process.stderr.write(`note: listing ${shown}, most recently updated first; counts cover the whole wiki — raise --limit (max 1000) or pass --all.\n`);
|
|
36
|
+
}
|
|
37
|
+
function buildKnowledgePageUpsertBody(options) {
|
|
38
|
+
const tags = readCsvOption(options.tags);
|
|
39
|
+
const expectedRevision = options.expectedRevision === undefined ? undefined : Number(options.expectedRevision);
|
|
40
|
+
if (expectedRevision !== undefined && (!options.expectedRevision?.trim() || !Number.isInteger(expectedRevision) || expectedRevision < 0)) {
|
|
41
|
+
throw new OxygenError("invalid_request", "--expected-revision must be a nonnegative integer.", { exitCode: 2 });
|
|
42
|
+
}
|
|
43
|
+
const folder = readOption(options.folder);
|
|
44
|
+
return {
|
|
45
|
+
...(folder ? { folderId: folder === "root" ? null : folder } : {}),
|
|
46
|
+
...(options.createOnly ? { createOnly: true } : {}),
|
|
47
|
+
...(readOption(options.slug) ? { slug: readOption(options.slug) } : {}),
|
|
48
|
+
...(readOption(options.id) ? { id: readOption(options.id) } : {}),
|
|
49
|
+
...(readOption(options.type) ? { type: readOption(options.type) } : {}),
|
|
50
|
+
...(readOption(options.title) ? { title: readOption(options.title) } : {}),
|
|
51
|
+
...(readOption(options.status) ? { status: readOption(options.status) } : {}),
|
|
52
|
+
...(tags.length > 0 ? { tags } : {}),
|
|
53
|
+
...(readOption(options.summary) ? { summary: readOption(options.summary) } : {}),
|
|
54
|
+
...(readOption(options.body) ? { body: readOption(options.body) } : {}),
|
|
55
|
+
...(options.dataJson ? { data: parseJsonObject(options.dataJson) } : {}),
|
|
56
|
+
...(expectedRevision !== undefined ? { expected_revision: expectedRevision } : {}),
|
|
57
|
+
...(options.approved ? { approved: true } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function buildKnowledgePageListBody(options) {
|
|
61
|
+
const tags = readCsvOption(options.tags);
|
|
62
|
+
const limit = readPositiveInt(options.limit);
|
|
63
|
+
return {
|
|
64
|
+
...(readOption(options.type) ? { type: readOption(options.type) } : {}),
|
|
65
|
+
...(readOption(options.status) ? { status: readOption(options.status) } : {}),
|
|
66
|
+
...(tags.length > 0 ? { tags } : {}),
|
|
67
|
+
...(limit !== undefined ? { limit } : {}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function knowledgeReadPath(path, parameters) {
|
|
71
|
+
const query = new URLSearchParams(Object.entries(parameters)
|
|
72
|
+
.filter(([, value]) => value !== undefined)
|
|
73
|
+
.map(([key, value]) => [key, String(value)]));
|
|
74
|
+
return query.size ? `${path}?${query}` : path;
|
|
75
|
+
}
|
|
76
|
+
function buildKnowledgeGraphBody(options) {
|
|
77
|
+
const maxNodes = readPositiveInt(options.maxNodes);
|
|
78
|
+
const center = readOption(options.center);
|
|
79
|
+
const depth = readPositiveInt(options.depth);
|
|
80
|
+
return {
|
|
81
|
+
...(maxNodes !== undefined ? { max_nodes: maxNodes } : {}),
|
|
82
|
+
...(center ? { center } : {}),
|
|
83
|
+
...(depth !== undefined ? { depth } : {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function buildKnowledgeLogListBody(options) {
|
|
87
|
+
const limit = readPositiveInt(options.limit);
|
|
88
|
+
return {
|
|
89
|
+
...(readOption(options.cursor) ? { cursor: readOption(options.cursor) } : {}),
|
|
90
|
+
...(limit !== undefined ? { limit } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function buildKnowledgeLogAppendBody(options) {
|
|
94
|
+
return {
|
|
95
|
+
...(readOption(options.event) ? { event: readOption(options.event) } : {}),
|
|
96
|
+
...(readOption(options.slug) ? { slug: readOption(options.slug) } : {}),
|
|
97
|
+
...(readOption(options.summary) ? { summary: readOption(options.summary) } : {}),
|
|
98
|
+
...(options.dataJson ? { data: parseJsonObject(options.dataJson) } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function buildKnowledgeSearchBody(query, options) {
|
|
102
|
+
const tags = readCsvOption(options.tags);
|
|
103
|
+
const limit = readPositiveInt(options.limit);
|
|
104
|
+
return {
|
|
105
|
+
query: query.trim(),
|
|
106
|
+
...(readOption(options.type) ? { type: readOption(options.type) } : {}),
|
|
107
|
+
...(readOption(options.status) ? { status: readOption(options.status) } : {}),
|
|
108
|
+
...(tags.length > 0 ? { tags } : {}),
|
|
109
|
+
...(limit !== undefined ? { limit } : {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// Post-write staleness hook for `knowledge page upsert|pin|archive`: clearing
|
|
113
|
+
// last_sync_at makes the next `knowledge sync --if-stale` run for real. A byte-
|
|
114
|
+
// perfect single-page refresh needs a dedicated sync endpoint (deferred); the
|
|
115
|
+
// hook is deliberately local-only — no whoami round-trip — and best-effort.
|
|
116
|
+
async function markKnowledgeMirrorStaleAfterWrite() {
|
|
117
|
+
try {
|
|
118
|
+
const credentials = await loadCredentials().catch(() => null);
|
|
119
|
+
if (!credentials)
|
|
120
|
+
return;
|
|
121
|
+
const cached = credentials.activeOrganization ?? credentials.identity?.organization ?? null;
|
|
122
|
+
const ref = process.env.OXYGEN_ORG?.trim() || cached?.id || cached?.slug;
|
|
123
|
+
if (!ref)
|
|
124
|
+
return;
|
|
125
|
+
// Resolve the ref to an org id offline — the hook stays network-free so a page
|
|
126
|
+
// write is never slowed by a whoami round-trip. A non-cached slug override
|
|
127
|
+
// (`--org <slug>` for an org we haven't cached) can't be mapped without a
|
|
128
|
+
// network call, so that mirror isn't stale-marked here; the 15-min --if-stale
|
|
129
|
+
// TTL and `knowledge sync --all` are the safety net.
|
|
130
|
+
let orgId = null;
|
|
131
|
+
if (cached && (ref === cached.id || ref === cached.slug))
|
|
132
|
+
orgId = cached.id;
|
|
133
|
+
else if (isUuid(ref))
|
|
134
|
+
orgId = ref;
|
|
135
|
+
if (!orgId)
|
|
136
|
+
return;
|
|
137
|
+
const target = knowledgeMirrorTargetFor(credentials.apiUrl, orgId);
|
|
138
|
+
if (mirrorExists(target.dir))
|
|
139
|
+
markMirrorStale(target.dir);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// A broken local mirror must never fail the page write itself.
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// One page's push: parse the local file, guard identity, upsert with the manifest
|
|
146
|
+
// revision as the optimistic-concurrency base, and translate the two expected
|
|
147
|
+
// gate outcomes (approval proposal, revision conflict) into statuses.
|
|
148
|
+
async function pushOneMirrorPage(dir, state, candidate) {
|
|
149
|
+
const { slug, kind, record } = candidate;
|
|
150
|
+
let contents;
|
|
151
|
+
try {
|
|
152
|
+
contents = readFileSync(pageFilePath(dir, slug), "utf8");
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return { slug, kind, status: "skipped", reason: "file_unreadable" };
|
|
156
|
+
}
|
|
157
|
+
const parsed = parseKnowledgePageMarkdown(contents);
|
|
158
|
+
if (record && parsed.id && parsed.id !== record.id) {
|
|
159
|
+
return { slug, kind, status: "skipped", reason: "id_mismatch_with_manifest" };
|
|
160
|
+
}
|
|
161
|
+
if (parsed.slug && parsed.slug !== slug) {
|
|
162
|
+
return { slug, kind, status: "skipped", reason: "frontmatter_slug_differs_from_filename" };
|
|
163
|
+
}
|
|
164
|
+
const body = {
|
|
165
|
+
slug,
|
|
166
|
+
body: parsed.body,
|
|
167
|
+
...(parsed.title ? { title: parsed.title } : {}),
|
|
168
|
+
...(parsed.type ? { type: parsed.type } : {}),
|
|
169
|
+
...(parsed.status ? { status: parsed.status } : {}),
|
|
170
|
+
...(parsed.tags ? { tags: parsed.tags } : {}),
|
|
171
|
+
// A frontmatter block owns the summary line: pushing an emptied summary
|
|
172
|
+
// clears it server-side; a plain new file leaves summary untouched.
|
|
173
|
+
...(parsed.hasFrontmatter ? { summary: parsed.summary } : {}),
|
|
174
|
+
...(record ? { expected_revision: record.revision } : {}),
|
|
175
|
+
};
|
|
176
|
+
try {
|
|
177
|
+
const data = await requestOxygen("/api/cli/knowledge/pages/upsert", { method: "POST", body });
|
|
178
|
+
const page = data.page ?? {};
|
|
179
|
+
if (typeof page.id === "string" && typeof page.revision === "number") {
|
|
180
|
+
// Record the pushed bytes as the manifest truth so the follow-up sync
|
|
181
|
+
// overwrites this file with the server rendering instead of quarantining
|
|
182
|
+
// the very edit we just pushed.
|
|
183
|
+
const localSha = localPageSha256(dir, slug);
|
|
184
|
+
if (localSha) {
|
|
185
|
+
state.pages[slug] = { id: page.id, revision: page.revision, content_sha256: localSha };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
slug,
|
|
190
|
+
kind,
|
|
191
|
+
status: "pushed",
|
|
192
|
+
...(typeof page.revision === "number" ? { revision: page.revision } : {}),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
if (error instanceof OxygenError && error.code === "knowledge_approval_required") {
|
|
197
|
+
const details = (error.details ?? {});
|
|
198
|
+
return {
|
|
199
|
+
slug,
|
|
200
|
+
kind,
|
|
201
|
+
status: "proposal_created",
|
|
202
|
+
// The API sends snake_case details; camelCase never matched, so push hid the proposal.
|
|
203
|
+
...(typeof details.proposal_id === "string" ? { proposal_id: details.proposal_id } : {}),
|
|
204
|
+
...(typeof details.proposal_slug === "string" ? { proposal_slug: details.proposal_slug } : {}),
|
|
205
|
+
...(typeof details.proposal_web_url === "string" ? { proposal_web_url: details.proposal_web_url } : {}),
|
|
206
|
+
reason: typeof details.reason === "string" ? details.reason : "sensitive_page",
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (error instanceof OxygenError && error.code === "knowledge_page_conflict") {
|
|
210
|
+
return { slug, kind, status: "conflict", reason: "revision_moved_run_knowledge_sync_first" };
|
|
211
|
+
}
|
|
212
|
+
if (error instanceof OxygenError) {
|
|
213
|
+
return { slug, kind, status: "failed", reason: error.code };
|
|
214
|
+
}
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// `oxygen knowledge push` — the write half of the local mirror: locally edited
|
|
219
|
+
// (dirty) and brand-new mirror files upsert back to the workspace wiki through
|
|
220
|
+
// the same gate as every other write surface, then a delta sync converges the
|
|
221
|
+
// mirror onto the server rendering. This is what lets a local agent (Claude
|
|
222
|
+
// Code, Codex) work the wiki as plain markdown files.
|
|
223
|
+
async function runKnowledgeMirrorPush(options) {
|
|
224
|
+
const target = await resolveKnowledgeMirrorTarget();
|
|
225
|
+
const state = readMirrorState(target.dir);
|
|
226
|
+
if (!state) {
|
|
227
|
+
throw new OxygenError("knowledge_mirror_missing", "No local knowledge mirror exists for this organization yet. Run `oxygen knowledge sync` first.", {
|
|
228
|
+
details: { mirror_path: target.dir },
|
|
229
|
+
exitCode: 1,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
const plan = planMirrorPush(target.dir, state);
|
|
233
|
+
if (options.dryRun) {
|
|
234
|
+
return {
|
|
235
|
+
dry_run: true,
|
|
236
|
+
candidates: plan.candidates.map(({ slug, kind, record }) => ({
|
|
237
|
+
slug,
|
|
238
|
+
kind,
|
|
239
|
+
...(record ? { base_revision: record.revision } : {}),
|
|
240
|
+
})),
|
|
241
|
+
skipped: plan.skipped,
|
|
242
|
+
mirror_path: target.dir,
|
|
243
|
+
web_url: target.webUrl,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
// Hold the mirror lock through the push phase so a concurrent sync can't
|
|
247
|
+
// rewrite files or the manifest mid-push; released before the convergence
|
|
248
|
+
// sync below, which takes the lock itself.
|
|
249
|
+
if (!acquireMirrorLock(target.dir)) {
|
|
250
|
+
throw new OxygenError("knowledge_sync_locked", "Another knowledge sync is running for this mirror. Retry shortly; a crashed sync's lock self-clears after 10 minutes.", {
|
|
251
|
+
details: { mirror_path: target.dir },
|
|
252
|
+
exitCode: 1,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
const outcomes = [];
|
|
256
|
+
let pushedCount = 0;
|
|
257
|
+
try {
|
|
258
|
+
for (const candidate of plan.candidates) {
|
|
259
|
+
outcomes.push(await pushOneMirrorPage(target.dir, state, candidate));
|
|
260
|
+
}
|
|
261
|
+
pushedCount = outcomes.filter((outcome) => outcome.status === "pushed").length;
|
|
262
|
+
if (pushedCount > 0) {
|
|
263
|
+
writeMirrorState(target.dir, state);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
releaseMirrorLock(target.dir);
|
|
268
|
+
}
|
|
269
|
+
// Converge: pull the server's rendering of everything just pushed (plus any
|
|
270
|
+
// proposal draft pages the gate filed) so local bytes match the server again.
|
|
271
|
+
let syncedAfter = false;
|
|
272
|
+
if (pushedCount > 0 || outcomes.some((outcome) => outcome.status === "proposal_created")) {
|
|
273
|
+
await runKnowledgeMirrorSync({});
|
|
274
|
+
syncedAfter = true;
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
pushed: pushedCount,
|
|
278
|
+
proposals: outcomes.filter((outcome) => outcome.status === "proposal_created"),
|
|
279
|
+
conflicts: outcomes.filter((outcome) => outcome.status === "conflict"),
|
|
280
|
+
failures: outcomes.filter((outcome) => outcome.status === "failed"),
|
|
281
|
+
skipped: [
|
|
282
|
+
...plan.skipped,
|
|
283
|
+
...outcomes
|
|
284
|
+
.filter((outcome) => outcome.status === "skipped")
|
|
285
|
+
.map(({ slug, reason }) => ({ file: `${slug}.md`, reason: reason ?? "skipped" })),
|
|
286
|
+
],
|
|
287
|
+
outcomes,
|
|
288
|
+
synced_after: syncedAfter,
|
|
289
|
+
mirror_path: target.dir,
|
|
290
|
+
web_url: target.webUrl,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
async function runKnowledgeMirrorStatus(options) {
|
|
294
|
+
const target = await resolveKnowledgeMirrorTarget();
|
|
295
|
+
const clearedConflicts = options.clearConflicts ? clearConflictFiles(target.dir) : undefined;
|
|
296
|
+
const state = readMirrorState(target.dir);
|
|
297
|
+
const conflicts = listConflictFiles(target.dir);
|
|
298
|
+
return {
|
|
299
|
+
mirror_path: target.dir,
|
|
300
|
+
exists: mirrorExists(target.dir),
|
|
301
|
+
pages_total: state ? Object.keys(state.pages).length : 0,
|
|
302
|
+
last_sync_at: state?.last_sync_at ?? null,
|
|
303
|
+
cursor: state?.cursor ?? null,
|
|
304
|
+
conflicts,
|
|
305
|
+
conflict_count: conflicts.length,
|
|
306
|
+
...(clearedConflicts !== undefined ? { cleared_conflicts: clearedConflicts } : {}),
|
|
307
|
+
web_url: target.webUrl,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
// `knowledge status --all` — freshness report across every locally mirrored
|
|
311
|
+
// workspace for the active API host. Pure filesystem read (no network): enumerate
|
|
312
|
+
// the org-id mirror dirs, read each `MirrorState`, and flag any whose last sync is
|
|
313
|
+
// older than the default TTL. Slugs are cross-referenced from the cached active
|
|
314
|
+
// org only (offline); other orgs report `org_id` alone.
|
|
315
|
+
async function runKnowledgeMirrorStatusAll() {
|
|
316
|
+
const credentials = await loadCredentials();
|
|
317
|
+
if (!credentials) {
|
|
318
|
+
throw new OxygenError("not_logged_in", "Run `oxygen login` before using CLI commands.", { exitCode: 1 });
|
|
319
|
+
}
|
|
320
|
+
const apiHost = new URL(credentials.apiUrl).host;
|
|
321
|
+
const orgIds = listLocalMirrors({ configDir: resolveDefaultConfigDir(), apiHost });
|
|
322
|
+
const cached = credentials.activeOrganization ?? credentials.identity?.organization ?? null;
|
|
323
|
+
const ttlMs = KNOWLEDGE_SYNC_DEFAULT_TTL_MINUTES * 60_000;
|
|
324
|
+
const mirrors = orgIds.map((orgId) => {
|
|
325
|
+
const target = knowledgeMirrorTargetFor(credentials.apiUrl, orgId);
|
|
326
|
+
const state = readMirrorState(target.dir);
|
|
327
|
+
const conflicts = listConflictFiles(target.dir);
|
|
328
|
+
const lastSyncMs = state?.last_sync_at ? Date.parse(state.last_sync_at) : Number.NaN;
|
|
329
|
+
const stale = !Number.isFinite(lastSyncMs) || Date.now() - lastSyncMs >= ttlMs;
|
|
330
|
+
return {
|
|
331
|
+
org_id: orgId,
|
|
332
|
+
...(cached && cached.id === orgId ? { slug: cached.slug ?? null } : {}),
|
|
333
|
+
mirror_path: target.dir,
|
|
334
|
+
exists: mirrorExists(target.dir),
|
|
335
|
+
pages_total: state ? Object.keys(state.pages).length : 0,
|
|
336
|
+
last_sync_at: state?.last_sync_at ?? null,
|
|
337
|
+
stale,
|
|
338
|
+
conflict_count: conflicts.length,
|
|
339
|
+
web_url: target.webUrl,
|
|
340
|
+
};
|
|
341
|
+
});
|
|
342
|
+
return {
|
|
343
|
+
api_host: apiHost,
|
|
344
|
+
count: mirrors.length,
|
|
345
|
+
mirrors,
|
|
346
|
+
web_url: `${credentials.apiUrl.replace(/\/$/, "")}/knowledge`,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
async function runKnowledgeMirrorPurge() {
|
|
350
|
+
const target = await resolveKnowledgeMirrorTarget();
|
|
351
|
+
const existed = existsSync(target.dir);
|
|
352
|
+
purgeMirror(target.dir);
|
|
353
|
+
return { purged: existed, mirror_path: target.dir, web_url: target.webUrl };
|
|
354
|
+
}
|
|
355
|
+
// `--print-path` emits the bare directory path (for shell substitution and
|
|
356
|
+
// editor tooling), not a JSON envelope — errors still use the failure envelope.
|
|
357
|
+
async function handleKnowledgeMirrorPrintPathAction() {
|
|
358
|
+
try {
|
|
359
|
+
const target = await resolveKnowledgeMirrorTarget();
|
|
360
|
+
process.stdout.write(`${target.dir}\n`);
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
emitCliFailure("knowledge status", error);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// Knowledge page routes accept either a UUID (`id`) or a slug (`slug`); split
|
|
367
|
+
// the single CLI argument the same way the templates commands do.
|
|
368
|
+
function knowledgePageRefBody(slugOrId) {
|
|
369
|
+
return isUuid(slugOrId) ? { id: slugOrId } : { slug: slugOrId };
|
|
370
|
+
}
|
|
371
|
+
function knowledgeProposalRefBody(idOrSlug) {
|
|
372
|
+
return isUuid(idOrSlug) ? { proposal_id: idOrSlug } : { slug: idOrSlug };
|
|
373
|
+
}
|
|
374
|
+
export function registerKnowledgeFamily(program, handleAsyncAction) {
|
|
375
|
+
// `knowledge bootstrap` is declared here, not inline in the group chain,
|
|
376
|
+
// because it is a command that both ACTS and owns a subcommand, and both
|
|
377
|
+
// levels take --json. Commander resolves a repeated option to the outer
|
|
378
|
+
// command, so `knowledge bootstrap status --json` sets the flag on THIS
|
|
379
|
+
// command, not on `status` — the same collision the `commands` group above
|
|
380
|
+
// handles. The named reference lets `status` read the parent's flag back.
|
|
381
|
+
const knowledgeBootstrapCommand = new Command("bootstrap")
|
|
382
|
+
.description(`Check saved company research with bootstrap status, or preview a manual company-research pass. Manual research uses direct LinkedIn company enrichment and website reads, saving company facts, a cited wiki note, and separate inferred GTM hypotheses. A bare call previews this manual pass for free; execution is customer-funded and requires --live plus --max-credits (hard cap ${KNOWLEDGE_BOOTSTRAP_MAX_CREDITS}). Automatic signup research is a separate platform-funded pass, runs once under the consent recorded at workspace creation, and may also research the creator. Check status before spending; an existing marker blocks a repeat unless --force re-arms it.`)
|
|
383
|
+
.option("--domain <domain>", "Company domain to research. Defaults to the recorded workspace domain; if none is available, the preview asks for one.")
|
|
384
|
+
.option("--linkedin <url>", "Optional company LinkedIn URL to use for the direct lookup. The returned company website must still match the domain.")
|
|
385
|
+
.option("--live", "Authorize this manual run to spend and write. Requires --max-credits. Without it the command previews and nothing is billed.")
|
|
386
|
+
.option("--approved", "Same as --live. Accepted because the global help footer names --approved as the way to authorize a credit-spending command.")
|
|
387
|
+
.option("--max-credits <credits>", `Hard credit ceiling for the whole pass. Required with --live, and clamped server-side to ${KNOWLEDGE_BOOTSTRAP_MAX_CREDITS}.`)
|
|
388
|
+
.option("--force", "Re-arm past an existing once-ever marker, the one deliberate way to research the same workspace twice. Without --live it only previews the re-arm for free: nothing is cleared or written. With --live it replaces the marker with a new pass and spends again.")
|
|
389
|
+
.option("--json", "Print a JSON envelope.")
|
|
390
|
+
.action(async (options) => {
|
|
391
|
+
await handleAsyncAction("knowledge bootstrap", options, () => {
|
|
392
|
+
const parameters = {
|
|
393
|
+
mode: options.live || options.approved ? "live" : "dry_run",
|
|
394
|
+
...(readOption(options.domain) ? { domain: readOption(options.domain) } : {}),
|
|
395
|
+
...(readOption(options.linkedin) ? { linkedin_url: readOption(options.linkedin) } : {}),
|
|
396
|
+
...(readPositiveNumber(options.maxCredits) !== undefined
|
|
397
|
+
? { max_credits: readPositiveNumber(options.maxCredits) }
|
|
398
|
+
: {}),
|
|
399
|
+
...(options.force ? { force: true } : {}),
|
|
400
|
+
};
|
|
401
|
+
return parameters.mode === "live"
|
|
402
|
+
? requestOxygen("/api/cli/knowledge/bootstrap", { method: "POST", body: parameters })
|
|
403
|
+
: requestOxygen(knowledgeReadPath("/api/cli/knowledge/bootstrap", parameters));
|
|
404
|
+
});
|
|
405
|
+
})
|
|
406
|
+
.addCommand(new Command("status")
|
|
407
|
+
.description("Report the once-ever bootstrap marker for this workspace: status (queued, running, completed, partial, skipped, failed, or never attempted), when it ran, its system run id, the credits it actually spent, the domain researched, the reason it stopped, and the pages it wrote. Free, reads only, and the honest way to find out whether the automatic pass already happened before you spend on a manual one.")
|
|
408
|
+
.option("--json", "Print a JSON envelope.")
|
|
409
|
+
.action(async (options) => {
|
|
410
|
+
// Commander hands a repeated --json to the outer command, so read the
|
|
411
|
+
// parent's flag back before deciding the output shape. Without this a
|
|
412
|
+
// machine caller asking for an envelope silently gets bare JSON.
|
|
413
|
+
const outputOptions = {
|
|
414
|
+
...options,
|
|
415
|
+
json: options.json || Boolean(knowledgeBootstrapCommand.opts().json),
|
|
416
|
+
};
|
|
417
|
+
await handleAsyncAction("knowledge bootstrap status", outputOptions, () => requestOxygen("/api/cli/knowledge/bootstrap?status=true"));
|
|
418
|
+
}));
|
|
419
|
+
program
|
|
420
|
+
.command("knowledge")
|
|
421
|
+
.description("Company knowledge wiki: slug-addressed pages, a [[wikilink]] graph, and a decision log.")
|
|
422
|
+
.addCommand(new Command("folders")
|
|
423
|
+
.description("Browse, create, and move nested folders for knowledge pages; slugs and wikilinks stay stable.")
|
|
424
|
+
.addCommand(addCompactListReadFlags(new Command("list")
|
|
425
|
+
.description("List workspace folders and page locations, including empty folders. Read-only, 0 credits.")
|
|
426
|
+
.option("--json", "Print a JSON envelope.")
|
|
427
|
+
.action(async (options, command) => {
|
|
428
|
+
await handleAsyncAction("knowledge folders list", options, () => {
|
|
429
|
+
const params = new URLSearchParams();
|
|
430
|
+
appendCompactListReadQuery(params, command, "folders");
|
|
431
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
432
|
+
return requestOxygen(`/api/cli/knowledge/folders${qs}`);
|
|
433
|
+
});
|
|
434
|
+
}), "folders"))
|
|
435
|
+
.addCommand(new Command("create")
|
|
436
|
+
.description("Create a persistent knowledge folder, optionally inside another folder.")
|
|
437
|
+
.requiredOption("--name <name>", "Folder name.")
|
|
438
|
+
.option("--parent <folder_id>", "Parent folder UUID from knowledge folders list. Omit for root.")
|
|
439
|
+
.option("--json", "Print a JSON envelope.")
|
|
440
|
+
.action(async (options) => {
|
|
441
|
+
await handleAsyncAction("knowledge folders create", options, () => requestOxygen("/api/cli/knowledge/folders/create", {
|
|
442
|
+
method: "POST",
|
|
443
|
+
body: {
|
|
444
|
+
name: options.name,
|
|
445
|
+
...(readOption(options.parent) ? { parentId: readOption(options.parent) } : {}),
|
|
446
|
+
},
|
|
447
|
+
}));
|
|
448
|
+
}))
|
|
449
|
+
.addCommand(new Command("move")
|
|
450
|
+
.description("Move a folder and its contents to another folder or the workspace root.")
|
|
451
|
+
.argument("<id>", "Folder UUID from knowledge folders list.")
|
|
452
|
+
.requiredOption("--parent <folder_id|root>", "Destination folder UUID, or root.")
|
|
453
|
+
.option("--json", "Print a JSON envelope.")
|
|
454
|
+
.action(async (id, options) => {
|
|
455
|
+
await handleAsyncAction("knowledge folders move", options, () => requestOxygen("/api/cli/knowledge/folders/move", {
|
|
456
|
+
method: "POST",
|
|
457
|
+
body: { id, parentId: options.parent === "root" ? null : options.parent },
|
|
458
|
+
}));
|
|
459
|
+
})))
|
|
460
|
+
.addCommand(new Command("page")
|
|
461
|
+
.description("Knowledge wiki pages (slug-addressed, revision-guarded).")
|
|
462
|
+
.addCommand(new Command("upsert")
|
|
463
|
+
.description("Create or update a knowledge wiki page. Free internal write; canonical edits require proposal approval.")
|
|
464
|
+
.option("--slug <slug>", "Stable page key. Omit to derive from title; an existing slug updates unless --create-only is set.")
|
|
465
|
+
.option("--folder <folder_id>", "Folder UUID, or root to clear placement; omit to preserve. Moving creates a revision and changes data.knowledge_folder_id.")
|
|
466
|
+
.option("--create-only", "Fail if the page already exists instead of updating it.")
|
|
467
|
+
.option("--id <page_id>", "Existing page UUID to update. For a new file, omit and use --create-only.")
|
|
468
|
+
.option("--type <type>", `Page type: ${KNOWLEDGE_PAGE_TYPES.join(", ")}. voice, brand and positioning are canonical copy once pinned. Defaults to other on create.`)
|
|
469
|
+
.option("--title <title>", "Page title. Required on create.")
|
|
470
|
+
.option("--status <status>", "draft, active, or archived. Restore with draft and --expected-revision; omit --body to retain content.")
|
|
471
|
+
.option("--tags <csv>", "Comma-separated page tags.")
|
|
472
|
+
.option("--summary <text>", "Short page summary.")
|
|
473
|
+
.option("--body <text>", "Page body (Markdown, supports [[wikilinks]]).")
|
|
474
|
+
.option("--data-json <json>", "Flexible JSON object for structured page data.")
|
|
475
|
+
.option("--expected-revision <n>", "Fail the write if the current page revision differs (optimistic concurrency guard).")
|
|
476
|
+
.option("--approved", "Accepted for compatibility but never honored for canonical live copy: a gated edit always files a proposal - approve it with `oxygen knowledge proposals approve <id>`.")
|
|
477
|
+
.option("--json", "Print a JSON envelope.")
|
|
478
|
+
.action(async (options) => {
|
|
479
|
+
await handleAsyncAction("knowledge page upsert", options, async () => {
|
|
480
|
+
const data = await requestOxygen("/api/cli/knowledge/pages/upsert", {
|
|
481
|
+
method: "POST",
|
|
482
|
+
body: buildKnowledgePageUpsertBody(options),
|
|
483
|
+
});
|
|
484
|
+
await markKnowledgeMirrorStaleAfterWrite();
|
|
485
|
+
return data;
|
|
486
|
+
});
|
|
487
|
+
}))
|
|
488
|
+
.addCommand(addContractFlags(new Command("get")
|
|
489
|
+
.description("Read one knowledge wiki page by slug or UUID. Use --view sources for its evidence ledger or --view full for content, links and sources. Read-only, 0 credits.")
|
|
490
|
+
.argument("<slug_or_id>", "Page slug or UUID.")
|
|
491
|
+
.option("--json", "Print a JSON envelope."), knowledgePageReadContract, { slug: { omit: true }, id: { omit: true } })
|
|
492
|
+
.action(async (slugOrId, options) => {
|
|
493
|
+
await handleAsyncAction("knowledge page get", options, () => requestOxygen(`/api/cli/knowledge/pages/get?${new URLSearchParams(Object.entries({ slug: slugOrId, ...readContractFlags(options, knowledgePageReadContract, { slug: { omit: true }, id: { omit: true } }) }).map(([key, value]) => [key, String(value)]))}`));
|
|
494
|
+
}))
|
|
495
|
+
.addCommand(new Command("list")
|
|
496
|
+
.description("List knowledge wiki pages. Read-only, 0 credits.")
|
|
497
|
+
.option("--type <type>", "Filter by page type.")
|
|
498
|
+
.option("--status <status>", "Filter by draft, active, or archived.")
|
|
499
|
+
.option("--tags <csv>", "Comma-separated tags that must be present.")
|
|
500
|
+
.option("--limit <n>", "Maximum pages to return.")
|
|
501
|
+
.option("--json", "Print a JSON envelope.")
|
|
502
|
+
.action(async (options) => {
|
|
503
|
+
await handleAsyncAction("knowledge pages", options, () => requestOxygen("/api/cli/knowledge/pages", {
|
|
504
|
+
method: "POST",
|
|
505
|
+
body: buildKnowledgePageListBody(options),
|
|
506
|
+
}));
|
|
507
|
+
}))
|
|
508
|
+
.addCommand(new Command("archive")
|
|
509
|
+
.description("Archive a knowledge wiki page while retaining its content and history. Links become unresolved. Restore with page upsert --status draft and the current --expected-revision.")
|
|
510
|
+
.argument("<slug_or_id>", "Page slug or UUID.")
|
|
511
|
+
.option("--expected-revision <n>", "Fail if the current page revision differs from the version you reviewed.")
|
|
512
|
+
.option("--json", "Print a JSON envelope.")
|
|
513
|
+
.action(async (slugOrId, options) => {
|
|
514
|
+
await handleAsyncAction("knowledge page archive", options, async () => {
|
|
515
|
+
const expectedRevision = readPositiveInt(options.expectedRevision);
|
|
516
|
+
if (options.expectedRevision !== undefined && (expectedRevision === undefined || !Number.isSafeInteger(expectedRevision))) {
|
|
517
|
+
throw new OxygenError("invalid_request", "--expected-revision must be a positive integer.", { exitCode: 2 });
|
|
518
|
+
}
|
|
519
|
+
const data = await requestOxygen("/api/cli/knowledge/pages/archive", {
|
|
520
|
+
method: "POST",
|
|
521
|
+
body: {
|
|
522
|
+
...knowledgePageRefBody(slugOrId),
|
|
523
|
+
...(expectedRevision !== undefined ? { expected_revision: expectedRevision } : {}),
|
|
524
|
+
},
|
|
525
|
+
});
|
|
526
|
+
await markKnowledgeMirrorStaleAfterWrite();
|
|
527
|
+
return data;
|
|
528
|
+
});
|
|
529
|
+
}))
|
|
530
|
+
.addCommand(new Command("restore")
|
|
531
|
+
.description("Restore a knowledge wiki page to an earlier revision, as a new revision; history is kept. Use --expected-revision to refuse if the page changed since.")
|
|
532
|
+
.argument("<slug_or_id>", "Page slug or UUID.")
|
|
533
|
+
.requiredOption("--revision <n>", "The revision to restore (see knowledge page revisions).")
|
|
534
|
+
.option("--expected-revision <n>", "Fail if the current page revision differs from the version you reviewed.")
|
|
535
|
+
.option("--json", "Print a JSON envelope.")
|
|
536
|
+
.action(async (slugOrId, options) => {
|
|
537
|
+
await handleAsyncAction("knowledge page restore", options, async () => {
|
|
538
|
+
const revision = readPositiveInt(options.revision);
|
|
539
|
+
const expectedRevision = readPositiveInt(options.expectedRevision);
|
|
540
|
+
if (revision === undefined || !Number.isSafeInteger(revision)) {
|
|
541
|
+
throw new OxygenError("invalid_request", "--revision must be a positive integer.", { exitCode: 2 });
|
|
542
|
+
}
|
|
543
|
+
if (options.expectedRevision !== undefined && (expectedRevision === undefined || !Number.isSafeInteger(expectedRevision))) {
|
|
544
|
+
throw new OxygenError("invalid_request", "--expected-revision must be a positive integer.", { exitCode: 2 });
|
|
545
|
+
}
|
|
546
|
+
const data = await requestOxygen("/api/cli/knowledge/pages/restore", {
|
|
547
|
+
method: "POST",
|
|
548
|
+
body: {
|
|
549
|
+
...knowledgePageRefBody(slugOrId),
|
|
550
|
+
revision,
|
|
551
|
+
...(expectedRevision !== undefined ? { expected_revision: expectedRevision } : {}),
|
|
552
|
+
},
|
|
553
|
+
});
|
|
554
|
+
await markKnowledgeMirrorStaleAfterWrite();
|
|
555
|
+
return data;
|
|
556
|
+
});
|
|
557
|
+
}))
|
|
558
|
+
.addCommand(new Command("pin")
|
|
559
|
+
.description("Pin a knowledge wiki page as the canonical default for its type. Canonical voice/brand/positioning pages are auto-applied to AI copy generation.")
|
|
560
|
+
.argument("<slug_or_id>", "Page slug or UUID.")
|
|
561
|
+
.option("--canonical", "Pin as the canonical default for its type (default).")
|
|
562
|
+
.option("--no-canonical", "Unpin instead of pin (clears the canonical default for this type).")
|
|
563
|
+
.option("--approved", "Accepted for compatibility but never honored: pinning a sensitive type (voice, brand, positioning, playbooks) always files a proposal - approve it with `oxygen knowledge proposals approve <id>`.")
|
|
564
|
+
.option("--json", "Print a JSON envelope.")
|
|
565
|
+
.action(async (slugOrId, options) => {
|
|
566
|
+
await handleAsyncAction("knowledge page pin", options, async () => {
|
|
567
|
+
const data = await requestOxygen("/api/cli/knowledge/pages/pin", {
|
|
568
|
+
method: "POST",
|
|
569
|
+
body: {
|
|
570
|
+
...knowledgePageRefBody(slugOrId),
|
|
571
|
+
canonical: options.canonical !== false,
|
|
572
|
+
...(options.approved ? { approved: true } : {}),
|
|
573
|
+
},
|
|
574
|
+
});
|
|
575
|
+
await markKnowledgeMirrorStaleAfterWrite();
|
|
576
|
+
return data;
|
|
577
|
+
});
|
|
578
|
+
}))
|
|
579
|
+
.addCommand(new Command("revisions")
|
|
580
|
+
.description("List the revision history of a knowledge wiki page, newest first. Read-only, 0 credits.")
|
|
581
|
+
.argument("<slug_or_id>", "Page slug or UUID.")
|
|
582
|
+
.option("--limit <n>", "Maximum revisions to return.")
|
|
583
|
+
.option("--json", "Print a JSON envelope.")
|
|
584
|
+
.action(async (slugOrId, options) => {
|
|
585
|
+
await handleAsyncAction("knowledge page revisions", options, () => {
|
|
586
|
+
const limit = readPositiveInt(options.limit);
|
|
587
|
+
return requestOxygen(knowledgeReadPath("/api/cli/knowledge/pages/revisions", {
|
|
588
|
+
...knowledgePageRefBody(slugOrId),
|
|
589
|
+
...(limit !== undefined ? { limit } : {}),
|
|
590
|
+
}));
|
|
591
|
+
});
|
|
592
|
+
}))
|
|
593
|
+
.addCommand(new Command("revision")
|
|
594
|
+
.description("Read one full revision snapshot of a knowledge wiki page. Read-only, 0 credits.")
|
|
595
|
+
.argument("<slug_or_id>", "Page slug or UUID.")
|
|
596
|
+
.argument("<revision>", "Revision number.")
|
|
597
|
+
.option("--json", "Print a JSON envelope.")
|
|
598
|
+
.action(async (slugOrId, revision, options) => {
|
|
599
|
+
await handleAsyncAction("knowledge page revision", options, () => {
|
|
600
|
+
const revisionNumber = readPositiveInt(revision);
|
|
601
|
+
if (revisionNumber === undefined) {
|
|
602
|
+
throw new OxygenError("invalid_request", "Revision must be a positive integer.", {
|
|
603
|
+
details: { revision },
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
return requestOxygen(knowledgeReadPath("/api/cli/knowledge/pages/revisions/get", {
|
|
607
|
+
...knowledgePageRefBody(slugOrId),
|
|
608
|
+
revision: revisionNumber,
|
|
609
|
+
}));
|
|
610
|
+
});
|
|
611
|
+
})))
|
|
612
|
+
.addCommand(new Command("search")
|
|
613
|
+
.description("Search wiki pages by meaning and keywords. Read-only, 0 credits. Plain language uses hybrid retrieval when available; check match_type and semantic_status. Quotes, OR, and exclusions stay lexical. If the platform-funded embedding allowance is unavailable, keyword search still works.")
|
|
614
|
+
.argument("<query>", "Search text. Supports quoted phrases, OR, and -exclude.")
|
|
615
|
+
.option("--type <type>", "Filter by page type.")
|
|
616
|
+
.option("--status <status>", "Filter by draft, active, or archived. Omitted, archived pages are excluded.")
|
|
617
|
+
.option("--tags <csv>", "Comma-separated tags that must be present.")
|
|
618
|
+
.option("--limit <n>", "Maximum pages to return.")
|
|
619
|
+
.option("--json", "Print a JSON envelope.")
|
|
620
|
+
.action(async (query, options) => {
|
|
621
|
+
await handleAsyncAction("knowledge search", options, () => requestOxygen("/api/cli/knowledge/search", {
|
|
622
|
+
method: "POST",
|
|
623
|
+
body: buildKnowledgeSearchBody(query, options),
|
|
624
|
+
}));
|
|
625
|
+
}))
|
|
626
|
+
.addCommand(addContractFlags(new Command("index")
|
|
627
|
+
.description("Compact wiki index: each page's slug, one-liner, tags, link degree, plus type/status counts over the whole wiki. Lists the 200 most recently updated pages by default. Read-only, 0 credits. semantic.budget is Oxygen's platform-funded embedding safety allowance; its dollar and query counters are Oxygen's spend, never charges against your credits.")
|
|
628
|
+
.option("--limit <n>", "Maximum pages to list. Defaults to 200; hard cap is 1000. Counts always cover the whole wiki.")
|
|
629
|
+
.option("--all", "List every page instead of the most recently updated window.")
|
|
630
|
+
.option("--json", "Print a JSON envelope."), knowledgeIndexReadContract, { limit: { omit: true } })
|
|
631
|
+
.action(async (options) => {
|
|
632
|
+
await handleAsyncAction("knowledge index", options, async () => {
|
|
633
|
+
const params = new URLSearchParams();
|
|
634
|
+
const limit = readPositiveInt(options.limit);
|
|
635
|
+
if (limit !== undefined)
|
|
636
|
+
params.set("limit", String(limit));
|
|
637
|
+
if (options.all)
|
|
638
|
+
params.set("all", "true");
|
|
639
|
+
for (const [key, value] of Object.entries(readContractFlags(options, knowledgeIndexReadContract, { limit: { omit: true } })))
|
|
640
|
+
params.set(key, String(value));
|
|
641
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
642
|
+
const data = await requestOxygen(`/api/cli/knowledge/index${qs}`);
|
|
643
|
+
if (!options.json)
|
|
644
|
+
writeKnowledgeIndexCapNotice(data);
|
|
645
|
+
return data;
|
|
646
|
+
});
|
|
647
|
+
}))
|
|
648
|
+
.addCommand(new Command("graph")
|
|
649
|
+
.description("Knowledge graph of pages and their [[wikilink]] edges. Pass --center to render one page's local neighborhood instead of the whole graph. Read-only, 0 credits.")
|
|
650
|
+
.option("--max-nodes <n>", "Maximum nodes to include in the graph.")
|
|
651
|
+
.option("--center <slug_or_id>", "Center the graph on one page and show only its neighborhood (local mode).")
|
|
652
|
+
.option("--depth <n>", "Local-graph hop depth (1-3, default 1). Only applies with --center.")
|
|
653
|
+
.option("--json", "Print a JSON envelope.")
|
|
654
|
+
.action(async (options) => {
|
|
655
|
+
await handleAsyncAction("knowledge graph", options, () => requestOxygen(knowledgeReadPath("/api/cli/knowledge/graph", buildKnowledgeGraphBody(options))));
|
|
656
|
+
}))
|
|
657
|
+
.addCommand(new Command("lint")
|
|
658
|
+
.description("Read-only structural wiki health report, 0 credits: orphans (unfilled seed stubs excluded), dead-ends, unresolved links, contradictions (disputed pages, conflicting trust tags, competing live-copy pages), missing canonicals, untagged pages, duplicate titles, oversized hubs, unfilled seed stubs (total plus the most-linked-to), researchable stubs, decisions missing a reversal condition, stale pages (untouched or superseded by newer sources), oversized bodies, and aged proposals.")
|
|
659
|
+
.option("--json", "Print a JSON envelope.")
|
|
660
|
+
.action(async (options) => {
|
|
661
|
+
await handleAsyncAction("knowledge lint", options, () => requestOxygen("/api/cli/knowledge/lint"));
|
|
662
|
+
})
|
|
663
|
+
.addCommand(new Command("relink")
|
|
664
|
+
.description("Recompute [[wikilink]] edges (and gap-fill unlinked mentions) across wiki pages.")
|
|
665
|
+
.option("--slugs <csv>", "Comma-separated page slugs to relink. Omit to relink every page.")
|
|
666
|
+
.option("--json", "Print a JSON envelope.")
|
|
667
|
+
.action(async (options) => {
|
|
668
|
+
await handleAsyncAction("knowledge lint relink", options, () => {
|
|
669
|
+
const slugs = readCsvOption(options.slugs);
|
|
670
|
+
return requestOxygen("/api/cli/knowledge/lint/relink", {
|
|
671
|
+
method: "POST",
|
|
672
|
+
body: slugs.length > 0 ? { slugs } : {},
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
})))
|
|
676
|
+
.addCommand(new Command("log")
|
|
677
|
+
.description("Knowledge decision and activity log.")
|
|
678
|
+
.addCommand(new Command("list")
|
|
679
|
+
.description("List knowledge log entries, newest first. Read-only, 0 credits.")
|
|
680
|
+
.option("--cursor <cursor>", "Pagination cursor from a previous page.")
|
|
681
|
+
.option("--limit <n>", "Maximum log entries to return.")
|
|
682
|
+
.option("--json", "Print a JSON envelope.")
|
|
683
|
+
.action(async (options) => {
|
|
684
|
+
await handleAsyncAction("knowledge log", options, () => requestOxygen(knowledgeReadPath("/api/cli/knowledge/log", buildKnowledgeLogListBody(options))));
|
|
685
|
+
}))
|
|
686
|
+
.addCommand(new Command("append")
|
|
687
|
+
.description("Append an entry to the knowledge log.")
|
|
688
|
+
.requiredOption("--event <event>", "note, query_filed, or decision.")
|
|
689
|
+
.option("--slug <slug>", "Related page slug.")
|
|
690
|
+
.option("--summary <text>", "Short summary of the log entry.")
|
|
691
|
+
.option("--data-json <json>", "Flexible JSON object for structured log data.")
|
|
692
|
+
.option("--json", "Print a JSON envelope.")
|
|
693
|
+
.action(async (options) => {
|
|
694
|
+
await handleAsyncAction("knowledge log append", options, () => requestOxygen("/api/cli/knowledge/log/append", {
|
|
695
|
+
method: "POST",
|
|
696
|
+
body: buildKnowledgeLogAppendBody(options),
|
|
697
|
+
}));
|
|
698
|
+
})))
|
|
699
|
+
.addCommand(addContractFlags(new Command("proposals")
|
|
700
|
+
.description("Review pending captured wiki changes. Optional summary traverses the live queue; exact proposal + value reads raw captured JSON. Read-only, 0 credits.")
|
|
701
|
+
.option("--status <status>", "open, decided, or all. Defaults to open.")
|
|
702
|
+
.option("--limit <n>", "Maximum proposals to return. Defaults to 50; server caps at 200.")
|
|
703
|
+
.option("--json", "Print a JSON envelope."), knowledgeProposalWindowContract)
|
|
704
|
+
.action(async (options) => {
|
|
705
|
+
await handleAsyncAction("knowledge proposals", options, () => {
|
|
706
|
+
const limit = readPositiveInt(options.limit);
|
|
707
|
+
return requestOxygen(knowledgeReadPath("/api/cli/knowledge/proposals", {
|
|
708
|
+
...readContractFlags(options, knowledgeProposalWindowContract),
|
|
709
|
+
...(readOption(options.status) ? { status: readOption(options.status) } : {}),
|
|
710
|
+
...(limit !== undefined ? { limit } : {}),
|
|
711
|
+
}));
|
|
712
|
+
});
|
|
713
|
+
})
|
|
714
|
+
.addCommand(new Command("approve")
|
|
715
|
+
.description("Approve a knowledge proposal and apply it to the target page.")
|
|
716
|
+
.argument("<id_or_slug>", "Proposal UUID or proposal page slug.")
|
|
717
|
+
.option("--force", "Apply even if the target page changed since the proposal was drafted.")
|
|
718
|
+
.option("--json", "Print a JSON envelope.")
|
|
719
|
+
.action(async (idOrSlug, options) => {
|
|
720
|
+
await handleAsyncAction("knowledge proposals approve", options, () => requestOxygen("/api/cli/knowledge/proposals/approve", {
|
|
721
|
+
method: "POST",
|
|
722
|
+
body: {
|
|
723
|
+
...knowledgeProposalRefBody(idOrSlug),
|
|
724
|
+
...(options.force ? { force: true } : {}),
|
|
725
|
+
},
|
|
726
|
+
}));
|
|
727
|
+
}))
|
|
728
|
+
.addCommand(new Command("reject")
|
|
729
|
+
.description("Reject a knowledge proposal without changing the target page.")
|
|
730
|
+
.argument("<id_or_slug>", "Proposal UUID or proposal page slug.")
|
|
731
|
+
.option("--json", "Print a JSON envelope.")
|
|
732
|
+
.action(async (idOrSlug, options) => {
|
|
733
|
+
await handleAsyncAction("knowledge proposals reject", options, () => requestOxygen("/api/cli/knowledge/proposals/reject", {
|
|
734
|
+
method: "POST",
|
|
735
|
+
body: knowledgeProposalRefBody(idOrSlug),
|
|
736
|
+
}));
|
|
737
|
+
})))
|
|
738
|
+
.addCommand(new Command("resolve")
|
|
739
|
+
.description("Resolve task-scoped workspace GTM context with readiness and revision provenance.")
|
|
740
|
+
.option("--purpose <purpose>", "onboarding, general, lead_sourcing, qualification, outbound_copy, or workflow_design.")
|
|
741
|
+
.option("--asset-type <csv>", "Comma-separated context asset types to include.")
|
|
742
|
+
.option("--asset-status <status>", "draft, active, archived, or all. Defaults to active.")
|
|
743
|
+
.option("--tags <csv>", "Comma-separated asset tags that must be present.")
|
|
744
|
+
.option("--include-archived", "Include archived assets when no asset status is set.")
|
|
745
|
+
.option("--max-assets <n>", "Maximum assets to include. Defaults to 10, max 50.")
|
|
746
|
+
.option("--require-ready", "Exit with a conflict error when required context sections are missing.")
|
|
747
|
+
.option("--json", "Print a JSON envelope.")
|
|
748
|
+
.action(async (options) => {
|
|
749
|
+
await handleAsyncAction("knowledge resolve", options, () => requestOxygen("/api/cli/knowledge/resolve", {
|
|
750
|
+
method: "POST",
|
|
751
|
+
body: buildContextResolveBody(options),
|
|
752
|
+
}));
|
|
753
|
+
}))
|
|
754
|
+
.addCommand(new Command("profile")
|
|
755
|
+
.description("Company profile and ICP memory.")
|
|
756
|
+
.option("--json", "Print a JSON envelope.")
|
|
757
|
+
.action(async (options) => {
|
|
758
|
+
await handleAsyncAction("knowledge profile", options, () => requestOxygen("/api/cli/knowledge/profile"));
|
|
759
|
+
})
|
|
760
|
+
.addCommand(new Command("update")
|
|
761
|
+
.description("Merge one or more profile sections into workspace GTM memory.")
|
|
762
|
+
.requiredOption("--data-json <json>", "JSON object with company, offering, icp, market, gtm_stack, or custom sections.")
|
|
763
|
+
.option("--summary <text>", "Optional concise summary for the profile.")
|
|
764
|
+
.option("--json", "Print a JSON envelope.")
|
|
765
|
+
.action(async (options) => {
|
|
766
|
+
await handleAsyncAction("knowledge profile update", options, () => requestOxygen("/api/cli/knowledge/profile/update", {
|
|
767
|
+
method: "POST",
|
|
768
|
+
body: {
|
|
769
|
+
data: parseJsonObject(options.dataJson ?? "{}"),
|
|
770
|
+
...(readOption(options.summary) ? { summary: readOption(options.summary) } : {}),
|
|
771
|
+
},
|
|
772
|
+
}));
|
|
773
|
+
})))
|
|
774
|
+
.addCommand(new Command("seed")
|
|
775
|
+
.description("Seed the knowledge starter pages: on first run creates draft ICP and Offers pages, [[wikilinked]] to each other. Idempotent; never overwrites your edits.")
|
|
776
|
+
.option("--json", "Print a JSON envelope.")
|
|
777
|
+
.action(async (options) => {
|
|
778
|
+
await handleAsyncAction("knowledge seed", options, () => requestOxygen("/api/cli/knowledge/seed", {
|
|
779
|
+
method: "POST",
|
|
780
|
+
body: {},
|
|
781
|
+
}));
|
|
782
|
+
}))
|
|
783
|
+
.addCommand(knowledgeBootstrapCommand)
|
|
784
|
+
.addCommand(new Command("synthesize")
|
|
785
|
+
.description("Synthesize knowledge from workspace evidence (one low-cost AI call, priced as one low-reasoning AI column cell): campaign learnings from auto-filed outreach outcomes, or your writing voice (a voice guide) learned from your sent LinkedIn and WhatsApp messages. Every run spends credits, the preview included, unless fewer than 3 sent messages exist (insufficient_source_messages, nothing charged); without --approved nothing is saved. To keep the guide a preview showed, pass its body back with --approved --body (no new AI call); --approved alone synthesizes and charges again. A saved voice guide becomes the canonical voice once a human approves its pin proposal (oxygen knowledge proposals approve <id>); it then grounds message, reply, sequence and post drafts and AI columns.")
|
|
786
|
+
.option("--kind <kind>", "What to synthesize: campaign_learnings (default) or voice (your writing voice from sent LinkedIn and WhatsApp messages).", "campaign_learnings")
|
|
787
|
+
.option("--sequence <sequence_id>", "Scope campaign learnings to one sequence's learnings page. Omit for the org-wide [[outreach-learnings]] page.")
|
|
788
|
+
.option("--approved", "Write the synthesized result (learnings page revision / voice guide saved with a pin proposal) instead of previewing.")
|
|
789
|
+
.option("--body <markdown>", "With --approved: save this body verbatim (the guide a preview showed, or your edit) with no new AI call, at 0 credits.")
|
|
790
|
+
.option("--title <title>", "Optional title for a newly created page/asset.")
|
|
791
|
+
.option("--limit <n>", "Voice only: how many recent sent messages to learn from (max 200).")
|
|
792
|
+
.option("--max-credits <n>", "Hard cap OXYGEN enforces on this call: it is charged exactly its estimate, so never more than n. A higher estimate is refused before any AI call, with nothing charged, and the refusal reports the estimate (--max-credits 0 shows it for free). --approved --body makes no AI call and needs none.")
|
|
793
|
+
.option("--json", "Print a JSON envelope.")
|
|
794
|
+
.action(async (options) => {
|
|
795
|
+
await handleAsyncAction("knowledge synthesize", options, () => requestOxygen("/api/cli/knowledge/synthesize", {
|
|
796
|
+
method: "POST",
|
|
797
|
+
body: {
|
|
798
|
+
kind: options.kind ?? "campaign_learnings",
|
|
799
|
+
...(readOption(options.sequence) ? { sequence: readOption(options.sequence) } : {}),
|
|
800
|
+
...(options.approved ? { approved: true } : {}),
|
|
801
|
+
...(readOption(options.body) ? { body: readOption(options.body) } : {}),
|
|
802
|
+
...(readOption(options.title) ? { title: readOption(options.title) } : {}),
|
|
803
|
+
...(readPositiveInt(options.limit) !== undefined ? { limit: readPositiveInt(options.limit) } : {}),
|
|
804
|
+
...(readNonNegativeNumber(options.maxCredits) !== undefined ? { max_credits: readNonNegativeNumber(options.maxCredits) } : {}),
|
|
805
|
+
},
|
|
806
|
+
}));
|
|
807
|
+
}))
|
|
808
|
+
.addCommand(new Command("agent")
|
|
809
|
+
.description("The scheduled knowledge synthesis agent: unattended distillation of auto-filed sources into learnings pages (default off, spend-capped). Canonical changes still go through the proposal queue.")
|
|
810
|
+
.addCommand(new Command("get")
|
|
811
|
+
.description("Show the synthesis-agent config (enabled, cadence, per-day credit cap).")
|
|
812
|
+
.option("--json", "Print a JSON envelope.")
|
|
813
|
+
.action(async (options) => {
|
|
814
|
+
await handleAsyncAction("knowledge agent get", options, () => requestOxygen("/api/cli/knowledge/agent"));
|
|
815
|
+
}))
|
|
816
|
+
.addCommand(new Command("set")
|
|
817
|
+
.description("Configure the synthesis agent. Enabling lets the background worker distill new sources on the cadence; it only writes working learnings pages.")
|
|
818
|
+
.option("--enabled", "Enable unattended synthesis.")
|
|
819
|
+
.option("--disabled", "Disable unattended synthesis.")
|
|
820
|
+
.option("--cadence-hours <n>", "Hours between synthesis cycles (1-168, default 24).")
|
|
821
|
+
.option("--max-credits-per-day <n>", "Managed-credit cap per UTC day (0-10,000, default 100).")
|
|
822
|
+
.option("--json", "Print a JSON envelope.")
|
|
823
|
+
.action(async (options) => {
|
|
824
|
+
await handleAsyncAction("knowledge agent set", options, () => {
|
|
825
|
+
const body = {};
|
|
826
|
+
if (options.enabled)
|
|
827
|
+
body.enabled = true;
|
|
828
|
+
if (options.disabled)
|
|
829
|
+
body.enabled = false;
|
|
830
|
+
// Presence decides inclusion so an absent flag keeps the stored value.
|
|
831
|
+
if (options.cadenceHours !== undefined)
|
|
832
|
+
body.cadence_hours = Number(options.cadenceHours);
|
|
833
|
+
if (options.maxCreditsPerDay !== undefined)
|
|
834
|
+
body.max_credits_per_day = Number(options.maxCreditsPerDay);
|
|
835
|
+
return requestOxygen("/api/cli/knowledge/agent", { method: "POST", body });
|
|
836
|
+
});
|
|
837
|
+
})))
|
|
838
|
+
.addCommand(new Command("sync")
|
|
839
|
+
.description("Clone or refresh the local knowledge mirror: server-rendered page markdown under the CLI config dir. Pass --all to mirror every workspace the credential can access.")
|
|
840
|
+
.option("--all", "Sync every organization the credential can access, each into its own mirror; one org's failure never aborts the rest.")
|
|
841
|
+
.option("--full", "Purge the mirror (including its cursor) and re-clone every page.")
|
|
842
|
+
.option("--if-stale", "Skip when the mirror completed a sync within the TTL (or one is already running).")
|
|
843
|
+
.option("--ttl <minutes>", "Freshness window for --if-stale, in minutes. Defaults to 15.")
|
|
844
|
+
.option("--json", "Print a JSON envelope.")
|
|
845
|
+
.action(async (options) => {
|
|
846
|
+
await handleAsyncAction("knowledge sync", options, () => options.all ? runKnowledgeMirrorSyncAll(options) : runKnowledgeMirrorSync(options));
|
|
847
|
+
}))
|
|
848
|
+
.addCommand(new Command("push")
|
|
849
|
+
.description("Push locally edited or newly created mirror pages back to the workspace wiki. Edits to canonical voice/brand/positioning pages become approval proposals instead of writing live.")
|
|
850
|
+
.option("--dry-run", "List what would be pushed without writing anything.")
|
|
851
|
+
.option("--json", "Print a JSON envelope.")
|
|
852
|
+
.action(async (options) => {
|
|
853
|
+
await handleAsyncAction("knowledge push", options, () => runKnowledgeMirrorPush(options));
|
|
854
|
+
}))
|
|
855
|
+
.addCommand(new Command("status")
|
|
856
|
+
.description("Report the local knowledge mirror: path, page count, last sync, quarantined conflicts. Pass --all to report every locally mirrored workspace for the active API host.")
|
|
857
|
+
.option("--all", "Report every locally mirrored workspace for the active API host, with a per-mirror freshness flag.")
|
|
858
|
+
.option("--print-path", "Print only the mirror directory path.")
|
|
859
|
+
.option("--clear-conflicts", "Delete quarantined conflict copies from the mirror sidecar.")
|
|
860
|
+
.option("--json", "Print a JSON envelope.")
|
|
861
|
+
.action(async (options) => {
|
|
862
|
+
if (options.printPath) {
|
|
863
|
+
await handleKnowledgeMirrorPrintPathAction();
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
await handleAsyncAction("knowledge status", options, () => options.all ? runKnowledgeMirrorStatusAll() : runKnowledgeMirrorStatus(options));
|
|
867
|
+
}))
|
|
868
|
+
.addCommand(new Command("purge")
|
|
869
|
+
.description("Delete the whole local knowledge mirror for the active API host and organization.")
|
|
870
|
+
.option("--json", "Print a JSON envelope.")
|
|
871
|
+
.action(async (options) => {
|
|
872
|
+
await handleAsyncAction("knowledge purge", options, () => runKnowledgeMirrorPurge());
|
|
873
|
+
}));
|
|
874
|
+
}
|