@signaliz/cli 1.0.15 → 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 +47 -3
  2. package/dist/bin.js +1221 -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) {
@@ -860,6 +990,28 @@ function getCampaignBuilderBuildKitFactory() {
860
990
  throw new Error(`Unable to load campaign build kit helper: ${primaryMessage}; fallback failed: ${fallbackError.message}`);
861
991
  }
862
992
  }
993
+ function getCampaignBuilderMemoryKitFactory() {
994
+ let primaryError;
995
+ try {
996
+ const sdk = require("@signaliz/sdk");
997
+ if (typeof sdk.createCampaignBuilderMemoryKit === "function") {
998
+ return sdk.createCampaignBuilderMemoryKit;
999
+ }
1000
+ primaryError = new Error("SDK missing createCampaignBuilderMemoryKit");
1001
+ } catch (err) {
1002
+ primaryError = err;
1003
+ }
1004
+ try {
1005
+ const sdk = require((0, import_node_path.join)(__dirname, "..", "..", "sdk", "dist", "index.js"));
1006
+ if (typeof sdk.createCampaignBuilderMemoryKit === "function") {
1007
+ return sdk.createCampaignBuilderMemoryKit;
1008
+ }
1009
+ throw new Error("local SDK missing createCampaignBuilderMemoryKit");
1010
+ } catch (fallbackError) {
1011
+ const primaryMessage = primaryError instanceof Error ? primaryError.message : String(primaryError || "SDK missing campaign memory kit helper");
1012
+ throw new Error(`Unable to load campaign memory kit helper: ${primaryMessage}; fallback failed: ${fallbackError.message}`);
1013
+ }
1014
+ }
863
1015
  function prompt(question) {
864
1016
  const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stderr });
865
1017
  return new Promise((resolve) => {
@@ -921,11 +1073,9 @@ Discovery:
921
1073
  --category NAME Optional category filter, e.g. ops or enrichment
922
1074
 
923
1075
  AI:
924
- ai multi-model Run Custom AI Enrichment - Multi Model
925
- --prompt "..." Prompt/template for each record (required)
926
- --model provider/model
927
- OpenRouter model id, e.g. google/gemini-2.5-flash
928
- --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
929
1079
  --input-file FILE Single JSON record
930
1080
  --output-fields a:text,b:number
931
1081
  Structured fields to return
@@ -933,12 +1083,10 @@ AI:
933
1083
  --pdf-url URL Attach a PDF URL to all records
934
1084
  --attachment-file FILE
935
1085
  Attach a local image/PDF/audio/video as base64
936
- --attachment-fields a,b
937
- Record fields containing attachment URLs/arrays
938
- --max-concurrency N Record concurrency, default 3, maximum 5
939
- --analysis-models a,b Optional Fusion analysis model panel
940
- --judge-model MODEL Optional Fusion judge model
941
- --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
942
1090
 
943
1091
  Lead Generation:
944
1092
  lead generate Start a B2B lead-generation job
@@ -1024,9 +1172,13 @@ GTM Kernel:
1024
1172
  gtm learning <id> Plan Brain learning for a campaign
1025
1173
  --include-network Include privacy-safe network lanes
1026
1174
  --write-mode MODE dry_run or write
1175
+ --holdout-min-sample-size N
1176
+ Minimum control/treatment subjects for measured lift
1027
1177
  gtm learning-run <id> Queue ready Brain learning phases
1028
1178
  --phases a,b Optional phase allow-list
1029
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
1030
1182
  gtm calibrate <id> Calibrate deliverability predictions
1031
1183
  --min-sample-size N Default server-side
1032
1184
  --write Write calibrations/patterns; otherwise dry-run
@@ -1050,6 +1202,12 @@ GTM Kernel:
1050
1202
  --provider NAME instantly, smartlead, heyreach, airbyte, custom_webhook
1051
1203
  --campaign-id ID Optional campaign scope
1052
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
1053
1211
 
1054
1212
  Native GTM Data Sources:
1055
1213
  Use these when you already know the exact source you want to run.
@@ -1088,6 +1246,8 @@ Campaign Builder:
1088
1246
  --output-file FILE Optional request file to write
1089
1247
  campaign-agent kit Emit request JSON, CLI commands, MCP calls, SDK example, and BYO pattern
1090
1248
  --kit-file FILE Optional kit JSON file to write
1249
+ campaign-agent memory-kit
1250
+ Emit memory readiness, seed runner, MCP calls, and proof commands
1091
1251
  campaign-agent plan Compose an approval-gated strategy-template MCP flow
1092
1252
  --goal "..." Campaign goal or brief
1093
1253
  --request-file FILE Reusable JSON CampaignBuilderAgentRequest
@@ -1140,8 +1300,12 @@ Campaign Builder:
1140
1300
  campaign rows <id> Retrieve build rows
1141
1301
  --limit N Page size (default: 50, max: 500)
1142
1302
  --cursor <id> Pagination cursor
1303
+ --include-data Include generated copy and rich row data
1304
+ --include-raw Include raw provider payloads
1143
1305
  --all Dump all rows (caution: unbounded)
1144
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)
1145
1309
  campaign approve <id> Approve pending delivery
1146
1310
  --destination-type json|csv|webhook
1147
1311
  --destination-id ID Optional destination/sink ID
@@ -1149,12 +1313,18 @@ Campaign Builder:
1149
1313
  campaign cancel <id> Cancel a running build
1150
1314
 
1151
1315
  Ops:
1152
- Canonical path: ops plan -> ops create -> ops run -> ops status -> ops results
1316
+ Canonical path: ops plan -> ops execute -> ops wait
1153
1317
  ops plan "..." Preview a prompt-first Op plan
1154
1318
  --blueprint NAME monitor_companies|build_leads|enrich_list|route_signals|launch_campaign
1155
1319
  --target-count N Target row count
1156
1320
  --cadence NAME manual|hourly|daily|weekly
1321
+ --wake-on-events a,b Event triggers such as company_funded,crm.updated
1157
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
1158
1328
  --company-domains a,b
1159
1329
  Company domains for monitor/list-based Ops
1160
1330
  --ai-prompt TEXT Custom AI enrichment or scoring instruction
@@ -1171,7 +1341,21 @@ Ops:
1171
1341
  --confirm-spend Acknowledge estimated credits/external writes
1172
1342
  --auto-run Run immediately after create when safe
1173
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
1174
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
1175
1359
  --company-domains a,b
1176
1360
  Company domains for monitor/list-based Ops
1177
1361
  --ai-prompt TEXT Custom AI enrichment or scoring instruction
@@ -1180,19 +1364,78 @@ Ops:
1180
1364
  ops create "..." Create an Op from a prompt
1181
1365
  --confirm-spend Acknowledge estimated credits/external writes
1182
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
1183
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
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
1184
1398
  --company-domains a,b
1185
1399
  Company domains for monitor/list-based Ops
1186
1400
  --ai-prompt TEXT Custom AI enrichment or scoring instruction
1187
1401
  --signal-prompt TEXT Custom signal-finding instruction
1188
1402
  --output-prompt TEXT Clean result formatting instruction
1189
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
1190
1410
  ops status <op_id> Check simple Op status
1191
1411
  ops results <op_id> Retrieve latest Op results
1192
1412
  --limit N Page size, default 100, max 500
1193
1413
  --cursor ID Pagination cursor
1194
1414
  --include-failed-runs
1195
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
1196
1439
  ops run-status <run_id>
1197
1440
  Inspect a Trigger.dev run or batch with --run-ids a,b
1198
1441
  --watch Poll until all runs reach a terminal state
@@ -1343,6 +1586,7 @@ Status and repair:
1343
1586
  provider-prepare Build read-only recipe, route, and preview arguments
1344
1587
  activate-route Dry-run or confirm provider route setup
1345
1588
  feedback-webhook Prepare feedback ingress
1589
+ feedback-pull Dry-run or confirm Instantly feedback pull
1346
1590
  learning <id> Plan Brain learning
1347
1591
  learning-run <id> Queue Brain learning phases
1348
1592
  calibrate <id> Calibrate deliverability
@@ -1746,6 +1990,35 @@ Options:
1746
1990
  readiness: `signaliz gtm readiness <campaign_id> [options]
1747
1991
 
1748
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
1749
2022
  `,
1750
2023
  campaigns: `signaliz gtm campaigns [options]
1751
2024
 
@@ -1810,7 +2083,13 @@ Options:
1810
2083
  --blueprint NAME monitor_companies|build_leads|enrich_list|route_signals|launch_campaign
1811
2084
  --target-count N Target row count
1812
2085
  --cadence NAME manual|hourly|daily|weekly
2086
+ --wake-on-events a,b Event triggers such as company_funded,crm.updated
1813
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
1814
2093
  --company-domains a,b Explicit account/domain list
1815
2094
  --json Machine-readable output
1816
2095
 
@@ -1826,12 +2105,52 @@ Options:
1826
2105
  --confirm-spend Acknowledge estimated credits/external writes
1827
2106
  --activate Create as active instead of draft
1828
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
1829
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
1830
2117
  --company-domains a,b Explicit account/domain list
1831
2118
  --json Machine-readable output
1832
2119
 
1833
2120
  Safe next step:
1834
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.
1835
2154
  `,
1836
2155
  execute: `signaliz ops execute "plain-English GTM goal" [options]
1837
2156
 
@@ -1842,13 +2161,38 @@ Options:
1842
2161
  --confirm-spend Acknowledge estimated credits/external writes
1843
2162
  --auto-run Run immediately after create when safe
1844
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
1845
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
1846
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
1847
2180
  --json Machine-readable output
1848
2181
  `,
1849
2182
  run: `signaliz ops run <op_id> [options]
1850
2183
 
1851
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
1852
2196
  `,
1853
2197
  status: `signaliz ops status <op_id> [options]
1854
2198
 
@@ -1863,6 +2207,51 @@ Options:
1863
2207
  --cursor ID Pagination cursor
1864
2208
  --include-failed-runs Include failed run output when supported
1865
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
1866
2255
  `
1867
2256
  };
