@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,96 @@
1
+ import { readCsvOption, readSelectedTranscriptExcerpt } from "../command-support.js";
2
+ import { requestOxygen } from "../http-client.js";
3
+ import { captureCurrentTranscript, collectFeedbackEnvironment, TranscriptCaptureError } from "../transcript.js";
4
+ import { readOption } from "../util.js";
5
+ import { OxygenError } from "@oxygen/shared/cli-result";
6
+ import { OXYGEN_VERSION } from "@oxygen/shared/version";
7
+ // Assemble the POST body for `oxygen feedback`. A transcript leaves the machine
8
+ // only after an explicit --include-transcript, --session-id, or --file choice;
9
+ // --no-transcript remains a backward-compatible veto. The local capture applies
10
+ // bounded, best-effort secret redaction, but the user still owns the decision to
11
+ // share broader session content. The environment snapshot never includes env
12
+ // variables or credential values.
13
+ function buildFeedbackBody(options) {
14
+ const message = readOption(options.message);
15
+ if (!message) {
16
+ throw new OxygenError("invalid_input", 'Provide your feedback with --message "...".', { exitCode: 1 });
17
+ }
18
+ const body = {
19
+ message,
20
+ category: readOption(options.category) ?? "feedback",
21
+ context: {
22
+ cli_version: OXYGEN_VERSION,
23
+ environment: collectFeedbackEnvironment(),
24
+ ...(readCsvOption(options.deepLinks).length ? { deep_links: readCsvOption(options.deepLinks) } : {}),
25
+ ...(readOption(options.operation) ? { operation: readOption(options.operation) } : {}),
26
+ ...(readOption(options.errorCode) ? { error_code: readOption(options.errorCode) } : {}),
27
+ ...(readCsvOption(options.runIds).length ? { run_ids: readCsvOption(options.runIds) } : {}),
28
+ ...(readCsvOption(options.tableIds).length ? { table_ids: readCsvOption(options.tableIds) } : {}),
29
+ ...(readOption(options.slackPermalink) ? { slack_permalink: readOption(options.slackPermalink) } : {}),
30
+ ...(readOption(options.slackChannelId) ? { slack_channel_id: readOption(options.slackChannelId) } : {}),
31
+ ...(readOption(options.customerWorkspace) ? { customer_workspace: readOption(options.customerWorkspace) } : {}),
32
+ },
33
+ };
34
+ if (readOption(options.severity))
35
+ body.severity = readOption(options.severity);
36
+ const sessionId = readOption(options.sessionId);
37
+ const file = readOption(options.file);
38
+ if (options.transcript !== false && options.transcriptText !== undefined) {
39
+ if (sessionId || file || options.includeTranscript) {
40
+ throw new OxygenError("invalid_input", "Choose --transcript-text or one local transcript option.", { exitCode: 1 });
41
+ }
42
+ body.transcript_text = readSelectedTranscriptExcerpt(options.transcriptText);
43
+ }
44
+ const includeTranscript = options.transcript !== false &&
45
+ (options.includeTranscript === true || Boolean(sessionId) || Boolean(file));
46
+ if (includeTranscript) {
47
+ try {
48
+ const captured = captureCurrentTranscript({
49
+ sessionId,
50
+ file,
51
+ });
52
+ if (captured) {
53
+ const { path: _path, ...payload } = captured;
54
+ body.transcript = payload;
55
+ }
56
+ else {
57
+ process.stderr.write("note: no local chat transcript found; sending your note without one.\n");
58
+ }
59
+ }
60
+ catch (error) {
61
+ if (error instanceof TranscriptCaptureError) {
62
+ throw new OxygenError("transcript_not_found", error.message, { exitCode: 1 });
63
+ }
64
+ throw error;
65
+ }
66
+ }
67
+ return body;
68
+ }
69
+ export function registerFeedbackFamily(program, handleAsyncAction) {
70
+ program
71
+ .command("feedback")
72
+ .description("Send feedback or a bug report to the OXYGEN team. This is the same immediate, zero-credit canonical Plain conversation write as `support chat`; transcripts are never attached unless explicitly requested.")
73
+ .option("-m, --message <message>", "Your feedback or bug report.")
74
+ .option("--severity <severity>", "low | normal | high. Defaults to normal.")
75
+ .option("--category <category>", "Optional category label. Defaults to 'feedback'.")
76
+ .option("--deep-links <urls>", "Comma-separated workspace links to include in the report context.")
77
+ .option("--operation <operation>", "The operation that failed.")
78
+ .option("--error-code <code>", "Error envelope code from the failed operation.")
79
+ .option("--run-ids <ids>", "Comma-separated affected run ids.")
80
+ .option("--table-ids <ids>", "Comma-separated affected table ids.")
81
+ .option("--slack-permalink <url>", "Customer-selected Slack message permalink for a forwarded report.")
82
+ .option("--slack-channel-id <id>", "Slack channel associated with the forwarded report.")
83
+ .option("--customer-workspace <label>", "Workspace name or slug associated with a forwarded report.")
84
+ .option("--transcript-text <text>", "Explicitly attach a selected excerpt (1-200,000 bytes) after best-effort secret redaction; exclusive with local transcript options.")
85
+ .option("--session-id <id>", "Explicitly attach a specific local session transcript by id.")
86
+ .option("--file <path>", "Explicitly attach a specific local transcript file.")
87
+ .option("--include-transcript", "Explicitly attach the most recently active local session after best-effort secret redaction. Review it for sensitive customer data first.")
88
+ .option("--no-transcript", "Deprecated safety override: send the note without a transcript.")
89
+ .option("--json", "Print a JSON envelope.")
90
+ .action(async (options) => {
91
+ await handleAsyncAction("feedback send", options, () => requestOxygen("/api/cli/feedback", {
92
+ method: "POST",
93
+ body: buildFeedbackBody(options),
94
+ }));
95
+ });
96
+ }
@@ -0,0 +1,3 @@
1
+ import { ExtractedCommandActionHandler } from "../command-support.js";
2
+ import { Command } from "commander";
3
+ export declare function registerFeedsFamily(program: Command, handleAsyncAction: ExtractedCommandActionHandler): void;
@@ -0,0 +1,486 @@
1
+ import { parseJsonObject, readPositiveInt, readRecordString } from "../cli-values.js";
2
+ import { CADENCE_FLAG_DESCRIPTION, readCsvOption, readFileIfPresent, readPositiveNumber, tableWebhookDeliveriesPath } from "../command-support.js";
3
+ import { addContractFlags, readContractFlags } from "../contract-flags.js";
4
+ import { addFeedReadFlags, feedReadWindowParams } from "../feed-read-flags.js";
5
+ import { requestOxygen } from "../http-client.js";
6
+ import { isRecord, readOption } from "../util.js";
7
+ import { OxygenError } from "@oxygen/shared/cli-result";
8
+ import { TABLE_FEED_UPDATE_CONTRACT, tableFeedUpdateIssues } from "@oxygen/shared/contracts/table-feed-update";
9
+ import { Command } from "commander";
10
+ function feedsListPath(options, tableArgument) {
11
+ const params = feedReadWindowParams(options);
12
+ const table = readOption(tableArgument) ?? readOption(options.table);
13
+ const transport = readOption(options.transport);
14
+ const status = readOption(options.status);
15
+ const state = readOption(options.state);
16
+ const limit = readPositiveInt(options.limit);
17
+ if (table)
18
+ params.set("table", table);
19
+ if (transport)
20
+ params.set("transport", transport);
21
+ if (status)
22
+ params.set("status", status);
23
+ if (state)
24
+ params.set("state", state);
25
+ if (limit)
26
+ params.set("limit", String(limit));
27
+ if (options.includeCursor)
28
+ params.set("include", "cursor");
29
+ const query = params.toString();
30
+ return `/api/cli/tables/feeds${query ? `?${query}` : ""}`;
31
+ }
32
+ function feedsGetPath(feed, options) {
33
+ const params = feedReadWindowParams(options, true);
34
+ params.set("feed", feed);
35
+ const include = [options.includeCursor ? "cursor" : null, options.includeRoute ? "route" : null].filter(Boolean);
36
+ if (include.length)
37
+ params.set("include", include.join(","));
38
+ return `/api/cli/tables/feeds?${params.toString()}`;
39
+ }
40
+ // The bind body mirrors the route's reader: everything the provider is asked for
41
+ // lives under `route`, everything about the cadence under `sync`. The two stay
42
+ // separated because the route re-derives every spend-relevant number (credits per
43
+ // page, incremental mode) from route.tool_id and never from what we send.
44
+ function readFeedBindBody(table, options) {
45
+ const toolIds = readCsvOption(options.toolIds);
46
+ const maxPages = readPositiveInt(options.maxPages);
47
+ const every = readOption(options.every);
48
+ const maxCreditsPerCycle = readPositiveNumber(options.maxCredits);
49
+ const maxRowsPerCycle = readPositiveInt(options.maxRows);
50
+ const filters = options.filtersJson ? parseJsonObject(readFileIfPresent(options.filtersJson)) : null;
51
+ return {
52
+ table,
53
+ transport: "pull",
54
+ feed_kind: options.kind,
55
+ route: {
56
+ tool_id: options.toolId,
57
+ // assertPullFeedBindArguments guarantees --request-json before this reader runs.
58
+ request: parseJsonObject(readFileIfPresent(options.requestJson)),
59
+ ...(toolIds.length > 0 ? { tool_ids: toolIds } : {}),
60
+ ...(readOption(options.rowsPath) ? { rows_path: readOption(options.rowsPath) } : {}),
61
+ ...(options.rowMappingJson ? { row_mapping: parseJsonObject(readFileIfPresent(options.rowMappingJson)) } : {}),
62
+ ...(readOption(options.cursorPath) ? { cursor_path: readOption(options.cursorPath) } : {}),
63
+ ...(readOption(options.cursorRequestKey) ? { cursor_request_key: readOption(options.cursorRequestKey) } : {}),
64
+ ...(maxPages !== undefined ? { max_pages: maxPages } : {}),
65
+ ...(readOption(options.upsertKey) ? { upsert_key: readOption(options.upsertKey) } : {}),
66
+ },
67
+ sync: {
68
+ ...(every ? { every } : {}),
69
+ ...(maxCreditsPerCycle !== undefined ? { max_credits_per_cycle: maxCreditsPerCycle } : {}),
70
+ ...(maxRowsPerCycle !== undefined ? { max_rows_per_cycle: maxRowsPerCycle } : {}),
71
+ },
72
+ ...(readOption(options.name) ? { name: readOption(options.name) } : {}),
73
+ ...(filters ? { filters } : {}),
74
+ ...(readOption(options.intentId) ? { intent_id: readOption(options.intentId) } : {}),
75
+ ...(readOption(options.inputShape) ? { input_shape: readOption(options.inputShape) } : {}),
76
+ ...(readOption(options.routeId) ? { route_id: readOption(options.routeId) } : {}),
77
+ ...(readOption(options.registryVersion) ? { registry_version: readOption(options.registryVersion) } : {}),
78
+ ...(options.sourcePrompt ? { source_prompt: readFileIfPresent(options.sourcePrompt) } : {}),
79
+ ...(options.approved ? { approved: true } : {}),
80
+ };
81
+ }
82
+ // `feeds bind` is one verb over two transports, told apart by --schema-json. Each
83
+ // list names the flags that only mean something on one side, in help order, so a
84
+ // flag from the other transport is refused by name instead of silently dropped.
85
+ const FEED_BIND_PULL_ONLY_FLAGS = [
86
+ ["kind", "--kind"],
87
+ ["toolId", "--tool-id"],
88
+ ["toolIds", "--tool-ids"],
89
+ ["requestJson", "--request-json"],
90
+ ["rowsPath", "--rows-path"],
91
+ ["rowMappingJson", "--row-mapping-json"],
92
+ ["cursorPath", "--cursor-path"],
93
+ ["cursorRequestKey", "--cursor-request-key"],
94
+ ["maxPages", "--max-pages"],
95
+ ["every", "--every"],
96
+ ["maxCredits", "--max-credits"],
97
+ ["maxRows", "--max-rows"],
98
+ ["filtersJson", "--filters-json"],
99
+ ["intentId", "--intent-id"],
100
+ ["inputShape", "--input-shape"],
101
+ ["routeId", "--route-id"],
102
+ ["registryVersion", "--registry-version"],
103
+ ["sourcePrompt", "--source-prompt"],
104
+ ["approved", "--approved"],
105
+ ];
106
+ const FEED_BIND_PUSH_ONLY_FLAGS = [
107
+ ["create", "--create"],
108
+ ["project", "--project"],
109
+ ["itemsPath", "--items-path"],
110
+ ["eventIdPath", "--event-id-path"],
111
+ ["eventTypePath", "--event-type-path"],
112
+ ["occurredAtPath", "--occurred-at-path"],
113
+ ["authMode", "--auth-mode"],
114
+ ];
115
+ // The third mode: a WEB INTENT feed, named by source instead of configured.
116
+ // OXYGEN's own receiver normalizes every reveal, so there is nothing to declare —
117
+ // which is also why this list is the accepted values rather than a free string.
118
+ const FEED_BIND_WEB_INTENT_SOURCES = ["rb2b"];
119
+ // Everything a web-intent feed cannot carry, in help order. --create, --name and
120
+ // --upsert-key are its only companions, so a flag belonging to either other mode
121
+ // is refused here by name, one round trip before the server would refuse it.
122
+ const FEED_BIND_WEB_INTENT_REFUSED_FLAGS = [
123
+ ["schemaJson", "--schema-json"],
124
+ ...FEED_BIND_PULL_ONLY_FLAGS,
125
+ ...FEED_BIND_PUSH_ONLY_FLAGS.filter(([key]) => key !== "create" && key !== "project"),
126
+ ];
127
+ // Pull mode keeps the contract commander enforced when --kind/--tool-id/
128
+ // --request-json were requiredOptions and <table> was required: same messages, same
129
+ // order (mandatory options before the argument), same stderr + exit 1 path. They
130
+ // stopped being commander-required only because a push feed must not carry them.
131
+ function assertPullFeedBindArguments(table, options, command) {
132
+ for (const [key, flag] of FEED_BIND_PUSH_ONLY_FLAGS) {
133
+ if (options[key] !== undefined) {
134
+ command.error(`error: ${flag} belongs to a push feed; add --schema-json <json-or-file> to bind one, or drop ${flag} to bind a pull feed.`);
135
+ }
136
+ }
137
+ const required = [
138
+ ["kind", "--kind <kind>"],
139
+ ["toolId", "--tool-id <tool>"],
140
+ ["requestJson", "--request-json <json-or-file>"],
141
+ ];
142
+ for (const [key, flags] of required) {
143
+ if (options[key] === undefined) {
144
+ command.error(`error: required option '${flags}' not specified`, { code: "commander.missingMandatoryOptionValue" });
145
+ }
146
+ }
147
+ if (table === undefined) {
148
+ command.error("error: missing required argument 'table'", { code: "commander.missingArgument" });
149
+ }
150
+ }
151
+ // The push bind body mirrors the route's declared-schema reader: transport push, the
152
+ // schema as an OBJECT (the server validates it against the table's real columns),
153
+ // and exactly one of an existing table or create_table. Pull fields are refused here
154
+ // by flag name, before any request, because the server would 400 on them anyway.
155
+ function readFeedPushBindBody(table, options) {
156
+ for (const [key, flag] of FEED_BIND_PULL_ONLY_FLAGS) {
157
+ if (options[key] !== undefined) {
158
+ throw new OxygenError("invalid_table_feed", `${flag} belongs to a pull feed; a push feed is filled by whatever posts to its endpoint. Drop ${flag}, or drop --schema-json to bind a pull feed.`, { details: { flag }, exitCode: 1 });
159
+ }
160
+ }
161
+ const schemaSource = readOption(options.schemaJson);
162
+ if (!schemaSource) {
163
+ throw new OxygenError("invalid_json", "--schema-json needs a JSON Schema object, inline or a path to a JSON file.", { exitCode: 1 });
164
+ }
165
+ const rowSchema = parseJsonObject(readFileIfPresent(schemaSource));
166
+ const tableRef = readOption(table);
167
+ const create = readOption(options.create);
168
+ if (readOption(options.project) && !create)
169
+ throw new OxygenError("invalid_request", "--project requires --create; the existing table keeps its project.", { exitCode: 2 });
170
+ if (tableRef && create) {
171
+ throw new OxygenError("invalid_request", "Pass a table or --create <name>, not both.", { exitCode: 1 });
172
+ }
173
+ if (!tableRef && !create) {
174
+ throw new OxygenError("invalid_request", "Pass the table id or slug that receives the rows, or --create <name> to create the table from the schema.", { exitCode: 1 });
175
+ }
176
+ const authMode = readOption(options.authMode);
177
+ if (authMode && authMode !== "secret" && authMode !== "none") {
178
+ throw new OxygenError("invalid_request", "--auth-mode must be secret or none.", { details: { auth_mode: authMode }, exitCode: 1 });
179
+ }
180
+ return {
181
+ transport: "push",
182
+ row_schema: rowSchema,
183
+ ...(tableRef ? { table: tableRef } : { create_table: { name: create, ...(readOption(options.project) ? { project: readOption(options.project) } : {}) } }),
184
+ ...(readOption(options.name) ? { name: readOption(options.name) } : {}),
185
+ ...(readOption(options.upsertKey) ? { upsert_key: readOption(options.upsertKey) } : {}),
186
+ ...(readOption(options.itemsPath) ? { items_path: readOption(options.itemsPath) } : {}),
187
+ ...(readOption(options.eventIdPath) ? { event_id_path: readOption(options.eventIdPath) } : {}),
188
+ ...(readOption(options.eventTypePath) ? { event_type_path: readOption(options.eventTypePath) } : {}),
189
+ ...(readOption(options.occurredAtPath) ? { occurred_at_path: readOption(options.occurredAtPath) } : {}),
190
+ ...(authMode ? { auth_mode: authMode } : {}),
191
+ };
192
+ }
193
+ // The web-intent bind body: transport push plus the source as the feed kind, and
194
+ // exactly one of an existing table or create_table. Nothing else is accepted —
195
+ // the reveal shape, the address, and the (zero) cost are all fixed by OXYGEN's own
196
+ // receiver — so every other flag is refused here by name, before any request.
197
+ function readFeedWebIntentBindBody(table, options) {
198
+ const source = readOption(options.source);
199
+ if (!source || !FEED_BIND_WEB_INTENT_SOURCES.includes(source)) {
200
+ throw new OxygenError("invalid_request", `--source must be one of: ${FEED_BIND_WEB_INTENT_SOURCES.join(", ")}.`, { details: { source: source ?? null, accepted: [...FEED_BIND_WEB_INTENT_SOURCES] }, exitCode: 1 });
201
+ }
202
+ for (const [key, flag] of FEED_BIND_WEB_INTENT_REFUSED_FLAGS) {
203
+ if (options[key] !== undefined) {
204
+ throw new OxygenError("invalid_table_feed", `${flag} does not apply to a --source ${source} feed: its rows come from OXYGEN's own website-visit receiver with a fixed shape. Drop ${flag}, or drop --source to bind a pull or --schema-json feed.`, { details: { flag, source }, exitCode: 1 });
205
+ }
206
+ }
207
+ const tableRef = readOption(table);
208
+ const create = readOption(options.create);
209
+ if (readOption(options.project) && !create)
210
+ throw new OxygenError("invalid_request", "--project requires --create; the existing table keeps its project.", { exitCode: 2 });
211
+ if (tableRef && create) {
212
+ throw new OxygenError("invalid_request", "Pass a table or --create <name>, not both.", { exitCode: 1 });
213
+ }
214
+ if (!tableRef && !create) {
215
+ throw new OxygenError("invalid_request", "Pass the table id or slug that receives the reveals, or --create <name> to create the table with the fixed reveal columns.", { exitCode: 1 });
216
+ }
217
+ return {
218
+ transport: "push",
219
+ feed_kind: source,
220
+ ...(tableRef ? { table: tableRef } : { create_table: { name: create, ...(readOption(options.project) ? { project: readOption(options.project) } : {}) } }),
221
+ ...(readOption(options.name) ? { name: readOption(options.name) } : {}),
222
+ ...(readOption(options.upsertKey) ? { upsert_key: readOption(options.upsertKey) } : {}),
223
+ };
224
+ }
225
+ // Human receipt for a web-intent bind. There is no secret and no webhook URL to
226
+ // print — this feed has no address — so what a reader needs instead is the table
227
+ // the reveals land in, that they cost nothing, and the one precondition that
228
+ // decides whether any row ever appears.
229
+ function writeFeedWebIntentBindReceipt(data) {
230
+ if (!isRecord(data))
231
+ return;
232
+ const feed = isRecord(data.feed) ? data.feed : {};
233
+ const table = isRecord(feed.table) ? feed.table : {};
234
+ const lines = [];
235
+ const feedId = readRecordString(feed, "id");
236
+ lines.push(`Web intent feed "${readRecordString(feed, "name") ?? "unnamed"}"${feedId ? ` (${feedId})` : ""} bound: RB2B website-visit reveals now land as rows.`);
237
+ const tableName = readRecordString(table, "display_name");
238
+ const tableSlug = readRecordString(table, "slug") ?? readRecordString(table, "id");
239
+ if (tableName || tableSlug) {
240
+ lines.push(` Table: ${tableName ?? tableSlug}${tableName && tableSlug ? ` (${tableSlug})` : ""}`);
241
+ }
242
+ const upsertKey = readRecordString(feed, "upsert_key");
243
+ if (upsertKey)
244
+ lines.push(` Upserted on: ${upsertKey}`);
245
+ lines.push(" Cost: 0 credits per reveal. Every reveal also stays a website_visit Signal.");
246
+ lines.push(" Rows only start once RB2B is connected to this workspace; there is no self-serve connect step yet.");
247
+ const nextStep = readRecordString(data, "next_step");
248
+ if (nextStep)
249
+ lines.push(` Next: ${nextStep}`);
250
+ const webUrl = readRecordString(data, "web_url") ?? readRecordString(data, "deepLink");
251
+ if (webUrl)
252
+ lines.push(` ${webUrl}`);
253
+ process.stderr.write(`${lines.join("\n")}\n`);
254
+ }
255
+ // Human receipt for a push-feed bind, on stderr ahead of the payload (stdout stays
256
+ // the machine JSON, like every other command). The secret is returned exactly once —
257
+ // only its hash is stored — so it is printed with the warning right next to it.
258
+ function writeFeedPushBindReceipt(data) {
259
+ if (!isRecord(data))
260
+ return;
261
+ const feed = isRecord(data.feed) ? data.feed : {};
262
+ const table = isRecord(feed.table) ? feed.table : {};
263
+ const lines = [];
264
+ const feedId = readRecordString(feed, "id");
265
+ lines.push(`Push feed "${readRecordString(feed, "name") ?? "unnamed"}"${feedId ? ` (${feedId})` : ""} bound: typed or rejected.`);
266
+ const tableName = readRecordString(table, "display_name");
267
+ const tableSlug = readRecordString(table, "slug") ?? readRecordString(table, "id");
268
+ if (tableName || tableSlug) {
269
+ lines.push(` Table: ${tableName ?? tableSlug}${tableName && tableSlug ? ` (${tableSlug})` : ""}`);
270
+ }
271
+ const webhookUrl = readRecordString(data, "webhookUrl") ?? readRecordString(feed, "endpoint_url");
272
+ if (webhookUrl)
273
+ lines.push(` Webhook URL: ${webhookUrl}`);
274
+ const secret = readRecordString(data, "secret");
275
+ if (secret) {
276
+ lines.push(` Secret: ${secret}`);
277
+ lines.push(" This secret is shown once and never again: store it now. Senders pass it in the x-oxygen-table-webhook-secret header.");
278
+ }
279
+ else {
280
+ lines.push(" No secret (auth mode none): anyone holding the webhook URL can post.");
281
+ }
282
+ const schema = isRecord(data.row_schema) ? data.row_schema : isRecord(feed.row_schema) ? feed.row_schema : null;
283
+ const properties = schema && isRecord(schema.properties) ? schema.properties : {};
284
+ const required = new Set(schema && Array.isArray(schema.required) ? schema.required.filter((entry) => typeof entry === "string") : []);
285
+ const names = Object.keys(properties);
286
+ lines.push(` Row schema (${names.length} propert${names.length === 1 ? "y" : "ies"}; a delivery that does not match is refused with 422):`);
287
+ for (const name of names) {
288
+ const property = properties[name];
289
+ const type = isRecord(property)
290
+ ? Array.isArray(property.type)
291
+ ? property.type.filter((entry) => typeof entry === "string").join("|")
292
+ : typeof property.type === "string" ? property.type : "any"
293
+ : "any";
294
+ lines.push(` ${name}: ${type || "any"}${required.has(name) ? " (required)" : ""}`);
295
+ }
296
+ if (schema?.additionalProperties === false)
297
+ lines.push(" no other properties allowed");
298
+ lines.push(" Cost: 0 credits per delivery.");
299
+ const nextStep = readRecordString(data, "next_step");
300
+ if (nextStep)
301
+ lines.push(` Next: ${nextStep}`);
302
+ const webUrl = readRecordString(data, "web_url") ?? readRecordString(data, "deepLink");
303
+ if (webUrl)
304
+ lines.push(` ${webUrl}`);
305
+ process.stderr.write(`${lines.join("\n")}\n`);
306
+ }
307
+ export function registerFeedsFamily(program, handleAsyncAction) {
308
+ program
309
+ .command("feeds")
310
+ .description("Live tables: bind a scheduled provider harvest (a FEED) to a table so it keeps filling itself on a cadence, then inspect, pause, resume, run, or unbind it. A feed is part of Tables, not a new primitive — `oxygen tables webhook` is the inbound (push) half of the same ledger.")
311
+ .addCommand(addFeedReadFlags(new Command("list")
312
+ .description("List table feeds for the workspace, newest first. Optional summary/full windows resume with next-offset; exact settings use feeds get --projection value. Read-only.")
313
+ .argument("[table]", "Optional table id or slug to filter by.")
314
+ .option("--table <table>", "Table id or slug. Equivalent to the positional argument.")
315
+ .option("--transport <transport>", "pull (scheduled provider harvest) or push (inbound webhook).")
316
+ .option("--status <status>", "Filter by active or disabled.")
317
+ .option("--state <state>", "Filter by run state: idle, running, error, or exhausted.")
318
+ .option("--limit <n>", "Maximum feeds to return (1-200). Defaults to 50.")
319
+ .option("--include-cursor", "Include the opaque provider page token in the cursor block.")
320
+ .option("--json", "Print a JSON envelope.")
321
+ .action((table, options) => handleAsyncAction("feeds list", options, () => requestOxygen(feedsListPath(options, table))))))
322
+ .addCommand(addFeedReadFlags(new Command("get")
323
+ .description("Get one table feed. Optional summary or lossless value JSON windows recover every public setting; resume value-cursor with the same feed/path/include-cursor. Read-only.")
324
+ .argument("<feed>", "Feed id or endpoint id.")
325
+ .option("--include-route", "Include the complete frozen request and fallback lanes for an in-place update.")
326
+ .option("--include-cursor", "Include the opaque provider page token in the cursor block.")
327
+ .option("--json", "Print a JSON envelope.")
328
+ .action((feed, options) => handleAsyncAction("feeds get", options, () => requestOxygen(feedsGetPath(feed, options)))), true))
329
+ .addCommand(new Command("bind")
330
+ .description("Bind a feed to a table: a scheduled provider harvest (pull), with --schema-json a typed-or-rejected push feed, or with --source rb2b the web-intent feed landing RB2B website-visit reveals as rows. A pull feed freezes the provider request into an existing table, then optionally arms a cadence so the table refills itself; arming a cadence on a paid provider is a STANDING spend grant — it requires --every, --max-credits, and --approved together, and without --every the feed is stored disarmed until you set one. A push feed is free: it returns a webhook URL and a secret shown once, refuses a delivery that does not match the schema with HTTP 422 (recorded in `oxygen feeds deliveries`, nothing written), and can create its table from the schema with --create <name>. `--source rb2b` is the web intent feed: OXYGEN already receives your RB2B website-visit reveals and records each one as a website_visit Signal, and this feed ALSO lands them as rows you can enrich, score, and sequence. It has no address, no schema, and no cadence to configure — the reveal shape is fixed — so binding is free and each reveal costs 0 credits on arrival. If RB2B is not connected to this workspace yet, rows only start once it is; there is no self-serve connect step yet.")
331
+ .argument("[table]", "Table id or slug that receives the rows. Required for a pull feed; a push or web-intent feed takes it or --create <name>.")
332
+ .option("--kind <kind>", "Pull feed, required. Feed kind: company_search or signal_search.")
333
+ .option("--tool-id <tool>", "Pull feed, required. Provider tool id the cycle calls, e.g. blitzapi.job_search.")
334
+ .option("--request-json <json-or-file>", "Pull feed, required. Provider request JSON for the tool, inline or a path to a JSON file. Take it from the plan route's `request`.")
335
+ .option("--schema-json <json-or-file>", "Bind a PUSH feed instead: the JSON Schema (an object with type, properties, required, additionalProperties) every delivered row must match, inline or a path to a JSON file.")
336
+ .option("--source <source>", `Bind a WEB INTENT push feed instead: ${FEED_BIND_WEB_INTENT_SOURCES.join(", ")} (rb2b = the website-visit reveals OXYGEN already receives for this workspace, also kept as website_visit Signals). Fixed row shape, no schema, no cadence, 0 credits per reveal. Takes a table or --create <name>, plus --name and --upsert-key.`)
337
+ .option("--create <name>", "Push or web-intent feed only. Create a new table with this name — its columns derived from --schema-json, or the fixed reveal columns for --source rb2b — instead of binding an existing table.")
338
+ .option("--project <project>", "Workspace project id or slug for the new --create table.")
339
+ .option("--items-path <path>", "Push feed only. Dot path to an array of row objects in the delivered payload.")
340
+ .option("--event-id-path <path>", "Push feed only. Dot path to the event id. Defaults to event_id, eventId, id, or a payload hash.")
341
+ .option("--event-type-path <path>", "Push feed only. Dot path to the event type. Defaults to type or event.")
342
+ .option("--occurred-at-path <path>", "Push feed only. Dot path to the event timestamp.")
343
+ .option("--auth-mode <mode>", "Push feed only. secret (default: senders include the returned secret in x-oxygen-table-webhook-secret) or none.")
344
+ .option("--tool-ids <csv>", "Comma-separated cascade of tool ids to try in order. Defaults to just --tool-id.")
345
+ .option("--rows-path <path>", "Dot path to the row array in the provider response.")
346
+ .option("--row-mapping-json <json-or-file>", "Row mapping JSON: table column key -> provider field path.")
347
+ .option("--cursor-path <path>", "Dot path to the next-page cursor in the provider response.")
348
+ .option("--cursor-request-key <key>", "Request key that receives the next cursor.")
349
+ .option("--max-pages <n>", "Maximum provider pages per cycle. Defaults to 1.")
350
+ .option("--upsert-key <key>", "Column key rows are upserted on, so a cycle or a repeated delivery updates instead of duplicating.")
351
+ .option("--name <name>", "Display name for the feed.")
352
+ .option("--every <sugar>", CADENCE_FLAG_DESCRIPTION)
353
+ .option("--max-credits <n>", "Credit ceiling PER sync cycle. Required with --approved to arm a cadence on a paid provider.")
354
+ .option("--max-rows <n>", "Advisory row ceiling per cycle, recorded on every cycle's run provenance.")
355
+ .option("--filters-json <json-or-file>", "Typed filters JSON recorded on the feed for provenance and re-planning.")
356
+ .option("--intent-id <id>", "Routing-registry intent id from the plan.")
357
+ .option("--input-shape <shape>", "Routing-registry input shape from the plan, e.g. market or watch_list.")
358
+ .option("--route-id <id>", "Route id from the plan this feed was bound from.")
359
+ .option("--registry-version <version>", "Routing-registry revision from the plan, retained with the frozen feed provenance.")
360
+ .option("--source-prompt <text-or-file>", "The prompt this feed was planned from, or a path to a prompt file.")
361
+ .option("--approved", "Approve the recurring paid pull. Required with --every and --max-credits on a paid provider.")
362
+ .option("--json", "Print a JSON envelope.")
363
+ .addHelpText("after", `
364
+ Examples:
365
+ oxygen feeds bind signal-events --kind signal_search --tool-id blitzapi.job_search --request-json ./request.json --every daily@9 --max-credits 25 --approved
366
+ oxygen feeds bind --create "Inbound leads" --schema-json '{"type":"object","properties":{"email":{"type":"string"},"score":{"type":"number"}},"required":["email"],"additionalProperties":false}' --upsert-key email
367
+ oxygen feeds bind --source rb2b --create "Website visitors"
368
+ `)
369
+ .action((table, options, command) => {
370
+ const webIntent = options.source !== undefined;
371
+ const push = options.schemaJson !== undefined;
372
+ // Pull usage errors keep commander's exact wording and path (stderr, exit 1),
373
+ // raised before any request exactly as the former requiredOption/<table> did.
374
+ if (!push && !webIntent)
375
+ assertPullFeedBindArguments(table, options, command);
376
+ return handleAsyncAction("feeds bind", options, async () => {
377
+ if (webIntent) {
378
+ const data = await requestOxygen("/api/cli/tables/feeds", {
379
+ method: "POST",
380
+ body: readFeedWebIntentBindBody(table, options),
381
+ });
382
+ // Nothing is withheld from stdout here (a web-intent feed has no
383
+ // secret and no address); the receipt exists so a human reads the
384
+ // table, the 0-credit cost, and what has to be true for rows to land.
385
+ if (!options.json)
386
+ writeFeedWebIntentBindReceipt(data);
387
+ return data;
388
+ }
389
+ if (!push) {
390
+ return requestOxygen("/api/cli/tables/feeds", {
391
+ method: "POST",
392
+ body: readFeedBindBody(table, options),
393
+ });
394
+ }
395
+ const data = await requestOxygen("/api/cli/tables/feeds", {
396
+ method: "POST",
397
+ body: readFeedPushBindBody(table, options),
398
+ });
399
+ // Ahead of the payload, and only for a human: the secret is shown once,
400
+ // so it must not scroll away inside the JSON. A machine caller reads it
401
+ // off the envelope.
402
+ if (!options.json)
403
+ writeFeedPushBindReceipt(data);
404
+ return data;
405
+ });
406
+ }))
407
+ .addCommand(addContractFlags(new Command("update")
408
+ .description("Update an existing feed in place. Inspect feeds get --include-route first. Source replacement resets its cursor; cadence, cap and source changes keep native standing authorization and budget checks. Configuration spends nothing.")
409
+ .argument("<feed>", "Feed id or endpoint id.")
410
+ .option("--json", "Print a JSON envelope."), TABLE_FEED_UPDATE_CONTRACT, {
411
+ feed: { omit: true }, sync: { flatten: true },
412
+ "sync.max_credits_per_cycle": { flag: "max-credits" },
413
+ "sync.max_rows_per_cycle": { flag: "max-rows" },
414
+ approved: { noNegate: true },
415
+ })
416
+ .action((feed, options) => handleAsyncAction("feeds update", options, () => {
417
+ const body = { ...readContractFlags(options, TABLE_FEED_UPDATE_CONTRACT, {
418
+ feed: { omit: true }, sync: { flatten: true },
419
+ "sync.max_credits_per_cycle": { flag: "max-credits" },
420
+ "sync.max_rows_per_cycle": { flag: "max-rows" }, approved: { noNegate: true },
421
+ }), feed };
422
+ const errors = tableFeedUpdateIssues(body);
423
+ if (errors.length)
424
+ throw new OxygenError("invalid_request", errors.join(" "), { exitCode: 1 });
425
+ return requestOxygen("/api/cli/tables/feeds", { method: "PATCH", body });
426
+ })))
427
+ .addCommand(new Command("pause")
428
+ .description("Pause a feed: the cron stays armed but every cycle it fires is refused, so nothing is fetched and nothing is billed until you resume. Free.")
429
+ .argument("<feed>", "Feed id or endpoint id.")
430
+ .option("--json", "Print a JSON envelope.")
431
+ .action((feed, options) => handleAsyncAction("feeds pause", options, () => requestOxygen("/api/cli/tables/feeds", {
432
+ method: "PATCH",
433
+ body: { feed, status: "disabled" },
434
+ }))))
435
+ .addCommand(new Command("resume")
436
+ .description("Resume a feed: re-activates it AND clears an error/exhausted stop, so the next cron tick can claim it again. Re-uses the cadence and spend cap already approved at bind time. Free.")
437
+ .argument("<feed>", "Feed id or endpoint id.")
438
+ .option("--json", "Print a JSON envelope.")
439
+ .action((feed, options) => handleAsyncAction("feeds resume", options, () => requestOxygen("/api/cli/tables/feeds", {
440
+ method: "PATCH",
441
+ // A feed that stopped itself (run_state error/exhausted) is
442
+ // deliberately unclaimable by the cron. Clearing the state here is
443
+ // the only way back, so `resume` does both or it is not a resume.
444
+ body: { feed, status: "active", run_state: "idle" },
445
+ }))))
446
+ .addCommand(new Command("run")
447
+ .description("Run one sync cycle now through the feed's own cron workflow (one harvest path, one cursor). --mode dry_run (default) renders the exact next-cycle provider request with the cursor or watermark already injected, for free. --mode live spends credits and requires --approved and --max-credits.")
448
+ .argument("<feed>", "Feed id or endpoint id.")
449
+ .option("--mode <mode>", "dry_run or live. Defaults to dry_run.")
450
+ .option("--max-credits <n>", "Required credit ceiling for a live cycle.")
451
+ .option("--approved", "Required for a live cycle after inspecting the dry run.")
452
+ .option("--json", "Print a JSON envelope.")
453
+ .action((feed, options) => {
454
+ const maxCredits = readPositiveNumber(options.maxCredits);
455
+ return handleAsyncAction("feeds run", options, () => requestOxygen("/api/cli/tables/feeds/run", {
456
+ method: "POST",
457
+ body: {
458
+ feed,
459
+ ...(readOption(options.mode) ? { mode: readOption(options.mode) } : {}),
460
+ ...(maxCredits !== undefined ? { max_credits: maxCredits } : {}),
461
+ ...(options.approved ? { approved: true } : {}),
462
+ },
463
+ }));
464
+ }))
465
+ .addCommand(new Command("deliveries")
466
+ .description("List the shared table delivery ledger: pull-feed sync cycles and inbound webhook deliveries land in the same place. Filter by --table (the identifier `feeds list` gives you) or by a webhook endpoint id. Read-only.")
467
+ .argument("[endpoint_id]", "Optional webhook endpoint id, such as tw_...")
468
+ .option("--table <table>", "Table id or slug whose ledger to read.")
469
+ .option("--status <status>", "Filter by received, completed, failed, or rejected (rejected = arrived while the feed was paused and was turned away; no rows written). A delivery refused with HTTP 429 for exceeding the plan's rate is not recorded here: the sender redelivers it.")
470
+ .option("--limit <n>", "Maximum deliveries to return. Defaults to 50.")
471
+ .option("--json", "Print a JSON envelope.")
472
+ .action((endpointId, options) => handleAsyncAction("feeds deliveries", options, () => requestOxygen(tableWebhookDeliveriesPath({
473
+ endpointId,
474
+ table: options.table,
475
+ status: options.status,
476
+ limit: options.limit,
477
+ })))))
478
+ .addCommand(new Command("unbind")
479
+ .description("Delete a feed: removes its cron FIRST, then the feed row. The table and every row it already wrote are kept.")
480
+ .argument("<feed>", "Feed id or endpoint id.")
481
+ .option("--json", "Print a JSON envelope.")
482
+ .action((feed, options) => handleAsyncAction("feeds unbind", options, () => requestOxygen("/api/cli/tables/feeds", {
483
+ method: "DELETE",
484
+ body: { feed },
485
+ }))));
486
+ }
@@ -0,0 +1,3 @@
1
+ import { ExtractedCommandActionHandler } from "../command-support.js";
2
+ import { Command } from "commander";
3
+ export declare function registerFindFamily(program: Command, handleAsyncAction: ExtractedCommandActionHandler): void;