@signaliz/cli 1.0.16 → 1.0.17

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 (3) hide show
  1. package/README.md +39 -3
  2. package/dist/bin.js +1127 -72
  3. package/package.json +2 -2
package/dist/bin.js CHANGED
@@ -9,18 +9,23 @@ var import_node_readline = require("readline");
9
9
  var import_node_child_process = require("child_process");
10
10
  var CONFIG_DIR = (0, import_node_path.join)((0, import_node_os.homedir)(), ".signaliz");
11
11
  var CONFIG_FILE = (0, import_node_path.join)(CONFIG_DIR, "config.json");
12
- function loadConfig() {
12
+ var cachedConfig;
13
+ function loadConfig(options = {}) {
14
+ if (cachedConfig && !options.refresh) return cachedConfig;
13
15
  try {
14
16
  if ((0, import_node_fs.existsSync)(CONFIG_FILE)) {
15
- return JSON.parse((0, import_node_fs.readFileSync)(CONFIG_FILE, "utf8"));
17
+ cachedConfig = JSON.parse((0, import_node_fs.readFileSync)(CONFIG_FILE, "utf8"));
18
+ return cachedConfig;
16
19
  }
17
20
  } catch {
18
21
  }
19
- return {};
22
+ cachedConfig = {};
23
+ return cachedConfig;
20
24
  }
21
25
  function saveConfig(config) {
22
26
  (0, import_node_fs.mkdirSync)(CONFIG_DIR, { recursive: true });
23
27
  (0, import_node_fs.writeFileSync)(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 384 });
28
+ cachedConfig = config;
24
29
  }