1868
2257
  function opsHelpFor(sub) {
@@ -1877,7 +2266,7 @@ Canonical path:
1877
2266
  signaliz ops results <op_id>
1878
2267
 
1879
2268
  Common commands:
1880
- proof, autopilot, plan, execute, create, run, status, results, doctor, connections
2269
+ proof, autopilot, plan, execute, create, schedule, run, status, results, wait, nango, doctor, connections
1881
2270
 
1882
2271
  Run: signaliz ops help <command>
1883
2272
  `;
@@ -1908,6 +2297,13 @@ Options:
1908
2297
  --outputs json,csv Delivery outputs, default json
1909
2298
  --dry-run Validate and plan without launching spendful work
1910
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
1911
2307
  --confirm-spend Acknowledge spendful generation
1912
2308
  --input-file FILE JSON config file from campaign scope or gtm prepare-build
1913
2309
  --wait Poll until completion
@@ -1929,12 +2325,22 @@ Options:
1929
2325
  --cursor ID Pagination cursor
1930
2326
  --qualified Only qualified rows
1931
2327
  --disqualified Only disqualified rows
2328
+ --include-data Include rich row data such as generated copy
2329
+ --include-raw Include raw provider/qualification payloads
1932
2330
  --all Dump all rows
1933
2331
  --json Machine-readable output
1934
2332
  `,
1935
2333
  artifacts: `signaliz campaign artifacts <campaign_build_id> [options]
1936
2334
 
1937
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
1938
2344
  `,
1939
2345
  approve: `signaliz campaign approve <campaign_build_id> --destination-type <json|csv|webhook>
1940
2346
 
@@ -1955,9 +2361,10 @@ Canonical path:
1955
2361
  signaliz campaign build --prompt "VP Sales at B2B SaaS" --confirm-spend --wait
1956
2362
  signaliz campaign rows <campaign_build_id> --limit 100
1957
2363
  signaliz campaign artifacts <campaign_build_id>
2364
+ signaliz campaign download <campaign_build_id> --format csv
1958
2365
 
1959
2366
  Commands:
1960
- scope, build, status, rows, artifacts, approve, cancel
2367
+ scope, build, status, rows, artifacts, download, approve, cancel
1961
2368
 
1962
2369
  Run: signaliz campaign help <command>
1963
2370
  `;
@@ -2370,20 +2777,20 @@ function aiAttachmentsFromFlags() {
2370
2777
  }
2371
2778
  async function ai(sub, rest) {
2372
2779
  if (sub !== "multi-model" && sub !== "multimodel") {
2373
- 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.");
2374
2784
  }
2375
2785
  const promptText = promptArg(rest);
2376
- const model = flagArg("model");
2377
- if (!promptText) die('Usage: signaliz ai multi-model --prompt "..." --model provider/model --confirm-spend');
2378
- 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');
2379
2787
  if (!hasFlag("confirm-spend")) {
2380
- 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.");
2381
2789
  }
2382
2790
  const sdk = getSdk();
2383
2791
  const records = aiRecordsFromFlags();
2384
2792
  const result = await sdk.ai.multiModel({
2385
2793
  prompt: promptText,
2386
- model,
2387
2794
  records,
2388
2795
  systemPrompt: flagArg("system-prompt"),
2389
2796
  temperature: numberFlag("temperature"),
@@ -2394,15 +2801,12 @@ async function ai(sub, rest) {
2394
2801
  attachmentFields: csvFlag("attachment-fields") || csvFlag("attachment-field"),
2395
2802
  pdfEngine: flagArg("pdf-engine"),
2396
2803
  fusion: {
2397
- required: !hasFlag("no-require-fusion"),
2398
- analysis_models: csvFlag("analysis-models"),
2399
- judge_model: flagArg("judge-model"),
2400
2804
  include_raw_analysis: hasFlag("include-raw-analysis")
2401
2805
  }
2402
2806
  });
2403
2807
  return output(result, () => {
2404
2808
  console.log("Custom AI Enrichment - Multi Model complete");
2405
- console.log(` Model: ${result.model || model}`);
2809
+ console.log(` Model: ${result.model || "google/gemini-2.5-flash"}`);
2406
2810
  console.log(` Records: ${result.results.length}`);
2407
2811
  console.log(` Credits: ${result.creditsUsed}`);
2408
2812
  console.log(` Attachments: ${result.multimodalCount}`);
@@ -2438,6 +2842,16 @@ async function campaignBuild() {
2438
2842
  if (hasFlag("dry-run")) config.dryRun = true;
2439
2843
  if (hasFlag("allow-downscale")) config.allowDownscale = true;
2440
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
+ }
2441
2855
  const outputs = (flagArg("outputs") || "json").split(",").map((s) => s.trim());
2442
2856
  const webhookUrl = flagArg("webhook-url");
2443
2857
  if (outputs.includes("csv") || outputs.includes("webhook") || webhookUrl) {
@@ -2480,6 +2894,7 @@ async function campaignBuild() {
2480
2894
  console.log(` Planned: ${result.plannedTargetCount ?? result.requestedTargetCount ?? config.targetCount ?? 50} leads`);
2481
2895
  if (result.estimatedCredits !== void 0) console.log(` Credits: ~${result.estimatedCredits}`);
2482
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)`);
2483
2898
  if (result.targetLimitApplied) console.log(` Note: ${result.targetLimitReason || "target count downscaled"}`);
2484
2899
  hint("Remove --dry-run and add --confirm-spend when ready to launch.");
2485
2900
  });
@@ -2493,6 +2908,7 @@ async function campaignBuild() {
2493
2908
  console.log(` Status: ${result.status}`);
2494
2909
  console.log(` Phase: ${result.currentPhase}`);
2495
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)`);
2496
2912
  hint(`signaliz campaign status ${result.campaignBuildId}`);
2497
2913
  });
2498
2914
  }
@@ -2522,6 +2938,7 @@ async function campaignBuild() {
2522
2938
  \u2713 Build ${finalStatus.status}`);
2523
2939
  console.log(` Rows: ${finalStatus.recordsSucceeded}/${finalStatus.recordsTotal} succeeded`);
2524
2940
  console.log(` Artifacts: ${finalStatus.artifactCount}`);
2941
+ printCampaignArtifactDownloadHint(finalStatus, result.campaignBuildId);
2525
2942
  if (finalStatus.status === "completed") {
2526
2943
  hint(`signaliz campaign rows ${result.campaignBuildId} --limit 100`);
2527
2944
  }
@@ -2565,6 +2982,35 @@ async function campaignScope() {
2565
2982
  hint("Next: run signaliz campaign build --input-file with the buildCampaignArgs JSON, or call build_campaign with the returned dry-run args.");
2566
2983
  });
2567
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
+ }
2568
3014
  async function campaignStatus() {
2569
3015
  const buildId = positionalAfter("status") || flagArg("campaign-build-id");
2570
3016
  if (!buildId) die("Usage: signaliz campaign status <campaign_build_id>");
@@ -2576,7 +3022,9 @@ async function campaignStatus() {
2576
3022
  console.log(`Status: ${status.status}`);
2577
3023
  console.log(`Phase: ${status.currentPhase || "N/A"}`);
2578
3024
  console.log(`Rows: ${status.recordsSucceeded}/${status.recordsTotal} succeeded, ${status.recordsFailed} failed`);
3025
+ printCampaignCustomerRowCounts(status);
2579
3026
  console.log(`Artifacts: ${status.artifactCount}`);
3027
+ printCampaignArtifactDownloadHint(status, buildId);
2580
3028
  if (status.triggerRunId) console.log(`Trigger: ${status.triggerRunId}`);
2581
3029
  if (status.staleRunningPhase) {
2582
3030
  const age = typeof status.phaseAgeSeconds === "number" ? `${status.phaseAgeSeconds}s` : "unknown age";
@@ -2590,12 +3038,18 @@ async function campaignStatus() {
2590
3038
  console.log(`Errors: ${status.errors.length}`);
2591
3039
  for (const error of status.errors.slice(0, 3)) console.log(` - ${error}`);
2592
3040
  }
2593
- if (status.status === "completed") {
3041
+ if (status.status === "completed" && status.nextAction) {
3042
+ hint(status.nextAction);
3043
+ } else if (status.status === "completed") {
2594
3044
  hint(`signaliz campaign rows ${buildId} --limit 100`);
2595
3045
  } else if (status.staleRunningPhase && status.triggerRunId) {
2596
3046
  hint(`signaliz ops run-status ${status.triggerRunId} --watch`);
2597
3047
  } else if (status.status === "running" || status.status === "queued") {
2598
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);
2599
3053
  } else if (status.status === "pending_approval") {
2600
3054
  hint(`signaliz campaign approve ${buildId} --destination-type webhook`);
2601
3055
  } else if (status.status === "failed" && status.nextAction) {
@@ -2603,6 +3057,22 @@ async function campaignStatus() {
2603
3057
  }
2604
3058
  });
2605
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
+ }
2606
3076
  async function campaignRows() {
2607
3077
  const buildId = positionalAfter("rows") || flagArg("campaign-build-id");
2608
3078
  if (!buildId) die("Usage: signaliz campaign rows <campaign_build_id>");
@@ -2617,6 +3087,8 @@ async function campaignRows() {
2617
3087
  do {
2618
3088
  const opts = { limit: Math.min(limit, 500) };
2619
3089
  if (nextCursor) opts.cursor = nextCursor;
3090
+ if (hasFlag("include-data")) opts.includeData = true;
3091
+ if (hasFlag("include-raw")) opts.includeRaw = true;
2620
3092
  const segment = flagArg("segment");
2621
3093
  if (segment) opts.segment = segment;
2622
3094
  const rowStatus = flagArg("row-status");
@@ -2641,6 +3113,8 @@ async function campaignRows() {
2641
3113
  } else {
2642
3114
  const opts = { limit: Math.min(limit, 500) };
2643
3115
  if (cursor) opts.cursor = cursor;
3116
+ if (hasFlag("include-data")) opts.includeData = true;
3117
+ if (hasFlag("include-raw")) opts.includeRaw = true;
2644
3118
  const segment = flagArg("segment");
2645
3119
  if (segment) opts.segment = segment;
2646
3120
  const rowStatus = flagArg("row-status");
@@ -2666,6 +3140,7 @@ async function campaignRows() {
2666
3140
  status: r.status || "",
2667
3141
  score: r.score ?? ""
2668
3142
  })));
3143
+ printCampaignCopySnippets(operatorResult.rows);
2669
3144
  console.log(`
2670
3145
  Showing ${operatorResult.rows.length} row(s)${operatorResult.hasMore ? " (more available)" : ""}`);
2671
3146
  if (operatorResult.nextCursor) {
@@ -2678,14 +3153,15 @@ function formatCampaignRowsForOperator(rows) {
2678
3153
  return rows.map((row) => {
2679
3154
  const rowRecord = record(row);
2680
3155
  const data = record(rowRecord.data);
3156
+ const copyData = record(data.copy);
2681
3157
  const rawCopy = record(data.raw_copy);
2682
3158
  const copy = {
2683
- subject: data.subject || rawCopy.subject || null,
2684
- opener: data.opener || rawCopy.opener || null,
2685
- body: data.body || rawCopy.body || null,
2686
- cta: data.cta || rawCopy.cta || null,
2687
- engine: data.copy_engine || rawCopy.engine || null,
2688
- 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
2689
3165
  };
2690
3166
  return {
2691
3167
  ...rowRecord,
@@ -2715,6 +3191,26 @@ function formatCampaignRowsForOperator(rows) {
2715
3191
  };
2716
3192
  });
2717
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
+ }
2718
3214
  async function campaignArtifacts() {
2719
3215
  const buildId = positionalAfter("artifacts") || flagArg("campaign-build-id");
2720
3216
  if (!buildId) die("Usage: signaliz campaign artifacts <campaign_build_id>");
@@ -2732,6 +3228,24 @@ async function campaignArtifacts() {
2732
3228
  }
2733
3229
  });
2734
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
+ }
2735
3249
  async function campaignApprove() {
2736
3250
  const buildId = positionalAfter("approve") || positionalAfter("approve-delivery") || flagArg("campaign-build-id");
2737
3251
  if (!buildId) die("Usage: signaliz campaign approve <campaign_build_id> --destination-type <json|csv|webhook>");
@@ -2787,6 +3301,8 @@ async function campaign(sub) {
2787
3301
  return campaignRows();
2788
3302
  case "artifacts":
2789
3303
  return campaignArtifacts();
3304
+ case "download":
3305
+ return campaignDownload();
2790
3306
  case "approve":
2791
3307
  case "approve-delivery":
2792
3308
  return campaignApprove();
@@ -2794,7 +3310,7 @@ async function campaign(sub) {
2794
3310
  return campaignCancel();
2795
3311
  default:
2796
3312
  die(`Unknown campaign subcommand: ${sub}
2797
- Available: scope, build, status, rows, artifacts, approve, cancel`);
3313
+ Available: scope, build, status, rows, artifacts, download, approve, cancel`);
2798
3314
  }
2799
3315
  }
