@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.
Files changed (293) hide show
  1. package/README.md +1 -1
  2. package/dist/admin-primary-providers-render.js +27 -19
  3. package/dist/cli-output.d.ts +12 -0
  4. package/dist/cli-output.js +395 -0
  5. package/dist/cli-paths.d.ts +1 -0
  6. package/dist/cli-paths.js +3 -0
  7. package/dist/cli-profile-context.d.ts +3 -0
  8. package/dist/cli-profile-context.js +13 -0
  9. package/dist/command-manifest.d.ts +15 -0
  10. package/dist/command-manifest.js +23 -1
  11. package/dist/command-support.d.ts +438 -0
  12. package/dist/command-support.js +1610 -0
  13. package/dist/commands/action-column.d.ts +3 -0
  14. package/dist/commands/action-column.js +43 -0
  15. package/dist/commands/activation.d.ts +3 -0
  16. package/dist/commands/activation.js +24 -0
  17. package/dist/commands/admin.d.ts +3 -0
  18. package/dist/commands/admin.js +347 -0
  19. package/dist/commands/ads.d.ts +3 -0
  20. package/dist/commands/ads.js +62 -0
  21. package/dist/commands/agent.d.ts +3 -0
  22. package/dist/commands/agent.js +274 -0
  23. package/dist/commands/api-keys.d.ts +3 -0
  24. package/dist/commands/api-keys.js +77 -0
  25. package/dist/commands/approvals.d.ts +3 -0
  26. package/dist/commands/approvals.js +518 -0
  27. package/dist/commands/auth.d.ts +3 -0
  28. package/dist/commands/auth.js +230 -0
  29. package/dist/commands/billing.d.ts +3 -0
  30. package/dist/commands/billing.js +357 -0
  31. package/dist/commands/blueprints.d.ts +3 -0
  32. package/dist/commands/blueprints.js +312 -0
  33. package/dist/commands/budget.d.ts +3 -0
  34. package/dist/commands/budget.js +55 -0
  35. package/dist/commands/callables.d.ts +3 -0
  36. package/dist/commands/callables.js +111 -0
  37. package/dist/commands/capabilities.d.ts +3 -0
  38. package/dist/commands/capabilities.js +44 -0
  39. package/dist/commands/cells.d.ts +3 -0
  40. package/dist/commands/cells.js +94 -0
  41. package/dist/commands/columns.d.ts +3 -0
  42. package/dist/commands/columns.js +1628 -0
  43. package/dist/commands/commands.d.ts +3 -0
  44. package/dist/commands/commands.js +45 -0
  45. package/dist/commands/comments.d.ts +3 -0
  46. package/dist/commands/comments.js +188 -0
  47. package/dist/commands/companies.d.ts +3 -0
  48. package/dist/commands/companies.js +312 -0
  49. package/dist/commands/connections.d.ts +3 -0
  50. package/dist/commands/connections.js +39 -0
  51. package/dist/commands/context.d.ts +3 -0
  52. package/dist/commands/context.js +170 -0
  53. package/dist/commands/copilot.d.ts +3 -0
  54. package/dist/commands/copilot.js +565 -0
  55. package/dist/commands/crm.d.ts +3 -0
  56. package/dist/commands/crm.js +1672 -0
  57. package/dist/commands/custom-integrations.d.ts +3 -0
  58. package/dist/commands/custom-integrations.js +140 -0
  59. package/dist/commands/dashboards.d.ts +3 -0
  60. package/dist/commands/dashboards.js +240 -0
  61. package/dist/commands/db.d.ts +3 -0
  62. package/dist/commands/db.js +190 -0
  63. package/dist/commands/deliverability.d.ts +3 -0
  64. package/dist/commands/deliverability.js +90 -0
  65. package/dist/commands/directory.d.ts +3 -0
  66. package/dist/commands/directory.js +101 -0
  67. package/dist/commands/domains.d.ts +3 -0
  68. package/dist/commands/domains.js +604 -0
  69. package/dist/commands/egress.d.ts +3 -0
  70. package/dist/commands/egress.js +130 -0
  71. package/dist/commands/email.d.ts +3 -0
  72. package/dist/commands/email.js +49 -0
  73. package/dist/commands/engagement.d.ts +3 -0
  74. package/dist/commands/engagement.js +189 -0
  75. package/dist/commands/enrich-column.d.ts +3 -0
  76. package/dist/commands/enrich-column.js +149 -0
  77. package/dist/commands/enrichment.d.ts +3 -0
  78. package/dist/commands/enrichment.js +49 -0
  79. package/dist/commands/feedback.d.ts +3 -0
  80. package/dist/commands/feedback.js +96 -0
  81. package/dist/commands/feeds.d.ts +3 -0
  82. package/dist/commands/feeds.js +486 -0
  83. package/dist/commands/find.d.ts +3 -0
  84. package/dist/commands/find.js +159 -0
  85. package/dist/commands/followers.d.ts +3 -0
  86. package/dist/commands/followers.js +39 -0
  87. package/dist/commands/formulas.d.ts +3 -0
  88. package/dist/commands/formulas.js +43 -0
  89. package/dist/commands/home.d.ts +3 -0
  90. package/dist/commands/home.js +22 -0
  91. package/dist/commands/inbox.d.ts +3 -0
  92. package/dist/commands/inbox.js +779 -0
  93. package/dist/commands/integrations.d.ts +3 -0
  94. package/dist/commands/integrations.js +290 -0
  95. package/dist/commands/knowledge.d.ts +3 -0
  96. package/dist/commands/knowledge.js +874 -0
  97. package/dist/commands/lead-sourcing.d.ts +3 -0
  98. package/dist/commands/lead-sourcing.js +52 -0
  99. package/dist/commands/limits.d.ts +3 -0
  100. package/dist/commands/limits.js +54 -0
  101. package/dist/commands/linkedin.d.ts +3 -0
  102. package/dist/commands/linkedin.js +275 -0
  103. package/dist/commands/login.d.ts +3 -0
  104. package/dist/commands/login.js +347 -0
  105. package/dist/commands/logout.d.ts +3 -0
  106. package/dist/commands/logout.js +12 -0
  107. package/dist/commands/mailboxes.d.ts +3 -0
  108. package/dist/commands/mailboxes.js +1290 -0
  109. package/dist/commands/managed-inboxes.d.ts +3 -0
  110. package/dist/commands/managed-inboxes.js +361 -0
  111. package/dist/commands/messages.d.ts +3 -0
  112. package/dist/commands/messages.js +78 -0
  113. package/dist/commands/notetaker.d.ts +3 -0
  114. package/dist/commands/notetaker.js +79 -0
  115. package/dist/commands/observability.d.ts +3 -0
  116. package/dist/commands/observability.js +125 -0
  117. package/dist/commands/orgs.d.ts +3 -0
  118. package/dist/commands/orgs.js +237 -0
  119. package/dist/commands/people.d.ts +3 -0
  120. package/dist/commands/people.js +353 -0
  121. package/dist/commands/posts.d.ts +3 -0
  122. package/dist/commands/posts.js +139 -0
  123. package/dist/commands/profiles.d.ts +3 -0
  124. package/dist/commands/profiles.js +250 -0
  125. package/dist/commands/projects.d.ts +3 -0
  126. package/dist/commands/projects.js +94 -0
  127. package/dist/commands/prompts.d.ts +3 -0
  128. package/dist/commands/prompts.js +84 -0
  129. package/dist/commands/publishing.d.ts +3 -0
  130. package/dist/commands/publishing.js +1419 -0
  131. package/dist/commands/recipes.d.ts +3 -0
  132. package/dist/commands/recipes.js +120 -0
  133. package/dist/commands/reviews.d.ts +3 -0
  134. package/dist/commands/reviews.js +75 -0
  135. package/dist/commands/rows.d.ts +3 -0
  136. package/dist/commands/rows.js +27 -0
  137. package/dist/commands/runs.d.ts +3 -0
  138. package/dist/commands/runs.js +37 -0
  139. package/dist/commands/search.d.ts +3 -0
  140. package/dist/commands/search.js +182 -0
  141. package/dist/commands/senders.d.ts +3 -0
  142. package/dist/commands/senders.js +393 -0
  143. package/dist/commands/sequences.d.ts +3 -0
  144. package/dist/commands/sequences.js +2322 -0
  145. package/dist/commands/session.d.ts +3 -0
  146. package/dist/commands/session.js +109 -0
  147. package/dist/commands/signals.d.ts +3 -0
  148. package/dist/commands/signals.js +289 -0
  149. package/dist/commands/signup-leads.d.ts +3 -0
  150. package/dist/commands/signup-leads.js +46 -0
  151. package/dist/commands/skills.d.ts +3 -0
  152. package/dist/commands/skills.js +50 -0
  153. package/dist/commands/sourcing.d.ts +3 -0
  154. package/dist/commands/sourcing.js +27 -0
  155. package/dist/commands/status.d.ts +3 -0
  156. package/dist/commands/status.js +51 -0
  157. package/dist/commands/supabase.d.ts +3 -0
  158. package/dist/commands/supabase.js +128 -0
  159. package/dist/commands/support.d.ts +3 -0
  160. package/dist/commands/support.js +651 -0
  161. package/dist/commands/suppressions.d.ts +3 -0
  162. package/dist/commands/suppressions.js +577 -0
  163. package/dist/commands/table-ingestions.d.ts +3 -0
  164. package/dist/commands/table-ingestions.js +145 -0
  165. package/dist/commands/table-runs.d.ts +3 -0
  166. package/dist/commands/table-runs.js +277 -0
  167. package/dist/commands/tables.d.ts +3 -0
  168. package/dist/commands/tables.js +3505 -0
  169. package/dist/commands/tags.d.ts +3 -0
  170. package/dist/commands/tags.js +147 -0
  171. package/dist/commands/tools.d.ts +3 -0
  172. package/dist/commands/tools.js +246 -0
  173. package/dist/commands/update.d.ts +3 -0
  174. package/dist/commands/update.js +106 -0
  175. package/dist/commands/verify.d.ts +3 -0
  176. package/dist/commands/verify.js +27 -0
  177. package/dist/commands/viewers.d.ts +3 -0
  178. package/dist/commands/viewers.js +72 -0
  179. package/dist/commands/voice.d.ts +3 -0
  180. package/dist/commands/voice.js +351 -0
  181. package/dist/commands/whatsapp.d.ts +3 -0
  182. package/dist/commands/whatsapp.js +226 -0
  183. package/dist/commands/whoami.d.ts +3 -0
  184. package/dist/commands/whoami.js +110 -0
  185. package/dist/commands/worker.d.ts +3 -0
  186. package/dist/commands/worker.js +60 -0
  187. package/dist/commands/workflows.d.ts +3 -0
  188. package/dist/commands/workflows.js +1752 -0
  189. package/dist/commands/workspace.d.ts +3 -0
  190. package/dist/commands/workspace.js +94 -0
  191. package/dist/compact-list-flags.d.ts +7 -0
  192. package/dist/compact-list-flags.js +12 -0
  193. package/dist/crm-support-flags.js +14 -5
  194. package/dist/customer-operation-commands.js +8 -6
  195. package/dist/diagnostic-uploader.d.ts +3 -1
  196. package/dist/diagnostic-uploader.js +29 -8
  197. package/dist/help.d.ts +5 -0
  198. package/dist/help.js +22 -0
  199. package/dist/index.d.ts +98 -1
  200. package/dist/index.js +331 -30719
  201. package/dist/local-module-loader.d.ts +4 -0
  202. package/dist/local-module-loader.js +156 -0
  203. package/dist/visual-commands.js +9 -5
  204. package/node_modules/@oxygen/cli-ugc/dist/commands.js +2 -2
  205. package/node_modules/@oxygen/shared/dist/agent-session-schedule.d.ts +36 -0
  206. package/node_modules/@oxygen/shared/dist/agent-session-schedule.js +69 -0
  207. package/node_modules/@oxygen/shared/dist/agent-sessions.d.ts +14 -5
  208. package/node_modules/@oxygen/shared/dist/agent-sessions.js +14 -3
  209. package/node_modules/@oxygen/shared/dist/capability-discovery.js +175 -14
  210. package/node_modules/@oxygen/shared/dist/compact-list-result.d.ts +6 -0
  211. package/node_modules/@oxygen/shared/dist/compact-list-result.js +27 -0
  212. package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.d.ts +12 -1
  213. package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.js +5 -2
  214. package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +4 -2
  215. package/node_modules/@oxygen/shared/dist/contracts/compact-list-reads.d.ts +57 -0
  216. package/node_modules/@oxygen/shared/dist/contracts/compact-list-reads.js +23 -0
  217. package/node_modules/@oxygen/shared/dist/contracts/crm-support.d.ts +52 -0
  218. package/node_modules/@oxygen/shared/dist/contracts/crm-support.js +43 -2
  219. package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +20 -16
  220. package/node_modules/@oxygen/shared/dist/contracts/knowledge-content-reads.js +1 -1
  221. package/node_modules/@oxygen/shared/dist/contracts/knowledge-proposal-reads.d.ts +49 -0
  222. package/node_modules/@oxygen/shared/dist/contracts/knowledge-proposal-reads.js +14 -0
  223. package/node_modules/@oxygen/shared/dist/contracts/knowledge-sync.js +1 -1
  224. package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +60 -7
  225. package/node_modules/@oxygen/shared/dist/contracts/operations.d.ts +9 -1
  226. package/node_modules/@oxygen/shared/dist/contracts/operations.js +13 -5
  227. package/node_modules/@oxygen/shared/dist/contracts/publishing-reads.js +1 -1
  228. package/node_modules/@oxygen/shared/dist/contracts/teammates.d.ts +16 -0
  229. package/node_modules/@oxygen/shared/dist/contracts/teammates.js +115 -0
  230. package/node_modules/@oxygen/shared/dist/cost-estimate-view.js +21 -3
  231. package/node_modules/@oxygen/shared/dist/cost-estimate.d.ts +31 -1
  232. package/node_modules/@oxygen/shared/dist/cost-estimate.js +56 -15
  233. package/node_modules/@oxygen/shared/dist/diagnostic-credentials.d.ts +2 -0
  234. package/node_modules/@oxygen/shared/dist/diagnostic-credentials.js +2 -0
  235. package/node_modules/@oxygen/shared/dist/diagnostic-lock.js +71 -44
  236. package/node_modules/@oxygen/shared/dist/diagnostic-spool.d.ts +18 -0
  237. package/node_modules/@oxygen/shared/dist/diagnostic-spool.js +348 -0
  238. package/node_modules/@oxygen/shared/dist/diagnostic-storage-failure.d.ts +10 -0
  239. package/node_modules/@oxygen/shared/dist/diagnostic-storage-failure.js +42 -0
  240. package/node_modules/@oxygen/shared/dist/diagnostic-storage.d.ts +1 -1
  241. package/node_modules/@oxygen/shared/dist/diagnostic-storage.js +59 -213
  242. package/node_modules/@oxygen/shared/dist/diagnostics.js +3 -2
  243. package/node_modules/@oxygen/shared/dist/email-dsn.d.ts +16 -5
  244. package/node_modules/@oxygen/shared/dist/email-dsn.js +97 -1
  245. package/node_modules/@oxygen/shared/dist/emailguard-placement-probe.js +5 -0
  246. package/node_modules/@oxygen/shared/dist/enrichment-intents.js +4 -5
  247. package/node_modules/@oxygen/shared/dist/file-import.d.ts +8 -0
  248. package/node_modules/@oxygen/shared/dist/file-import.js +21 -2
  249. package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +30 -0
  250. package/node_modules/@oxygen/shared/dist/hosted-ai.js +20 -0
  251. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +6 -2
  252. package/node_modules/@oxygen/shared/dist/knowledge-seed-content.d.ts +17 -7
  253. package/node_modules/@oxygen/shared/dist/knowledge-seed-content.js +81 -17
  254. package/node_modules/@oxygen/shared/dist/langfuse.js +8 -4
  255. package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +1 -1
  256. package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +6 -3
  257. package/node_modules/@oxygen/shared/dist/mailbox-import.js +9 -9
  258. package/node_modules/@oxygen/shared/dist/mcp-server-info.d.ts +8 -0
  259. package/node_modules/@oxygen/shared/dist/mcp-server-info.js +10 -0
  260. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +10 -0
  261. package/node_modules/@oxygen/shared/dist/object-storage.js +14 -0
  262. package/node_modules/@oxygen/shared/dist/plan-limits-view.d.ts +5 -0
  263. package/node_modules/@oxygen/shared/dist/plan-limits-view.js +17 -1
  264. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
  265. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
  266. package/node_modules/@oxygen/shared/dist/provider-selection.d.ts +15 -0
  267. package/node_modules/@oxygen/shared/dist/provider-selection.js +3 -0
  268. package/node_modules/@oxygen/shared/dist/repricing.d.ts +1 -1
  269. package/node_modules/@oxygen/shared/dist/repricing.js +2 -2
  270. package/node_modules/@oxygen/shared/dist/schedule-label.d.ts +6 -0
  271. package/node_modules/@oxygen/shared/dist/schedule-label.js +12 -0
  272. package/node_modules/@oxygen/shared/dist/sequence-failures.d.ts +1 -0
  273. package/node_modules/@oxygen/shared/dist/sequence-failures.js +20 -6
  274. package/node_modules/@oxygen/shared/dist/teammate-groups.d.ts +55 -0
  275. package/node_modules/@oxygen/shared/dist/teammate-groups.js +74 -0
  276. package/node_modules/@oxygen/shared/dist/teammate-messages.d.ts +52 -0
  277. package/node_modules/@oxygen/shared/dist/teammate-messages.js +88 -0
  278. package/node_modules/@oxygen/shared/dist/teammate-notes.d.ts +58 -0
  279. package/node_modules/@oxygen/shared/dist/teammate-notes.js +73 -0
  280. package/node_modules/@oxygen/shared/dist/teammate-routines.d.ts +95 -0
  281. package/node_modules/@oxygen/shared/dist/teammate-routines.js +266 -0
  282. package/node_modules/@oxygen/shared/dist/teammate-tasks.d.ts +56 -0
  283. package/node_modules/@oxygen/shared/dist/teammate-tasks.js +114 -0
  284. package/node_modules/@oxygen/shared/dist/teammates.d.ts +78 -0
  285. package/node_modules/@oxygen/shared/dist/teammates.js +140 -0
  286. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +120 -0
  287. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  288. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  289. package/node_modules/@oxygen/shared/dist/visual-render.d.ts +9 -1
  290. package/node_modules/@oxygen/shared/dist/visual-render.js +12 -5
  291. package/node_modules/@oxygen/shared/dist/warmup-metrics.d.ts +5 -0
  292. package/node_modules/@oxygen/shared/dist/warmup-metrics.js +44 -0
  293. package/package.json +1 -1