25
30
  function arg(name) {
26
31
  const found = process.argv.find((a) => a.startsWith(`--${name}=`));
@@ -98,6 +103,7 @@ function normalizeCampaignBuildInput(raw) {
98
103
  const signals = nested("signals", "signals");
99
104
  const copy = nested("copy", "copy");
100
105
  const delivery = nested("delivery", "delivery");
106
+ const learningHoldout = nested("learning_holdout", "learningHoldout");
101
107
  return {
102
108
  name: get("name", "name"),
103
109
  prompt: get("prompt", "prompt"),
@@ -110,6 +116,14 @@ function normalizeCampaignBuildInput(raw) {
110
116
  dedupKeys: get("dedup_keys", "dedupKeys"),
111
117
  brainPreflight: get("brain_preflight", "brainPreflight"),
112
118
  brainDefaults: get("brain_defaults", "brainDefaults"),
119
+ learningHoldout: learningHoldout ? {
120
+ ...learningHoldout,
121
+ controlPercentage: learningHoldout.control_percentage ?? learningHoldout.controlPercentage,
122
+ minControlSize: learningHoldout.min_control_size ?? learningHoldout.minControlSize,
123
+ experimentKey: learningHoldout.experiment_key ?? learningHoldout.experimentKey,
124
+ sourceCampaignId: learningHoldout.source_campaign_id ?? learningHoldout.sourceCampaignId,
125
+ primaryMetric: learningHoldout.primary_metric ?? learningHoldout.primaryMetric
126
+ } : void 0,
113
127
  deliveryRisk: get("delivery_risk", "deliveryRisk"),
114
128
  icp: nested("icp", "icp"),
115
129
  policy: policy ? {
@@ -145,7 +159,6 @@ var promptValueFlags = /* @__PURE__ */ new Set([
145
159
  "action-name",
146
160
  "actor-id",
147
161
  "actor-type",
148
- "analysis-models",
149
162
  "attachment-field",
150
163
  "attachment-fields",
151
164
  "attachment-file",
@@ -193,6 +206,8 @@ var promptValueFlags = /* @__PURE__ */ new Set([
193
206
  "domains",
194
207
  "enhancers-file",
195
208
  "enhancers-json",
209
+ "field-map-file",
210
+ "field-map-json",
196
211
  "file-url",
197
212
  "integration-id",
198
213
  "image-url",
@@ -203,13 +218,14 @@ var promptValueFlags = /* @__PURE__ */ new Set([
203
218
  "icp",
204
219
  "integration-display-name",
205
220
  "integration-route",
206
- "judge-model",
221
+ "interval-ms",
207
222
  "layer",
208
223
  "layers",
209
224
  "limit",
210
225
  "lead-count",
211
226
  "log-limit",
212
227
  "max-concurrency",
228
+ "max-polls",
213
229
  "max-retries",
214
230
  "max-tokens",
215
231
  "max-credits",
@@ -218,9 +234,18 @@ var promptValueFlags = /* @__PURE__ */ new Set([
218
234
  "memory-type",
219
235
  "model",
220
236
  "name",
237
+ "holdout-min-sample-size",
238
+ "holdout-control-percentage",
239
+ "holdout-experiment-key",
240
+ "holdout-min-control-size",
241
+ "holdout-primary-metric",
242
+ "holdout-source-campaign-id",
243
+ "http-method",
221
244
  "min-sample-size",
222
245
  "min-workspace-count",
223
246
  "nango-connection-id",
247
+ "nango-action",
248
+ "nango-proxy-path",
224
249
  "network-days",
225
250
  "output-format",
226
251
  "output-file",
@@ -245,11 +270,14 @@ var promptValueFlags = /* @__PURE__ */ new Set([
245
270
  "provider-link-id",
246
271
  "provider-name",
247
272
  "provider-workspace-id",
273
+ "proxy-path",
248
274
  "qualification-file",
249
275
  "qualification-json",
250
276
  "request-file",
251
277
  "records-file",
252
278
  "records-json",
279
+ "required-field",
280
+ "required-fields",
253
281
  "revenue-motion",
254
282
  "readiness-json",
255
283
  "rationale",
@@ -275,12 +303,20 @@ var promptValueFlags = /* @__PURE__ */ new Set([
275
303
  "target-icp-file",
276
304
  "target-icp-json",
277
305
  "temperature",
306
+ "timeout-ms",
278
307
  "tool-name",
279
308
  "url",
280
309
  "user-display-name",
281
310
  "user-email",
282
311
  "video-url",
312
+ "wait-interval-ms",
313
+ "wait-max-polls",
314
+ "wait-timeout-ms",
283
315
  "write-mode",
316
+ "timezone",
317
+ "wake-event",
318
+ "wake-events",
319
+ "wake-on-events",
284
320
  "workspace-connection-id",
285
321
  "workspace-integration-id",
286
322
  "workspace-label",
@@ -291,6 +327,7 @@ var promptValueFlags = /* @__PURE__ */ new Set([
291
327
  "brain-defaults-file",
292
328
  "memory-dimension-filters-file",
293
329
  "memory-dimension-filters-json",
330
+ "method",
294
331
  "metadata-file",
295
332
  "metadata-json",
296
333
  "plan-file",
@@ -361,6 +398,9 @@ function commandBooleanFlag(flags, name, defaultValue = false) {
361
398
  if (Array.isArray(value)) return value.length > 0 ? parseCommandBoolean(value.at(-1), defaultValue) : defaultValue;
362
399
  return parseCommandBoolean(value, defaultValue);
363
400
  }
401
+ function commandOptionalBooleanFlag(flags, name) {
402
+ return commandFlagValue(flags, name) === void 0 ? void 0 : commandBooleanFlag(flags, name);
403
+ }
364
404
  function parseCommandBoolean(value, defaultValue) {
365
405
  if (value === void 0) return defaultValue;
366
406
  if (typeof value === "boolean") return value;
@@ -609,6 +649,50 @@ function printExecutionRefs(refs) {
609
649
  if (ref.replay_command) console.log(` replay: ${ref.replay_command}`);
610
650
  }
611
651
  }
652
+ function combineExecutionRefs(...groups) {
653
+ const refs = /* @__PURE__ */ new Map();
654
+ for (const group of groups) {
655
+ if (!Array.isArray(group)) continue;
656
+ for (const ref of group) {
657
+ if (!ref?.kind || !ref?.id) continue;
658
+ refs.set(`${ref.kind}:${ref.id}`, ref);
659
+ }
660
+ }
661
+ return Array.from(refs.values());
662
+ }
663
+ function opsWaitOptions() {
664
+ return {
665
+ interval_ms: numberFlag("interval-ms") ?? numberFlag("wait-interval-ms"),
666
+ max_polls: numberFlag("max-polls") ?? numberFlag("wait-max-polls"),
667
+ timeout_ms: numberFlag("timeout-ms") ?? numberFlag("wait-timeout-ms"),
668
+ limit: numberFlag("limit"),
669
+ cursor: flagArg("cursor"),
670
+ include_failed_runs: hasFlag("include-failed-runs"),
671
+ include_results: !hasFlag("no-results")
672
+ };
673
+ }
674
+ function printOpsWaitForResults(result) {
675
+ const status = result.status || {};
676
+ console.log(`Op: ${result.op_id || result.opId || status.op_id || status.opId || "unknown"}`);
677
+ console.log(`Status: ${status.status || "unknown"}`);
678
+ console.log(`Phase: ${status.phase || "unknown"}`);
679
+ console.log(`Polls: ${result.polls ?? 0}${result.timed_out || result.timedOut ? " (timed out)" : ""}`);
680
+ if (result.results) {
681
+ const summary = result.results.summary || {};
682
+ const count = summary.results_count ?? summary.resultsCount ?? result.results.results_count ?? result.results.resultsCount ?? 0;
683
+ const total = summary.results_total ?? summary.resultsTotal ?? result.results.results_total ?? result.results.resultsTotal;
684
+ const delivery = summary.delivery_status ?? summary.deliveryStatus ?? result.results.delivery_status ?? result.results.deliveryStatus;
685
+ console.log(`Results: ${count}`);
686
+ if (total !== void 0 && total !== null) console.log(`Total: ${total}`);
687
+ if (delivery) console.log(`Deliver: ${delivery}`);
688
+ if (Array.isArray(result.results.results) && result.results.results.length) {
689
+ console.log("\nRows");
690
+ for (const row of result.results.results.slice(0, 10)) console.log(JSON.stringify(row));
691
+ }
692
+ }
693
+ if (result.next_action || result.nextAction) console.log(`Next: ${result.next_action || result.nextAction}`);
694
+ printExecutionRefs(result.execution_refs || result.executionRefs);
695
+ }
612
696
  function printQueueProducerEnvelope(envelope) {
613
697
  if (!envelope || typeof envelope !== "object") return;
614
698
  const queueName = envelope.queue_name ?? envelope.queueName;
@@ -635,6 +719,52 @@ function printPrimitiveGraph(primitives) {
635
719
  console.log(`- ${primitive.id || "unknown"}@${primitive.version || "unknown"} (${primitive.permission_level || "unknown"}, ${retry}, ${concurrency}, ${rate})`);
636
720
  }
637
721
  }
722
+ function printOpsExecutionContract(plan) {
723
+ const contract = record(plan?.execution_contract || plan?.executionContract);
724
+ if (!Object.keys(contract).length) return;
725
+ const runNow = record(contract.run_now || contract.runNow);
726
+ const schedule = record(contract.schedule);
727
+ const approval = record(contract.approval);
728
+ const monitor = record(contract.monitor);
729
+ const resultContract = record(contract.result_contract || contract.resultContract);
730
+ const sequence = firstArray(contract.sequence);
731
+ const nangoRoute = record(contract.nango_route || contract.nangoRoute);
732
+ const createTool = schedule.create_tool || schedule.createTool || "ops_create";
733
+ const activateTool = schedule.activate_tool || schedule.activateTool || "ops_execute";
734
+ const schedulePath = createTool === activateTool ? String(createTool) : `${createTool} -> ${activateTool}`;
735
+ const statusTool = monitor.status_tool || monitor.statusTool || "ops_status";
736
+ const waitTool = monitor.wait_tool || monitor.waitTool || resultContract.wait_tool || resultContract.waitTool || "ops_wait";
737
+ const resultsTool = monitor.results_tool || monitor.resultsTool || "ops_results";
738
+ const resultShape = firstArray(resultContract.shape).map(String);
739
+ console.log("\nExecution contract");
740
+ console.log(`- Mode: ${contract.mode || "run_once"}`);
741
+ console.log(`- Run now: ${runNow.tool || "ops_execute"}`);
742
+ console.log(`- Schedule: ${schedule.cadence || "manual"} via ${schedulePath}`);
743
+ if (schedule.recurrence) console.log(`- Recurrence: ${schedule.recurrence}`);
744
+ console.log(`- Monitor/results: ${waitTool} (or ${statusTool} -> ${resultsTool})`);
745
+ if (resultShape.length) console.log(`- Result shape: ${resultShape.join(", ")}`);
746
+ if (approval.tool) {
747
+ console.log(`- Approval: ${approval.required ? `${approval.tool} required` : "not required"}${approval.reason ? ` - ${approval.reason}` : ""}`);
748
+ }
749
+ if (sequence.length) {
750
+ console.log(`- Sequence: ${sequence.map((step) => step.tool || "unknown_tool").join(" -> ")}`);
751
+ }
752
+ if (Object.keys(nangoRoute).length) {
753
+ console.log("\nNango API route");
754
+ console.log(`- Connect: ${nangoRoute.connect_tool || nangoRoute.connectTool || "nango_connect_session_create"}`);
755
+ console.log(`- Discover: ${nangoRoute.discover_tool || nangoRoute.discoverTool || "nango_mcp_tools_list"}`);
756
+ console.log(`- Dry run: ${nangoRoute.dry_run_tool || nangoRoute.dryRunTool || "nango_mcp_tool_call"}`);
757
+ console.log(`- Execute: ${nangoRoute.execute_tool || nangoRoute.executeTool || "nango_mcp_tool_call"} after approval`);
758
+ if (nangoRoute.execute_and_wait_tool || nangoRoute.executeAndWaitTool) {
759
+ console.log(`- Execute and wait: ${nangoRoute.execute_and_wait_tool || nangoRoute.executeAndWaitTool}`);
760
+ }
761
+ const nangoScheduleTool = nangoRoute.schedule_and_wait_tool || nangoRoute.scheduleAndWaitTool || nangoRoute.schedule_tool || nangoRoute.scheduleTool;
762
+ if (nangoScheduleTool) {
763
+ console.log(`- Schedule: ${nangoScheduleTool} after approval`);
764
+ }
765
+ if (nangoRoute.write_gate || nangoRoute.writeGate) console.log(`- Gate: ${nangoRoute.write_gate || nangoRoute.writeGate}`);
766
+ }
767
+ }
638
768
  function normalizeSavedCommandArgs(args) {
639
769
  const separator = args.indexOf("--");
640
770
  const raw = separator === -1 ? args : args.slice(separator + 1);
@@ -758,7 +888,7 @@ function opsSaved(sub, rest) {
758
888
  stdio: "inherit",
759
889
  env: process.env
760
890
  });
761
- const latest = loadConfig();
891
+ const latest = loadConfig({ refresh: true });
762
892
  const latestSaved = latest.saved_commands || {};
763
893
  const latestCommand = latestSaved[name];
764
894
  if (latestCommand) {
@@ -943,11 +1073,9 @@ Discovery:
943
1073
  --category NAME Optional category filter, e.g. ops or enrichment
944
1074
 
945
1075
  AI:
946
- ai multi-model Run Custom AI Enrichment - Multi Model
947
- --prompt "..." Prompt/template for each record (required)
948
- --model provider/model
949
- OpenRouter model id, e.g. google/gemini-2.5-flash
950
- --records-file FILE JSON array, or object with records/inputs/data
1076
+ ai multi-model Run Custom AI Enrichment - Multi Model
1077
+ --prompt "..." Prompt/template for each record (required)
1078
+ --records-file FILE JSON array, or object with records/inputs/data
951
1079
  --input-file FILE Single JSON record
952
1080
  --output-fields a:text,b:number
953
1081
  Structured fields to return
@@ -955,12 +1083,10 @@ AI:
955
1083
  --pdf-url URL Attach a PDF URL to all records
956
1084
  --attachment-file FILE
957
1085
  Attach a local image/PDF/audio/video as base64
958
- --attachment-fields a,b
959
- Record fields containing attachment URLs/arrays
960
- --max-concurrency N Record concurrency, default 3, maximum 5
961
- --analysis-models a,b Optional Fusion analysis model panel
962
- --judge-model MODEL Optional Fusion judge model
963
- --confirm-spend Required to run spendful Fusion enrichment
1086
+ --attachment-fields a,b
1087
+ Record fields containing attachment URLs/arrays
1088
+ --max-concurrency N Record concurrency, default 3, maximum 5
1089
+ --confirm-spend Required to run spendful AI enrichment
964
1090
 
965
1091
  Lead Generation:
966
1092
  lead generate Start a B2B lead-generation job
@@ -1046,9 +1172,13 @@ GTM Kernel:
1046
1172
  gtm learning <id> Plan Brain learning for a campaign
1047
1173
  --include-network Include privacy-safe network lanes
1048
1174
  --write-mode MODE dry_run or write
1175
+ --holdout-min-sample-size N
1176
+ Minimum control/treatment subjects for measured lift
1049
1177
  gtm learning-run <id> Queue ready Brain learning phases
1050
1178
  --phases a,b Optional phase allow-list
1051
1179
  --write-mode MODE dry_run or write, default dry_run
1180
+ --holdout-min-sample-size N
1181
+ Minimum control/treatment subjects for measured lift
1052
1182
  gtm calibrate <id> Calibrate deliverability predictions
1053
1183
  --min-sample-size N Default server-side
1054
1184
  --write Write calibrations/patterns; otherwise dry-run
@@ -1072,6 +1202,12 @@ GTM Kernel:
1072
1202
  --provider NAME instantly, smartlead, heyreach, airbyte, custom_webhook
1073
1203
  --campaign-id ID Optional campaign scope
1074
1204
  --write-mode MODE Brain-cycle mode, default dry_run
1205
+ gtm feedback-pull Pull Instantly feedback into the learning loop
1206
+ --source SOURCE webhook_events or emails, default webhook_events
1207
+ --campaign-id ID Optional campaign scope
1208
+ --provider-campaign-id ID
1209
+ Instantly campaign id
1210
+ --confirm Write feedback/memory rows; otherwise dry-run
1075
1211
 
1076
1212
  Native GTM Data Sources:
1077
1213
  Use these when you already know the exact source you want to run.
@@ -1164,8 +1300,12 @@ Campaign Builder:
1164
1300
  campaign rows <id> Retrieve build rows
1165
1301
  --limit N Page size (default: 50, max: 500)
1166
1302
  --cursor <id> Pagination cursor
1303
+ --include-data Include generated copy and rich row data
1304
+ --include-raw Include raw provider payloads
1167
1305
  --all Dump all rows (caution: unbounded)
1168
1306
  campaign artifacts <id> List artifacts with download URLs
1307
+ campaign download <id> Generate fresh signed artifact URLs
1308
+ --format csv|ndjson Artifact format (default: csv)
1169
1309
  campaign approve <id> Approve pending delivery
1170
1310
  --destination-type json|csv|webhook
1171
1311
  --destination-id ID Optional destination/sink ID
@@ -1173,12 +1313,18 @@ Campaign Builder:
1173
1313
  campaign cancel <id> Cancel a running build
1174
1314
 
1175
1315
  Ops:
1176
- Canonical path: ops plan -> ops create -> ops run -> ops status -> ops results
1316
+ Canonical path: ops plan -> ops execute -> ops wait
1177
1317
  ops plan "..." Preview a prompt-first Op plan
1178
1318
  --blueprint NAME monitor_companies|build_leads|enrich_list|route_signals|launch_campaign
1179
1319
  --target-count N Target row count
1180
1320
  --cadence NAME manual|hourly|daily|weekly
1321
+ --wake-on-events a,b Event triggers such as company_funded,crm.updated
1181
1322
  --destinations a,b Destinations such as slack,csv,google_sheets,webhook,airbyte,nango
1323
+ --workspace-connection-id ID
1324
+ Nango workspace connection for API delivery
1325
+ --provider-config-key KEY
1326
+ Nango integration/provider key
1327
+ --action-name NAME Nango action; use --proxy-path PATH for proxy mode
1182
1328
  --company-domains a,b
1183
1329
  Company domains for monitor/list-based Ops
1184
1330
  --ai-prompt TEXT Custom AI enrichment or scoring instruction
@@ -1195,7 +1341,21 @@ Ops:
1195
1341
  --confirm-spend Acknowledge estimated credits/external writes
1196
1342
  --auto-run Run immediately after create when safe
1197
1343
  --no-auto-run Create only
1344
+ --wait Auto-run, poll status, and fetch results
1345
+ --interval-ms N Polling interval for --wait, default 5000
1346
+ --max-polls N Polling limit for --wait, default 12, max 30
1347
+ --timeout-ms N Total wait timeout for --wait, default 60000
1348
+ --limit N Result rows after terminal status
1349
+ --no-results Stop after final status without fetching results
1350
+ --cadence NAME manual|hourly|daily|weekly
1351
+ --timezone TZ IANA timezone for recurring Ops, e.g. America/Phoenix
1352
+ --wake-on-events a,b Event triggers such as company_funded,crm.updated
1198
1353
  --destinations a,b Destinations such as slack,csv,google_sheets,webhook,airbyte,nango
1354
+ --workspace-connection-id ID
1355
+ Nango workspace connection for API delivery
1356
+ --provider-config-key KEY
1357
+ Nango integration/provider key
1358
+ --action-name NAME Nango action; use --proxy-path PATH for proxy mode
1199
1359
  --company-domains a,b
1200
1360
  Company domains for monitor/list-based Ops
1201
1361
  --ai-prompt TEXT Custom AI enrichment or scoring instruction
@@ -1204,19 +1364,78 @@ Ops:
1204
1364
  ops create "..." Create an Op from a prompt
1205
1365
  --confirm-spend Acknowledge estimated credits/external writes
1206
1366
  --activate Create as active instead of draft
1367
+ --cadence NAME manual|hourly|daily|weekly
1368
+ --timezone TZ IANA timezone for recurring Ops, e.g. America/Phoenix
1369
+ --wake-on-events a,b Event triggers such as company_funded,crm.updated
1370
+ --destinations a,b Destinations such as slack,csv,google_sheets,webhook,airbyte,nango
1371
+ --workspace-connection-id ID
1372
+ Nango workspace connection for API delivery
1373
+ --provider-config-key KEY
1374
+ Nango integration/provider key
1375
+ --action-name NAME Nango action; use --proxy-path PATH for proxy mode
1376
+ --company-domains a,b
1377
+ Company domains for monitor/list-based Ops
1378
+ --ai-prompt TEXT Custom AI enrichment or scoring instruction
1379
+ --signal-prompt TEXT Custom signal-finding instruction
1380
+ --output-prompt TEXT Clean result formatting instruction
1381
+ ops schedule "..." Create and activate a recurring Op, daily by default
1382
+ ops recur "..." Alias for ops schedule
1383
+ --confirm-spend Acknowledge estimated credits/external writes
1384
+ --wait Run first tick and fetch results through ops_schedule_and_wait
1385
+ --interval-ms N Polling interval for --wait, default 5000
1386
+ --max-polls N Polling limit for --wait, default 12, max 30
1387
+ --timeout-ms N Total wait timeout for --wait, default 60000
1388
+ --limit N Result rows after terminal status
1389
+ --cadence NAME hourly|daily|weekly (default: daily)
1390
+ --timezone TZ IANA timezone for recurring Ops, e.g. America/Phoenix
1391
+ --wake-on-events a,b Event triggers such as company_funded,crm.updated
1207
1392
  --destinations a,b Destinations such as slack,csv,google_sheets,webhook,airbyte,nango
1393
+ --workspace-connection-id ID
1394
+ Nango workspace connection for API delivery
1395
+ --provider-config-key KEY
1396
+ Nango integration/provider key
1397
+ --action-name NAME Nango action; use --proxy-path PATH for proxy mode
1208
1398
  --company-domains a,b
1209
1399
  Company domains for monitor/list-based Ops
1210
1400
  --ai-prompt TEXT Custom AI enrichment or scoring instruction
1211
1401
  --signal-prompt TEXT Custom signal-finding instruction
1212
1402
  --output-prompt TEXT Clean result formatting instruction
1213
1403
  ops run <op_id> Run an Op immediately
1404
+ --wait Poll status and fetch results after queueing
1405
+ --interval-ms N Polling interval for --wait, default 5000
1406
+ --max-polls N Polling limit for --wait, default 12, max 30
1407
+ --timeout-ms N Total wait timeout for --wait, default 60000
1408
+ --limit N Result rows after terminal status
1409
+ --no-results Stop after final status without fetching results
1214
1410
  ops status <op_id> Check simple Op status
1215
1411
  ops results <op_id> Retrieve latest Op results
1216
1412
  --limit N Page size, default 100, max 500
1217
1413
  --cursor ID Pagination cursor
1218
1414
  --include-failed-runs
1219
1415
  Include failed run output when supported
1416
+ ops wait <op_id> Wait for an Op and return results (MCP parity: ops_wait)
1417
+ --interval-ms N Polling interval, default 5000
1418
+ --max-polls N Polling limit, default 12, max 30
1419
+ --timeout-ms N Total wait timeout, default 60000
1420
+ --limit N Result page size after terminal status
1421
+ --no-results Stop after final status without fetching results
1422
+ ops nango <subcommand> Connect, discover, call, schedule, and poll Nango tools
1423
+ flow|search Prepare a provider search/connect/Ops route plan
1424
+ connect Start Nango Connect auth for a provider
1425
+ tools List action tools for a workspace connection
1426
+ call <action> Dry-run or execute a Nango action
1427
+ proof|audit|read-write
1428
+ Audit connected rows and optional live read/write probes
1429
+ --read-proxy-path PATH
1430
+ Provider API path for approved live read proof
1431
+ --write-proxy-path PATH
1432
+ Provider API path for approved live write proof
1433
+ --execute-read-probe --confirm
1434
+ Run the read probe
1435
+ --execute-write-probe --confirm-write
1436
+ Run the write probe with safe test input
1437
+ schedule|recur Create a recurring Nango-backed Op; add --wait for first results
1438
+ result Poll async Nango action result
1220
1439
  ops run-status <run_id>
1221
1440
  Inspect a Trigger.dev run or batch with --run-ids a,b
1222
1441
  --watch Poll until all runs reach a terminal state
@@ -1367,6 +1586,7 @@ Status and repair:
1367
1586
  provider-prepare Build read-only recipe, route, and preview arguments
1368
1587
  activate-route Dry-run or confirm provider route setup
1369
1588
  feedback-webhook Prepare feedback ingress
1589
+ feedback-pull Dry-run or confirm Instantly feedback pull
1370
1590
  learning <id> Plan Brain learning
1371
1591
  learning-run <id> Queue Brain learning phases
1372
1592
  calibrate <id> Calibrate deliverability
@@ -1770,6 +1990,35 @@ Options:
1770
1990
  readiness: `signaliz gtm readiness <campaign_id> [options]
1771
1991
 
1772
1992
  Alias for signaliz gtm execution.
1993
+ `,
1994
+ "feedback-pull": `signaliz gtm feedback-pull [options]
1995
+
1996
+ Dry-run or confirm an Instantly feedback pull. Dry-run is the default and
1997
+ counts provider records without writing feedback rows, memory, routine outcomes,
1998
+ or sync cursors. Confirmed writes require --confirm or --confirm-write.
1999
+
2000
+ Options:
2001
+ --source SOURCE webhook_events or emails, default webhook_events
2002
+ --provider-campaign-id ID
2003
+ Instantly campaign id
2004
+ --campaign-id ID Optional linked GTM campaign id
2005
+ --campaign-build-id ID Optional Campaign Builder build id
2006
+ --provider-link-id ID Optional provider link id
2007
+ --integration-id ID Optional Instantly integration id
2008
+ --from DATE Webhook-event pull window start
2009
+ --to DATE Webhook-event pull window end
2010
+ --limit N Page size, default server-side, max 100
2011
+ --max-pages N Max pages, default server-side, max 20
2012
+ --email-type TYPE received, sent, or manual when source=emails
2013
+ --mode MODE emode_focused, emode_others, or emode_all
2014
+ --sort-order ORDER asc or desc
2015
+ --no-memory Do not create memory rows on confirmed writes
2016
+ --no-routine-outcomes Do not write routine outcomes
2017
+ --no-brain-cycle Do not queue feedback-triggered Brain learning
2018
+ --write-mode MODE Brain-cycle mode, default dry_run
2019
+ --confirm Write feedback/memory rows; otherwise dry-run
2020
+ --confirm-write Alias for --confirm
2021
+ --json Machine-readable output
1773
2022
  `,
1774
2023
  campaigns: `signaliz gtm campaigns [options]
1775
2024
 
@@ -1834,7 +2083,13 @@ Options:
1834
2083
  --blueprint NAME monitor_companies|build_leads|enrich_list|route_signals|launch_campaign
1835
2084
  --target-count N Target row count
1836
2085
  --cadence NAME manual|hourly|daily|weekly
2086
+ --wake-on-events a,b Event triggers such as company_funded,crm.updated
1837
2087
  --destinations a,b Destinations such as json,csv,slack,airbyte,nango
2088
+ --workspace-connection-id ID
2089
+ Nango workspace connection for API delivery
2090
+ --provider-config-key KEY
2091
+ Nango integration/provider key
2092
+ --action-name NAME Nango action; use --proxy-path PATH for proxy mode
1838
2093
  --company-domains a,b Explicit account/domain list
1839
2094
  --json Machine-readable output
1840
2095
 
@@ -1850,12 +2105,52 @@ Options:
1850
2105
  --confirm-spend Acknowledge estimated credits/external writes
1851
2106
  --activate Create as active instead of draft
1852
2107
  --target-count N Target row count
2108
+ --cadence NAME manual|hourly|daily|weekly
2109
+ --timezone TZ IANA timezone for recurring Ops, e.g. America/Phoenix
2110
+ --wake-on-events a,b Event triggers such as company_funded,crm.updated
1853
2111
  --destinations a,b Destinations such as json,csv,slack,airbyte,nango
2112
+ --workspace-connection-id ID
2113
+ Nango workspace connection for API delivery
2114
+ --provider-config-key KEY
2115
+ Nango integration/provider key
2116
+ --action-name NAME Nango action; use --proxy-path PATH for proxy mode
1854
2117
  --company-domains a,b Explicit account/domain list
1855
2118
  --json Machine-readable output
1856
2119
 
1857
2120
  Safe next step:
1858
2121
  signaliz ops run <op_id>
2122
+ `,
2123
+ schedule: `signaliz ops schedule "plain-English recurring GTM goal" [options]
2124
+
2125
+ Create and activate a recurring Op from a prompt. Defaults to daily cadence
2126
+ unless --cadence is provided. Requires --confirm-spend for spendful work.
2127
+
2128
+ Options:
2129
+ --confirm-spend Acknowledge estimated credits/external writes
2130
+ --wait Create the recurring Op, run the first tick, and fetch results
2131
+ --interval-ms N Polling interval for --wait, default 5000
2132
+ --max-polls N Polling limit for --wait, default 12, max 30
2133
+ --timeout-ms N Total wait timeout for --wait, default 60000
2134
+ --limit N Result rows after terminal status
2135
+ --target-count N Target row count
2136
+ --cadence NAME hourly|daily|weekly (default: daily)
2137
+ --timezone TZ IANA timezone for recurring Ops, e.g. America/Phoenix
2138
+ --wake-on-events a,b Event triggers such as company_funded,crm.updated
2139
+ --destinations a,b Destinations such as json,csv,slack,airbyte,nango
2140
+ --workspace-connection-id ID
2141
+ Nango workspace connection for API delivery
2142
+ --provider-config-key KEY
2143
+ Nango integration/provider key
2144
+ --action-name NAME Nango action; use --proxy-path PATH for proxy mode
2145
+ --company-domains a,b Explicit account/domain list
2146
+ --json Machine-readable output
2147
+
2148
+ Safe next step:
2149
+ signaliz ops status <op_id>
2150
+ `,
2151
+ recur: `signaliz ops recur "plain-English recurring GTM goal" [options]
2152
+
2153
+ Alias for signaliz ops schedule.
1859
2154
  `,
1860
2155
  execute: `signaliz ops execute "plain-English GTM goal" [options]
1861
2156
 
@@ -1866,13 +2161,38 @@ Options:
1866
2161
  --confirm-spend Acknowledge estimated credits/external writes
1867
2162
  --auto-run Run immediately after create when safe
1868
2163
  --no-auto-run Create only
2164
+ --wait Auto-run, poll status, and fetch results
2165
+ --interval-ms N Polling interval for --wait, default 5000
2166
+ --max-polls N Polling limit for --wait, default 12, max 30
2167
+ --timeout-ms N Total wait timeout for --wait, default 60000
2168
+ --limit N Result rows to return after terminal status
2169
+ --no-results Stop after final status without fetching results
1869
2170
  --target-count N Target row count
2171
+ --cadence NAME manual|hourly|daily|weekly
2172
+ --timezone TZ IANA timezone for recurring Ops, e.g. America/Phoenix
2173
+ --wake-on-events a,b Event triggers such as company_funded,crm.updated
1870
2174
  --destinations a,b Destinations such as json,csv,slack,airbyte,nango
2175
+ --workspace-connection-id ID
2176
+ Nango workspace connection for API delivery
2177
+ --provider-config-key KEY
2178
+ Nango integration/provider key
2179
+ --action-name NAME Nango action; use --proxy-path PATH for proxy mode
1871
2180
  --json Machine-readable output
1872
2181
  `,
1873
2182
  run: `signaliz ops run <op_id> [options]
1874
2183
 
1875
2184
  Run a created Op immediately.
2185
+
2186
+ Options:
2187
+ --force Force a run when supported
2188
+ --instruction TEXT Optional run instruction
2189
+ --wait Poll status and fetch results after queueing
2190
+ --interval-ms N Polling interval for --wait, default 5000
2191
+ --max-polls N Polling limit for --wait, default 12, max 30
2192
+ --timeout-ms N Total wait timeout for --wait, default 60000
2193
+ --limit N Result rows to return after terminal status
2194
+ --no-results Stop after final status without fetching results
2195
+ --json Machine-readable output
1876
2196
  `,
1877
2197
  status: `signaliz ops status <op_id> [options]
1878
2198
 
@@ -1887,6 +2207,51 @@ Options:
1887
2207
  --cursor ID Pagination cursor
1888
2208
  --include-failed-runs Include failed run output when supported
1889
2209
  --json Machine-readable output
2210
+ `,
2211
+ wait: `signaliz ops wait <op_id> [options]
2212
+
2213
+ Poll Op status until it completes, blocks, fails, or needs action, then fetch
2214
+ the latest results automatically.
2215
+
2216
+ Options:
2217
+ --interval-ms N Polling interval, default 5000
2218
+ --max-polls N Polling limit, default 12, max 30
2219
+ --timeout-ms N Total wait timeout, default 60000
2220
+ --limit N Result rows to return after terminal status
2221
+ --include-failed-runs Include failed run output when fetching results
2222
+ --no-results Only wait for terminal status; do not fetch results
2223
+ --json Machine-readable output
2224
+ `,
2225
+ nango: `signaliz ops nango <flow|search|connect|tools|call|proof|schedule|result> [options]
2226
+
2227
+ Connect and operate customer-owned vendor APIs through Nango from the Ops
2228
+ namespace. This is the Ops-first route for "connect to any tool, do the work,
2229
+ and get the result" flows.
2230
+
2231
+ Examples:
2232
+ signaliz ops nango search hubspot
2233
+ signaliz ops nango connect --provider-id hubspot
2234
+ signaliz ops nango tools --workspace-connection-id <id> --provider-config-key hubspot
2235
+ signaliz ops nango call upsert-contact --workspace-connection-id <id> --input-json '{"email":"buyer@example.com"}' --dry-run
2236
+ signaliz ops nango call --workspace-connection-id <id> --proxy-path /v2/leads/list --method GET --dry-run
2237
+ signaliz ops nango call upsert-contact --workspace-connection-id <id> --input-json '{"email":"buyer@example.com"}' --execute --confirm --wait
2238
+ signaliz ops nango proof --json
2239
+ signaliz ops nango proof --workspace-connection-id <id> --read-proxy-path /v2/accounts --execute-read-probe --confirm
2240
+ signaliz ops nango proof --workspace-connection-id <id> --write-proxy-path /v2/custom-tags --method POST --write-input-json '{"label":"safe-proof"}' --execute-write-probe --confirm-write
2241
+ signaliz ops nango schedule "Monitor accounts daily and upsert approved rows" --workspace-connection-id <id> --provider-config-key hubspot --action-name upsert-contact --confirm-spend --wait
2242
+ signaliz ops nango result --status-url /action/<action_id>
2243
+
2244
+ Subcommands:
2245
+ flow|search Prepare provider search, connect, action, and Ops route calls
2246
+ connect Start Nango Connect auth for a provider
2247
+ tools List Nango action tools for a workspace connection
2248
+ call <action> Dry-run by default; add --execute --confirm for approved writes, --wait for result readback
2249
+ accepts --proxy-path for direct Nango proxy mode
2250
+ proof|audit|read-write Audit connected Nango rows, tools, proxy readiness, and optional live read/write probes
2251
+ accepts --read-proxy-path, --write-proxy-path, --execute-read-probe, --execute-write-probe
2252
+ schedule|recur PROMPT Create a recurring Nango-backed Op through ops_nango_schedule; --wait uses ops_nango_schedule_and_wait
2253
+ accepts --input-json/--input-file, --field-map-json, and --required-fields
2254
+ result Poll async Nango action result
1890
2255
  `
1891
2256
  };
1892
2257
  function opsHelpFor(sub) {
@@ -1901,7 +2266,7 @@ Canonical path:
1901
2266
  signaliz ops results <op_id>
1902
2267
 
1903
2268
  Common commands:
1904
- proof, autopilot, plan, execute, create, run, status, results, doctor, connections
2269
+ proof, autopilot, plan, execute, create, schedule, run, status, results, wait, nango, doctor, connections
1905
2270
 
1906
2271
  Run: signaliz ops help <command>
1907
2272
  `;
@@ -1932,6 +2297,13 @@ Options:
1932
2297
  --outputs json,csv Delivery outputs, default json
1933
2298
  --dry-run Validate and plan without launching spendful work
1934
2299
  --allow-downscale Allow oversized target counts to be clamped
2300
+ --learning-holdout Assign explicit control vs learned cohorts
2301
+ --holdout-control-percentage N
2302
+ Control share, e.g. 0.2 or 20 for 20%
2303
+ --holdout-min-control-size N
2304
+ Minimum control rows for large builds, default 50
2305
+ --holdout-experiment-key KEY
2306
+ Stable experiment key for feedback attribution
1935
2307
  --confirm-spend Acknowledge spendful generation
1936
2308
  --input-file FILE JSON config file from campaign scope or gtm prepare-build
1937
2309
  --wait Poll until completion
@@ -1953,12 +2325,22 @@ Options:
1953
2325
  --cursor ID Pagination cursor
1954
2326
  --qualified Only qualified rows
1955
2327
  --disqualified Only disqualified rows
2328
+ --include-data Include rich row data such as generated copy
2329
+ --include-raw Include raw provider/qualification payloads
1956
2330
  --all Dump all rows
1957
2331
  --json Machine-readable output
1958
2332
  `,
1959
2333
  artifacts: `signaliz campaign artifacts <campaign_build_id> [options]
1960
2334
 
1961
2335
  List generated artifacts and signed download URLs.
2336
+ `,
2337
+ download: `signaliz campaign download <campaign_build_id> [options]
2338
+
2339
+ Generate fresh signed URLs for a downloadable artifact.
2340
+
2341
+ Options:
2342
+ --format csv|ndjson Artifact format, default csv
2343
+ --json Machine-readable output
1962
2344
  `,
1963
2345
  approve: `signaliz campaign approve <campaign_build_id> --destination-type <json|csv|webhook>
1964
2346
 
@@ -1979,9 +2361,10 @@ Canonical path:
1979
2361
  signaliz campaign build --prompt "VP Sales at B2B SaaS" --confirm-spend --wait
1980
2362
  signaliz campaign rows <campaign_build_id> --limit 100
1981
2363
  signaliz campaign artifacts <campaign_build_id>
2364
+ signaliz campaign download <campaign_build_id> --format csv
1982
2365
 
1983
2366
  Commands:
1984
- scope, build, status, rows, artifacts, approve, cancel
2367
+ scope, build, status, rows, artifacts, download, approve, cancel
1985
2368
 
1986
2369
  Run: signaliz campaign help <command>
1987
2370
  `;
@@ -2394,20 +2777,20 @@ function aiAttachmentsFromFlags() {
2394
2777
  }
2395
2778
  async function ai(sub, rest) {
2396
2779
  if (sub !== "multi-model" && sub !== "multimodel") {
2397
- die('Usage: signaliz ai multi-model --prompt "..." --model provider/model --confirm-spend');
2780
+ die('Usage: signaliz ai multi-model --prompt "..." --confirm-spend');
2781
+ }
2782
+ if (flagArg("model") !== void 0 || hasFlag("model") || flagArg("analysis-models") !== void 0 || hasFlag("analysis-models") || flagArg("judge-model") !== void 0 || hasFlag("judge-model")) {
2783
+ die("Custom AI model selection is no longer supported. Signaliz-hosted custom AI uses the default model and returns model readback metadata.");
2398
2784
  }
2399
2785
  const promptText = promptArg(rest);
2400
- const model = flagArg("model");
2401
- if (!promptText) die('Usage: signaliz ai multi-model --prompt "..." --model provider/model --confirm-spend');
2402
- if (!model) die("--model is required. Use an OpenRouter id such as google/gemini-2.5-flash.");
2786
+ if (!promptText) die('Usage: signaliz ai multi-model --prompt "..." --confirm-spend');
2403
2787
  if (!hasFlag("confirm-spend")) {
2404
- die("This uses OpenRouter Fusion and can spend credits. Re-run with --confirm-spend to approve.");
2788
+ die("This runs custom AI enrichment and can spend credits. Re-run with --confirm-spend to approve.");
2405
2789
  }
2406
2790
  const sdk = getSdk();
2407
2791
  const records = aiRecordsFromFlags();
2408
2792
  const result = await sdk.ai.multiModel({
2409
2793
  prompt: promptText,
2410
- model,
2411
2794
  records,
2412
2795
  systemPrompt: flagArg("system-prompt"),
2413
2796
  temperature: numberFlag("temperature"),
@@ -2418,15 +2801,12 @@ async function ai(sub, rest) {
2418
2801
  attachmentFields: csvFlag("attachment-fields") || csvFlag("attachment-field"),
2419
2802
  pdfEngine: flagArg("pdf-engine"),
2420
2803
  fusion: {
2421
- required: !hasFlag("no-require-fusion"),
2422
- analysis_models: csvFlag("analysis-models"),
2423
- judge_model: flagArg("judge-model"),
2424
2804
  include_raw_analysis: hasFlag("include-raw-analysis")
2425
2805
  }
2426
2806
  });
2427
2807
  return output(result, () => {
2428
2808
  console.log("Custom AI Enrichment - Multi Model complete");
2429
- console.log(` Model: ${result.model || model}`);
2809
+ console.log(` Model: ${result.model || "google/gemini-2.5-flash"}`);
2430
2810
  console.log(` Records: ${result.results.length}`);
2431
2811
  console.log(` Credits: ${result.creditsUsed}`);
2432
2812
  console.log(` Attachments: ${result.multimodalCount}`);
@@ -2462,6 +2842,16 @@ async function campaignBuild() {
2462
2842
  if (hasFlag("dry-run")) config.dryRun = true;
2463
2843
  if (hasFlag("allow-downscale")) config.allowDownscale = true;
2464
2844
  if (hasFlag("confirm-spend")) config.confirmSpend = true;
2845
+ if (hasFlag("learning-holdout") || flagArg("holdout-control-percentage") || flagArg("holdout-min-control-size") || flagArg("holdout-experiment-key")) {
2846
+ config.learningHoldout = {
2847
+ enabled: true,
2848
+ controlPercentage: numberFlag("holdout-control-percentage") ?? 0.2,
2849
+ minControlSize: numberFlag("holdout-min-control-size") ?? 50,
2850
+ experimentKey: flagArg("holdout-experiment-key") || void 0,
2851
+ sourceCampaignId: flagArg("holdout-source-campaign-id") || void 0,
2852
+ primaryMetric: flagArg("holdout-primary-metric") || void 0
2853
+ };
2854
+ }
2465
2855
  const outputs = (flagArg("outputs") || "json").split(",").map((s) => s.trim());
2466
2856
  const webhookUrl = flagArg("webhook-url");
2467
2857
  if (outputs.includes("csv") || outputs.includes("webhook") || webhookUrl) {
@@ -2504,6 +2894,7 @@ async function campaignBuild() {
2504
2894
  console.log(` Planned: ${result.plannedTargetCount ?? result.requestedTargetCount ?? config.targetCount ?? 50} leads`);
2505
2895
  if (result.estimatedCredits !== void 0) console.log(` Credits: ~${result.estimatedCredits}`);
2506
2896
  if (result.estimatedDurationSeconds !== void 0) console.log(` Duration: ~${result.estimatedDurationSeconds}s`);
2897
+ if (result.learningHoldout?.enabled) console.log(` Holdout: enabled (${Math.round(Number(result.learningHoldout.control_percentage || 0) * 100)}% control)`);
2507
2898
  if (result.targetLimitApplied) console.log(` Note: ${result.targetLimitReason || "target count downscaled"}`);
2508
2899
  hint("Remove --dry-run and add --confirm-spend when ready to launch.");
2509
2900
  });
@@ -2517,6 +2908,7 @@ async function campaignBuild() {
2517
2908
  console.log(` Status: ${result.status}`);
2518
2909
  console.log(` Phase: ${result.currentPhase}`);
2519
2910
  console.log(` Target: ${config.targetCount || 50} leads`);
2911
+ if (result.learningHoldout?.enabled) console.log(` Holdout: enabled (${Math.round(Number(result.learningHoldout.control_percentage || 0) * 100)}% control)`);
2520
2912
  hint(`signaliz campaign status ${result.campaignBuildId}`);
2521
2913
  });
2522
2914
  }
@@ -2546,6 +2938,7 @@ async function campaignBuild() {
2546
2938
  \u2713 Build ${finalStatus.status}`);
2547
2939
  console.log(` Rows: ${finalStatus.recordsSucceeded}/${finalStatus.recordsTotal} succeeded`);
2548
2940
  console.log(` Artifacts: ${finalStatus.artifactCount}`);
2941
+ printCampaignArtifactDownloadHint(finalStatus, result.campaignBuildId);
2549
2942
  if (finalStatus.status === "completed") {
2550
2943
  hint(`signaliz campaign rows ${result.campaignBuildId} --limit 100`);
2551
2944
  }
@@ -2589,6 +2982,35 @@ async function campaignScope() {
2589
2982
  hint("Next: run signaliz campaign build --input-file with the buildCampaignArgs JSON, or call build_campaign with the returned dry-run args.");
2590
2983
  });
2591
2984
  }
2985
+ function campaignArtifactDownloads(status) {
2986
+ const downloads = Array.isArray(status?.artifactDownloads) ? status.artifactDownloads : Array.isArray(status?.artifact_downloads) ? status.artifact_downloads : [];
2987
+ return downloads.map(record);
2988
+ }
2989
+ function campaignArtifactDownloadUrl(download) {
2990
+ const url = download.downloadUrl || download.download_url || download.signedUrl || download.signed_url;
2991
+ return typeof url === "string" && url.trim() ? url.trim() : null;
2992
+ }
2993
+ function campaignPreferredArtifactDownload(status) {
2994
+ const downloads = campaignArtifactDownloads(status);
2995
+ if (downloads.length === 0) return null;
2996
+ return downloads.find((download) => {
2997
+ const format = String(download.format || download.artifactType || download.artifact_type || "").toLowerCase();
2998
+ return format === "csv";
2999
+ }) || downloads[0];
3000
+ }
3001
+ function printCampaignArtifactDownloadHint(status, buildId) {
3002
+ const download = campaignPreferredArtifactDownload(status);
3003
+ const url = download ? campaignArtifactDownloadUrl(download) : null;
3004
+ if (url) {
3005
+ const format = String(download?.format || download?.artifactType || download?.artifact_type || "artifact");
3006
+ const rowCount = Number(download?.rowCount ?? download?.row_count ?? 0);
3007
+ console.log(`Download: ${format}${rowCount > 0 ? `, ${rowCount} rows` : ""}`);
3008
+ console.log(` ${url}`);
3009
+ return;
3010
+ }
3011
+ const artifactCount = Number(status?.artifactCount ?? status?.artifact_count ?? 0);
3012
+ if (artifactCount > 0) hint(`signaliz campaign download ${buildId} --format csv`);
3013
+ }
2592
3014
  async function campaignStatus() {
2593
3015
  const buildId = positionalAfter("status") || flagArg("campaign-build-id");
2594
3016
  if (!buildId) die("Usage: signaliz campaign status <campaign_build_id>");
@@ -2600,7 +3022,9 @@ async function campaignStatus() {
2600
3022
  console.log(`Status: ${status.status}`);
2601
3023
  console.log(`Phase: ${status.currentPhase || "N/A"}`);
2602
3024
  console.log(`Rows: ${status.recordsSucceeded}/${status.recordsTotal} succeeded, ${status.recordsFailed} failed`);
3025
+ printCampaignCustomerRowCounts(status);
2603
3026
  console.log(`Artifacts: ${status.artifactCount}`);
3027
+ printCampaignArtifactDownloadHint(status, buildId);
2604
3028
  if (status.triggerRunId) console.log(`Trigger: ${status.triggerRunId}`);
2605
3029
  if (status.staleRunningPhase) {
2606
3030
  const age = typeof status.phaseAgeSeconds === "number" ? `${status.phaseAgeSeconds}s` : "unknown age";
@@ -2614,12 +3038,18 @@ async function campaignStatus() {
2614
3038
  console.log(`Errors: ${status.errors.length}`);
2615
3039
  for (const error of status.errors.slice(0, 3)) console.log(` - ${error}`);
2616
3040
  }
2617
- if (status.status === "completed") {
3041
+ if (status.status === "completed" && status.nextAction) {
3042
+ hint(status.nextAction);
3043
+ } else if (status.status === "completed") {
2618
3044
  hint(`signaliz campaign rows ${buildId} --limit 100`);
2619
3045
  } else if (status.staleRunningPhase && status.triggerRunId) {
2620
3046
  hint(`signaliz ops run-status ${status.triggerRunId} --watch`);
2621
3047
  } else if (status.status === "running" || status.status === "queued") {
2622
3048
  hint(`signaliz campaign status ${buildId} (poll again in ~10s)`);
3049
+ } else if (status.status === "pending_approval" && status.approvalAction) {
3050
+ hint(status.approvalAction);
3051
+ } else if (status.status === "pending_approval" && status.nextAction) {
3052
+ hint(status.nextAction);
2623
3053
  } else if (status.status === "pending_approval") {
2624
3054
  hint(`signaliz campaign approve ${buildId} --destination-type webhook`);
2625
3055
  } else if (status.status === "failed" && status.nextAction) {
@@ -2627,6 +3057,22 @@ async function campaignStatus() {
2627
3057
  }
2628
3058
  });
2629
3059
  }
3060
+ function printCampaignCustomerRowCounts(status) {
3061
+ const counts = status?.customerRowCounts || status?.customer_row_counts;
3062
+ if (!counts || typeof counts !== "object") return;
3063
+ const value = (camel, snake) => counts[camel] ?? counts[snake] ?? "n/a";
3064
+ const requested = value("requestedTarget", "requested_target");
3065
+ console.log(`Customer rows: accepted ${value("acceptedRows", "accepted_rows")}/${requested}, copied ${value("copiedRows", "copied_rows")}, approval-ready ${value("approvalReadyRows", "approval_ready_rows")}, QA-ready ${value("qaReadyRows", "qa_ready_rows")}, delivered ${value("deliveredRows", "delivered_rows")}`);
3066
+ const shortfalls = [
3067
+ ["accepted", value("acceptedShortfall", "accepted_shortfall")],
3068
+ ["usable", value("usableShortfall", "usable_shortfall")],
3069
+ ["QA-ready", value("qaReadyShortfall", "qa_ready_shortfall")],
3070
+ ["delivery", value("deliveryShortfall", "delivery_shortfall")]
3071
+ ].filter(([, count]) => typeof count === "number" && count > 0);
3072
+ if (shortfalls.length > 0) {
3073
+ console.log(`Shortfalls: ${shortfalls.map(([label, count]) => `${label} ${count}`).join(", ")}`);
3074
+ }
3075
+ }
2630
3076
  async function campaignRows() {
2631
3077
  const buildId = positionalAfter("rows") || flagArg("campaign-build-id");
2632
3078
  if (!buildId) die("Usage: signaliz campaign rows <campaign_build_id>");
@@ -2641,6 +3087,8 @@ async function campaignRows() {
2641
3087
  do {
2642
3088
  const opts = { limit: Math.min(limit, 500) };
2643
3089
  if (nextCursor) opts.cursor = nextCursor;
3090
+ if (hasFlag("include-data")) opts.includeData = true;
3091
+ if (hasFlag("include-raw")) opts.includeRaw = true;
2644
3092
  const segment = flagArg("segment");
2645
3093
  if (segment) opts.segment = segment;
2646
3094
  const rowStatus = flagArg("row-status");
@@ -2665,6 +3113,8 @@ async function campaignRows() {
2665
3113
  } else {
2666
3114
  const opts = { limit: Math.min(limit, 500) };
2667
3115
  if (cursor) opts.cursor = cursor;
3116
+ if (hasFlag("include-data")) opts.includeData = true;
3117
+ if (hasFlag("include-raw")) opts.includeRaw = true;
2668
3118
  const segment = flagArg("segment");
2669
3119
  if (segment) opts.segment = segment;
2670
3120
  const rowStatus = flagArg("row-status");
@@ -2690,6 +3140,7 @@ async function campaignRows() {
2690
3140
  status: r.status || "",
2691
3141
  score: r.score ?? ""
2692
3142
  })));
3143
+ printCampaignCopySnippets(operatorResult.rows);
2693
3144
  console.log(`
2694
3145
  Showing ${operatorResult.rows.length} row(s)${operatorResult.hasMore ? " (more available)" : ""}`);
2695
3146
  if (operatorResult.nextCursor) {
@@ -2702,14 +3153,15 @@ function formatCampaignRowsForOperator(rows) {
2702
3153
  return rows.map((row) => {
2703
3154
  const rowRecord = record(row);
2704
3155
  const data = record(rowRecord.data);
3156
+ const copyData = record(data.copy);
2705
3157
  const rawCopy = record(data.raw_copy);
2706
3158
  const copy = {
2707
- subject: data.subject || rawCopy.subject || null,
2708
- opener: data.opener || rawCopy.opener || null,
2709
- body: data.body || rawCopy.body || null,
2710
- cta: data.cta || rawCopy.cta || null,
2711
- engine: data.copy_engine || rawCopy.engine || null,
2712
- model: data.copy_model || null
3159
+ subject: data.subject || copyData.subject || rawCopy.subject || null,
3160
+ opener: data.opener || copyData.opener || rawCopy.opener || null,
3161
+ body: data.body || copyData.body || rawCopy.body || null,
3162
+ cta: data.cta || copyData.cta || rawCopy.cta || null,
3163
+ engine: data.copy_engine || copyData.engine || rawCopy.engine || null,
3164
+ model: data.copy_model || copyData.model || null
2713
3165
  };
2714
3166
  return {
2715
3167
  ...rowRecord,
@@ -2739,6 +3191,26 @@ function formatCampaignRowsForOperator(rows) {
2739
3191
  };
2740
3192
  });
2741
3193
  }
3194
+ function printCampaignCopySnippets(rows, limit = 5) {
3195
+ const rowsWithCopy = rows.filter((row) => {
3196
+ const copy = record(row.copy);
3197
+ return Boolean(copy.subject || copy.opener || copy.body || copy.cta);
3198
+ });
3199
+ if (rowsWithCopy.length === 0) return;
3200
+ console.log("\nCopy:");
3201
+ for (const row of rowsWithCopy.slice(0, limit)) {
3202
+ const copy = record(row.copy);
3203
+ const label = row.email || row.name || row.company || `row ${row.index ?? ""}`.toString().trim();
3204
+ console.log(`- ${label}`);
3205
+ if (copy.subject) console.log(` Subject: ${copy.subject}`);
3206
+ if (copy.opener) console.log(` Opener: ${copy.opener}`);
3207
+ if (copy.body) console.log(` Body: ${copy.body}`);
3208
+ if (copy.cta) console.log(` CTA: ${copy.cta}`);
3209
+ }
3210
+ if (rowsWithCopy.length > limit) {
3211
+ console.log(` ...${rowsWithCopy.length - limit} more row(s) with copy`);
3212
+ }
3213
+ }
2742
3214
  async function campaignArtifacts() {
2743
3215
  const buildId = positionalAfter("artifacts") || flagArg("campaign-build-id");
2744
3216
  if (!buildId) die("Usage: signaliz campaign artifacts <campaign_build_id>");
@@ -2756,6 +3228,24 @@ async function campaignArtifacts() {
2756
3228
  }
2757
3229
  });
2758
3230
  }
3231
+ async function campaignDownload() {
3232
+ const buildId = positionalAfter("download") || flagArg("campaign-build-id");
3233
+ if (!buildId) die("Usage: signaliz campaign download <campaign_build_id> [--format csv|ndjson]");
3234
+ const sdk = getSdk();
3235
+ const format = flagArg("format") || "csv";
3236
+ const result = await sdk.campaigns.downloadCampaignArtifact(buildId, { format });
3237
+ output(result, () => {
3238
+ console.log(`Artifact: ${result.format} (${result.rowCount} rows)`);
3239
+ if (result.partCount > 1) console.log(`Parts: ${result.partCount}`);
3240
+ const parts = Array.isArray(result.parts) ? result.parts : [];
3241
+ for (const part of parts) {
3242
+ const url = part.signedUrl;
3243
+ if (!url) continue;
3244
+ const label = result.partCount > 1 ? `part ${part.partIndex}` : "download";
3245
+ console.log(`${label}: ${url}`);
3246
+ }
3247
+ });
3248
+ }
2759
3249
  async function campaignApprove() {
2760
3250
  const buildId = positionalAfter("approve") || positionalAfter("approve-delivery") || flagArg("campaign-build-id");
2761
3251
  if (!buildId) die("Usage: signaliz campaign approve <campaign_build_id> --destination-type <json|csv|webhook>");
@@ -2811,6 +3301,8 @@ async function campaign(sub) {
2811
3301
  return campaignRows();
2812
3302
  case "artifacts":
2813
3303
  return campaignArtifacts();
3304
+ case "download":
3305
+ return campaignDownload();
2814
3306
  case "approve":
2815
3307
  case "approve-delivery":
2816
3308
  return campaignApprove();
@@ -2818,7 +3310,7 @@ async function campaign(sub) {
2818
3310
  return campaignCancel();
2819
3311
  default:
2820
3312
  die(`Unknown campaign subcommand: ${sub}
2821
- Available: scope, build, status, rows, artifacts, approve, cancel`);
3313
+ Available: scope, build, status, rows, artifacts, download, approve, cancel`);
2822
3314
  }
2823
3315
  }
2824
3316
  var CAMPAIGN_AGENT_APPROVAL_TYPES = [
@@ -2864,9 +3356,9 @@ Examples:
2864
3356
  signaliz campaign-agent dry-run --goal "Build a local services campaign" --target-count 250 --use-local-leads --json
2865
3357
  signaliz campaign-agent build-approved --goal "Build a strategy-template campaign" --confirm-launch --approve-all --approved-by operator@example.com --spend-limit 500
2866
3358
  signaliz campaign-agent build-approved --request-file campaign-builder-agent-request.json --confirm-launch --approve-all --approved-by operator@example.com --spend-limit 500 --wait --approve-delivery --delivery-destination-type json
2867
- signaliz campaign-agent review <campaign_build_id> --limit 100 --json
3359
+ signaliz campaign-agent review <campaign_build_id> --limit 100 --include-data --json
2868
3360
  signaliz campaign-agent status <campaign_build_id> --json
2869
- signaliz campaign-agent rows <campaign_build_id> --limit 100 --json
3361
+ signaliz campaign-agent rows <campaign_build_id> --limit 100 --include-data --json
2870
3362
 
2871
3363
  Plan options:
2872
3364
  --goal TEXT Campaign goal or brief
@@ -2874,7 +3366,7 @@ Plan options:
2874
3366
  --kit-file FILE Write campaign-agent kit output to a JSON file
2875
3367
  --seed-manifest-file FILE
2876
3368
  Manifest path for memory-kit seed dry-runs
2877
- --source NAME memory-kit seed source: agency, workflow-patterns, instantly-feedback, or all
3369
+ --source NAME memory-kit seed source: north-star, agency, workflow-patterns, instantly-feedback, or all
2878
3370
  --request-output-file FILE
2879
3371
  Request filename to reference inside kit commands
2880
3372
  --request-file FILE Reusable JSON CampaignBuilderAgentRequest; flags override file fields
@@ -3118,6 +3610,8 @@ async function campaignAgentReview(sub, rest) {
3118
3610
  cursor: commandStringFlag(flags, "cursor"),
3119
3611
  segment: commandStringFlag(flags, "segment"),
3120
3612
  rowStatus: commandStringFlag(flags, "row-status"),
3613
+ includeData: commandOptionalBooleanFlag(flags, "include-data"),
3614
+ includeRaw: commandOptionalBooleanFlag(flags, "include-raw"),
3121
3615
  qualified: commandBooleanFlag(flags, "qualified") ? true : commandBooleanFlag(flags, "disqualified") ? false : void 0
3122
3616
  };
3123
3617
  const review = await agent.reviewBuild?.(buildId, rowOptions) || await campaignAgentBuildReviewFromCampaignApis(agent, campaigns, buildId, rowOptions);
@@ -3141,6 +3635,8 @@ async function campaignAgentReview(sub, rest) {
3141
3635
  cursor: commandStringFlag(flags, "cursor"),
3142
3636
  segment: commandStringFlag(flags, "segment"),
3143
3637
  rowStatus: commandStringFlag(flags, "row-status"),
3638
+ includeData: commandOptionalBooleanFlag(flags, "include-data"),
3639
+ includeRaw: commandOptionalBooleanFlag(flags, "include-raw"),
3144
3640
  qualified: commandBooleanFlag(flags, "qualified") ? true : commandBooleanFlag(flags, "disqualified") ? false : void 0
3145
3641
  };
3146
3642
  const result2 = await (agent.getBuildRows?.bind(agent) || campaigns.rows?.bind(campaigns) || campaigns.getCampaignBuildRows?.bind(campaigns))?.(buildId, rowOptions);
@@ -3732,7 +4228,9 @@ function printCampaignAgentBuildStatus(status) {
3732
4228
  console.log(`Status: ${status.status}`);
3733
4229
  console.log(`Phase: ${status.currentPhase || "N/A"}`);
3734
4230
  console.log(`Rows: ${status.recordsSucceeded}/${status.recordsTotal} succeeded, ${status.recordsFailed} failed`);
4231
+ printCampaignCustomerRowCounts(status);
3735
4232
  console.log(`Artifacts: ${status.artifactCount}`);
4233
+ if (status.campaignBuildId) printCampaignArtifactDownloadHint(status, status.campaignBuildId);
3736
4234
  if (status.triggerRunId) console.log(`Trigger: ${status.triggerRunId}`);
3737
4235
  if (Array.isArray(status.warnings) && status.warnings.length) {
3738
4236
  console.log(`Warnings: ${status.warnings.length}`);
@@ -3742,8 +4240,14 @@ function printCampaignAgentBuildStatus(status) {
3742
4240
  console.log(`Errors: ${status.errors.length}`);
3743
4241
  for (const error of status.errors.slice(0, 3)) console.log(` - ${error}`);
3744
4242
  }
3745
- if (status.status === "completed") {
4243
+ if (status.status === "completed" && status.nextAction) {
4244
+ hint(status.nextAction);
4245
+ } else if (status.status === "completed") {
3746
4246
  hint(`signaliz campaign-agent rows ${status.campaignBuildId} --limit 100`);
4247
+ } else if (status.status === "pending_approval" && status.approvalAction) {
4248
+ hint(status.approvalAction);
4249
+ } else if (status.status === "pending_approval" && status.nextAction) {
4250
+ hint(status.nextAction);
3747
4251
  } else if (status.status === "pending_approval") {
3748
4252
  hint(`signaliz campaign-agent approve ${status.campaignBuildId} --destination-type webhook`);
3749
4253
  } else if ((status.status === "running" || status.status === "queued") && status.triggerRunId) {
@@ -3761,6 +4265,7 @@ function printCampaignAgentBuildReview(review) {
3761
4265
  console.log(`Emails: ${summary.rowsWithEmail || 0}`);
3762
4266
  console.log(`Copy ready: ${summary.copyReadyRows || 0}`);
3763
4267
  console.log(`Artifacts: ${summary.artifactCount || 0}`);
4268
+ if (review.campaignBuildId) printCampaignArtifactDownloadHint(status, review.campaignBuildId);
3764
4269
  console.log(`Delivery approval ready: ${summary.readyForDeliveryApproval === true ? "yes" : "no"}`);
3765
4270
  const gates = Array.isArray(review.gates) ? review.gates : [];
3766
4271
  if (gates.length) {
@@ -3898,6 +4403,7 @@ function printCampaignAgentRows(result) {
3898
4403
  status: row.status || "",
3899
4404
  score: row.score ?? ""
3900
4405
  })));
4406
+ printCampaignCopySnippets(result.rows);
3901
4407
  console.log(`
3902
4408
  Showing ${result.rows.length} row(s)${result.hasMore ? " (more available)" : ""}`);
3903
4409
  if (result.nextCursor) {
@@ -4690,6 +5196,7 @@ async function gtm(sub, rest) {
4690
5196
  days: numberFlag("days"),
4691
5197
  networkDays: numberFlag("network-days"),
4692
5198
  minSampleSize: numberFlag("min-sample-size"),
5199
+ holdoutMinSampleSize: numberFlag("holdout-min-sample-size"),
4693
5200
  minWorkspaceCount: numberFlag("min-workspace-count"),
4694
5201
  limit: numberFlag("limit")
4695
5202
  });
@@ -4700,6 +5207,10 @@ async function gtm(sub, rest) {
4700
5207
  console.log("GTM Brain learning plan");
4701
5208
  console.log(` Campaign: ${campaignId}`);
4702
5209
  console.log(` Lanes: ${ready}/${lanes.length} ready`);
5210
+ if (result.holdout_lift?.status) {
5211
+ const delta = result.holdout_lift?.lift?.primary_metric_delta;
5212
+ console.log(` Holdout: ${result.holdout_lift.status}${delta !== void 0 ? ` (${delta} ${result.holdout_lift.primary_metric || "primary_metric"})` : ""}`);
5213
+ }
4703
5214
  console.log(` Calls: ${calls.length}`);
4704
5215
  for (const call of calls.slice(0, 5)) {
4705
5216
  const state = call.ready === false ? "blocked" : "ready";
@@ -4721,6 +5232,7 @@ async function gtm(sub, rest) {
4721
5232
  days: numberFlag("days"),
4722
5233
  networkDays: numberFlag("network-days"),
4723
5234
  minSampleSize: numberFlag("min-sample-size"),
5235
+ holdoutMinSampleSize: numberFlag("holdout-min-sample-size"),
4724
5236
  minWorkspaceCount: numberFlag("min-workspace-count"),
4725
5237
  minPrivacyK: numberFlag("min-privacy-k"),
4726
5238
  limit: numberFlag("limit")
@@ -4974,6 +5486,103 @@ async function gtm(sub, rest) {
4974
5486
  hint(`signaliz gtm bootstrap${common.campaignId ? ` --campaign-id ${common.campaignId}` : ""} --include-samples`);
4975
5487
  });
4976
5488
  }
5489
+ if (sub === "feedback-pull" || sub === "instantly-feedback-pull" || sub === "pull-feedback") {
5490
+ const provider = (flagArg("provider") || "instantly").toLowerCase();
5491
+ if (provider !== "instantly") {
5492
+ die("gtm feedback-pull currently supports --provider instantly. Use gtm feedback-webhook for provider-agnostic ingress.");
5493
+ }
5494
+ const source = flagArg("source") || flagArg("pull-source") || "webhook_events";
5495
+ if (!["webhook_events", "emails"].includes(source)) {
5496
+ die("--source must be webhook_events or emails");
5497
+ }
5498
+ if (flagArg("source") && flagArg("pull-source") && flagArg("source") !== flagArg("pull-source")) {
5499
+ die("--source and --pull-source must match when both are provided");
5500
+ }
5501
+ const campaignId = flagArg("campaign-id");
5502
+ const campaignBuildId = flagArg("campaign-build-id") || flagArg("build-id");
5503
+ const providerLinkId = flagArg("provider-link-id");
5504
+ const providerCampaignId = flagArg("provider-campaign-id") || flagArg("instantly-campaign-id") || positionalAfter(sub);
5505
+ if (!campaignId && !campaignBuildId && !providerLinkId && !providerCampaignId) {
5506
+ die("Usage: signaliz gtm feedback-pull --provider-campaign-id <instantly_campaign_id> [--source webhook_events|emails] [--confirm]");
5507
+ }
5508
+ const hasEmailHistoryFlags = Boolean(flagArg("email-type") || flagArg("mode") || flagArg("sort-order"));
5509
+ if (hasEmailHistoryFlags && source !== "emails") {
5510
+ die("--email-type, --mode, and --sort-order require --source emails");
5511
+ }
5512
+ const emailType = source === "emails" ? flagArg("email-type") || "received" : void 0;
5513
+ if (emailType !== void 0 && !["received", "sent", "manual"].includes(emailType)) {
5514
+ die("--email-type must be received, sent, or manual");
5515
+ }
5516
+ const mode = source === "emails" ? flagArg("mode") || "emode_all" : void 0;
5517
+ if (mode !== void 0 && !["emode_focused", "emode_others", "emode_all"].includes(mode)) {
5518
+ die("--mode must be emode_focused, emode_others, or emode_all");
5519
+ }
5520
+ const sortOrder = source === "emails" ? flagArg("sort-order") || "asc" : void 0;
5521
+ if (sortOrder !== void 0 && !["asc", "desc"].includes(sortOrder)) {
5522
+ die("--sort-order must be asc or desc");
5523
+ }
5524
+ if (hasFlag("write") && !hasFlag("confirm") && !hasFlag("confirm-write")) {
5525
+ die("Write-mode feedback pulls require --confirm or --confirm-write. Run without --confirm first for a dry-run count.");
5526
+ }
5527
+ const confirmed = hasFlag("confirm") || hasFlag("confirm-write");
5528
+ const dryRun = !confirmed;
5529
+ const result = await sdk.gtm.pullInstantlyFeedback({
5530
+ campaignId,
5531
+ campaignBuildId,
5532
+ providerLinkId,
5533
+ providerCampaignId,
5534
+ instantlyCampaignId: flagArg("instantly-campaign-id"),
5535
+ integrationId: flagArg("integration-id"),
5536
+ source,
5537
+ instantlyProfile: flagArg("instantly-profile") || flagArg("profile"),
5538
+ cursor: flagArg("cursor"),
5539
+ from: flagArg("from") || flagArg("since"),
5540
+ to: flagArg("to") || flagArg("until"),
5541
+ limit: numberFlag("limit"),
5542
+ maxPages: numberFlag("max-pages"),
5543
+ emailType,
5544
+ mode,
5545
+ sortOrder,
5546
+ createMemory: !hasFlag("no-memory"),
5547
+ writeRoutineOutcomes: !hasFlag("no-routine-outcomes"),
5548
+ dryRun,
5549
+ runBrainCycle: !hasFlag("no-brain-cycle"),
5550
+ brainCycleWriteMode: flagArg("write-mode") || "dry_run",
5551
+ brainCyclePhases: csvFlag("brain-cycle-phases"),
5552
+ brainCycleMinIngested: numberFlag("brain-cycle-min-ingested"),
5553
+ brainCycleMinIntervalMinutes: numberFlag("brain-cycle-min-interval-minutes")
5554
+ });
5555
+ return output(result, () => {
5556
+ console.log("GTM Instantly feedback pull");
5557
+ console.log(` Mode: ${dryRun ? "dry_run" : "write"}`);
5558
+ console.log(` Source: ${result.source || source}`);
5559
+ if (result.provider_campaign_id || providerCampaignId) console.log(` Campaign: ${result.provider_campaign_id || providerCampaignId}`);
5560
+ if (result.run_id) console.log(` Run ID: ${result.run_id}`);
5561
+ if (result.status) console.log(` Status: ${result.status}`);
5562
+ if (result.max_pages !== void 0) console.log(` Pages: ${result.max_pages}`);
5563
+ const countFields = [
5564
+ ["Seen", result.webhook_events_seen ?? result.email_records_seen ?? result.source_events],
5565
+ ["Normalized", result.feedback_events_synced ?? result.events_normalized],
5566
+ ["Ingested", result.ingested],
5567
+ ["Duplicates", result.duplicates],
5568
+ ["Unmatched", result.unmatched]
5569
+ ];
5570
+ for (const [label, value] of countFields) {
5571
+ if (value !== void 0 && value !== null) console.log(` ${label}: ${value}`);
5572
+ }
5573
+ if (Array.isArray(result.blockers) && result.blockers.length) console.log(` Blockers: ${result.blockers.join("; ")}`);
5574
+ if (dryRun) {
5575
+ if (result.run_id) hint(`signaliz ops run-status ${result.run_id} --watch`);
5576
+ hint("Review the dry-run counts before writing feedback, memory, and outcomes: rerun with --confirm.");
5577
+ } else if (campaignId) {
5578
+ hint(`signaliz gtm learning ${campaignId} --include-network`);
5579
+ } else if (campaignBuildId) {
5580
+ hint(`signaliz gtm execution --campaign-build-id ${campaignBuildId}`);
5581
+ } else {
5582
+ hint("After the pull completes, run signaliz gtm bootstrap --include-samples to verify feedback and memory readiness.");
5583
+ }
5584
+ });
5585
+ }
4977
5586
  if (sub === "nango") return gtmNango(rest[0], rest.slice(1));
4978
5587
  if (!sub || sub === "list" || sub === "sources") {
4979
5588
  const result = await sdk.leads.listNativeGtmCapabilities(flagArg("category"));
@@ -5034,7 +5643,7 @@ ${i + 1}. ${m.capability_id} (score ${m.score}) \u2014 ${m.label}`);
5034
5643
  const result = await sdk.leads.checkStatus(jobId);
5035
5644
  return output(result);
5036
5645
  }
5037
- die("Usage: signaliz gtm <context|bootstrap|strategy-memory|agent-template|agent-plan|plan|commit-plan|prepare-build|campaigns|campaign|existing-campaigns|audit-existing|memory|learning|execution|integrations|provider-prepare|activate-route|list|find|run|status|nango>");
5646
+ die("Usage: signaliz gtm <context|bootstrap|strategy-memory|agent-template|agent-plan|plan|commit-plan|prepare-build|campaigns|campaign|existing-campaigns|audit-existing|memory|learning|execution|integrations|provider-prepare|activate-route|feedback-webhook|feedback-pull|list|find|run|status|nango>");
5038
5647
  }
5039
5648
  function nangoActionInput() {
5040
5649
  const fromFile = readJsonFlag("input-file");
@@ -5051,30 +5660,146 @@ function nangoBaseOptions() {
5051
5660
  nangoConnectionId: flagArg("nango-connection-id")
5052
5661
  };
5053
5662
  }
5054
- function nangoConnectOptions() {
5663
+ function nangoConnectOptions(defaultSurface = "gtm_kernel") {
5055
5664
  return {
5056
5665
  providerId: flagArg("provider-id") || flagArg("provider"),
5057
5666
  integrationId: flagArg("integration-id") || flagArg("provider-config-key"),
5058
5667
  providerDisplayName: flagArg("provider-display-name") || flagArg("provider-name"),
5059
5668
  integrationDisplayName: flagArg("integration-display-name"),
5060
5669
  allowedIntegrations: csvFlag("allowed-integrations"),
5061
- surface: flagArg("surface") || "gtm_kernel",
5670
+ surface: flagArg("surface") || defaultSurface,
5062
5671
  source: flagArg("source") || "signaliz_cli",
5063
5672
  userEmail: flagArg("user-email"),
5064
5673
  userDisplayName: flagArg("user-display-name")
5065
5674
  };
5066
5675
  }
5676
+ function nangoPositionalValues(rest = []) {
5677
+ const values = [];
5678
+ for (let index = 0; index < rest.length; index++) {
5679
+ const value = rest[index];
5680
+ if (value.startsWith("--")) {
5681
+ if (!value.includes("=") && rest[index + 1] && !rest[index + 1].startsWith("--")) index++;
5682
+ continue;
5683
+ }
5684
+ values.push(value);
5685
+ }
5686
+ return values;
5687
+ }
5688
+ function nangoFlowOptions(rest = []) {
5689
+ const positionalSearch = nangoPositionalValues(rest).join(" ");
5690
+ const search = flagArg("search") || flagArg("query") || positionalSearch || void 0;
5691
+ return {
5692
+ ...nangoBaseOptions(),
5693
+ query: flagArg("query"),
5694
+ search,
5695
+ providerId: flagArg("provider-id") || flagArg("provider"),
5696
+ provider: flagArg("provider"),
5697
+ intent: flagArg("intent"),
5698
+ actionName: flagArg("action-name") || flagArg("action"),
5699
+ toolName: flagArg("tool-name"),
5700
+ proxyPath: flagArg("proxy-path") || flagArg("nango-proxy-path"),
5701
+ method: flagArg("method"),
5702
+ input: nangoActionInput(),
5703
+ cadence: flagArg("cadence"),
5704
+ fieldMap: readInlineJsonFlag("field-map-json") || readJsonFlag("field-map-file"),
5705
+ requiredFields: csvFlag("required-fields") || csvFlag("required-field"),
5706
+ confirmSpend: hasFlag("confirm-spend"),
5707
+ writeConfirmed: hasFlag("write-confirmed") || hasFlag("confirm-write") || void 0,
5708
+ layer: flagArg("layer"),
5709
+ campaignId: flagArg("campaign-id"),
5710
+ limit: numberFlag("limit"),
5711
+ includeProviderCatalog: hasFlag("skip-provider-catalog") ? false : hasFlag("include-provider-catalog") ? true : void 0
5712
+ };
5713
+ }
5067
5714
  function nangoStatusUrl(result) {
5068
5715
  return result?.status_url || result?.statusUrl || result?.nango_result?.status_url || result?.nango_result?.statusUrl;
5069
5716
  }
5070
- async function gtmNango(sub, rest) {
5717
+ function nangoActionId(result) {
5718
+ return result?.action_id || result?.actionId || result?.nango_result?.action_id || result?.nango_result?.actionId || result?.nango_result?.id;
5719
+ }
5720
+ function nangoActionStatus(result) {
5721
+ return result?.status || result?.nango_result?.status;
5722
+ }
5723
+ function isTerminalNangoActionStatus(status) {
5724
+ return ["completed", "succeeded", "success", "failed", "error", "cancelled", "canceled"].includes(String(status || "").toLowerCase());
5725
+ }
5726
+ async function waitForNangoActionResult(nango, call, opts = {}) {
5727
+ const intervalMs = Math.max(250, Number(opts.intervalMs ?? 5e3));
5728
+ const maxPolls = Math.max(1, Math.min(60, Math.trunc(Number(opts.maxPolls ?? 12))));
5729
+ const timeoutMs = Math.max(250, Number(opts.timeoutMs ?? 6e4));
5730
+ let statusUrl = nangoStatusUrl(call);
5731
+ let actionId = nangoActionId(call);
5732
+ let latest = statusUrl || actionId ? void 0 : call;
5733
+ let polls = 0;
5734
+ const startedAt = Date.now();
5735
+ while ((statusUrl || actionId) && polls < maxPolls && Date.now() - startedAt <= timeoutMs) {
5736
+ polls += 1;
5737
+ latest = await nango.getNangoActionResult({ actionId, statusUrl });
5738
+ statusUrl = nangoStatusUrl(latest) || statusUrl;
5739
+ actionId = nangoActionId(latest) || actionId;
5740
+ if (isTerminalNangoActionStatus(nangoActionStatus(latest))) break;
5741
+ if (polls < maxPolls && Date.now() - startedAt < timeoutMs) await sleep(intervalMs);
5742
+ }
5743
+ const status = nangoActionStatus(latest || call);
5744
+ const timedOut = Boolean((statusUrl || actionId) && !isTerminalNangoActionStatus(status) && (polls >= maxPolls || Date.now() - startedAt > timeoutMs));
5745
+ return {
5746
+ call,
5747
+ result: latest,
5748
+ status,
5749
+ action_id: actionId,
5750
+ actionId,
5751
+ status_url: statusUrl,
5752
+ statusUrl,
5753
+ polls,
5754
+ max_polls: maxPolls,
5755
+ maxPolls,
5756
+ interval_ms: intervalMs,
5757
+ intervalMs,
5758
+ timeout_ms: timeoutMs,
5759
+ timeoutMs,
5760
+ timed_out: timedOut,
5761
+ timedOut
5762
+ };
5763
+ }
5764
+ async function gtmNango(sub, rest, namespace = "gtm") {
5071
5765
  const sdk = getSdk();
5766
+ const nango = namespace === "ops" ? sdk.ops : sdk.gtm;
5767
+ const commandPrefix = namespace === "ops" ? "signaliz ops nango" : "signaliz gtm nango";
5768
+ const defaultSurface = namespace === "ops" ? "ops_destinations" : "gtm_kernel";
5769
+ if (sub === "flow" || sub === "plan" || sub === "prepare" || sub === "search") {
5770
+ const result = await nango.prepareNangoIntegrationFlow(nangoFlowOptions(rest));
5771
+ return output(result, () => {
5772
+ console.log(`Nango integration flow: ${result?.status || "prepared"}`);
5773
+ if (result?.provider_id) console.log(` Provider: ${result.provider_id}`);
5774
+ if (result?.integration_id) console.log(` Integration: ${result.integration_id}`);
5775
+ if (result?.selected_connection?.id) console.log(` Workspace connection: ${result.selected_connection.id}`);
5776
+ const providers = Array.isArray(result?.provider_library?.providers) ? result.provider_library.providers : [];
5777
+ if (providers.length) {
5778
+ console.log(" Provider matches:");
5779
+ for (const provider of providers.slice(0, 5)) console.log(` - ${provider.name || provider.id} (${provider.id})`);
5780
+ }
5781
+ const connections = Array.isArray(result?.matched_connections) ? result.matched_connections : [];
5782
+ if (connections.length) {
5783
+ console.log(" Saved connections:");
5784
+ for (const connection of connections.slice(0, 5)) {
5785
+ const ready = connection.ready_for_route ? "ready" : connection.needs_attention ? "needs reconnect" : "not ready";
5786
+ console.log(` - ${connection.connector_name || connection.provider_config_key || connection.id}: ${ready} (${connection.id})`);
5787
+ }
5788
+ }
5789
+ const nextActions = Array.isArray(result?.next_actions) ? result.next_actions : [];
5790
+ if (nextActions.length) {
5791
+ console.log(" Next calls:");
5792
+ for (const action of nextActions.slice(0, 5)) console.log(` - ${action.tool}: ${action.reason || "next step"}`);
5793
+ }
5794
+ if (result?.next_action) hint(result.next_action);
5795
+ });
5796
+ }
5072
5797
  if (sub === "connect" || sub === "auth" || sub === "authorize") {
5073
- const options = nangoConnectOptions();
5798
+ const options = nangoConnectOptions(defaultSurface);
5074
5799
  if (!options.providerId && !options.integrationId && (!options.allowedIntegrations || options.allowedIntegrations.length === 0)) {
5075
- die("Usage: signaliz gtm nango connect --provider-id <provider> [--integration-id <key>] [--allowed-integrations a,b]");
5800
+ die(`Usage: ${commandPrefix} connect --provider-id <provider> [--integration-id <key>] [--allowed-integrations a,b]`);
5076
5801
  }
5077
- const result = await sdk.gtm.createNangoConnectSession(options);
5802
+ const result = await nango.createNangoConnectSession(options);
5078
5803
  return output(result, () => {
5079
5804
  console.log(`Nango connect session: ${result?.status || "created"}`);
5080
5805
  if (result?.provider_id || options.providerId) console.log(` Provider: ${result?.provider_id || options.providerId}`);
@@ -5082,11 +5807,11 @@ async function gtmNango(sub, rest) {
5082
5807
  if (result?.connect_link || result?.auth_window_url) console.log(` Auth link: ${result.connect_link || result.auth_window_url}`);
5083
5808
  if (result?.expires_at) console.log(` Expires: ${result.expires_at}`);
5084
5809
  if (result?.next_action) console.log(` Next: ${result.next_action}`);
5085
- hint("After authorization finishes, run signaliz gtm nango tools --workspace-connection-id <id> --provider-config-key <key>");
5810
+ hint(`After authorization finishes, run ${commandPrefix} tools --workspace-connection-id <id> --provider-config-key <key>`);
5086
5811
  });
5087
5812
  }
5088
5813
  if (!sub || sub === "tools" || sub === "list") {
5089
- const result = await sdk.gtm.listNangoTools({
5814
+ const result = await nango.listNangoTools({
5090
5815
  ...nangoBaseOptions(),
5091
5816
  format: flagArg("format"),
5092
5817
  includeRaw: hasFlag("include-raw")
@@ -5097,39 +5822,153 @@ async function gtmNango(sub, rest) {
5097
5822
  for (const tool of tools2.slice(0, 10)) {
5098
5823
  console.log(`- ${tool.name || tool.action_name || tool.id}: ${tool.description || tool.type || "action"}`);
5099
5824
  }
5100
- hint(`signaliz gtm nango call <action_name> --workspace-connection-id <id> --input-json '{"email":"buyer@example.com"}' --dry-run`);
5825
+ hint(`${commandPrefix} call <action_name> --workspace-connection-id <id> --input-json '{"email":"buyer@example.com"}' --dry-run`);
5826
+ });
5827
+ }
5828
+ if (namespace === "ops" && (sub === "proof" || sub === "audit" || sub === "read-write")) {
5829
+ const result = await nango.proveNangoReadWrite({
5830
+ ...nangoBaseOptions(),
5831
+ limit: numberFlag("limit"),
5832
+ includeRaw: hasFlag("include-raw"),
5833
+ readProxyPath: flagArg("read-proxy-path") || flagArg("proxy-path"),
5834
+ writeProxyPath: flagArg("write-proxy-path"),
5835
+ method: flagArg("method") || flagArg("http-method"),
5836
+ executeReadProbe: hasFlag("execute-read-probe"),
5837
+ executeWriteProbe: hasFlag("execute-write-probe"),
5838
+ confirm: hasFlag("confirm"),
5839
+ confirmWrite: hasFlag("confirm-write") || hasFlag("confirm"),
5840
+ input: nangoActionInput(),
5841
+ writeInput: readInlineJsonFlag("write-input-json") || readJsonFlag("write-input-file")
5842
+ });
5843
+ return output(result, () => {
5844
+ const summary = result?.summary || {};
5845
+ const connectedRows = summary.connected_connection_rows ?? summary.connection_rows ?? 0;
5846
+ const connectedUnique = summary.connected_unique_connections ?? summary.unique_connections ?? 0;
5847
+ console.log(`Nango proof: ${result?.status || "unknown"}`);
5848
+ console.log(`Connections: ${summary.connection_rows ?? 0} rows / ${summary.unique_connections ?? 0} unique`);
5849
+ console.log(`Connected: ${connectedRows} rows / ${connectedUnique} unique`);
5850
+ console.log(`Reconnect required: ${summary.reconnect_required ?? summary.needs_attention_connection_rows ?? 0}`);
5851
+ console.log(`Action tools: ${summary.action_tools ?? 0}`);
5852
+ console.log(`Runtime reads: ${summary.runtime_read_verified ?? 0}/${connectedRows}`);
5853
+ console.log(`Writes: ${summary.write_verified ?? 0}/${connectedRows}`);
5854
+ if (summary.duplicate_connection_rows) console.log(`Duplicates: ${summary.duplicate_connection_rows}`);
5855
+ if (summary.duplicate_connected_connection_rows) console.log(`Connected duplicates: ${summary.duplicate_connected_connection_rows}`);
5856
+ if (result?.next_action) console.log(`Next: ${result.next_action}`);
5857
+ });
5858
+ }
5859
+ if (namespace === "ops" && (sub === "schedule" || sub === "recur")) {
5860
+ const promptText = promptArg(rest);
5861
+ if (!promptText) {
5862
+ die(`Usage: ${commandPrefix} ${sub} "plain-English recurring goal" --workspace-connection-id <id> --action-name <action> [--confirm-spend] [--wait]`);
5863
+ }
5864
+ const scheduleInput = {
5865
+ prompt: promptText,
5866
+ blueprint: flagArg("blueprint"),
5867
+ targetCount: numberFlag("target-count"),
5868
+ cadence: flagArg("cadence") || "daily",
5869
+ wakeOnEvents: csvFlag("wake-on-events") || csvFlag("wake-events") || csvFlag("wake-event"),
5870
+ timezone: flagArg("timezone"),
5871
+ companyDomains: csvFlag("company-domains") || csvFlag("domains"),
5872
+ customAiPrompt: flagArg("ai-prompt") || flagArg("custom-ai-prompt"),
5873
+ signalPrompt: flagArg("signal-prompt"),
5874
+ outputPrompt: flagArg("output-prompt") || flagArg("result-prompt"),
5875
+ confirmSpend: hasFlag("confirm-spend"),
5876
+ ...nangoBaseOptions(),
5877
+ actionName: flagArg("action-name") || flagArg("action") || flagArg("nango-action"),
5878
+ toolName: flagArg("tool-name"),
5879
+ proxyPath: flagArg("proxy-path") || flagArg("nango-proxy-path"),
5880
+ method: flagArg("method") || flagArg("http-method"),
5881
+ input: nangoActionInput(),
5882
+ fieldMap: readInlineJsonFlag("field-map-json") || readJsonFlag("field-map-file"),
5883
+ requiredFields: csvFlag("required-fields") || csvFlag("required-field"),
5884
+ writeConfirmed: hasFlag("write-confirmed") || hasFlag("confirm") || hasFlag("confirm-write") || void 0,
5885
+ agentWriteConfirmed: hasFlag("agent-write-confirmed") || void 0
5886
+ };
5887
+ if (hasFlag("wait")) {
5888
+ const combined = await nango.scheduleNangoActionAndWait({
5889
+ ...scheduleInput,
5890
+ force: hasFlag("force"),
5891
+ instruction: flagArg("instruction"),
5892
+ intervalMs: numberFlag("interval-ms") ?? numberFlag("wait-interval-ms"),
5893
+ maxPolls: numberFlag("max-polls") ?? numberFlag("wait-max-polls"),
5894
+ timeoutMs: numberFlag("timeout-ms") ?? numberFlag("wait-timeout-ms"),
5895
+ limit: numberFlag("limit"),
5896
+ cursor: flagArg("cursor"),
5897
+ includeFailedRuns: hasFlag("include-failed-runs"),
5898
+ includeResults: !hasFlag("no-results")
5899
+ });
5900
+ return output(combined, () => {
5901
+ console.log(`Nango Op scheduled: ${combined.schedule?.op_id || combined.op_id}`);
5902
+ printOpsWaitForResults(combined);
5903
+ });
5904
+ }
5905
+ const result = await nango.scheduleNangoAction(scheduleInput);
5906
+ return output(result, () => {
5907
+ if (result.approval_required || result.error_code === "APPROVAL_REQUIRED") {
5908
+ console.log("Approval required");
5909
+ if (result.plan) {
5910
+ console.log(`Plan: ${result.plan.title || result.plan.plan_id || "pending approval"}`);
5911
+ if (result.plan.ui_summary) console.log(`Summary: ${result.plan.ui_summary}`);
5912
+ printPlanSchedule(result.plan);
5913
+ printOpsExecutionContract(result.plan);
5914
+ }
5915
+ console.log(`Next: ${result.next_action || "Retry with --confirm-spend after review."}`);
5916
+ hint(`${commandPrefix} ${sub} ${JSON.stringify(promptText)} --confirm-spend`);
5917
+ return;
5918
+ }
5919
+ console.log(`Nango Op scheduled: ${result.op_id}`);
5920
+ console.log(`Status: ${result.status || "scheduled"}`);
5921
+ if (result.plan) printOpsExecutionContract(result.plan);
5922
+ console.log(`Next: ${result.next_action || "Run or wait for the next scheduled tick."}`);
5923
+ if (result.op_id) hint(`signaliz ops wait ${result.op_id}`);
5101
5924
  });
5102
5925
  }
5103
5926
  if (sub === "call" || sub === "run") {
5104
- const actionName = rest[0] || flagArg("action-name");
5927
+ const actionName = nangoPositionalValues(rest)[0] || flagArg("action-name");
5105
5928
  const toolName = flagArg("tool-name");
5106
- if (!actionName && !toolName) {
5107
- die(`Usage: signaliz gtm nango call <action_name> --workspace-connection-id <id> --input-json '{"email":"buyer@example.com"}' [--execute --confirm]`);
5929
+ const proxyPath = flagArg("proxy-path") || flagArg("nango-proxy-path");
5930
+ if (!actionName && !toolName && !proxyPath) {
5931
+ die(`Usage: ${commandPrefix} call <action_name> --workspace-connection-id <id> --input-json '{"email":"buyer@example.com"}' [--execute --confirm], or ${commandPrefix} call --workspace-connection-id <id> --proxy-path /provider/path --method GET`);
5108
5932
  }
5109
- const result = await sdk.gtm.callNangoTool({
5933
+ const callOptions = {
5110
5934
  ...nangoBaseOptions(),
5111
5935
  actionName,
5112
5936
  toolName,
5937
+ deliveryMode: proxyPath ? "proxy" : void 0,
5938
+ proxyPath,
5939
+ method: flagArg("method") || flagArg("http-method"),
5113
5940
  input: nangoActionInput(),
5114
- async: hasFlag("async") || void 0,
5941
+ async: hasFlag("async") || hasFlag("wait") || void 0,
5115
5942
  maxRetries: numberFlag("max-retries"),
5116
5943
  dryRun: hasFlag("execute") ? false : hasFlag("dry-run") ? true : void 0,
5117
5944
  confirm: hasFlag("confirm") || void 0,
5118
5945
  confirmWrite: hasFlag("confirm-write") || void 0
5119
- });
5120
- return output(result, () => {
5946
+ };
5947
+ const waitOptions = {
5948
+ intervalMs: numberFlag("interval-ms") ?? numberFlag("wait-interval-ms"),
5949
+ maxPolls: numberFlag("max-polls") ?? numberFlag("wait-max-polls"),
5950
+ timeoutMs: numberFlag("timeout-ms") ?? numberFlag("wait-timeout-ms")
5951
+ };
5952
+ const waited = hasFlag("wait") && namespace === "ops" ? await nango.callNangoToolAndWait({ ...callOptions, ...waitOptions }) : null;
5953
+ const result = waited?.call || await nango.callNangoTool(callOptions);
5954
+ const finalResult = waited || (hasFlag("wait") ? await waitForNangoActionResult(nango, result, waitOptions) : result);
5955
+ return output(finalResult, () => {
5121
5956
  console.log(`Nango action ${result?.status || "submitted"}: ${actionName || toolName}`);
5122
5957
  const statusUrl = nangoStatusUrl(result);
5123
5958
  if (statusUrl) console.log(` Status URL: ${statusUrl}`);
5959
+ if (finalResult !== result) {
5960
+ console.log(` Result: ${finalResult.status || "unknown"} (${finalResult.polls}/${finalResult.max_polls} polls)`);
5961
+ if (finalResult.timed_out) console.log(" Timed out before a terminal Nango result.");
5962
+ }
5124
5963
  if (result?.next_action) console.log(` Next: ${result.next_action}`);
5125
- if (statusUrl) hint(`signaliz gtm nango result --status-url ${statusUrl}`);
5964
+ if (statusUrl && finalResult === result) hint(`${commandPrefix} result --status-url ${statusUrl}`);
5126
5965
  });
5127
5966
  }
5128
5967
  if (sub === "result" || sub === "status") {
5129
- const actionId = rest[0] || flagArg("action-id");
5968
+ const actionId = nangoPositionalValues(rest)[0] || flagArg("action-id");
5130
5969
  const statusUrl = flagArg("status-url");
5131
- if (!actionId && !statusUrl) die("Usage: signaliz gtm nango result --action-id <id> OR --status-url /action/<id>");
5132
- const result = await sdk.gtm.getNangoActionResult({ actionId, statusUrl });
5970
+ if (!actionId && !statusUrl) die(`Usage: ${commandPrefix} result --action-id <id> OR --status-url /action/<id>`);
5971
+ const result = await nango.getNangoActionResult({ actionId, statusUrl });
5133
5972
  return output(result, () => {
5134
5973
  console.log(`Nango action result: ${result?.status || "unknown"}`);
5135
5974
  if (result?.action_id || actionId) console.log(` Action ID: ${result?.action_id || actionId}`);
@@ -5137,7 +5976,7 @@ async function gtmNango(sub, rest) {
5137
5976
  if (nextStatusUrl) console.log(` Status URL: ${nextStatusUrl}`);
5138
5977
  });
5139
5978
  }
5140
- die("Usage: signaliz gtm nango <connect|tools|call|result>");
5979
+ die(`Usage: ${commandPrefix} <flow|search|connect|tools|call|schedule|result>`);
5141
5980
  }
5142
5981
  async function email(sub, rest) {
5143
5982
  const sdk = getSdk();
@@ -5342,26 +6181,74 @@ async function ops(sub, rest) {
5342
6181
  const savedRest = sub === "save" ? rest : rest.slice(1);
5343
6182
  return opsSaved(savedSub, savedRest);
5344
6183
  }
6184
+ if (sub === "nango") return gtmNango(rest[0], rest.slice(1), "ops");
5345
6185
  if (sub === "plan" && !promptArg(rest)) {
5346
6186
  die('Usage: signaliz ops plan "monitor these companies daily..."');
5347
6187
  }
5348
6188
  if (sub === "create" && !promptArg(rest)) {
5349
6189
  die('Usage: signaliz ops create "build 100 leads that fit..." [--confirm-spend]');
5350
6190
  }
6191
+ if ((sub === "schedule" || sub === "recur") && !promptArg(rest)) {
6192
+ die('Usage: signaliz ops schedule "monitor these companies daily..." [--confirm-spend]');
6193
+ }
5351
6194
  if (sub === "execute" && !promptArg(rest)) {
5352
6195
  die('Usage: signaliz ops execute "launch a campaign for this ICP..." [--dry-run|--confirm-spend]');
5353
6196
  }
5354
6197
  const sdk = getSdk();
6198
+ const opsOptionalBooleanFlag = (name) => {
6199
+ const value = flagArg(name);
6200
+ if (value !== void 0) return !["0", "false", "no", "off"].includes(value.toLowerCase());
6201
+ return hasFlag(name) ? true : void 0;
6202
+ };
6203
+ const opsNangoDestination = () => {
6204
+ const destination = { type: "nango" };
6205
+ let hasDetails = false;
6206
+ const set = (key, value) => {
6207
+ if (value === void 0 || value === null || value === "") return;
6208
+ destination[key] = value;
6209
+ hasDetails = true;
6210
+ };
6211
+ const nangoAction = flagArg("nango-action");
6212
+ const nangoProxyPath = flagArg("nango-proxy-path");
6213
+ set("connectionId", flagArg("workspace-connection-id") || flagArg("connection-id"));
6214
+ set("providerConfigKey", flagArg("provider-config-key"));
6215
+ set("integrationId", flagArg("integration-id"));
6216
+ set("nangoConnectionId", flagArg("nango-connection-id"));
6217
+ set("actionName", flagArg("action-name") || nangoAction);
6218
+ set("nangoAction", nangoAction);
6219
+ set("proxyPath", flagArg("proxy-path") || nangoProxyPath);
6220
+ set("nangoProxyPath", nangoProxyPath);
6221
+ set("method", flagArg("method") || flagArg("http-method"));
6222
+ set("writeConfirmed", opsOptionalBooleanFlag("write-confirmed"));
6223
+ set("agentWriteConfirmed", opsOptionalBooleanFlag("agent-write-confirmed"));
6224
+ return hasDetails ? destination : void 0;
6225
+ };
6226
+ const isNangoDestinationType = (type) => {
6227
+ const normalized = type.toLowerCase().replace(/[-\s]/g, "_");
6228
+ return ["nango", "api", "managed_api", "customer_api", "external_api", "nango_api"].includes(normalized);
6229
+ };
5355
6230
  const opsDestinations = () => {
5356
6231
  const destinations = csvFlag("destinations") || csvFlag("destination");
5357
- return destinations?.map((type) => ({ type }));
6232
+ const nangoDestination = opsNangoDestination();
6233
+ const out = (destinations || []).map((type) => nangoDestination && isNangoDestinationType(type) ? { ...nangoDestination, type } : { type });
6234
+ if (nangoDestination && !out.some((destination) => isNangoDestinationType(String(destination.type)))) {
6235
+ out.push(nangoDestination);
6236
+ }
6237
+ return out.length > 0 ? out : void 0;
5358
6238
  };
6239
+ const opsWakeEvents = () => csvFlag("wake-on-events") || csvFlag("wake-events") || csvFlag("wake-event");
5359
6240
  const opsCompanyDomains = () => csvFlag("company-domains") || csvFlag("domains");
5360
6241
  const opsCustomPrompts = () => ({
5361
6242
  custom_ai_prompt: flagArg("ai-prompt") || flagArg("custom-ai-prompt"),
5362
6243
  signal_prompt: flagArg("signal-prompt"),
5363
6244
  output_prompt: flagArg("output-prompt") || flagArg("result-prompt")
5364
6245
  });
6246
+ const printPlanSchedule2 = (plan) => {
6247
+ if (!plan || typeof plan !== "object") return;
6248
+ const wakeEvents = Array.isArray(plan.wake_on_events) ? plan.wake_on_events : plan.wakeOnEvents;
6249
+ console.log(`Schedule: ${plan.cadence || "manual"}`);
6250
+ console.log(`Wake: ${Array.isArray(wakeEvents) && wakeEvents.length ? wakeEvents.join(", ") : "cadence only"}`);
6251
+ };
5365
6252
  if (sub === "plan") {
5366
6253
  const promptText = promptArg(rest);
5367
6254
  const result = await sdk.ops.plan({
@@ -5369,6 +6256,7 @@ async function ops(sub, rest) {
5369
6256
  blueprint: flagArg("blueprint"),
5370
6257
  target_count: numberFlag("target-count"),
5371
6258
  cadence: flagArg("cadence"),
6259
+ wake_on_events: opsWakeEvents(),
5372
6260
  destinations: opsDestinations(),
5373
6261
  company_domains: opsCompanyDomains(),
5374
6262
  ...opsCustomPrompts()
@@ -5378,10 +6266,11 @@ async function ops(sub, rest) {
5378
6266
  if (result.ui_summary) console.log(`Summary: ${result.ui_summary}`);
5379
6267
  console.log(`Status: ${result.status}`);
5380
6268
  console.log(`Outcome: ${result.blueprint}`);
5381
- console.log(`Cadence: ${result.cadence}`);
6269
+ printPlanSchedule2(result);
5382
6270
  console.log(`Target: ${result.target_count}`);
5383
6271
  console.log(`Credits: ${result.estimated_credits}`);
5384
6272
  if (result.destination_status) console.log(`Deliver: ${result.destination_status}`);
6273
+ printOpsExecutionContract(result);
5385
6274
  printPrimitiveGraph(result.primitive_graph);
5386
6275
  console.log(`Next: ${result.next_action}`);
5387
6276
  });
@@ -5400,6 +6289,26 @@ async function ops(sub, rest) {
5400
6289
  console.log(`${primary.title}: ${primary.outcome}`);
5401
6290
  console.log(`Prompt: ${primary.prompt}`);
5402
6291
  console.log(`CLI: ${primary.cli_command}`);
6292
+ const packet = primary.operating_packet || primary.operatingPacket || result.operating_packet || result.operatingPacket;
6293
+ if (packet?.schedule) {
6294
+ console.log(`Schedule: ${packet.schedule.cadence} - ${packet.schedule.recurrence}`);
6295
+ console.log(`Results: ${packet.result_contract?.wait_tool || packet.resultContract?.waitTool || packet.result_contract?.retrieve_tool || packet.resultContract?.retrieveTool || "ops_wait"}`);
6296
+ }
6297
+ const nangoRoute = packet?.nango_route || packet?.nangoRoute;
6298
+ if (nangoRoute?.enabled) {
6299
+ console.log("\nNango route");
6300
+ console.log(`- Discover: ${nangoRoute.discover_tool || nangoRoute.discoverTool}`);
6301
+ console.log(`- Dry run: ${nangoRoute.dry_run_tool || nangoRoute.dryRunTool}`);
6302
+ console.log(`- Execute: ${nangoRoute.execute_tool || nangoRoute.executeTool} after confirm=true`);
6303
+ if (nangoRoute.execute_and_wait_tool || nangoRoute.executeAndWaitTool) {
6304
+ console.log(`- Wait: ${nangoRoute.execute_and_wait_tool || nangoRoute.executeAndWaitTool}`);
6305
+ }
6306
+ const nangoScheduleTool = nangoRoute.schedule_and_wait_tool || nangoRoute.scheduleAndWaitTool || nangoRoute.schedule_tool || nangoRoute.scheduleTool;
6307
+ if (nangoScheduleTool) {
6308
+ console.log(`- Schedule: ${nangoScheduleTool} after approval`);
6309
+ }
6310
+ console.log(`- Gate: ${nangoRoute.write_gate || nangoRoute.writeGate}`);
6311
+ }
5403
6312
  if (primary.mcp_sequence?.length) {
5404
6313
  console.log("\nMCP flow");
5405
6314
  for (const [index, step] of primary.mcp_sequence.entries()) {
@@ -5411,22 +6320,49 @@ async function ops(sub, rest) {
5411
6320
  }
5412
6321
  if (sub === "execute") {
5413
6322
  const promptText = promptArg(rest);
6323
+ const shouldWait = hasFlag("wait");
5414
6324
  const result = await sdk.ops.execute({
5415
6325
  prompt: promptText,
5416
6326
  blueprint: flagArg("blueprint"),
5417
6327
  target_count: numberFlag("target-count"),
5418
6328
  cadence: flagArg("cadence"),
6329
+ wake_on_events: opsWakeEvents(),
6330
+ timezone: flagArg("timezone"),
5419
6331
  destinations: opsDestinations(),
5420
6332
  company_domains: opsCompanyDomains(),
5421
6333
  ...opsCustomPrompts(),
5422
6334
  confirm_spend: hasFlag("confirm-spend"),
5423
6335
  dry_run: hasFlag("dry-run"),
5424
- auto_run: hasFlag("auto-run") ? true : hasFlag("no-auto-run") ? false : void 0,
6336
+ auto_run: hasFlag("auto-run") ? true : hasFlag("no-auto-run") ? false : shouldWait ? true : void 0,
5425
6337
  output_format: flagArg("output-format") === "markdown" ? "markdown" : "json"
5426
6338
  });
6339
+ if (shouldWait && result.op_id && !result.approval_required && result.error_code !== "APPROVAL_REQUIRED" && !hasFlag("dry-run")) {
6340
+ const waited = await sdk.ops.waitForResults({
6341
+ op_id: result.op_id,
6342
+ ...opsWaitOptions()
6343
+ });
6344
+ const combined = {
6345
+ ...waited,
6346
+ execute: result,
6347
+ run_id: result.run_id ?? waited.status?.run_id ?? waited.status?.runId,
6348
+ runId: result.runId ?? result.run_id ?? waited.status?.runId ?? waited.status?.run_id,
6349
+ execution_refs: combineExecutionRefs(result.execution_refs, waited.execution_refs)
6350
+ };
6351
+ return output(combined, () => {
6352
+ if (result.run_id || result.runId) console.log(`Run: ${result.run_id || result.runId}`);
6353
+ printOpsWaitForResults(combined);
6354
+ });
6355
+ }
5427
6356
  return output(result, () => {
5428
6357
  if (result.approval_required || result.error_code === "APPROVAL_REQUIRED") {
5429
6358
  console.log("Approval required");
6359
+ if (result.plan) {
6360
+ console.log(`Plan: ${result.plan.title || result.plan.plan_id || "pending approval"}`);
6361
+ if (result.plan.ui_summary) console.log(`Summary: ${result.plan.ui_summary}`);
6362
+ console.log(`Status: ${result.plan.status || result.status || "needs_approval"}`);
6363
+ printPlanSchedule2(result.plan);
6364
+ printOpsExecutionContract(result.plan);
6365
+ }
5430
6366
  if (result.estimated_credits !== void 0) console.log(`Credits: ${result.estimated_credits}`);
5431
6367
  console.log(`Next: ${result.next_action || "Retry with --confirm-spend after approval."}`);
5432
6368
  hint(`signaliz ops execute ${JSON.stringify(promptText)} --confirm-spend`);
@@ -5436,13 +6372,19 @@ async function ops(sub, rest) {
5436
6372
  console.log(`Dry run: ${result.plan.title}`);
5437
6373
  if (result.plan.ui_summary) console.log(`Summary: ${result.plan.ui_summary}`);
5438
6374
  console.log(`Status: ${result.plan.status}`);
6375
+ printPlanSchedule2(result.plan);
5439
6376
  console.log(`Credits: ${result.plan.estimated_credits}`);
6377
+ printOpsExecutionContract(result.plan);
5440
6378
  console.log(`Next: ${result.next_action || result.plan.next_action}`);
5441
6379
  return;
5442
6380
  }
5443
6381
  console.log(`Op: ${result.op_id || "not created"}`);
5444
6382
  if (result.run_id) console.log(`Run: ${result.run_id}`);
5445
6383
  console.log(`Status: ${result.status || "unknown"}`);
6384
+ if (result.plan) {
6385
+ printPlanSchedule2(result.plan);
6386
+ printOpsExecutionContract(result.plan);
6387
+ }
5446
6388
  console.log(`Next: ${result.next_action || "Check status for progress."}`);
5447
6389
  if (result.op_id) hint(`signaliz ops status ${result.op_id}`);
5448
6390
  });
@@ -5505,6 +6447,8 @@ Available: ${result.available_types.join(", ")}`);
5505
6447
  blueprint: flagArg("blueprint"),
5506
6448
  target_count: numberFlag("target-count"),
5507
6449
  cadence: flagArg("cadence"),
6450
+ wake_on_events: opsWakeEvents(),
6451
+ timezone: flagArg("timezone"),
5508
6452
  destinations: opsDestinations(),
5509
6453
  company_domains: opsCompanyDomains(),
5510
6454
  ...opsCustomPrompts(),
@@ -5512,15 +6456,99 @@ Available: ${result.available_types.join(", ")}`);
5512
6456
  activate: hasFlag("activate")
5513
6457
  });
5514
6458
  return output(result, () => {
6459
+ if (result.approval_required || result.error_code === "APPROVAL_REQUIRED") {
6460
+ console.log("Approval required");
6461
+ if (result.plan) {
6462
+ console.log(`Plan: ${result.plan.title || result.plan.plan_id || "pending approval"}`);
6463
+ if (result.plan.ui_summary) console.log(`Summary: ${result.plan.ui_summary}`);
6464
+ console.log(`Status: ${result.plan.status || result.status || "needs_approval"}`);
6465
+ printPlanSchedule2(result.plan);
6466
+ printOpsExecutionContract(result.plan);
6467
+ }
6468
+ if (result.estimated_credits !== void 0) console.log(`Credits: ${result.estimated_credits}`);
6469
+ console.log(`Next: ${result.next_action || "Retry with --confirm-spend after approval."}`);
6470
+ hint(`signaliz ops create ${JSON.stringify(promptText)} --confirm-spend`);
6471
+ return;
6472
+ }
5515
6473
  console.log(`Op created: ${result.op_id}`);
5516
6474
  console.log(`Status: ${result.status}`);
6475
+ if (result.plan) {
6476
+ printPlanSchedule2(result.plan);
6477
+ printOpsExecutionContract(result.plan);
6478
+ }
5517
6479
  console.log(`Next: ${result.next_action}`);
5518
6480
  hint(`signaliz ops run ${result.op_id}`);
5519
6481
  });
5520
6482
  }
6483
+ if (sub === "schedule" || sub === "recur") {
6484
+ const promptText = promptArg(rest);
6485
+ const shouldWait = hasFlag("wait");
6486
+ const scheduleInput = {
6487
+ prompt: promptText,
6488
+ blueprint: flagArg("blueprint"),
6489
+ target_count: numberFlag("target-count"),
6490
+ cadence: flagArg("cadence") || "daily",
6491
+ wake_on_events: opsWakeEvents(),
6492
+ timezone: flagArg("timezone"),
6493
+ destinations: opsDestinations(),
6494
+ company_domains: opsCompanyDomains(),
6495
+ ...opsCustomPrompts(),
6496
+ confirm_spend: hasFlag("confirm-spend")
6497
+ };
6498
+ if (shouldWait) {
6499
+ const combined = await sdk.ops.scheduleAndWait({
6500
+ ...scheduleInput,
6501
+ force: hasFlag("force"),
6502
+ instruction: flagArg("instruction"),
6503
+ ...opsWaitOptions()
6504
+ });
6505
+ return output(combined, () => {
6506
+ console.log(`Op scheduled: ${combined.schedule?.op_id || combined.op_id}`);
6507
+ printOpsWaitForResults(combined);
6508
+ });
6509
+ }
6510
+ const result = await sdk.ops.schedule(scheduleInput);
6511
+ return output(result, () => {
6512
+ if (result.approval_required || result.error_code === "APPROVAL_REQUIRED") {
6513
+ console.log("Approval required");
6514
+ if (result.plan) {
6515
+ console.log(`Plan: ${result.plan.title || result.plan.plan_id || "pending approval"}`);
6516
+ if (result.plan.ui_summary) console.log(`Summary: ${result.plan.ui_summary}`);
6517
+ console.log(`Status: ${result.plan.status || result.status || "needs_approval"}`);
6518
+ printPlanSchedule2(result.plan);
6519
+ printOpsExecutionContract(result.plan);
6520
+ }
6521
+ if (result.estimated_credits !== void 0) console.log(`Credits: ${result.estimated_credits}`);
6522
+ console.log(`Next: ${result.next_action || "Retry with --confirm-spend after approval."}`);
6523
+ hint(`signaliz ops schedule ${JSON.stringify(promptText)} --confirm-spend`);
6524
+ return;
6525
+ }
6526
+ console.log(`Op scheduled: ${result.op_id}`);
6527
+ if (result.run_id) console.log(`Run: ${result.run_id}`);
6528
+ console.log(`Status: ${result.status}`);
6529
+ if (result.plan) {
6530
+ printPlanSchedule2(result.plan);
6531
+ printOpsExecutionContract(result.plan);
6532
+ }
6533
+ console.log(`Next: ${result.next_action}`);
6534
+ if (result.op_id) hint(`signaliz ops run ${result.op_id} --wait --limit 100`);
6535
+ });
6536
+ }
5521
6537
  if (sub === "run") {
5522
6538
  const opId = rest[0];
5523
6539
  if (!opId) die("Usage: signaliz ops run <op_id>");
6540
+ if (hasFlag("wait")) {
6541
+ const result2 = await sdk.ops.runAndWait({
6542
+ op_id: opId,
6543
+ force: hasFlag("force"),
6544
+ instruction: flagArg("instruction"),
6545
+ ...opsWaitOptions()
6546
+ });
6547
+ return output(result2, () => {
6548
+ if (result2.run?.run_id || result2.run?.runId) console.log(`Run: ${result2.run.run_id || result2.run.runId}`);
6549
+ printOpsWaitForResults(result2);
6550
+ });
6551
+ }
5524
6552
  const result = await sdk.ops.run({ op_id: opId, force: hasFlag("force"), instruction: flagArg("instruction") });
5525
6553
  return output(result, () => {
5526
6554
  console.log(`Op running: ${result.op_id}`);
@@ -5553,14 +6581,38 @@ Available: ${result.available_types.join(", ")}`);
5553
6581
  include_failed_runs: hasFlag("include-failed-runs")
5554
6582
  });
5555
6583
  return output(result, () => {
5556
- const hasMore = result.has_more || result.hasMore;
5557
- const nextCursor = result.next_cursor ?? result.nextCursor;
5558
- console.log(`Results: ${result.results_count}${hasMore ? " (more available)" : ""}`);
5559
- for (const row of result.results.slice(0, 10)) console.log(JSON.stringify(row));
6584
+ const summary = result.summary || {};
6585
+ const hasMore = summary.has_more ?? summary.hasMore ?? result.has_more ?? result.hasMore;
6586
+ const nextCursor = summary.next_cursor ?? summary.nextCursor ?? result.next_cursor ?? result.nextCursor;
6587
+ const resultsCount = summary.results_count ?? summary.resultsCount ?? result.results_count ?? result.resultsCount ?? 0;
6588
+ const resultsTotal = summary.results_total ?? summary.resultsTotal ?? result.results_total ?? result.resultsTotal;
6589
+ const deliveryStatus = summary.delivery_status ?? summary.deliveryStatus ?? result.delivery_status ?? result.deliveryStatus;
6590
+ const resultsUrl = summary.results_url ?? summary.resultsUrl ?? result.results_url ?? result.resultsUrl;
6591
+ const nextAction = summary.next_action ?? summary.nextAction ?? result.next_action ?? result.nextAction;
6592
+ console.log(`Results: ${resultsCount}${hasMore ? " (more available)" : ""}`);
6593
+ if (summary.label) console.log(`Summary: ${summary.label}`);
6594
+ console.log(`Status: ${summary.status || result.status || "unknown"} (${summary.phase || result.phase || "unknown"})`);
6595
+ if (resultsTotal !== void 0 && resultsTotal !== null) console.log(`Total: ${resultsTotal}`);
6596
+ if (deliveryStatus) console.log(`Deliver: ${deliveryStatus}`);
6597
+ if (resultsUrl) console.log(`Open: ${resultsUrl}`);
6598
+ if (nextAction) console.log(`Next: ${nextAction}`);
6599
+ if (result.results.length) {
6600
+ console.log("\nRows");
6601
+ for (const row of result.results.slice(0, 10)) console.log(JSON.stringify(row));
6602
+ }
5560
6603
  if (hasMore && nextCursor) hint(`signaliz ops results ${opId} --cursor ${nextCursor}`);
5561
6604
  printExecutionRefs(result.execution_refs);
5562
6605
  });
5563
6606
  }
6607
+ if (sub === "wait" || sub === "await") {
6608
+ const opId = rest[0] || flagArg("op-id");
6609
+ if (!opId) die("Usage: signaliz ops wait <op_id>");
6610
+ const result = await sdk.ops.waitForResults({
6611
+ op_id: opId,
6612
+ ...opsWaitOptions()
6613
+ });
6614
+ return output(result, () => printOpsWaitForResults(result));
6615
+ }
5564
6616
  if (sub === "run-status" || sub === "trigger-status" || sub === "watch") {
5565
6617
  const runIds = csvFlag("run-ids");
5566
6618
  const runId = rest[0] || flagArg("run-id");
@@ -5889,13 +6941,15 @@ Available: ${result.available_types.join(", ")}`);
5889
6941
  const result = await sdk.ops.attachSinkToRoutine({ routine_id: routineId, sink_id: sinkId });
5890
6942
  return output(result);
5891
6943
  }
5892
- die("Usage: signaliz ops <proof|autopilot|plan|execute|quickstart|create|run|status|results|run-status|queue|logs|replay|dashboard|doctor|connections|sinks|routines|routine|attach-sink|saved>");
6944
+ die("Usage: signaliz ops <proof|autopilot|plan|execute|quickstart|create|schedule|run|status|results|wait|nango|run-status|queue|logs|replay|dashboard|doctor|connections|sinks|routines|routine|attach-sink|saved>");
5893
6945
  }
5894
6946
  var OPS_SHORTCUTS = {
5895
6947
  "/plan": "plan",
5896
6948
  plan: "plan",
5897
6949
  "/goal": "create",
5898
6950
  goal: "create",
6951
+ schedule: "schedule",
6952
+ recur: "schedule",
5899
6953
  quickstart: "quickstart",
5900
6954
  template: "template",
5901
6955
  proof: "proof",
@@ -5904,6 +6958,7 @@ var OPS_SHORTCUTS = {
5904
6958
  run: "run",
5905
6959
  status: "status",
5906
6960
  results: "results",
6961
+ wait: "wait",
5907
6962
  queue: "queue",
5908
6963
  debug: "debug",
5909
6964
  watch: "watch",