2800
3316
  var CAMPAIGN_AGENT_APPROVAL_TYPES = [
@@ -2816,6 +3332,7 @@ var CAMPAIGN_AGENT_HELP = `signaliz campaign-agent <command> [options]
2816
3332
  Strategy-template campaign builder:
2817
3333
  init Emit reusable CampaignBuilderAgentRequest JSON
2818
3334
  kit Emit request JSON, CLI commands, MCP calls, SDK example, and BYO pattern
3335
+ memory-kit Emit memory readiness, seed runner, MCP calls, and proof commands
2819
3336
  plan Compose an approval-gated strategy-template MCP flow
2820
3337
  doctor Inspect built-in lanes, BYO routes, Memory, Brain, and approvals
2821
3338
  proof Prove memory, plan, approvals, and build dry-run without spend
@@ -2831,6 +3348,7 @@ Strategy-template campaign builder:
2831
3348
  Examples:
2832
3349
  signaliz campaign-agent init --strategy-template non-medical-home-care --target-count 250 --use-local-leads --json > campaign-request.json
2833
3350
  signaliz campaign-agent kit --strategy-template non-medical-home-care --target-count 250 --use-local-leads --json
3351
+ signaliz campaign-agent memory-kit --strategy-template non-medical-home-care --target-count 250 --use-local-leads --json
2834
3352
  signaliz campaign-agent doctor --request-file campaign-request.json --json
2835
3353
  signaliz campaign-agent plan --goal "Build a strategy-template campaign for my ICP" --strategy-template non-medical-home-care --target-count 500 --builtins lead_generation,email_finding,email_verification,signals
2836
3354
  signaliz campaign-agent proof --goal "Build a strategy-template campaign for my ICP" --strategy-template non-medical-home-care --target-count 100 --json
@@ -2838,14 +3356,17 @@ Examples:
2838
3356
  signaliz campaign-agent dry-run --goal "Build a local services campaign" --target-count 250 --use-local-leads --json
2839
3357
  signaliz campaign-agent build-approved --goal "Build a strategy-template campaign" --confirm-launch --approve-all --approved-by operator@example.com --spend-limit 500
2840
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
2841
- signaliz campaign-agent review <campaign_build_id> --limit 100 --json
3359
+ signaliz campaign-agent review <campaign_build_id> --limit 100 --include-data --json
2842
3360
  signaliz campaign-agent status <campaign_build_id> --json
2843
- signaliz campaign-agent rows <campaign_build_id> --limit 100 --json
3361
+ signaliz campaign-agent rows <campaign_build_id> --limit 100 --include-data --json
2844
3362
 
2845
3363
  Plan options:
2846
3364
  --goal TEXT Campaign goal or brief
2847
3365
  --output-file FILE Write init output to a reusable request JSON file
2848
3366
  --kit-file FILE Write campaign-agent kit output to a JSON file
3367
+ --seed-manifest-file FILE
3368
+ Manifest path for memory-kit seed dry-runs
3369
+ --source NAME memory-kit seed source: north-star, agency, workflow-patterns, instantly-feedback, or all
2849
3370
  --request-output-file FILE
2850
3371
  Request filename to reference inside kit commands
2851
3372
  --request-file FILE Reusable JSON CampaignBuilderAgentRequest; flags override file fields
@@ -2900,7 +3421,7 @@ async function campaignAgent(sub, rest) {
2900
3421
  await campaignAgentReview(sub, rest);
2901
3422
  return;
2902
3423
  }
2903
- if (!["init", "template", "request-template", "new", "kit", "build-kit", "blueprint", "plan", "doctor", "readiness", "preflight", "proof", "smoke", "commit-plan", "dry-run", "build-approved", "launch-approved"].includes(sub)) {
3424
+ if (!["init", "template", "request-template", "new", "kit", "build-kit", "blueprint", "memory-kit", "seed-kit", "memory", "plan", "doctor", "readiness", "preflight", "proof", "smoke", "commit-plan", "dry-run", "build-approved", "launch-approved"].includes(sub)) {
2904
3425
  process.stdout.write(CAMPAIGN_AGENT_HELP);
2905
3426
  return;
2906
3427
  }
@@ -2946,6 +3467,38 @@ async function campaignAgent(sub, rest) {
2946
3467
  if (proof?.command) console.log(`Next: ${proof.command}`);
2947
3468
  return;
2948
3469
  }
3470
+ if (["memory-kit", "seed-kit", "memory"].includes(sub)) {
3471
+ const requestFromFile2 = campaignAgentRequestFileFromFlags(flags);
3472
+ const goal2 = commandStringFlag(flags, "goal", "brief") || promptArg(rest) || campaignAgentStringValue(requestFromFile2?.goal) || "Build a strategy-template campaign for mature operators in the target ICP.";
3473
+ const request2 = requestFromFile2 ? mergeCampaignAgentRequests(
3474
+ requestFromFile2,
3475
+ campaignAgentRequestFromFlags(goal2, flags, { includeDefaults: false })
3476
+ ) : campaignAgentRequestTemplateFromFlags(flags, rest);
3477
+ const createMemoryKit = getCampaignBuilderMemoryKitFactory();
3478
+ const kit = createMemoryKit({
3479
+ ...request2,
3480
+ includeLocalLeads: commandBooleanFlag(flags, "use-local-leads"),
3481
+ includeCustomToolPlaceholder: commandBooleanFlag(flags, "with-byo-placeholder") || commandBooleanFlag(flags, "include-custom-tool-placeholder") || commandBooleanFlag(flags, "include-byo-placeholder"),
3482
+ includeNango: !commandBooleanFlag(flags, "no-nango-catalog"),
3483
+ requestFile: commandStringFlag(flags, "request-output-file", "request-file-name") || commandStringFlag(flags, "request-file") || "campaign-request.json",
3484
+ seedManifestFile: commandStringFlag(flags, "seed-manifest-file", "manifest") || ".gtm-kernel-import-state/campaign-memory-dry-run.json",
3485
+ source: commandStringFlag(flags, "source", "memory-source", "seed-source"),
3486
+ workspaceId: commandStringFlag(flags, "workspace-id"),
3487
+ cliPackage: commandStringFlag(flags, "cli-package"),
3488
+ sdkPackage: commandStringFlag(flags, "sdk-package")
3489
+ });
3490
+ const outputFile = commandStringFlag(flags, "kit-file", "output-file", "out");
3491
+ if (outputFile) {
3492
+ (0, import_node_fs.writeFileSync)(outputFile, `${JSON.stringify(kit, null, 2)}
3493
+ `, "utf8");
3494
+ }
3495
+ if (!outputFile || jsonMode()) return output(kit, () => printCampaignAgentMemoryKit(kit));
3496
+ console.log(`Campaign memory kit written: ${outputFile}`);
3497
+ const commands = firstArray(kit.cli_commands);
3498
+ const status = commands.find((command) => command.id === "memory_status");
3499
+ if (status?.command) console.log(`Next: ${status.command}`);
3500
+ return;
3501
+ }
2949
3502
  const requestFromFile = campaignAgentRequestFileFromFlags(flags);
2950
3503
  const isDoctor = sub === "doctor" || sub === "readiness" || sub === "preflight";
2951
3504
  const goal = commandStringFlag(flags, "goal", "brief") || promptArg(rest) || campaignAgentStringValue(requestFromFile?.goal) || (isDoctor ? "Build a strategy-template campaign for the target ICP." : void 0);
@@ -3057,6 +3610,8 @@ async function campaignAgentReview(sub, rest) {
3057
3610
  cursor: commandStringFlag(flags, "cursor"),
3058
3611
  segment: commandStringFlag(flags, "segment"),
3059
3612
  rowStatus: commandStringFlag(flags, "row-status"),
3613
+ includeData: commandOptionalBooleanFlag(flags, "include-data"),
3614
+ includeRaw: commandOptionalBooleanFlag(flags, "include-raw"),
3060
3615
  qualified: commandBooleanFlag(flags, "qualified") ? true : commandBooleanFlag(flags, "disqualified") ? false : void 0
3061
3616
  };
3062
3617
  const review = await agent.reviewBuild?.(buildId, rowOptions) || await campaignAgentBuildReviewFromCampaignApis(agent, campaigns, buildId, rowOptions);
@@ -3080,6 +3635,8 @@ async function campaignAgentReview(sub, rest) {
3080
3635
  cursor: commandStringFlag(flags, "cursor"),
3081
3636
  segment: commandStringFlag(flags, "segment"),
3082
3637
  rowStatus: commandStringFlag(flags, "row-status"),
3638
+ includeData: commandOptionalBooleanFlag(flags, "include-data"),
3639
+ includeRaw: commandOptionalBooleanFlag(flags, "include-raw"),
3083
3640
  qualified: commandBooleanFlag(flags, "qualified") ? true : commandBooleanFlag(flags, "disqualified") ? false : void 0
3084
3641
  };
3085
3642
  const result2 = await (agent.getBuildRows?.bind(agent) || campaigns.rows?.bind(campaigns) || campaigns.getCampaignBuildRows?.bind(campaigns))?.(buildId, rowOptions);
@@ -3604,6 +4161,39 @@ function printCampaignAgentBuildKit(kit) {
3604
4161
  for (const action of nextActions.slice(0, 4)) console.log(`- ${action}`);
3605
4162
  }
3606
4163
  }
4164
+ function printCampaignAgentMemoryKit(kit) {
4165
+ console.log(`Campaign memory kit: ${kit.strategy_template || "custom strategy"}`);
4166
+ if (kit.request_file) console.log(`Request file: ${kit.request_file}`);
4167
+ if (kit.seed_manifest_file) console.log(`Seed manifest: ${kit.seed_manifest_file}`);
4168
+ const sources = firstArray(kit.memory_sources);
4169
+ console.log(`Sources: ${sources.map((source) => source.id || source.seed_source).filter(Boolean).join(", ") || "none"}`);
4170
+ console.log(`Playbooks: ${firstArray(kit.operating_playbooks).join(", ") || "none"}`);
4171
+ const commands = firstArray(kit.cli_commands);
4172
+ if (commands.length) {
4173
+ console.log("\nCLI:");
4174
+ for (const command of commands.slice(0, 4)) {
4175
+ console.log(`- ${command.label || command.id}: ${command.command}`);
4176
+ }
4177
+ }
4178
+ const calls = firstArray(kit.mcp_calls);
4179
+ if (calls.length) {
4180
+ console.log("\nMCP:");
4181
+ for (const call of calls.slice(0, 3)) {
4182
+ console.log(`- ${call.tool}: ${call.safety}`);
4183
+ }
4184
+ }
4185
+ const seedRunner = record(kit.seed_runner);
4186
+ if (seedRunner.dry_run_command) {
4187
+ console.log("\nSeed runner:");
4188
+ console.log(`- Dry run: ${seedRunner.dry_run_command}`);
4189
+ if (seedRunner.verify_manifest_command) console.log(`- Verify: ${seedRunner.verify_manifest_command}`);
4190
+ }
4191
+ const nextActions = firstArray(kit.next_actions);
4192
+ if (nextActions.length) {
4193
+ console.log("\nNext:");
4194
+ for (const action of nextActions.slice(0, 4)) console.log(`- ${action}`);
4195
+ }
4196
+ }
3607
4197
  function printCampaignAgentPlan(plan) {
3608
4198
  console.log(`Plan: ${plan.campaignName || plan.goal || plan.planId}`);
3609
4199
  if (plan.planId) console.log(`Plan ID: ${plan.planId}`);
@@ -3638,7 +4228,9 @@ function printCampaignAgentBuildStatus(status) {
3638
4228
  console.log(`Status: ${status.status}`);
3639
4229
  console.log(`Phase: ${status.currentPhase || "N/A"}`);
3640
4230
  console.log(`Rows: ${status.recordsSucceeded}/${status.recordsTotal} succeeded, ${status.recordsFailed} failed`);
4231
+ printCampaignCustomerRowCounts(status);
3641
4232
  console.log(`Artifacts: ${status.artifactCount}`);
4233
+ if (status.campaignBuildId) printCampaignArtifactDownloadHint(status, status.campaignBuildId);
3642
4234
  if (status.triggerRunId) console.log(`Trigger: ${status.triggerRunId}`);
3643
4235
  if (Array.isArray(status.warnings) && status.warnings.length) {
3644
4236
  console.log(`Warnings: ${status.warnings.length}`);
@@ -3648,8 +4240,14 @@ function printCampaignAgentBuildStatus(status) {
3648
4240
  console.log(`Errors: ${status.errors.length}`);
3649
4241
  for (const error of status.errors.slice(0, 3)) console.log(` - ${error}`);
3650
4242
  }
3651
- if (status.status === "completed") {
4243
+ if (status.status === "completed" && status.nextAction) {
4244
+ hint(status.nextAction);
4245
+ } else if (status.status === "completed") {
3652
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);
3653
4251
  } else if (status.status === "pending_approval") {
3654
4252
  hint(`signaliz campaign-agent approve ${status.campaignBuildId} --destination-type webhook`);
3655
4253
  } else if ((status.status === "running" || status.status === "queued") && status.triggerRunId) {
@@ -3667,6 +4265,7 @@ function printCampaignAgentBuildReview(review) {
3667
4265
  console.log(`Emails: ${summary.rowsWithEmail || 0}`);
3668
4266
  console.log(`Copy ready: ${summary.copyReadyRows || 0}`);
3669
4267
  console.log(`Artifacts: ${summary.artifactCount || 0}`);
4268
+ if (review.campaignBuildId) printCampaignArtifactDownloadHint(status, review.campaignBuildId);
3670
4269
  console.log(`Delivery approval ready: ${summary.readyForDeliveryApproval === true ? "yes" : "no"}`);
3671
4270
  const gates = Array.isArray(review.gates) ? review.gates : [];
3672
4271
  if (gates.length) {
@@ -3804,6 +4403,7 @@ function printCampaignAgentRows(result) {
3804
4403
  status: row.status || "",
3805
4404
  score: row.score ?? ""
3806
4405
  })));
4406
+ printCampaignCopySnippets(result.rows);
3807
4407
  console.log(`
3808
4408
  Showing ${result.rows.length} row(s)${result.hasMore ? " (more available)" : ""}`);
3809
4409
  if (result.nextCursor) {
@@ -4596,6 +5196,7 @@ async function gtm(sub, rest) {
4596
5196
  days: numberFlag("days"),
4597
5197
  networkDays: numberFlag("network-days"),
4598
5198
  minSampleSize: numberFlag("min-sample-size"),
5199
+ holdoutMinSampleSize: numberFlag("holdout-min-sample-size"),
4599
5200
  minWorkspaceCount: numberFlag("min-workspace-count"),
4600
5201
  limit: numberFlag("limit")
4601
5202
  });
@@ -4606,6 +5207,10 @@ async function gtm(sub, rest) {
4606
5207
  console.log("GTM Brain learning plan");
4607
5208
  console.log(` Campaign: ${campaignId}`);
4608
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
+ }
4609
5214
  console.log(` Calls: ${calls.length}`);
4610
5215
  for (const call of calls.slice(0, 5)) {
4611
5216
  const state = call.ready === false ? "blocked" : "ready";
@@ -4627,6 +5232,7 @@ async function gtm(sub, rest) {
4627
5232
  days: numberFlag("days"),
4628
5233
  networkDays: numberFlag("network-days"),
4629
5234
  minSampleSize: numberFlag("min-sample-size"),
5235
+ holdoutMinSampleSize: numberFlag("holdout-min-sample-size"),
4630
5236
  minWorkspaceCount: numberFlag("min-workspace-count"),
4631
5237
  minPrivacyK: numberFlag("min-privacy-k"),
4632
5238
  limit: numberFlag("limit")
@@ -4880,6 +5486,103 @@ async function gtm(sub, rest) {
4880
5486
  hint(`signaliz gtm bootstrap${common.campaignId ? ` --campaign-id ${common.campaignId}` : ""} --include-samples`);
4881
5487
  });
4882
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
+ }
4883
5586
  if (sub === "nango") return gtmNango(rest[0], rest.slice(1));
4884
5587
  if (!sub || sub === "list" || sub === "sources") {
4885
5588
  const result = await sdk.leads.listNativeGtmCapabilities(flagArg("category"));
@@ -4940,7 +5643,7 @@ ${i + 1}. ${m.capability_id} (score ${m.score}) \u2014 ${m.label}`);
4940
5643
  const result = await sdk.leads.checkStatus(jobId);
