@oxygen-agent/cli 1.1014.135 → 1.1014.618
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 +1669 -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 +3492 -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 +17 -14
- 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.js +5 -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,109 @@
|
|
|
1
|
+
import { parseJsonObject, readNonNegativeInt } from "../cli-values.js";
|
|
2
|
+
import { parseJsonArray } from "../command-support.js";
|
|
3
|
+
import { addSessionOutput, addSessionStatus, getSessionUsage, startSession, updateSessionStep } from "../session.js";
|
|
4
|
+
import { readOption } from "../util.js";
|
|
5
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
function parseStringArray(value) {
|
|
8
|
+
const parsed = parseJsonArray(value);
|
|
9
|
+
const labels = parsed
|
|
10
|
+
.map((entry) => typeof entry === "string" ? entry.trim() : "")
|
|
11
|
+
.filter(Boolean);
|
|
12
|
+
if (labels.length !== parsed.length || labels.length === 0) {
|
|
13
|
+
throw new OxygenError("invalid_session", "Session steps must be a non-empty JSON array of strings.", {
|
|
14
|
+
exitCode: 1,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return labels;
|
|
18
|
+
}
|
|
19
|
+
function normalizeSessionStepStatus(value) {
|
|
20
|
+
const normalized = value?.trim().toLowerCase();
|
|
21
|
+
if (normalized === "pending"
|
|
22
|
+
|| normalized === "running"
|
|
23
|
+
|| normalized === "completed"
|
|
24
|
+
|| normalized === "error"
|
|
25
|
+
|| normalized === "skipped") {
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
throw new OxygenError("invalid_session_status", "Session status must be pending, running, completed, error, or skipped.", {
|
|
29
|
+
details: { status: value },
|
|
30
|
+
exitCode: 1,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export function registerSessionFamily(program, handleAsyncAction) {
|
|
34
|
+
program
|
|
35
|
+
.command("session")
|
|
36
|
+
.description("Local agent session plan and output commands.")
|
|
37
|
+
.addCommand(new Command("start")
|
|
38
|
+
.description("Start a local Oxygen agent session or update a step.")
|
|
39
|
+
.option("--steps <json>", "JSON array of step labels.")
|
|
40
|
+
.option("--user-prompt <text>", "Original user request for local session context.")
|
|
41
|
+
.option("--update <index>", "Update a step index instead of replacing the plan.")
|
|
42
|
+
.option("--status <status>", "Step status: pending, running, completed, error, or skipped.")
|
|
43
|
+
.option("--session-id <id>", "Session id to update. Defaults to the current session.")
|
|
44
|
+
.option("--json", "Print a JSON envelope.")
|
|
45
|
+
.action(async (options) => {
|
|
46
|
+
// skipcq: JS-0116 — async Promise-wraps the synchronous session helpers to satisfy handleAsyncAction's Promise<unknown> action
|
|
47
|
+
await handleAsyncAction("session start", options, async () => {
|
|
48
|
+
const updateIndex = readNonNegativeInt(options.update);
|
|
49
|
+
if (updateIndex !== undefined) {
|
|
50
|
+
return updateSessionStep({
|
|
51
|
+
sessionId: readOption(options.sessionId),
|
|
52
|
+
index: updateIndex,
|
|
53
|
+
status: normalizeSessionStepStatus(options.status),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (!options.steps) {
|
|
57
|
+
throw new OxygenError("invalid_session", "Pass --steps as a JSON array of labels.", {
|
|
58
|
+
exitCode: 1,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return startSession({
|
|
62
|
+
steps: parseStringArray(options.steps),
|
|
63
|
+
userPrompt: readOption(options.userPrompt),
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}))
|
|
67
|
+
.addCommand(new Command("status")
|
|
68
|
+
.description("Attach a live status message to the current local session.")
|
|
69
|
+
.requiredOption("--message <message>", "Status message.")
|
|
70
|
+
.option("--step-index <index>", "Optional step index. Defaults to the running step.")
|
|
71
|
+
.option("--session-id <id>", "Session id. Defaults to the current session.")
|
|
72
|
+
.option("--json", "Print a JSON envelope.")
|
|
73
|
+
.action(async (options) => {
|
|
74
|
+
// skipcq: JS-0116 — async Promise-wraps the synchronous addSessionStatus helper to satisfy handleAsyncAction's Promise<unknown> action
|
|
75
|
+
await handleAsyncAction("session status", options, async () => addSessionStatus({
|
|
76
|
+
sessionId: readOption(options.sessionId),
|
|
77
|
+
message: options.message,
|
|
78
|
+
stepIndex: readNonNegativeInt(options.stepIndex) ?? null,
|
|
79
|
+
}));
|
|
80
|
+
}))
|
|
81
|
+
.addCommand(new Command("output")
|
|
82
|
+
.description("Register a local session output file or table.")
|
|
83
|
+
.option("--csv <path>", "CSV output path.")
|
|
84
|
+
.option("--file <path>", "Generic output file path.")
|
|
85
|
+
.option("--table <table>", "Workspace table id or slug.")
|
|
86
|
+
.option("--label <label>", "Human-readable output label.")
|
|
87
|
+
.option("--meta-json <json>", "Optional JSON metadata object.")
|
|
88
|
+
.option("--session-id <id>", "Session id. Defaults to the current session.")
|
|
89
|
+
.option("--json", "Print a JSON envelope.")
|
|
90
|
+
.action(async (options) => {
|
|
91
|
+
// skipcq: JS-0116 — async Promise-wraps the synchronous addSessionOutput helper to satisfy handleAsyncAction's Promise<unknown> action
|
|
92
|
+
await handleAsyncAction("session output", options, async () => addSessionOutput({
|
|
93
|
+
sessionId: readOption(options.sessionId),
|
|
94
|
+
csv: readOption(options.csv),
|
|
95
|
+
file: readOption(options.file),
|
|
96
|
+
table: readOption(options.table),
|
|
97
|
+
label: readOption(options.label),
|
|
98
|
+
meta: options.metaJson ? parseJsonObject(options.metaJson) : null,
|
|
99
|
+
}));
|
|
100
|
+
}))
|
|
101
|
+
.addCommand(new Command("usage")
|
|
102
|
+
.description("Summarize the current local session. Org credit usage is available through billing usage.")
|
|
103
|
+
.option("--session-id <id>", "Session id. Defaults to the current session.")
|
|
104
|
+
.option("--json", "Print a JSON envelope.")
|
|
105
|
+
.action(async (options) => {
|
|
106
|
+
// skipcq: JS-0116 — async Promise-wraps the synchronous getSessionUsage helper to satisfy handleAsyncAction's Promise<unknown> action
|
|
107
|
+
await handleAsyncAction("session usage", options, async () => getSessionUsage({ sessionId: readOption(options.sessionId) }));
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { parseJsonObject, readPositiveInt } from "../cli-values.js";
|
|
2
|
+
import { CADENCE_FLAG_DESCRIPTION, buildCrmLeadsTodayPath, buildCrmSignalRecordBody, readCsvOption, readFileIfPresent, readPositiveNumber, readSearchPlanJson } from "../command-support.js";
|
|
3
|
+
import { requestOxygen } from "../http-client.js";
|
|
4
|
+
import { readOption } from "../util.js";
|
|
5
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
function buildSignalsListPath(options) {
|
|
8
|
+
const params = new URLSearchParams();
|
|
9
|
+
if (options.types)
|
|
10
|
+
params.set("types", options.types);
|
|
11
|
+
if (options.since)
|
|
12
|
+
params.set("since", options.since);
|
|
13
|
+
if (options.limit)
|
|
14
|
+
params.set("limit", options.limit);
|
|
15
|
+
const query = params.toString();
|
|
16
|
+
return query ? `/api/cli/signals?${query}` : "/api/cli/signals";
|
|
17
|
+
}
|
|
18
|
+
function readSignalsSearchPlanBody(options, promptArg) {
|
|
19
|
+
// The prompt may arrive as --prompt or as the positional argument; the flag wins.
|
|
20
|
+
const promptSource = options.prompt ?? promptArg;
|
|
21
|
+
const targetCount = readPositiveInt(options.targetCount);
|
|
22
|
+
const filters = readSignalSearchFilters(options);
|
|
23
|
+
return {
|
|
24
|
+
// Omit when neither was given so the request still reaches the server, which
|
|
25
|
+
// answers with a clean `prompt is required.` instead of crashing readFileIfPresent.
|
|
26
|
+
...(promptSource !== undefined ? { prompt: readFileIfPresent(promptSource) } : {}),
|
|
27
|
+
family: options.family,
|
|
28
|
+
...(readOption(options.scope) ? { scope: readOption(options.scope) } : {}),
|
|
29
|
+
...(targetCount !== undefined ? { target_count: targetCount } : {}),
|
|
30
|
+
...(filters ? { filters } : {}),
|
|
31
|
+
...(options.estimate ? { estimate: true } : {}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function readSignalsSearchRunBody(options, promptArg) {
|
|
35
|
+
// The prompt may arrive as --prompt or as the positional argument; the flag wins.
|
|
36
|
+
const promptSource = options.prompt ?? promptArg;
|
|
37
|
+
const prompt = promptSource ? readFileIfPresent(promptSource) : null;
|
|
38
|
+
const plan = options.planJson ? readSearchPlanJson(options.planJson) : null;
|
|
39
|
+
// A post search needs no prompt: its --keywords and filters are the plan,
|
|
40
|
+
// as they are for the free preview (the server names the table after them).
|
|
41
|
+
const postSearchFilters = !prompt && !plan && (readOption(options.source) ?? readOption(options.family)) === "linkedin_posts"
|
|
42
|
+
? readSignalSearchFilters(options)
|
|
43
|
+
: null;
|
|
44
|
+
const postSearch = Array.isArray(postSearchFilters?.keywords) && postSearchFilters.keywords.length > 0;
|
|
45
|
+
if (!prompt && !plan && !postSearch) {
|
|
46
|
+
throw new OxygenError("invalid_request", "Pass --prompt or --plan-json (linkedin_posts needs only --keywords).", { exitCode: 1 });
|
|
47
|
+
}
|
|
48
|
+
if (prompt && !readOption(options.family)) {
|
|
49
|
+
throw new OxygenError("invalid_request", "Pass --family when planning from --prompt.", { exitCode: 1 });
|
|
50
|
+
}
|
|
51
|
+
const maxPages = readPositiveInt(options.maxPages);
|
|
52
|
+
const maxCredits = readPositiveNumber(options.maxCredits);
|
|
53
|
+
const targetCount = readPositiveInt(options.targetCount);
|
|
54
|
+
const maxCreditsPerCycle = readPositiveNumber(options.maxCreditsPerCycle);
|
|
55
|
+
const maxRowsPerCycle = readPositiveInt(options.maxRowsPerCycle);
|
|
56
|
+
// Filters describe a prompt that is being (re)planned. A saved plan already
|
|
57
|
+
// froze its own, so they are never re-sent with one; typed alongside a saved
|
|
58
|
+
// plan they are refused, because dropping them would run live unfiltered. A
|
|
59
|
+
// prompt-less linkedin_posts search (no plan) is planned from its filters.
|
|
60
|
+
const typedFilters = readSignalSearchFilters(options);
|
|
61
|
+
if (plan && !prompt && typedFilters) {
|
|
62
|
+
throw new OxygenError("invalid_request", `A saved plan keeps the filters it was planned with, so ${Object.keys(typedFilters).join(", ")} cannot be added to --plan-json. `
|
|
63
|
+
+ "Re-plan with oxygen signals search plan --prompt <goal> --family <family> and the filter flags, or pass them to signals search run --prompt.", { exitCode: 1 });
|
|
64
|
+
}
|
|
65
|
+
const filters = prompt ? typedFilters : postSearchFilters;
|
|
66
|
+
return {
|
|
67
|
+
...(readOption(options.source) ? { source: readOption(options.source) } : {}),
|
|
68
|
+
...(prompt ? { prompt } : {}),
|
|
69
|
+
...(plan ? { plan } : {}),
|
|
70
|
+
...(readOption(options.family) ? { family: readOption(options.family) } : {}),
|
|
71
|
+
...(readOption(options.scope) ? { scope: readOption(options.scope) } : {}),
|
|
72
|
+
...(readOption(options.routeId) ? { route_id: readOption(options.routeId) } : {}),
|
|
73
|
+
...(readOption(options.toolId) ? { tool_id: readOption(options.toolId) } : {}),
|
|
74
|
+
...(readOption(options.table) ? { table: readOption(options.table) } : {}),
|
|
75
|
+
...(readOption(options.project) ? { project: readOption(options.project) } : {}),
|
|
76
|
+
...(readOption(options.upsertKey) ? { upsert_key: readOption(options.upsertKey) } : {}),
|
|
77
|
+
...(readOption(options.mode) ? { mode: readOption(options.mode) } : {}),
|
|
78
|
+
...(maxPages !== undefined ? { max_pages: maxPages } : {}),
|
|
79
|
+
...(maxCredits !== undefined ? { max_credits: maxCredits } : {}),
|
|
80
|
+
...(targetCount !== undefined ? { target_count: targetCount } : {}),
|
|
81
|
+
...(filters ? { filters } : {}),
|
|
82
|
+
...(options.approved ? { approved: true } : {}),
|
|
83
|
+
...(options.bindFeed ? { bind_feed: true } : {}),
|
|
84
|
+
...(readOption(options.every) ? { every: readOption(options.every) } : {}),
|
|
85
|
+
...(maxCreditsPerCycle !== undefined ? { max_credits_per_cycle: maxCreditsPerCycle } : {}),
|
|
86
|
+
...(maxRowsPerCycle !== undefined ? { max_rows_per_cycle: maxRowsPerCycle } : {}),
|
|
87
|
+
...(readOption(options.credentialMode) ? { credential_mode: readOption(options.credentialMode) } : {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// SignalSearchFilters mirror (structural — the server validates every field and
|
|
91
|
+
// rejects unknown values by name). --filters-json wins per top-level path so an
|
|
92
|
+
// agent can pass a precise object while still using flags for the rest.
|
|
93
|
+
function readSignalSearchFilters(options) {
|
|
94
|
+
const filters = {};
|
|
95
|
+
const keywords = readCsvOption(options.keywords);
|
|
96
|
+
if (keywords.length > 0)
|
|
97
|
+
filters.keywords = keywords;
|
|
98
|
+
const allKeywords = readCsvOption(options.allKeywords);
|
|
99
|
+
if (allKeywords.length > 0)
|
|
100
|
+
filters.all_keywords = allKeywords;
|
|
101
|
+
const excludeKeywords = readCsvOption(options.excludeKeywords);
|
|
102
|
+
if (excludeKeywords.length > 0)
|
|
103
|
+
filters.exclude_keywords = excludeKeywords;
|
|
104
|
+
const sort = readOption(options.sort);
|
|
105
|
+
if (sort)
|
|
106
|
+
filters.sort_by = sort;
|
|
107
|
+
const minReactions = readOption(options.minReactions);
|
|
108
|
+
if (minReactions)
|
|
109
|
+
filters.min_reactions = minReactions;
|
|
110
|
+
const postedBy = readOption(options.postedBy);
|
|
111
|
+
if (postedBy)
|
|
112
|
+
filters.posted_by = postedBy;
|
|
113
|
+
const aiFilter = readOption(options.aiFilter);
|
|
114
|
+
if (aiFilter)
|
|
115
|
+
filters.ai_filter = aiFilter;
|
|
116
|
+
const countries = readCsvOption(options.countries);
|
|
117
|
+
if (countries.length > 0)
|
|
118
|
+
filters.countries = countries;
|
|
119
|
+
const location = readOption(options.location);
|
|
120
|
+
if (location)
|
|
121
|
+
filters.location = location;
|
|
122
|
+
const lastDays = readPositiveInt(options.lastDays);
|
|
123
|
+
if (lastDays !== undefined)
|
|
124
|
+
filters.last_days = lastDays;
|
|
125
|
+
const domains = readCsvOption(options.domains);
|
|
126
|
+
if (domains.length > 0)
|
|
127
|
+
filters.domains = domains;
|
|
128
|
+
const fundingTypes = readCsvOption(options.fundingTypes);
|
|
129
|
+
if (fundingTypes.length > 0)
|
|
130
|
+
filters.funding_types = fundingTypes;
|
|
131
|
+
const categories = readCsvOption(options.categories);
|
|
132
|
+
if (categories.length > 0)
|
|
133
|
+
filters.categories = categories;
|
|
134
|
+
const explicit = options.filtersJson ? parseJsonObject(readFileIfPresent(options.filtersJson)) : null;
|
|
135
|
+
if (explicit) {
|
|
136
|
+
for (const [key, value] of Object.entries(explicit)) {
|
|
137
|
+
filters[key] = value;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return Object.keys(filters).length > 0 ? filters : null;
|
|
141
|
+
}
|
|
142
|
+
export function registerSignalsFamily(program, handleAsyncAction) {
|
|
143
|
+
program
|
|
144
|
+
.command("signals")
|
|
145
|
+
.description("The Signals primitive: record intent events, read the event feed and type registry, and rank leads with signals leads-today. Tag a registered type with `oxygen tags attach signal_type <type> --tags <labels>`.")
|
|
146
|
+
.addCommand(new Command("list")
|
|
147
|
+
.description("List the raw GTM signal-event feed newest-first (website_visit / profile_view / post_reaction / new_follower), each resolved onto the person it attached to. Read-only.")
|
|
148
|
+
.option("--types <types>", "Comma-separated signal types to include. Defaults to all four.")
|
|
149
|
+
.option("--since <days>", "Look-back window in days. Defaults to 7; your plan's maximum (30 on free, up to 365) is in `oxygen limits show`.")
|
|
150
|
+
.option("--limit <limit>", "Maximum events to return. Defaults to 25; your plan's maximum (100 on free, up to 1,000) is in `oxygen limits show`.")
|
|
151
|
+
.option("--json", "Print a JSON envelope.")
|
|
152
|
+
.action(async (options) => {
|
|
153
|
+
await handleAsyncAction("signals list", options, () => requestOxygen(buildSignalsListPath(options)));
|
|
154
|
+
}))
|
|
155
|
+
.addCommand(new Command("registry")
|
|
156
|
+
.description("Show registered signal types with intent weight, capture status, and workspace tags. Tag a type with `oxygen tags attach signal_type <type> --tags <labels>`. Read-only.")
|
|
157
|
+
.option("--tag <tag>", "Only signal types carrying this workspace tag; use tags attach to set it.")
|
|
158
|
+
.option("--json", "Print a JSON envelope.")
|
|
159
|
+
.action(async (options) => {
|
|
160
|
+
await handleAsyncAction("signals registry", options, () => requestOxygen(`/api/cli/signals/registry${options.tag ? `?tag=${encodeURIComponent(options.tag)}` : ""}`));
|
|
161
|
+
}))
|
|
162
|
+
.addCommand(new Command("record")
|
|
163
|
+
.description("Record one inbound GTM signal (website_visit, profile_view, post_engager, new_follower). Internal write — free, no approval. Idempotent on --external-event-id.")
|
|
164
|
+
.requiredOption("--event <event>", "Signal event: website_visit | profile_view | post_engager | new_follower.")
|
|
165
|
+
.option("--external-event-id <id>", "Stable provider event id (redelivery/idempotency guard). Derived from --session-id when omitted.")
|
|
166
|
+
.option("--email <email>", "Person email (identity).")
|
|
167
|
+
.option("--linkedin-url <url>", "Person LinkedIn URL (identity when no email).")
|
|
168
|
+
.option("--company-domain <domain>", "Company domain for the company assert.")
|
|
169
|
+
.option("--company-name <name>", "Company name.")
|
|
170
|
+
.option("--first-name <name>", "Person first name.")
|
|
171
|
+
.option("--last-name <name>", "Person last name.")
|
|
172
|
+
.option("--page-url <url>", "Page the signal fired on (website visit).")
|
|
173
|
+
.option("--session-id <id>", "Provider session id (provenance + idempotency).")
|
|
174
|
+
.option("--payload-json <json>", "JSON object merged into the signal payload (wins over the flags above).")
|
|
175
|
+
.option("--json", "Print a JSON envelope.")
|
|
176
|
+
.action(async (options) => {
|
|
177
|
+
await handleAsyncAction("signals record", options, () => requestOxygen("/api/cli/signals", {
|
|
178
|
+
method: "POST",
|
|
179
|
+
body: buildCrmSignalRecordBody(options),
|
|
180
|
+
}));
|
|
181
|
+
}))
|
|
182
|
+
.addCommand(new Command("leads-today")
|
|
183
|
+
.description("List people to contact today, ranked by their most recent GTM intent signal. When the output has rankedBy, equal signals are ordered by the company's ICP fit (see each lead's fit). Do-not-contact filtering is best-effort (email-only leads surface unchecked).")
|
|
184
|
+
.option("--limit <limit>", "Maximum leads to return. Defaults to 25, max 100.")
|
|
185
|
+
.option("--within-days <days>", "Signal look-back window in days. Defaults to 7, max 30.")
|
|
186
|
+
.option("--json", "Print a JSON envelope.")
|
|
187
|
+
.action(async (options) => {
|
|
188
|
+
await handleAsyncAction("signals leads-today", options, () => requestOxygen(buildCrmLeadsTodayPath(options)));
|
|
189
|
+
}))
|
|
190
|
+
.addCommand(new Command("search")
|
|
191
|
+
.description("Source EXTERNAL market signals into a table: hiring, technology adoption, funding, acquisitions, news, and public LinkedIn posts by keyword (linkedin_posts). This is the sourcing half of Signals — `signals list` reads the events already captured for your workspace.")
|
|
192
|
+
.addCommand(new Command("plan")
|
|
193
|
+
.description("Compile a signal-sourcing request into ordered provider routes without provider calls: the chain, per-route applied/dropped filters, credit estimate, table blueprint, and whether the route can be kept LIVE on a cadence. Free.")
|
|
194
|
+
.argument("[prompt]", "Prompt text or @file (same as --prompt). The --prompt flag wins if both are given.")
|
|
195
|
+
.option("--prompt <text-or-file>", "Signal-sourcing prompt, or a path to a prompt file.")
|
|
196
|
+
.requiredOption("--family <family>", "Signal family: hiring, tech, funding, acquisition, news, job_change, or linkedin_posts (public LinkedIn posts matching keywords).")
|
|
197
|
+
.option("--scope <scope>", "market (discover new companies) or watch_list (track companies you name via --domains). Defaults per family.")
|
|
198
|
+
.option("--target-count <n>", "Desired row count for routing and estimates. linkedin_posts: posts fetched per keyword before the AI filter (default 100), which also sets the Why it matches credit ceiling (1.5 per fetched post).")
|
|
199
|
+
.option("--keywords <csv>", "Comma-separated keywords, e.g. job titles for hiring or technology names for tech. For linkedin_posts: a post must mention AT LEAST ONE of these (each is its own search, at most 5).")
|
|
200
|
+
.option("--all-keywords <csv>", "linkedin_posts: a post must mention ALL of these.")
|
|
201
|
+
.option("--exclude-keywords <csv>", "linkedin_posts: drop posts that mention any of these, e.g. hiring.")
|
|
202
|
+
.option("--sort <order>", "linkedin_posts: recent (latest first), relevance (top match) or top (most reactions first).")
|
|
203
|
+
.option("--min-reactions <n>", "linkedin_posts: keep only posts with at least this many reactions.")
|
|
204
|
+
.option("--posted-by <url>", "linkedin_posts: only posts by this LinkedIn profile or company page URL.")
|
|
205
|
+
.option("--ai-filter <text>", "linkedin_posts: keep only posts an AI classifier judges to match this description (at most 500 characters). Free, unless the plan returns an ai_filter block: that block names the family it covers and its credits_per_item.")
|
|
206
|
+
.option("--countries <csv>", "Comma-separated ISO 3166-1 alpha-2 country codes.")
|
|
207
|
+
.option("--location <text>", "Country or state NAME for providers that filter on a location string.")
|
|
208
|
+
.option("--last-days <n>", "Only signals from the last N days (1-3650).")
|
|
209
|
+
.option("--domains <csv>", "Comma-separated company domains. Required for watch_list scope.")
|
|
210
|
+
.option("--funding-types <csv>", "Comma-separated funding types for the funding family.")
|
|
211
|
+
.option("--categories <csv>", "Comma-separated provider event categories.")
|
|
212
|
+
.option("--filters-json <json-or-file>", "Filters JSON inline or a path to a JSON file; wins over individual flags per top-level filter path.")
|
|
213
|
+
.option("--estimate", "Run the free server-side preflight pass: resolves provider enum values and, where a provider publishes one, a free match count (zero credits).")
|
|
214
|
+
.option("--json", "Print a JSON envelope.")
|
|
215
|
+
.action(async (promptArg, options) => {
|
|
216
|
+
await handleAsyncAction("signals search plan", options, () => requestOxygen("/api/cli/signals/search/plan", {
|
|
217
|
+
method: "POST",
|
|
218
|
+
body: readSignalsSearchPlanBody(options, promptArg),
|
|
219
|
+
}));
|
|
220
|
+
}))
|
|
221
|
+
.addCommand(new Command("preview")
|
|
222
|
+
.description("Preview public LinkedIn posts for free on Oxygen's Managed rail (family linkedin_posts): up to 25 per keyword, with author, engagement and the --ai-filter applied. Nothing is created or charged. Each --keywords entry is one search against a small hourly search budget shared across OXYGEN. After repricing, own-key previews are paused because they would make billable Up2Data calls without a credit approval; use Managed for the preview or quote a direct scraper tool run. Then signals search run --family linkedin_posts with the same flags creates the table.")
|
|
223
|
+
.option("--family <family>", "linkedin_posts, the only family with a free preview.", "linkedin_posts")
|
|
224
|
+
.option("--keywords <csv>", "A post must mention AT LEAST ONE of these (each is its own search, at most 5).")
|
|
225
|
+
.option("--all-keywords <csv>", "A post must mention ALL of these.")
|
|
226
|
+
.option("--exclude-keywords <csv>", "Drop posts that mention any of these, e.g. hiring.")
|
|
227
|
+
.option("--last-days <n>", "Only posts from the last N days: 1 = 24 hours, up to 7 = week, up to 30 = month.")
|
|
228
|
+
.option("--sort <order>", "recent (latest first), relevance (top match) or top (most reactions first).")
|
|
229
|
+
.option("--min-reactions <n>", "Keep only posts with at least this many reactions.")
|
|
230
|
+
.option("--posted-by <url>", "Only posts by this LinkedIn profile or company page URL.")
|
|
231
|
+
.option("--ai-filter <text>", "Keep only posts an AI classifier judges to match this description (at most 500 characters). Free, and each kept post shows why it matches (ai_filter_reason).")
|
|
232
|
+
.option("--filters-json <json-or-file>", "Filters JSON inline or a path to a JSON file; wins over individual flags per top-level filter path.")
|
|
233
|
+
.option("--credential-mode <mode>", "managed (default): Oxygen's key. user_api_key: your own Up2Data key saved in Connections, which spends your Up2Data searches instead of the shared hourly budget.")
|
|
234
|
+
.option("--json", "Print a JSON envelope.")
|
|
235
|
+
.action(async (options) => {
|
|
236
|
+
await handleAsyncAction("signals search preview", options, () => requestOxygen("/api/cli/signals/search/preview", {
|
|
237
|
+
method: "POST",
|
|
238
|
+
body: {
|
|
239
|
+
family: options.family,
|
|
240
|
+
filters: readSignalSearchFilters(options) ?? {},
|
|
241
|
+
...(readOption(options.credentialMode) ? { credential_mode: readOption(options.credentialMode) } : {}),
|
|
242
|
+
},
|
|
243
|
+
}));
|
|
244
|
+
}))
|
|
245
|
+
.addCommand(new Command("run")
|
|
246
|
+
.description("Return a dry-run request or queue a live signal-search ingestion run. Live requires --approved and --max-credits (linkedin_posts is free, --approved only, unless --ai-filter is set: its Why it matches reasons cost credits per kept post, quoted by the dry run, or none on the workspace's own OpenRouter key, and a dry-run ai_filter quote prices the filter itself). Add --bind-feed --every to also bind a pull feed to the same table in the same call, so the table keeps refilling on a cadence.")
|
|
247
|
+
.argument("[prompt]", "Prompt text or @file (same as --prompt); requires --family. The --prompt flag wins if both are given.")
|
|
248
|
+
.option("--prompt <text-or-file>", "Signal-sourcing prompt, or a path to a prompt file. Requires --family.")
|
|
249
|
+
.option("--plan-json <json-or-file>", "Plan JSON returned by signals search plan, or a path to a JSON file.")
|
|
250
|
+
.option("--source <linkedin_posts>", "Run the native post-search lane using --filters-json or --keywords; no prompt needed.")
|
|
251
|
+
.option("--family <family>", "Signal family when planning from --prompt: hiring, tech, funding, acquisition, news, job_change, or linkedin_posts.")
|
|
252
|
+
.option("--scope <scope>", "market or watch_list when planning from --prompt.")
|
|
253
|
+
.option("--route-id <id>", "Route id from the plan to execute.")
|
|
254
|
+
.option("--tool-id <tool>", "Tool id from the plan to execute.")
|
|
255
|
+
.option("--table <table>", "Existing table id or slug to receive rows. If omitted for live, Oxygen creates one from the plan blueprint.")
|
|
256
|
+
.option("--project <project>", "Folder (project) id or slug the created table lands in. Ignored with --table. Defaults to the workspace default folder.")
|
|
257
|
+
.option("--upsert-key <key>", "Column key used for live upsert. Defaults to the plan upsert key.")
|
|
258
|
+
.option("--mode <mode>", "dry_run or live. Defaults to dry_run.")
|
|
259
|
+
.option("--max-pages <n>", "Maximum provider pages to ingest. Defaults to 1.")
|
|
260
|
+
.option("--max-credits <n>", "Credit ceiling for live runs. linkedin_posts needs it only when the dry run quotes credits for the --ai-filter reasons or the filter itself.")
|
|
261
|
+
.option("--target-count <n>", "Desired row count when planning. linkedin_posts: posts fetched per keyword before the AI filter (default 100); with --ai-filter the Why it matches ceiling is 1.5 credits per fetched post, but only kept posts are charged.")
|
|
262
|
+
.option("--keywords <csv>", "Comma-separated keywords when planning from --prompt. For linkedin_posts: a post must mention at least one.")
|
|
263
|
+
.option("--all-keywords <csv>", "linkedin_posts: a post must mention ALL of these, when planning from --prompt.")
|
|
264
|
+
.option("--exclude-keywords <csv>", "linkedin_posts: drop posts that mention any of these, when planning from --prompt.")
|
|
265
|
+
.option("--sort <order>", "linkedin_posts: recent, relevance or top, when planning from --prompt.")
|
|
266
|
+
.option("--min-reactions <n>", "linkedin_posts: at least this many reactions, when planning from --prompt.")
|
|
267
|
+
.option("--posted-by <url>", "linkedin_posts: only posts by this profile or company page, when planning from --prompt.")
|
|
268
|
+
.option("--ai-filter <text>", "linkedin_posts: keep only posts an AI classifier judges to match this description (at most 500 characters), when planning from --prompt. Other families only where the plan returns an ai_filter block.")
|
|
269
|
+
.option("--countries <csv>", "Comma-separated ISO 3166-1 alpha-2 country codes when planning from --prompt.")
|
|
270
|
+
.option("--location <text>", "Country or state NAME when planning from --prompt.")
|
|
271
|
+
.option("--last-days <n>", "Only signals from the last N days when planning from --prompt.")
|
|
272
|
+
.option("--domains <csv>", "Comma-separated company domains when planning from --prompt.")
|
|
273
|
+
.option("--funding-types <csv>", "Comma-separated funding types when planning from --prompt.")
|
|
274
|
+
.option("--categories <csv>", "Comma-separated provider event categories when planning from --prompt.")
|
|
275
|
+
.option("--filters-json <json-or-file>", "Filters JSON inline or a path to a JSON file when planning from --prompt.")
|
|
276
|
+
.option("--approved", "Required for live runs after inspecting dry-run output.")
|
|
277
|
+
.option("--bind-feed", "Also bind a pull feed to the target table so it keeps refilling. Requires --every; refused when the route cannot pull incrementally.")
|
|
278
|
+
.option("--every <sugar>", CADENCE_FLAG_DESCRIPTION)
|
|
279
|
+
.option("--max-credits-per-cycle <n>", "Credit ceiling PER sync cycle for the bound feed.")
|
|
280
|
+
.option("--max-rows-per-cycle <n>", "Advisory row ceiling per sync cycle for the bound feed.")
|
|
281
|
+
.option("--credential-mode <mode>", "linkedin_posts: managed (default) or user_api_key to search on your own Up2Data key saved in Connections, billed by Up2Data. The table's bound feed keeps using it.")
|
|
282
|
+
.option("--json", "Print a JSON envelope.")
|
|
283
|
+
.action(async (promptArg, options) => {
|
|
284
|
+
await handleAsyncAction("signals search run", options, () => requestOxygen("/api/cli/signals/search/run", {
|
|
285
|
+
method: "POST",
|
|
286
|
+
body: readSignalsSearchRunBody(options, promptArg),
|
|
287
|
+
}));
|
|
288
|
+
})));
|
|
289
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readPositiveInt } from "../cli-values.js";
|
|
2
|
+
import { requestOxygen } from "../http-client.js";
|
|
3
|
+
import { readOption } from "../util.js";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
export function registerSignupLeadsFamily(program, handleAsyncAction) {
|
|
6
|
+
program
|
|
7
|
+
.command("signup-leads")
|
|
8
|
+
.description("Signup lead delivery queue commands.")
|
|
9
|
+
.addCommand(new Command("deliveries")
|
|
10
|
+
.description("Inspect and retry durable signup lead webhook deliveries.")
|
|
11
|
+
.addCommand(new Command("list")
|
|
12
|
+
.description("List signup lead webhook deliveries for the current organization.")
|
|
13
|
+
.option("--status <status>", "Filter by pending, running, succeeded, skipped, retrying, failed, or dead.")
|
|
14
|
+
.option("--limit <n>", "Maximum deliveries to return. Defaults to 50.")
|
|
15
|
+
.option("--json", "Print a JSON envelope.")
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
await handleAsyncAction("signup-leads deliveries list", options, () => {
|
|
18
|
+
const params = new URLSearchParams();
|
|
19
|
+
const status = readOption(options.status);
|
|
20
|
+
const limit = readPositiveInt(options.limit);
|
|
21
|
+
if (status)
|
|
22
|
+
params.set("status", status);
|
|
23
|
+
if (limit)
|
|
24
|
+
params.set("limit", String(limit));
|
|
25
|
+
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
26
|
+
return requestOxygen(`/api/cli/signup-leads/deliveries${suffix}`);
|
|
27
|
+
});
|
|
28
|
+
}))
|
|
29
|
+
.addCommand(new Command("get")
|
|
30
|
+
.description("Get one signup lead webhook delivery by delivery id or event id.")
|
|
31
|
+
.argument("<id>", "Delivery id or event id.")
|
|
32
|
+
.option("--json", "Print a JSON envelope.")
|
|
33
|
+
.action(async (id, options) => {
|
|
34
|
+
await handleAsyncAction("signup-leads deliveries get", options, () => requestOxygen(`/api/cli/signup-leads/deliveries?id=${encodeURIComponent(id)}`));
|
|
35
|
+
}))
|
|
36
|
+
.addCommand(new Command("retry")
|
|
37
|
+
.description("Retry one failed signup lead webhook delivery.")
|
|
38
|
+
.argument("<id>", "Delivery id or event id.")
|
|
39
|
+
.option("--json", "Print a JSON envelope.")
|
|
40
|
+
.action(async (id, options) => {
|
|
41
|
+
await handleAsyncAction("signup-leads deliveries retry", options, () => requestOxygen("/api/cli/signup-leads/deliveries", {
|
|
42
|
+
method: "POST",
|
|
43
|
+
body: { id },
|
|
44
|
+
}));
|
|
45
|
+
})));
|
|
46
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { doctorAgentSkills, getAgentSkill, installAgentSkills, listAgentSkills, searchAgentSkills } from "../skills.js";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
export function registerSkillsFamily(program, handleAsyncAction) {
|
|
4
|
+
program
|
|
5
|
+
.command("skills")
|
|
6
|
+
.description("Find and install Oxygen guides, or create and import private workspace skills with skills workspace.")
|
|
7
|
+
.addCommand(new Command("list")
|
|
8
|
+
.description("List Oxygen agent skills available from the hosted skill index.")
|
|
9
|
+
.option("--api-url <url>", "Oxygen app URL. Defaults to OXYGEN_API_URL or https://oxygen-agent.com.")
|
|
10
|
+
.option("--json", "Print a JSON envelope.")
|
|
11
|
+
.action(async (options) => {
|
|
12
|
+
await handleAsyncAction("skills list", options, () => listAgentSkills(options));
|
|
13
|
+
}))
|
|
14
|
+
.addCommand(new Command("search")
|
|
15
|
+
.description("Find a bounded set of Oxygen skills and matching documents from an outcome.")
|
|
16
|
+
.argument("<query...>", "Outcome, primitive, provider, or operating task.")
|
|
17
|
+
.option("--limit <n>", "Maximum results (default 5, max 10).")
|
|
18
|
+
.option("--api-url <url>", "Oxygen app URL. Defaults to OXYGEN_API_URL or https://oxygen-agent.com.")
|
|
19
|
+
.option("--json", "Print a JSON envelope.")
|
|
20
|
+
.action(async (queryParts, options) => {
|
|
21
|
+
await handleAsyncAction("skills search", options, () => searchAgentSkills(queryParts.join(" "), options));
|
|
22
|
+
}))
|
|
23
|
+
.addCommand(new Command("get")
|
|
24
|
+
.description("Hydrate one exact Oxygen skill entrypoint and its document index.")
|
|
25
|
+
.argument("<skill-name>", "Exact name returned by skills search.")
|
|
26
|
+
.option("--file <path>", "Read one manifest-listed supporting file relative to the selected skill.")
|
|
27
|
+
.option("--api-url <url>", "Oxygen app URL. Defaults to OXYGEN_API_URL or https://oxygen-agent.com.")
|
|
28
|
+
.option("--json", "Print a JSON envelope.")
|
|
29
|
+
.action(async (skillName, options) => {
|
|
30
|
+
await handleAsyncAction("skills get", options, () => getAgentSkill(skillName, options));
|
|
31
|
+
}))
|
|
32
|
+
.addCommand(new Command("doctor")
|
|
33
|
+
.description("Check Oxygen skill index reachability and local installer prerequisites.")
|
|
34
|
+
.option("--api-url <url>", "Oxygen app URL. Defaults to OXYGEN_API_URL or https://oxygen-agent.com.")
|
|
35
|
+
.option("--json", "Print a JSON envelope.")
|
|
36
|
+
.action(async (options) => {
|
|
37
|
+
await handleAsyncAction("skills doctor", options, () => doctorAgentSkills(options));
|
|
38
|
+
}))
|
|
39
|
+
.addCommand(new Command("install")
|
|
40
|
+
.description("Install Oxygen agent skills into local agent skill directories only; uses 0 credits and does not change the Oxygen workspace or call a provider. Also removes retired Oxygen skills it installed earlier.")
|
|
41
|
+
.option("--api-url <url>", "Oxygen app URL. Defaults to OXYGEN_API_URL or https://oxygen-agent.com.")
|
|
42
|
+
.option("--agents <agents...>", "Space or comma separated agents. Defaults to codex, claude-code, and cursor.")
|
|
43
|
+
.option("--skill <skill>", "Skill name or '*'. Defaults to '*'.")
|
|
44
|
+
.option("--project", "Install into the current project instead of global agent scope. A flag with no value: run it from the project directory; a path argument is rejected.")
|
|
45
|
+
.option("--copy", "Copy skill files instead of symlinking when supported by npx skills. Default on Windows, where symlinks need Developer Mode or admin.")
|
|
46
|
+
.option("--json", "Print a JSON envelope.")
|
|
47
|
+
.action(async (options) => {
|
|
48
|
+
await handleAsyncAction("skills install", options, () => installAgentSkills(options));
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { readFileIfPresent } from "../command-support.js";
|
|
2
|
+
import { requestOxygen } from "../http-client.js";
|
|
3
|
+
import { readOption } from "../util.js";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
export function registerSourcingFamily(program, handleAsyncAction) {
|
|
6
|
+
program
|
|
7
|
+
.command("sourcing")
|
|
8
|
+
.description("Plan account, people, profile, and enrichment-handoff sourcing workflows.")
|
|
9
|
+
.addCommand(new Command("plan")
|
|
10
|
+
.description("Classify a sourcing request and recommend provider routes without executing paid tools.")
|
|
11
|
+
.requiredOption("--prompt <file|text>", "Sourcing prompt text, or a local file path containing the prompt.")
|
|
12
|
+
.option("--strategy <id>", "Optional strategy id such as account_first_lead_sourcing or known_account_org_chart.")
|
|
13
|
+
.option("--table <table_id>", "Optional target table id or slug for follow-up commands.")
|
|
14
|
+
.option("--materialize-preview", "Create a preview table for the plan. Defaults to no table side effect.")
|
|
15
|
+
.option("--json", "Print a JSON envelope.")
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
await handleAsyncAction("sourcing plan", options, () => requestOxygen("/api/cli/sourcing/plan", {
|
|
18
|
+
method: "POST",
|
|
19
|
+
body: {
|
|
20
|
+
prompt: readFileIfPresent(options.prompt),
|
|
21
|
+
...(readOption(options.strategy) ? { strategy: readOption(options.strategy) } : {}),
|
|
22
|
+
...(readOption(options.table) ? { target_table_id: readOption(options.table) } : {}),
|
|
23
|
+
...(options.materializePreview ? { materialize_preview: true } : {}),
|
|
24
|
+
},
|
|
25
|
+
}));
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { defaultApiUrl, loadCredentials } from "../credentials.js";
|
|
2
|
+
import { requestOxygen } from "../http-client.js";
|
|
3
|
+
import { isVersionGreater, isVersionLess } from "@oxygen/shared/semver";
|
|
4
|
+
import { OXYGEN_VERSION } from "@oxygen/shared/version";
|
|
5
|
+
export function registerStatusFamily(program, handleAsyncAction) {
|
|
6
|
+
program
|
|
7
|
+
.command("status")
|
|
8
|
+
.description("Compare the local Oxygen CLI version against the active profile's deployed Oxygen API. Read-only, 0 credits. "
|
|
9
|
+
+ "`compatible` is the field that matters: false means commands are refused until the CLI updates. "
|
|
10
|
+
+ "`in_sync` and `skew` compare reported versions. A CLI may be ahead during a rollout; "
|
|
11
|
+
+ "if the difference persists, share the status SHA with support.")
|
|
12
|
+
.option("--json", "Print a JSON envelope.")
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
await handleAsyncAction("status", options, async () => {
|
|
15
|
+
// Resolve the API URL exactly like authenticated commands do: the
|
|
16
|
+
// active profile (honoring --profile / OXYGEN_PROFILE / OXYGEN_API_URL
|
|
17
|
+
// overrides), falling back to the default prod URL only when no
|
|
18
|
+
// credentials are stored. Without this, requestOxygen's
|
|
19
|
+
// requireAuth:false path skips loadCredentials() and always hits prod,
|
|
20
|
+
// so `status` reports a different deployment than every other command.
|
|
21
|
+
const credentials = await loadCredentials();
|
|
22
|
+
const apiUrl = credentials?.apiUrl ?? defaultApiUrl();
|
|
23
|
+
const server = await requestOxygen("/api/health", {
|
|
24
|
+
credentials: credentials ?? { token: "", apiUrl },
|
|
25
|
+
requireAuth: false,
|
|
26
|
+
enforceMinimumCliVersion: false,
|
|
27
|
+
});
|
|
28
|
+
// Explicit skew semantics: in_sync=false alone forced every ops sweep
|
|
29
|
+
// to hand-interpret whether a version gap was safe (fleet friction,
|
|
30
|
+
// recurring since 2026-06-18). `skew` names the direction and
|
|
31
|
+
// `compatible` answers the only question that matters: is this CLI at
|
|
32
|
+
// or above the server's enforced minimum?
|
|
33
|
+
const minimum = server.minimum_cli_version ?? null;
|
|
34
|
+
return {
|
|
35
|
+
client_version: OXYGEN_VERSION,
|
|
36
|
+
api_url: apiUrl,
|
|
37
|
+
server_version: server.server_version,
|
|
38
|
+
minimum_cli_version: minimum,
|
|
39
|
+
sha: server.sha,
|
|
40
|
+
region: server.region,
|
|
41
|
+
in_sync: server.server_version === OXYGEN_VERSION,
|
|
42
|
+
skew: isVersionGreater(OXYGEN_VERSION, server.server_version)
|
|
43
|
+
? "client_ahead"
|
|
44
|
+
: isVersionLess(OXYGEN_VERSION, server.server_version)
|
|
45
|
+
? "client_behind"
|
|
46
|
+
: "in_sync",
|
|
47
|
+
compatible: minimum === null || !isVersionLess(OXYGEN_VERSION, minimum),
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|