@@ -0,0 +1,3 @@
1
+ import { ExtractedCommandActionHandler } from "../command-support.js";
2
+ import { Command } from "commander";
3
+ export declare function registerCommandsFamily(program: Command, handleAsyncAction: ExtractedCommandActionHandler, binaryName: any): void;
@@ -0,0 +1,45 @@
1
+ import { readPositiveInt } from "../cli-values.js";
2
+ import { buildCommandManifest, getCommandManifestEntry, searchCommandManifest, suggestCommandNames } from "../command-manifest.js";
3
+ import { OxygenError } from "@oxygen/shared/cli-result";
4
+ import { Command } from "commander";
5
+ export function registerCommandsFamily(program, handleAsyncAction, binaryName) {
6
+ const commandsCommand = program
7
+ .command("commands")
8
+ .description("Discover CLI commands. With no subcommand, print the backwards-compatible full manifest.")
9
+ .option("--json", "Print a JSON envelope.")
10
+ .action(async (options) => {
11
+ await handleAsyncAction("commands", options, async () => buildCommandManifest(program, binaryName));
12
+ });
13
+ commandsCommand
14
+ .addCommand(new Command("search")
15
+ .description("Find a bounded set of CLI commands from a natural-language outcome.")
16
+ .argument("<query...>", "Outcome or capability to find.")
17
+ .option("--limit <n>", "Maximum results (default 10, max 25).")
18
+ .option("--json", "Print a JSON envelope.")
19
+ .action(async (queryParts, options) => {
20
+ const outputOptions = { ...options, json: options.json || Boolean(commandsCommand.opts().json) };
21
+ await handleAsyncAction("commands search", outputOptions, async () => {
22
+ const manifest = buildCommandManifest(program, binaryName);
23
+ return searchCommandManifest(manifest, queryParts.join(" "), readPositiveInt(options.limit) ?? 10);
24
+ });
25
+ }))
26
+ .addCommand(new Command("get")
27
+ .description("Hydrate one exact CLI command with its arguments, flags, and safety markers.")
28
+ .argument("<command...>", "Exact command name returned by commands search.")
29
+ .option("--json", "Print a JSON envelope.")
30
+ .action(async (commandParts, options) => {
31
+ const outputOptions = { ...options, json: options.json || Boolean(commandsCommand.opts().json) };
32
+ await handleAsyncAction("commands get", outputOptions, async () => {
33
+ const manifest = buildCommandManifest(program, binaryName);
34
+ const exactName = commandParts.join(" ");
35
+ const command = getCommandManifestEntry(manifest, exactName);
36
+ if (!command) {
37
+ throw new OxygenError("command_not_found", `Unknown Oxygen command: ${exactName}`, {
38
+ details: { did_you_mean: suggestCommandNames(manifest, exactName) },
39
+ exitCode: 2,
40
+ });
41
+ }
42
+ return command;
43
+ });
44
+ }));
45
+ }
@@ -0,0 +1,3 @@
1
+ import { ExtractedCommandActionHandler } from "../command-support.js";
2
+ import { Command } from "commander";
3
+ export declare function registerCommentsFamily(program: Command, handleAsyncAction: ExtractedCommandActionHandler): void;
@@ -0,0 +1,188 @@
1
+ import { readPositiveInt } from "../cli-values.js";
2
+ import { COLLAB_BETA_NOTE, COLLAB_SUBJECT_PATH_HELP, COLLAB_SUBJECT_REF_HELP, collabLink, collabSnippet, collabStyles, collabText, collabTrailer, collectRepeatable, formatCollabAge, formatCollabWriteResult, handleCollabAction, readCollabPath, requireCollabSubject } from "../command-support.js";
3
+ import { requestOxygen } from "../http-client.js";
4
+ import { isRecord, readOption } from "../util.js";
5
+ import { OxygenError } from "@oxygen/shared/cli-result";
6
+ import { COLLAB_SUBJECT_KINDS, COLLAB_SUBJECT_KINDS_PROSE, COLLAB_SUBJECT_PROSE } from "@oxygen/shared/collab";
7
+ import { Command } from "commander";
8
+ /** What a thread on each kind is usually about, for the group descriptions. */
9
+ const COLLAB_SUBJECT_GLOSS = COLLAB_SUBJECT_KINDS
10
+ .map((kind) => `${kind} (${COLLAB_SUBJECT_PROSE[kind]})`)
11
+ .join("; ");
12
+ /**
13
+ * `comments add`. Without --reply this is one POST. With --reply the CLI first
14
+ * resolves which thread owns that id, because the reply endpoint is keyed by
15
+ * THREAD while the id a user has in hand (from `comments list`, or from the
16
+ * `next_step` the API prints) is a COMMENT. Passing a thread id works too and
17
+ * replies at its top level — both ids sit side by side in the list output, and
18
+ * refusing the wrong one would be pedantry, not safety.
19
+ */
20
+ async function handleCommentsAddAction(body, options) {
21
+ await handleCollabAction("comments add", options, async () => {
22
+ const subject = requireCollabSubject(options.on);
23
+ const path = readCollabPath(options.path);
24
+ const mentions = options.mention.map((value) => value.trim()).filter(Boolean);
25
+ const replyTo = readOption(options.reply);
26
+ if (!replyTo) {
27
+ const title = readOption(options.title);
28
+ return requestOxygen("/api/cli/collab/threads", {
29
+ method: "POST",
30
+ body: {
31
+ subject_kind: subject.kind,
32
+ subject_id: subject.id,
33
+ ...(path ? { subject_path: path } : {}),
34
+ ...(title ? { title } : {}),
35
+ body,
36
+ ...(mentions.length > 0 ? { mentions } : {}),
37
+ },
38
+ });
39
+ }
40
+ const target = await resolveCollabReplyTarget(subject, replyTo);
41
+ return requestOxygen(`/api/cli/collab/threads/${encodeURIComponent(target.threadId)}/comments`, {
42
+ method: "POST",
43
+ body: {
44
+ body,
45
+ ...(target.parentCommentId ? { parent_comment_id: target.parentCommentId } : {}),
46
+ ...(mentions.length > 0 ? { mentions } : {}),
47
+ },
48
+ });
49
+ }, formatCollabWriteResult);
50
+ }
51
+ async function resolveCollabReplyTarget(subject, replyTo) {
52
+ const params = new URLSearchParams({
53
+ subject: `${subject.kind}:${subject.id}`,
54
+ // `all`, not `open`: replying to a resolved thread is how a client reopens
55
+ // a conversation, and scoping the lookup to open threads would report the
56
+ // id as missing instead.
57
+ status: "all",
58
+ });
59
+ const listed = await requestOxygen(`/api/cli/collab/threads?${params.toString()}`);
60
+ const threads = Array.isArray(listed.threads) ? listed.threads.filter(isRecord) : [];
61
+ for (const thread of threads) {
62
+ if (thread.id === replyTo)
63
+ return { threadId: replyTo, parentCommentId: null };
64
+ const comments = Array.isArray(thread.comments) ? thread.comments.filter(isRecord) : [];
65
+ if (comments.some((comment) => comment.id === replyTo) && typeof thread.id === "string") {
66
+ return { threadId: thread.id, parentCommentId: replyTo };
67
+ }
68
+ }
69
+ throw new OxygenError("comment_not_found", `No comment or thread "${replyTo}" on ${subject.kind}:${subject.id}. List them with \`oxygen comments list --on ${subject.kind}:${subject.id} --status all\`.`, { details: { reply: replyTo, subject: `${subject.kind}:${subject.id}` }, exitCode: 4 });
70
+ }
71
+ function formatCollabThreads(data) {
72
+ const styles = collabStyles();
73
+ const threads = Array.isArray(data.threads) ? data.threads.filter(isRecord) : [];
74
+ const subject = collabText(data.subject, "this object");
75
+ const lines = ["", styles.bold(`Comments on ${subject}`)];
76
+ if (threads.length === 0) {
77
+ lines.push(` ${styles.dim(collabText(data.hint, "No comment threads here yet."))}`);
78
+ }
79
+ for (const thread of threads) {
80
+ const status = collabText(thread.status, "open");
81
+ const badge = status === "resolved" ? styles.dim("[resolved]") : styles.green("[open]");
82
+ const title = collabText(thread.title, "");
83
+ lines.push("", ` ${badge} ${collabText(thread.id)} ${styles.dim(`${thread.comment_count ?? 0} comment(s) · ${formatCollabAge(thread.created_at)} old`)}`);
84
+ if (title)
85
+ lines.push(` ${styles.bold(title)}`);
86
+ if (typeof thread.subject_path === "string" && thread.subject_path) {
87
+ lines.push(` ${styles.dim(`on ${thread.subject_path}`)}`);
88
+ }
89
+ const comments = Array.isArray(thread.comments) ? thread.comments.filter(isRecord) : [];
90
+ for (const comment of comments) {
91
+ const author = collabText(comment.author_email, "someone");
92
+ const marker = comment.parent_comment_id ? " ↳" : " •";
93
+ const edited = comment.edited_at ? styles.dim(" (edited)") : "";
94
+ const text = comment.deleted === true
95
+ ? styles.dim("(retracted)")
96
+ : collabSnippet(comment.body, 72);
97
+ lines.push(` ${marker} ${styles.dim(`${formatCollabAge(comment.created_at)} ${author}`)} ${text}${edited}`);
98
+ lines.push(` ${styles.dim(collabText(comment.id))}`);
99
+ }
100
+ const threadLink = collabLink(thread);
101
+ if (threadLink)
102
+ lines.push(` ${styles.dim(threadLink)}`);
103
+ }
104
+ lines.push(...collabTrailer(data));
105
+ lines.push("");
106
+ return lines.join("\n");
107
+ }
108
+ export function registerCommentsFamily(program, handleAsyncAction) {
109
+ program
110
+ .command("comments")
111
+ .description(`Threaded comments on any workspace object — ${COLLAB_SUBJECT_KINDS_PROSE} — so the client's go/no-go stops living in a Google Doc. A comment is state ABOUT a GTM artifact, never the artifact: resolving a thread changes no campaign. Address the subject with --on <kind>:<id>, and one row, cell, or column of a table with --path row:<rowId>[#<columnKey>] | column:<columnKey>. Threads are open until somebody resolves them; any member may resolve. The thing that actually BLOCKS work is an approval gate — see \`oxygen approvals\`. What a thread is usually about, per kind: ${COLLAB_SUBJECT_GLOSS}. ${COLLAB_BETA_NOTE}`)
112
+ .addCommand(new Command("add")
113
+ .description("Comment on a workspace object. Without --reply this opens a NEW thread with this comment as its first; with --reply it answers inside the existing one. @email tokens in the body are resolved to workspace members and notified, so --mention is only needed for someone you did not name in the text.")
114
+ .argument("<body...>", "What you want to say. Quote it, or let the shell pass it as words.")
115
+ .requiredOption("--on <kind:id>", COLLAB_SUBJECT_REF_HELP)
116
+ .option("--path <path>", COLLAB_SUBJECT_PATH_HELP)
117
+ .option("--reply <id>", "Answer inside an existing thread. Pass a comment id to nest under it, or a thread id to reply at that thread's top level.")
118
+ .option("--mention <email>", "Notify a workspace member. Repeatable.", collectRepeatable, [])
119
+ .option("--title <text>", "Title for a NEW thread. Ignored when replying.")
120
+ .option("--json", "Print a JSON envelope.")
121
+ .action(async (body, options) => {
122
+ await handleCommentsAddAction(body.join(" "), options);
123
+ }))
124
+ .addCommand(new Command("list")
125
+ .description("Every comment thread on one object, newest first, with its comments inline. Defaults to open threads only.")
126
+ .requiredOption("--on <kind:id>", COLLAB_SUBJECT_REF_HELP)
127
+ .option("--path <path>", `${COLLAB_SUBJECT_PATH_HELP} Omit to see every thread on the object.`)
128
+ .option("--status <status>", "open (default), resolved, or all.")
129
+ .option("--limit <n>", "Maximum threads to return.")
130
+ .option("--json", "Print a JSON envelope.")
131
+ .action(async (options) => {
132
+ await handleCollabAction("comments list", options, () => {
133
+ const subject = requireCollabSubject(options.on);
134
+ const path = readCollabPath(options.path);
135
+ const params = new URLSearchParams({ subject: `${subject.kind}:${subject.id}` });
136
+ if (path)
137
+ params.set("path", path);
138
+ const status = readOption(options.status);
139
+ if (status)
140
+ params.set("status", status);
141
+ const limit = readPositiveInt(options.limit);
142
+ if (limit !== undefined)
143
+ params.set("limit", String(limit));
144
+ return requestOxygen(`/api/cli/collab/threads?${params.toString()}`);
145
+ }, formatCollabThreads);
146
+ }))
147
+ .addCommand(new Command("show")
148
+ .description("One thread by its own id, with every comment. This is what to run when all you have is the id out of a notification email or a `/collab/threads/<id>` link — `list` needs the object the thread hangs off, which that link does not tell you.")
149
+ .argument("<threadId>", "Thread id (from a notification email, a deep-link, or `oxygen comments list`).")
150
+ .option("--json", "Print a JSON envelope.")
151
+ .action(async (threadId, options) => {
152
+ await handleCollabAction("comments show", options, () => requestOxygen(`/api/cli/collab/threads/${encodeURIComponent(threadId)}`),
153
+ // Same payload shape `comments list` returns, one thread long, so the
154
+ // renderer is the same one.
155
+ formatCollabThreads);
156
+ }))
157
+ .addCommand(new Command("resolve")
158
+ .description("Mark a thread settled, or reopen it with --reopen. Idempotent both ways: re-resolving keeps the original resolver and timestamp. This is conversation state only — it never unblocks an approval gate.")
159
+ .argument("<threadId>", "Thread id (from `oxygen comments list`).")
160
+ .option("--reopen", "Reopen a resolved thread instead of resolving it.")
161
+ .option("--json", "Print a JSON envelope.")
162
+ .action(async (threadId, options) => {
163
+ await handleCollabAction("comments resolve", options, () => requestOxygen(`/api/cli/collab/threads/${encodeURIComponent(threadId)}/resolve`, {
164
+ method: "POST",
165
+ body: { resolved: !options.reopen },
166
+ }), formatCollabWriteResult);
167
+ }))
168
+ .addCommand(new Command("edit")
169
+ .description("Rewrite one of your own comments. AUTHOR ONLY. The edit stamps edited_at — a thread that decided a launch is evidence, and evidence that changes without a trace is not evidence.")
170
+ .argument("<commentId>", "Comment id (from `oxygen comments list`).")
171
+ .argument("<body...>", "The replacement text.")
172
+ .option("--json", "Print a JSON envelope.")
173
+ .action(async (commentId, body, options) => {
174
+ await handleCollabAction("comments edit", options, () => requestOxygen(`/api/cli/collab/comments/${encodeURIComponent(commentId)}`, {
175
+ method: "PATCH",
176
+ body: { body: body.join(" ") },
177
+ }), formatCollabWriteResult);
178
+ }))
179
+ .addCommand(new Command("delete")
180
+ .description("Retract one of your own comments. AUTHOR ONLY, idempotent. The row stays so the thread remains a complete audit trail; the text is redacted on every read path.")
181
+ .argument("<commentId>", "Comment id (from `oxygen comments list`).")
182
+ .option("--json", "Print a JSON envelope.")
183
+ .action(async (commentId, options) => {
184
+ await handleCollabAction("comments delete", options, () => requestOxygen(`/api/cli/collab/comments/${encodeURIComponent(commentId)}`, {
185
+ method: "DELETE",
186
+ }), formatCollabWriteResult);
187
+ }));
188
+ }
@@ -0,0 +1,3 @@
1
+ import { ExtractedCommandActionHandler } from "../command-support.js";
2
+ import { Command } from "commander";
3
+ export declare function registerCompaniesFamily(program: Command, handleAsyncAction: ExtractedCommandActionHandler): void;
@@ -0,0 +1,312 @@
1
+ import { parseJsonObject, readJsonObjectOption, readPositiveInt } from "../cli-values.js";
2
+ import { readCsvOption, readFileIfPresent, readFilterSelectionOption, readPositiveNumber, readRangeOption, 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 readCompaniesSearchPlanBody(options, promptArg) {
8
+ // The prompt may arrive as --prompt or as the positional argument; the flag wins.
9
+ const promptSource = options.prompt ?? promptArg;
10
+ const targetCount = readPositiveInt(options.targetCount);
11
+ const filters = readCompanySearchFilters(options);
12
+ return {
13
+ // Omit when neither was given so the request still reaches the server, which
14
+ // answers with a clean `prompt is required.` instead of crashing readFileIfPresent.
15
+ ...(promptSource !== undefined ? { prompt: readFileIfPresent(promptSource) } : {}),
16
+ ...(targetCount !== undefined ? { target_count: targetCount } : {}),
17
+ ...(options.sourceIntent ? { source_intent: options.sourceIntent } : {}),
18
+ ...(filters ? { filters } : {}),
19
+ ...(options.estimate ? { estimate: true } : {}),
20
+ ...(options.materializePreview ? { materialize_preview: true } : {}),
21
+ };
22
+ }
23
+ function readCompaniesSearchRunBody(options, promptArg) {
24
+ // The prompt may arrive as --prompt or as the positional argument; the flag wins.
25
+ const promptSource = options.prompt ?? promptArg;
26
+ const prompt = promptSource ? readFileIfPresent(promptSource) : null;
27
+ const plan = options.planJson ? readSearchPlanJson(options.planJson) : null;
28
+ if (!prompt && !plan && options.source !== "company_search" && options.source !== "hiring" && options.source !== "google_maps") {
29
+ throw new OxygenError("invalid_request", "Pass --prompt, --plan-json, or --source company_search|hiring|google_maps with --source-filters-json.", { exitCode: 1 });
30
+ }
31
+ const maxPages = readPositiveInt(options.maxPages);
32
+ const maxCredits = readPositiveNumber(options.maxCredits);
33
+ const targetCount = readPositiveInt(options.targetCount);
34
+ // With --source the server reads only --source-filters-json. Forward the planner's
35
+ // filters anyway so the server refuses them by name instead of the CLI dropping
36
+ // them and the run buying an unfiltered search (OXP-1.39.2 S6).
37
+ const filters = prompt || options.source ? readCompanySearchFilters(options) : null;
38
+ return {
39
+ ...(prompt ? { prompt } : {}),
40
+ ...(plan ? { plan } : {}),
41
+ ...(options.source ? { source: options.source } : {}),
42
+ ...(options.sourceFiltersJson ? { source_filters: parseJsonObject(readFileIfPresent(options.sourceFiltersJson)) } : {}),
43
+ ...(readOption(options.tableName) ? { table_name: readOption(options.tableName) } : {}),
44
+ ...(options.routeId ? { route_id: options.routeId } : {}),
45
+ ...(options.toolId ? { tool_id: options.toolId } : {}),
46
+ ...(options.table ? { table: options.table } : {}),
47
+ ...(options.project ? { project: options.project } : {}),
48
+ ...(options.upsertKey ? { upsert_key: options.upsertKey } : {}),
49
+ ...(options.mode ? { mode: options.mode } : {}),
50
+ ...(maxPages !== undefined ? { max_pages: maxPages } : {}),
51
+ ...(maxCredits !== undefined ? { max_credits: maxCredits } : {}),
52
+ ...(targetCount !== undefined ? { target_count: targetCount } : {}),
53
+ ...(options.sourceIntent ? { source_intent: options.sourceIntent } : {}),
54
+ ...(filters ? { filters } : {}),
55
+ ...(prompt && options.estimate ? { estimate: true } : {}),
56
+ ...(options.approved ? { approved: true } : {}),
57
+ };
58
+ }
59
+ // CompanySearchFilters mirror (structural — the server validates the shape). Builds a
60
+ // filters object from individual --industries/--countries/--employees/etc. flags, then
61
+ // lets --filters-json win per top-level filter path so an agent can pass a precise object
62
+ // while still using convenience flags for the rest.
63
+ function readCompanySearchFilters(options) {
64
+ const filters = {};
65
+ const industries = readIncludeExclude(options.industries, options.excludeIndustries);
66
+ if (industries)
67
+ filters.industries = industries;
68
+ const keywords = readIncludeExclude(options.keywords, options.excludeKeywords);
69
+ if (keywords)
70
+ filters.keywords = keywords;
71
+ const countries = readCsvOption(options.countries);
72
+ if (countries.length > 0)
73
+ filters.geo = { countries };
74
+ const employeeCount = readRangeOption(options.employees, "--employees");
75
+ if (employeeCount)
76
+ filters.employee_count = employeeCount;
77
+ const fundingStages = readCsvOption(options.fundingStages);
78
+ if (fundingStages.length > 0)
79
+ filters.funding = { stages: fundingStages };
80
+ const technologies = readIncludeExclude(options.technologies);
81
+ if (technologies)
82
+ filters.technologies = technologies;
83
+ const revenue = readRangeOption(options.revenue, "--revenue");
84
+ if (revenue)
85
+ filters.revenue_usd = revenue;
86
+ const founded = readRangeOption(options.founded, "--founded");
87
+ if (founded)
88
+ filters.founded_year = founded;
89
+ const lookalike = readCsvOption(options.lookalike);
90
+ if (lookalike.length > 0)
91
+ filters.lookalike_domains = lookalike;
92
+ const explicit = options.filtersJson ? parseJsonObject(readFileIfPresent(options.filtersJson)) : null;
93
+ if (explicit) {
94
+ for (const [key, value] of Object.entries(explicit)) {
95
+ filters[key] = value;
96
+ }
97
+ }
98
+ return Object.keys(filters).length > 0 ? filters : null;
99
+ }
100
+ function readIncludeExclude(include, exclude) {
101
+ const includeValues = readCsvOption(include);
102
+ const excludeValues = readCsvOption(exclude);
103
+ const result = {};
104
+ if (includeValues.length > 0)
105
+ result.include = includeValues;
106
+ if (excludeValues.length > 0)
107
+ result.exclude = excludeValues;
108
+ return Object.keys(result).length > 0 ? result : null;
109
+ }
110
+ // `source` names the companies table the Employee Finder fans out over. The
111
+ // offset/limit window rides INSIDE `source` when the source is declared here; with
112
+ // a saved plan it rides as `source_overrides`, which the run route applies on top
113
+ // of the plan's own source.
114
+ /** The company lanes that share Company Search's flow; anything else is a typo, not a provider. */
115
+ function readCompanySearchSource(value) {
116
+ const source = readOption(value) ?? "company_search";
117
+ if (source !== "company_search" && source !== "hiring" && source !== "google_maps") {
118
+ throw new OxygenError("invalid_request", "--source must be company_search, hiring or google_maps.", { exitCode: 1 });
119
+ }
120
+ return source;
121
+ }
122
+ function readCompanySourceQuery(value) {
123
+ const source = readCompanySearchSource(value);
124
+ return source === "company_search" ? "" : `?source=${source}`;
125
+ }
126
+ function readCompaniesEnrichBody(table, options) {
127
+ const body = { table };
128
+ const fields = readCsvOption(options.missingFields);
129
+ const providers = readCsvOption(options.providers);
130
+ const selection = readCompaniesEnrichSelection(options);
131
+ const mode = readOption(options.mode);
132
+ const maxCredits = readPositiveNumber(options.maxCredits);
133
+ if (fields.length > 0)
134
+ body.missing_fields = fields;
135
+ if (providers.length > 0)
136
+ body.providers = providers;
137
+ if (selection)
138
+ body.selection = selection;
139
+ if (mode)
140
+ body.mode = mode;
141
+ if (maxCredits !== undefined)
142
+ body.max_credits = maxCredits;
143
+ if (options.approved)
144
+ body.approved = true;
145
+ if (options.force !== undefined)
146
+ body.force = Boolean(options.force);
147
+ return body;
148
+ }
149
+ function readCompaniesEnrichSelection(options) {
150
+ const explicitSelection = readJsonObjectOption(options.selectionJson);
151
+ const hasAll = Boolean(options.all);
152
+ const limit = readPositiveInt(options.limit);
153
+ const rowIds = readCsvOption(options.rowIds);
154
+ const filterSelection = readFilterSelectionOption(options.filterJson);
155
+ const selectedModes = [
156
+ Boolean(explicitSelection),
157
+ hasAll,
158
+ Boolean(limit),
159
+ rowIds.length > 0,
160
+ Boolean(filterSelection),
161
+ ].filter(Boolean).length;
162
+ if (selectedModes > 1) {
163
+ throw new OxygenError("invalid_company_enrichment", "Pass only one row scope option.", { exitCode: 1 });
164
+ }
165
+ if (explicitSelection)
166
+ return explicitSelection;
167
+ if (hasAll)
168
+ return { mode: "all" };
169
+ if (limit)
170
+ return { mode: "limit", limit };
171
+ if (rowIds.length > 0)
172
+ return { mode: "row_ids", row_ids: rowIds };
173
+ if (filterSelection)
174
+ return filterSelection;
175
+ return undefined;
176
+ }
177
+ export function registerCompaniesFamily(program, handleAsyncAction) {
178
+ program
179
+ .command("companies")
180
+ .description("Company prospecting and account enrichment workflows.")
181
+ .addCommand(new Command("search")
182
+ .description("Plan, dry-run, or queue provider-backed company search.")
183
+ .addCommand(new Command("filters")
184
+ .description("List Company Search filter fields and options without a provider call, including every enum field's full accepted-value list. --source hiring lists the Job postings filters instead (job.* and company.*).")
185
+ .option("--source <source>", "company_search (default), google_maps, or hiring: live job postings for the roles you name, one row per posting with a link to it.")
186
+ .option("--json", "Print a JSON envelope.")
187
+ .action(async (options) => {
188
+ await handleAsyncAction("companies search filters", options, () => requestOxygen(`/api/cli/companies/search/preview${readCompanySourceQuery(options.source)}`));
189
+ }))
190
+ .addCommand(new Command("preview")
191
+ .description("Preview up to 50 rows free, using native filters from companies search filters. --source google_maps previews 20 businesses with a q business-and-area filter; total_results is unavailable when the API publishes no total. --source hiring previews matching job postings with an exact total. Totals, notes and the priced next step come first and the rows last; each row is compact (name, domain, LinkedIn, headcount, industry, location, a one-line description) unless --view full.")
192
+ .requiredOption("--filters-json <json-or-file>", 'Native filters as JSON or @file/path. Google Maps requires q, placeId or cid; other sources accept {} for all results.')
193
+ .option("--source <source>", "company_search (default), hiring or google_maps. Filters for hiring nest under job and company, e.g. {\"job\":{\"title\":{\"include\":[\"Account Executive\"]}}}.")
194
+ .option("--view <view>", "compact (default) or full: full adds each row's provider record as raw_source, about 2.5 KB a company.")
195
+ .option("--json", "Print a JSON envelope.")
196
+ .action(async (options) => {
197
+ await handleAsyncAction("companies search preview", options, () => requestOxygen("/api/cli/companies/search/preview", {
198
+ method: "POST",
199
+ body: {
200
+ filters: parseJsonObject(readFileIfPresent(options.filtersJson)),
201
+ ...(readCompanySearchSource(options.source) !== "company_search" ? { source: readCompanySearchSource(options.source) } : {}),
202
+ ...(options.view !== undefined ? { view: options.view } : {}),
203
+ },
204
+ }));
205
+ }))
206
+ .addCommand(new Command("plan")
207
+ .description("Compile a company-search prompt into ordered provider routes without provider calls. To turn a list of company NAMES into websites or domains, pass --source-intent url_recovery (e.g. --prompt \"Find the websites for these companies: <names>\" --source-intent url_recovery). The plan carries provider_availability: a route on a benched managed provider reads degraded with a next_action.")
208
+ .argument("[prompt]", "Prompt text or @file (same as --prompt). The --prompt flag wins if both are given.")
209
+ .option("--prompt <text-or-file>", "Company-search prompt, or a path to a prompt file.")
210
+ .option("--target-count <n>", "Desired company count. Omit it to plan every match the live route can reach, up to a tenth of the spendable credits. Single-plan ceiling 50,000; larger requests return an explicit clamp warning and segmentation guidance.")
211
+ .option("--source-intent <intent>", "Override detected intent: sizing, structured, lookalike, technology, hiring, local, known_source, concept, web, url, or fallback.")
212
+ .option("--filters-json <json-or-file>", "CompanySearchFilters JSON inline or a @file/path; wins over individual flags per top-level filter path.")
213
+ .option("--industries <csv>", "Comma-separated industries to include.")
214
+ .option("--exclude-industries <csv>", "Comma-separated industries to exclude.")
215
+ .option("--keywords <csv>", "Comma-separated keywords to include.")
216
+ .option("--exclude-keywords <csv>", "Comma-separated keywords to exclude.")
217
+ .option("--countries <csv>", "Comma-separated ISO 3166-1 alpha-2 country codes.")
218
+ .option("--employees <range>", "Employee count range: 20-200, 500+, or -50.")
219
+ .option("--funding-stages <csv>", "Comma-separated funding stages: pre_seed, seed, series_a, ...")
220
+ .option("--technologies <csv>", "Comma-separated technologies to include.")
221
+ .option("--revenue <range>", "Annual revenue (USD) range: 1000000-20000000, 1000000+, or -5000000.")
222
+ .option("--founded <range>", "Founded year range: 2015-2024, 2020+, or -2010.")
223
+ .option("--lookalike <csv>", "Comma-separated lookalike company domains.")
224
+ .option("--estimate", "Run a free server-side preflight pass: resolves provider enums and a free count probe for an estimated match count (zero credits).")
225
+ .option("--materialize-preview", "Create a route-plan table, without company results. For free company rows use companies search preview.")
226
+ .option("--json", "Print a JSON envelope.")
227
+ .action(async (promptArg, options) => {
228
+ await handleAsyncAction("companies search plan", options, () => requestOxygen("/api/cli/companies/search/plan", {
229
+ method: "POST",
230
+ body: readCompaniesSearchPlanBody(options, promptArg),
231
+ }));
232
+ }))
233
+ .addCommand(new Command("run")
234
+ .description("Return a dry-run request or queue a live company-search ingestion run.")
235
+ .option("--source <source>", "Use company_search, hiring or google_maps with --source-filters-json instead of a prompt or plan. hiring sources the live job postings for the roles you name, one row per posting (up to 5,000 per search), each with its posting URL, date and hiring company.")
236
+ .option("--source-filters-json <json-or-file>", 'Same nested filters as the free preview, e.g. {"employee_count":{"min":10}} or, with --source hiring, {"job":{"title":{"include":["Account Executive"]}}}.')
237
+ .option("--table-name <name>", "Name the table this run creates. Ignored when --table names an existing table, which must already exist.")
238
+ .argument("[prompt]", "Prompt text or @file (same as --prompt). The --prompt flag wins if both are given.")
239
+ .option("--prompt <text-or-file>", "Company-search prompt, or a path to a prompt file.")
240
+ .option("--plan-json <json-or-file>", "Plan JSON returned by companies search plan, or a path to a JSON file.")
241
+ .option("--route-id <id>", "Route id from the plan to execute.")
242
+ .option("--tool-id <tool>", "Tool id from the plan to execute.")
243
+ .option("--table <table>", "Existing table id or slug to receive rows. If omitted for live, Oxygen creates a table.")
244
+ .option("--project <project>", "Folder (project) id or slug the created table lands in. Ignored with --table. Defaults to the workspace default folder.")
245
+ .option("--upsert-key <column>", "Column key used for live upsert. Must match the plan upsert key, usually domain.")
246
+ .option("--mode <mode>", "dry_run or live. Defaults to dry_run.")
247
+ .option("--max-pages <n>", "Maximum provider pages to ingest. Defaults to the route estimate, and is itself capped by --target-count: asking for more pages than that many rows needs cannot add rows.")
248
+ .option("--max-credits <n>", "Required credit ceiling for live runs.")
249
+ .option("--target-count <n>", "How many rows to source, independently of the free preview size. Omit it to take every match, up to what one query reaches and a tenth of the spendable credits (target_count_default says which bound applied). Maps previews up to 20; page size varies by source. Total matches may be unavailable. This count bounds pagination: --max-pages cannot exceed the pages it needs, and a run that stops because of it reports stopped_by row_target.")
250
+ .option("--source-intent <intent>", "Override detected intent when planning from --prompt.")
251
+ .option("--filters-json <json-or-file>", "CompanySearchFilters JSON inline or a @file/path when planning from --prompt; wins over individual flags per top-level filter path.")
252
+ .option("--industries <csv>", "Comma-separated industries to include when planning from --prompt.")
253
+ .option("--exclude-industries <csv>", "Comma-separated industries to exclude when planning from --prompt.")
254
+ .option("--keywords <csv>", "Comma-separated keywords to include when planning from --prompt.")
255
+ .option("--exclude-keywords <csv>", "Comma-separated keywords to exclude when planning from --prompt.")
256
+ .option("--countries <csv>", "Comma-separated ISO 3166-1 alpha-2 country codes when planning from --prompt.")
257
+ .option("--employees <range>", "Employee count range when planning from --prompt: 20-200, 500+, or -50.")
258
+ .option("--funding-stages <csv>", "Comma-separated funding stages when planning from --prompt.")
259
+ .option("--technologies <csv>", "Comma-separated technologies to include when planning from --prompt.")
260
+ .option("--revenue <range>", "Annual revenue (USD) range when planning from --prompt.")
261
+ .option("--founded <range>", "Founded year range when planning from --prompt.")
262
+ .option("--lookalike <csv>", "Comma-separated lookalike company domains when planning from --prompt.")
263
+ .option("--estimate", "Run a free server-side preflight pass when planning from --prompt: resolves provider enums and a free count probe (zero credits).")
264
+ .option("--approved", "Required for live runs after inspecting dry-run output.")
265
+ .option("--json", "Print a JSON envelope.")
266
+ .action(async (promptArg, options) => {
267
+ await handleAsyncAction("companies search run", options, () => requestOxygen("/api/cli/companies/search/run", {
268
+ method: "POST",
269
+ body: readCompaniesSearchRunBody(options, promptArg),
270
+ }));
271
+ })))
272
+ .addCommand(new Command("enrich")
273
+ .description("Preview or run a company enrichment waterfall over an existing table.")
274
+ .addCommand(new Command("preview")
275
+ .description("Inspect missing company fields, provider routing, and credit estimates without provider calls.")
276
+ .argument("<table>", "Table id or slug.")
277
+ .option("--missing-fields <fields>", "Comma-separated fields to fill; accepts every key `oxygen find company --list-fields` prints, not just the identity set. Derived fields (description, founded_year, hq_country, funding_stage, latest_funding_round, investors, job_openings, parent_company, subsidiaries) are read from a payload the run already fetches and add no provider lane of their own.")
278
+ .option("--providers <providers>", "Comma-separated provider order pool. Defaults to scraper,blitzapi,apollo,peopledatalabs,theirstack,leadmagic.")
279
+ .option("--all", "Preview all rows.")
280
+ .option("--limit <n>", "Preview a limited row scope.")
281
+ .option("--row-ids <ids>", "Comma-separated row ids.")
282
+ .option("--filter-json <json>", "Filter object or array for row selection.")
283
+ .option("--selection-json <json>", "Raw table action selection JSON.")
284
+ .option("--json", "Print a JSON envelope.")
285
+ .action(async (table, options) => {
286
+ await handleAsyncAction("companies enrich preview", options, () => requestOxygen("/api/cli/company-enrichment/preview", {
287
+ method: "POST",
288
+ body: readCompaniesEnrichBody(table, options),
289
+ }));
290
+ }))
291
+ .addCommand(new Command("run")
292
+ .description("Queue a live company enrichment waterfall after explicit approval, or return a dry-run plan.")
293
+ .argument("<table>", "Table id or slug.")
294
+ .option("--missing-fields <fields>", "Comma-separated fields to fill.")
295
+ .option("--providers <providers>", "Comma-separated provider pool.")
296
+ .option("--mode <mode>", "dry_run or live. Defaults to live. dry_run returns the same plan as `companies enrich preview` and creates no column; only a live run creates the target columns it fills. To reuse a published workspace Function, inspect `functions list` and attach it with `functions bind` before running the column.")
297
+ .option("--max-credits <n>", "Required credit ceiling for live runs.")
298
+ .option("--approved", "Approve this live paid run after inspecting the preview.")
299
+ .option("--all", "Run on all rows.")
300
+ .option("--limit <n>", "Run on a limited row scope.")
301
+ .option("--row-ids <ids>", "Comma-separated row ids.")
302
+ .option("--filter-json <json>", "Filter object or array for row selection.")
303
+ .option("--selection-json <json>", "Raw table action selection JSON.")
304
+ .option("--force", "Re-run the waterfall audit column even when it already has a value.")
305
+ .option("--json", "Print a JSON envelope.")
306
+ .action(async (table, options) => {
307
+ await handleAsyncAction("companies enrich run", options, () => requestOxygen("/api/cli/company-enrichment/run", {
308
+ method: "POST",
309
+ body: readCompaniesEnrichBody(table, options),
310
+ }));
311
+ })));
312
+ }
@@ -0,0 +1,3 @@
1
+ import { ExtractedCommandActionHandler } from "../command-support.js";
2
+ import { Command } from "commander";
3
+ export declare function registerConnectionsFamily(program: Command, handleAsyncAction: ExtractedCommandActionHandler): void;
@@ -0,0 +1,39 @@
1
+ import { requestOxygen } from "../http-client.js";
2
+ import { readOption } from "../util.js";
3
+ import { Command } from "commander";
4
+ export function registerConnectionsFamily(program, handleAsyncAction) {
5
+ program.addCommand(new Command("connections")
6
+ .description("Read a LinkedIn account's 1st-degree connections (your network) into a Clay-like workspace table you can enrich, qualify, and enroll from. Imports run as a slow, durable background drip under a dedicated conservative read budget — they never burst and never starve the sequencer's own reads.")
7
+ .addCommand(new Command("import")
8
+ .description("Start (or re-arm) a connections import. Connections drip into a workspace table over many ticks; poll `connections status` to watch them accrue. The import keeps the table fresh by periodically re-walking the network in the background (new connections dedupe in). No messages are sent and no Oxygen credits are charged.")
9
+ .option("--account <ref>", "Sender account to import (sender id, connection id, or Unipile account id). Omit to import every active LinkedIn account.")
10
+ .option("--table <ref>", "Existing workspace table (id or slug) to import into. Omit to create or reuse a 'LinkedIn Connections' table.")
11
+ .option("--json", "Print a JSON envelope.")
12
+ .action(async (options) => {
13
+ await handleAsyncAction("connections import", options, () => {
14
+ const account = readOption(options.account);
15
+ const table = readOption(options.table);
16
+ return requestOxygen("/api/cli/linkedin/connections/import", {
17
+ method: "POST",
18
+ body: {
19
+ ...(account ? { account } : {}),
20
+ ...(table ? { table } : {}),
21
+ },
22
+ });
23
+ });
24
+ }))
25
+ .addCommand(new Command("status")
26
+ .description("Show connections-import progress (per-sender drip status, connections ingested, last error) plus a preview of the connections table and a deep-link to open it.")
27
+ .option("--account <ref>", "Scope to one sender account (sender id, connection id, or Unipile account id).")
28
+ .option("--json", "Print a JSON envelope.")
29
+ .action(async (options) => {
30
+ await handleAsyncAction("connections status", options, () => {
31
+ const account = readOption(options.account);
32
+ const params = new URLSearchParams();
33
+ if (account)
34
+ params.set("account", account);
35
+ const suffix = params.toString();
36
+ return requestOxygen(`/api/cli/linkedin/connections/status${suffix ? `?${suffix}` : ""}`);
37
+ });
38
+ })));
39
+ }
@@ -0,0 +1,3 @@
1
+ import { ExtractedCommandActionHandler } from "../command-support.js";
2
+ import { Command } from "commander";
3
+ export declare function registerContextFamily(program: Command, handleAsyncAction: ExtractedCommandActionHandler): void;