4941
5644
  return output(result);
4942
5645
  }
4943
- 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>");
4944
5647
  }
4945
5648
  function nangoActionInput() {
4946
5649
  const fromFile = readJsonFlag("input-file");
@@ -4957,30 +5660,146 @@ function nangoBaseOptions() {
4957
5660
  nangoConnectionId: flagArg("nango-connection-id")
4958
5661
  };
4959
5662
  }
4960
- function nangoConnectOptions() {
5663
+ function nangoConnectOptions(defaultSurface = "gtm_kernel") {
4961
5664
  return {
4962
5665
  providerId: flagArg("provider-id") || flagArg("provider"),
4963
5666
  integrationId: flagArg("integration-id") || flagArg("provider-config-key"),
4964
5667
  providerDisplayName: flagArg("provider-display-name") || flagArg("provider-name"),
4965
5668
  integrationDisplayName: flagArg("integration-display-name"),
4966
5669
  allowedIntegrations: csvFlag("allowed-integrations"),
4967
- surface: flagArg("surface") || "gtm_kernel",
5670
+ surface: flagArg("surface") || defaultSurface,
4968
5671
  source: flagArg("source") || "signaliz_cli",
4969
5672
  userEmail: flagArg("user-email"),
4970
5673
  userDisplayName: flagArg("user-display-name")
4971
5674
  };
4972
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
+ }
4973
5714
  function nangoStatusUrl(result) {
4974
5715
  return result?.status_url || result?.statusUrl || result?.nango_result?.status_url || result?.nango_result?.statusUrl;
4975
5716
  }
4976
- 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") {
4977
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
+ }
4978
5797
  if (sub === "connect" || sub === "auth" || sub === "authorize") {
4979
- const options = nangoConnectOptions();
5798
+ const options = nangoConnectOptions(defaultSurface);
4980
5799
  if (!options.providerId && !options.integrationId && (!options.allowedIntegrations || options.allowedIntegrations.length === 0)) {
4981
- 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]`);
4982
5801
  }
4983
- const result = await sdk.gtm.createNangoConnectSession(options);
5802
+ const result = await nango.createNangoConnectSession(options);
4984
5803
  return output(result, () => {
4985
5804
  console.log(`Nango connect session: ${result?.status || "created"}`);
4986
5805
  if (result?.provider_id || options.providerId) console.log(` Provider: ${result?.provider_id || options.providerId}`);
@@ -4988,11 +5807,11 @@ async function gtmNango(sub, rest) {
4988
5807
  if (result?.connect_link || result?.auth_window_url) console.log(` Auth link: ${result.connect_link || result.auth_window_url}`);
4989
5808
  if (result?.expires_at) console.log(` Expires: ${result.expires_at}`);
4990
5809
  if (result?.next_action) console.log(` Next: ${result.next_action}`);
4991
- 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>`);
4992
5811
  });
4993
5812
  }
4994
5813
  if (!sub || sub === "tools" || sub === "list") {
4995
- const result = await sdk.gtm.listNangoTools({
5814
+ const result = await nango.listNangoTools({
4996
5815
  ...nangoBaseOptions(),
4997
5816
  format: flagArg("format"),
4998
5817
  includeRaw: hasFlag("include-raw")
@@ -5003,39 +5822,153 @@ async function gtmNango(sub, rest) {
5003
5822
  for (const tool of tools2.slice(0, 10)) {
5004
5823
  console.log(`- ${tool.name || tool.action_name || tool.id}: ${tool.description || tool.type || "action"}`);
5005
5824
  }
5006
- 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}`);
5007
5924
  });
5008
5925
  }
5009
5926
  if (sub === "call" || sub === "run") {
5010
- const actionName = rest[0] || flagArg("action-name");
5927
+ const actionName = nangoPositionalValues(rest)[0] || flagArg("action-name");
5011
5928
  const toolName = flagArg("tool-name");
5012
- if (!actionName && !toolName) {
5013
- 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`);
5014
5932
  }
5015
- const result = await sdk.gtm.callNangoTool({
5933
+ const callOptions = {
5016
5934
  ...nangoBaseOptions(),
5017
5935
  actionName,
5018
5936
  toolName,
5937
+ deliveryMode: proxyPath ? "proxy" : void 0,
5938
+ proxyPath,
5939
+ method: flagArg("method") || flagArg("http-method"),
5019
5940
  input: nangoActionInput(),
5020
- async: hasFlag("async") || void 0,
5941
+ async: hasFlag("async") || hasFlag("wait") || void 0,
5021
5942
  maxRetries: numberFlag("max-retries"),
5022
5943
  dryRun: hasFlag("execute") ? false : hasFlag("dry-run") ? true : void 0,
5023
5944
  confirm: hasFlag("confirm") || void 0,
5024
5945
  confirmWrite: hasFlag("confirm-write") || void 0
5025
- });
5026
- 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, () => {
5027
5956
  console.log(`Nango action ${result?.status || "submitted"}: ${actionName || toolName}`);
5028
5957
  const statusUrl = nangoStatusUrl(result);
5029
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
+ }
5030
5963
  if (result?.next_action) console.log(` Next: ${result.next_action}`);
5031
- if (statusUrl) hint(`signaliz gtm nango result --status-url ${statusUrl}`);
5964
+ if (statusUrl && finalResult === result) hint(`${commandPrefix} result --status-url ${statusUrl}`);
5032
5965
  });
5033
5966
  }
5034
5967
  if (sub === "result" || sub === "status") {
5035
- const actionId = rest[0] || flagArg("action-id");
5968
+ const actionId = nangoPositionalValues(rest)[0] || flagArg("action-id");
5036
5969
  const statusUrl = flagArg("status-url");
5037
- if (!actionId && !statusUrl) die("Usage: signaliz gtm nango result --action-id <id> OR --status-url /action/<id>");
5038
- 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 });
5039
5972
  return output(result, () => {
5040
5973
  console.log(`Nango action result: ${result?.status || "unknown"}`);
5041
5974
  if (result?.action_id || actionId) console.log(` Action ID: ${result?.action_id || actionId}`);
@@ -5043,7 +5976,7 @@ async function gtmNango(sub, rest) {
5043
5976
  if (nextStatusUrl) console.log(` Status URL: ${nextStatusUrl}`);
5044
5977
  });
5045
5978
  }
5046
- die("Usage: signaliz gtm nango <connect|tools|call|result>");
5979
+ die(`Usage: ${commandPrefix} <flow|search|connect|tools|call|schedule|result>`);
5047
5980
  }
5048
5981
  async function email(sub, rest) {
5049
5982
  const sdk = getSdk();
@@ -5248,26 +6181,74 @@ async function ops(sub, rest) {
5248
6181
  const savedRest = sub === "save" ? rest : rest.slice(1);
5249
6182
  return opsSaved(savedSub, savedRest);
5250
6183
  }
6184
+ if (sub === "nango") return gtmNango(rest[0], rest.slice(1), "ops");
5251
6185
  if (sub === "plan" && !promptArg(rest)) {
5252
6186
  die('Usage: signaliz ops plan "monitor these companies daily..."');
5253
6187
  }
5254
6188
  if (sub === "create" && !promptArg(rest)) {
5255
6189
  die('Usage: signaliz ops create "build 100 leads that fit..." [--confirm-spend]');
5256
6190
  }
6191
+ if ((sub === "schedule" || sub === "recur") && !promptArg(rest)) {
6192
+ die('Usage: signaliz ops schedule "monitor these companies daily..." [--confirm-spend]');
6193
+ }
5257
6194
  if (sub === "execute" && !promptArg(rest)) {
5258
6195
  die('Usage: signaliz ops execute "launch a campaign for this ICP..." [--dry-run|--confirm-spend]');
5259
6196
  }
5260
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
+ };
5261
6230
  const opsDestinations = () => {
5262
6231
  const destinations = csvFlag("destinations") || csvFlag("destination");
5263
- 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;
5264
6238
  };
6239
+ const opsWakeEvents = () => csvFlag("wake-on-events") || csvFlag("wake-events") || csvFlag("wake-event");
5265
6240
  const opsCompanyDomains = () => csvFlag("company-domains") || csvFlag("domains");
5266
6241
  const opsCustomPrompts = () => ({
5267
6242
  custom_ai_prompt: flagArg("ai-prompt") || flagArg("custom-ai-prompt"),
5268
6243
  signal_prompt: flagArg("signal-prompt"),
5269
6244
  output_prompt: flagArg("output-prompt") || flagArg("result-prompt")
5270
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
+ };
5271
6252
  if (sub === "plan") {
5272
6253
  const promptText = promptArg(rest);
5273
6254
  const result = await sdk.ops.plan({
@@ -5275,6 +6256,7 @@ async function ops(sub, rest) {
5275
6256
  blueprint: flagArg("blueprint"),
5276
6257
  target_count: numberFlag("target-count"),
5277
6258
  cadence: flagArg("cadence"),
6259
+ wake_on_events: opsWakeEvents(),
5278
6260
  destinations: opsDestinations(),
5279
6261
  company_domains: opsCompanyDomains(),
5280
6262
  ...opsCustomPrompts()
@@ -5284,10 +6266,11 @@ async function ops(sub, rest) {
5284
6266
  if (result.ui_summary) console.log(`Summary: ${result.ui_summary}`);
5285
6267
  console.log(`Status: ${result.status}`);
5286
6268
  console.log(`Outcome: ${result.blueprint}`);
5287
- console.log(`Cadence: ${result.cadence}`);
6269
+ printPlanSchedule2(result);
5288
6270
  console.log(`Target: ${result.target_count}`);
5289
6271
  console.log(`Credits: ${result.estimated_credits}`);
5290
6272
  if (result.destination_status) console.log(`Deliver: ${result.destination_status}`);
6273
+ printOpsExecutionContract(result);
5291
6274
  printPrimitiveGraph(result.primitive_graph);
5292
6275
  console.log(`Next: ${result.next_action}`);
5293
6276
  });
@@ -5306,6 +6289,26 @@ async function ops(sub, rest) {
5306
6289
  console.log(`${primary.title}: ${primary.outcome}`);
5307
6290
  console.log(`Prompt: ${primary.prompt}`);
5308
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
+ }
5309
6312
  if (primary.mcp_sequence?.length) {
5310
6313
  console.log("\nMCP flow");
5311
6314
  for (const [index, step] of primary.mcp_sequence.entries()) {
@@ -5317,22 +6320,49 @@ async function ops(sub, rest) {
5317
6320
  }
5318
6321
  if (sub === "execute") {
5319
6322
  const promptText = promptArg(rest);
6323
+ const shouldWait = hasFlag("wait");
5320
6324
  const result = await sdk.ops.execute({
5321
6325
  prompt: promptText,
5322
6326
  blueprint: flagArg("blueprint"),
5323
6327
  target_count: numberFlag("target-count"),
5324
6328
  cadence: flagArg("cadence"),
6329
+ wake_on_events: opsWakeEvents(),
6330
+ timezone: flagArg("timezone"),
5325
6331
  destinations: opsDestinations(),
5326
6332
  company_domains: opsCompanyDomains(),
5327
6333
  ...opsCustomPrompts(),
5328
6334
  confirm_spend: hasFlag("confirm-spend"),
5329
6335
  dry_run: hasFlag("dry-run"),
5330
- 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,
5331
6337
  output_format: flagArg("output-format") === "markdown" ? "markdown" : "json"
5332
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
+ }
5333
6356
  return output(result, () => {
5334
6357
  if (result.approval_required || result.error_code === "APPROVAL_REQUIRED") {
5335
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
+ }
5336
6366
  if (result.estimated_credits !== void 0) console.log(`Credits: ${result.estimated_credits}`);
5337
6367
  console.log(`Next: ${result.next_action || "Retry with --confirm-spend after approval."}`);
5338
6368
  hint(`signaliz ops execute ${JSON.stringify(promptText)} --confirm-spend`);
@@ -5342,13 +6372,19 @@ async function ops(sub, rest) {
5342
6372
  console.log(`Dry run: ${result.plan.title}`);
5343
6373
  if (result.plan.ui_summary) console.log(`Summary: ${result.plan.ui_summary}`);
5344
6374
  console.log(`Status: ${result.plan.status}`);
6375
+ printPlanSchedule2(result.plan);
5345
6376
  console.log(`Credits: ${result.plan.estimated_credits}`);
6377
+ printOpsExecutionContract(result.plan);
5346
6378
  console.log(`Next: ${result.next_action || result.plan.next_action}`);
5347
6379
  return;
5348
6380
  }
5349
6381
  console.log(`Op: ${result.op_id || "not created"}`);
5350
6382
  if (result.run_id) console.log(`Run: ${result.run_id}`);
5351
6383
  console.log(`Status: ${result.status || "unknown"}`);
6384
+ if (result.plan) {
6385
+ printPlanSchedule2(result.plan);
6386
+ printOpsExecutionContract(result.plan);
6387
+ }
5352
6388
  console.log(`Next: ${result.next_action || "Check status for progress."}`);
5353
6389
  if (result.op_id) hint(`signaliz ops status ${result.op_id}`);
5354
6390
  });
@@ -5411,6 +6447,8 @@ Available: ${result.available_types.join(", ")}`);
5411
6447
  blueprint: flagArg("blueprint"),
5412
6448
  target_count: numberFlag("target-count"),
5413
6449
  cadence: flagArg("cadence"),
6450
+ wake_on_events: opsWakeEvents(),
6451
+ timezone: flagArg("timezone"),
5414
6452
  destinations: opsDestinations(),
5415
6453
  company_domains: opsCompanyDomains(),
5416
6454
  ...opsCustomPrompts(),
@@ -5418,15 +6456,99 @@ Available: ${result.available_types.join(", ")}`);
5418
6456
  activate: hasFlag("activate")
5419
6457
  });
5420
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
+ }
5421
6473
  console.log(`Op created: ${result.op_id}`);
5422
6474
  console.log(`Status: ${result.status}`);
6475
+ if (result.plan) {
6476
+ printPlanSchedule2(result.plan);
6477
+ printOpsExecutionContract(result.plan);
6478
+ }
5423
6479
  console.log(`Next: ${result.next_action}`);
5424
6480
  hint(`signaliz ops run ${result.op_id}`);
5425
6481
  });
5426
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
+ }
5427
6537
  if (sub === "run") {
5428
6538
  const opId = rest[0];
5429
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
+ }
5430
6552
  const result = await sdk.ops.run({ op_id: opId, force: hasFlag("force"), instruction: flagArg("instruction") });
5431
6553
  return output(result, () => {
5432
6554
  console.log(`Op running: ${result.op_id}`);
@@ -5459,14 +6581,38 @@ Available: ${result.available_types.join(", ")}`);
5459
6581
  include_failed_runs: hasFlag("include-failed-runs")
5460
6582
  });
5461
6583
  return output(result, () => {
5462
- const hasMore = result.has_more || result.hasMore;
5463
- const nextCursor = result.next_cursor ?? result.nextCursor;
5464
- console.log(`Results: ${result.results_count}${hasMore ? " (more available)" : ""}`);
5465
- 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
+ }
5466
6603
  if (hasMore && nextCursor) hint(`signaliz ops results ${opId} --cursor ${nextCursor}`);
5467
6604
  printExecutionRefs(result.execution_refs);
5468
6605
  });
5469
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
+ }
5470
6616
  if (sub === "run-status" || sub === "trigger-status" || sub === "watch") {
5471
6617
  const runIds = csvFlag("run-ids");
5472
6618
  const runId = rest[0] || flagArg("run-id");
@@ -5795,13 +6941,15 @@ Available: ${result.available_types.join(", ")}`);
5795
6941
  const result = await sdk.ops.attachSinkToRoutine({ routine_id: routineId, sink_id: sinkId });
5796
6942
  return output(result);
5797
6943
  }
5798
- 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>");
5799
6945
  }
5800
6946
  var OPS_SHORTCUTS = {
5801
6947
  "/plan": "plan",
5802
6948
  plan: "plan",
5803
6949
  "/goal": "create",
5804
6950
  goal: "create",
6951
+ schedule: "schedule",
6952
+ recur: "schedule",
5805
6953
  quickstart: "quickstart",
5806
6954
  template: "template",
5807
6955
  proof: "proof",
@@ -5810,6 +6958,7 @@ var OPS_SHORTCUTS = {
5810
6958
  run: "run",
5811
6959
  status: "status",
5812
6960
  results: "results",
6961
+ wait: "wait",
5813
6962
  queue: "queue",
5814
6963
  debug: "debug",
5815
6964
  watch: "watch",