@signaliz/sdk 1.0.11 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1197,6 +1197,13 @@ var DEFAULT_CAMPAIGN_BUILDER_BUILT_INS = [
1197
1197
  "email_verification",
1198
1198
  "signals"
1199
1199
  ];
1200
+ var CAMPAIGN_BUILDER_BUILT_IN_ROUTE_IDS = {
1201
+ lead_generation: "signaliz-lead-generation",
1202
+ local_leads: "signaliz-local-leads",
1203
+ email_finding: "signaliz-email-finding",
1204
+ email_verification: "signaliz-email-verification",
1205
+ signals: "signaliz-signals"
1206
+ };
1200
1207
  var DEFAULT_CAMPAIGN_BUILDER_APPROVAL_TYPES = [
1201
1208
  "memory_retrieval",
1202
1209
  "spend",
@@ -1693,6 +1700,10 @@ var CampaignBuilderAgent = class {
1693
1700
  }
1694
1701
  return plan;
1695
1702
  }
1703
+ async readiness(request, options = {}) {
1704
+ const plan = await this.createPlan(request, options);
1705
+ return createCampaignBuilderReadiness(plan);
1706
+ }
1696
1707
  async commitPlan(plan, options = {}) {
1697
1708
  const writeMode = options.confirm === true && options.dryRun === false;
1698
1709
  if (writeMode && !options.approvedBy) {
@@ -1880,6 +1891,15 @@ var CampaignBuilderAgent = class {
1880
1891
  async approveDelivery(campaignBuildId, destinationType, options) {
1881
1892
  return this.approveCampaignDelivery(campaignBuildId, destinationType, options);
1882
1893
  }
1894
+ async reviewBuild(campaignBuildId, options = {}) {
1895
+ const status = await this.getBuildStatus(campaignBuildId);
1896
+ const rows = await this.getBuildRows(campaignBuildId, {
1897
+ ...options,
1898
+ limit: options.limit ?? 100
1899
+ });
1900
+ const artifacts = await this.listBuildArtifacts(campaignBuildId);
1901
+ return createCampaignBuilderBuildReview(status, rows, artifacts);
1902
+ }
1883
1903
  async getCampaignBuildStatus(campaignBuildId) {
1884
1904
  const data = await this.callMcp("get_campaign_build_status", {
1885
1905
  campaign_build_id: campaignBuildId
@@ -2044,6 +2064,91 @@ function createCampaignBuilderAgentPlan(request, context = {}) {
2044
2064
  warnings: context.warnings ?? []
2045
2065
  };
2046
2066
  }
2067
+ function createCampaignBuilderReadiness(plan) {
2068
+ const lanes = plan.routes.map((route) => createCampaignBuilderReadinessLane(route, plan));
2069
+ const builtInLanes = lanes.filter((lane) => lane.builtIn);
2070
+ const customRoutes = lanes.filter((lane) => lane.customRoute);
2071
+ const strategyMemory = asRecord(plan.strategyMemoryStatus);
2072
+ const deliveryRisk = asRecord(plan.buildRequest.deliveryRisk);
2073
+ const deliveryRiskBlockers = arrayOfStrings(deliveryRisk.blockers) ?? [];
2074
+ const deliveryRiskValue = asRecord(deliveryRisk.risk);
2075
+ const deliveryRiskLevel = stringValue(deliveryRiskValue.risk_level ?? deliveryRiskValue.riskLevel);
2076
+ const memoryReady = !plan.strategyMemoryStatus || strategyMemory.ready !== false;
2077
+ const kernelPlanReady = Object.keys(asRecord(plan.kernelPlan)).length > 0;
2078
+ const deliveryRiskReady = !plan.buildRequest.deliveryRisk || deliveryRiskBlockers.length === 0 && !["high", "critical", "blocked"].includes(String(deliveryRiskLevel ?? "").toLowerCase());
2079
+ const builtInsReady = builtInLanes.length > 0 && builtInLanes.every((lane) => lane.ready);
2080
+ const customRoutesReady = customRoutes.every((lane) => lane.ready);
2081
+ const approvalGatesReady = plan.approvals.length > 0;
2082
+ const gates = [
2083
+ {
2084
+ id: "campaign_plan_created",
2085
+ label: "Campaign plan created",
2086
+ passed: Boolean(plan.planId && plan.mcpFlow.length > 0),
2087
+ detail: plan.planId ? `Plan ${plan.planId} has ${plan.mcpFlow.length} MCP step(s).` : "No campaign-builder plan was created."
2088
+ },
2089
+ {
2090
+ id: "built_in_lanes_ready",
2091
+ label: "Built-in lanes ready",
2092
+ passed: builtInsReady,
2093
+ detail: `${builtInLanes.filter((lane) => lane.ready).length}/${builtInLanes.length} Signaliz built-in lane(s) are present.`
2094
+ },
2095
+ {
2096
+ id: "strategy_memory_ready",
2097
+ label: "Strategy memory ready",
2098
+ passed: memoryReady,
2099
+ detail: plan.strategyMemoryStatus ? "Strategy memory readiness was checked." : "Strategy memory readiness was not requested."
2100
+ },
2101
+ {
2102
+ id: "kernel_plan_attached",
2103
+ label: "Kernel plan attached",
2104
+ passed: kernelPlanReady,
2105
+ detail: kernelPlanReady ? "Kernel planner evidence is attached." : "Kernel planner evidence is not attached."
2106
+ },
2107
+ {
2108
+ id: "delivery_risk_clear",
2109
+ label: "Delivery risk clear",
2110
+ passed: deliveryRiskReady,
2111
+ detail: plan.buildRequest.deliveryRisk ? `Delivery risk is ${deliveryRiskLevel || "available"} with ${deliveryRiskBlockers.length} blocker(s).` : "Delivery risk preflight was not requested."
2112
+ },
2113
+ {
2114
+ id: "custom_routes_prepared",
2115
+ label: "Custom routes prepared",
2116
+ passed: customRoutesReady,
2117
+ detail: customRoutes.length === 0 ? "No BYO routes were requested." : `${customRoutes.filter((lane) => lane.ready).length}/${customRoutes.length} BYO route(s) have read-only setup steps.`
2118
+ },
2119
+ {
2120
+ id: "approval_gates_present",
2121
+ label: "Approval gates present",
2122
+ passed: approvalGatesReady,
2123
+ detail: approvalGatesReady ? `${plan.approvals.length} approval gate(s) are attached.` : "No approval gates are attached."
2124
+ }
2125
+ ];
2126
+ const blockers = [
2127
+ ...gates.filter((gate) => !gate.passed).map((gate) => gate.detail),
2128
+ ...lanes.flatMap((lane) => lane.blockers)
2129
+ ];
2130
+ const score = gates.length === 0 ? 0 : Math.round(gates.filter((gate) => gate.passed).length / gates.length * 100);
2131
+ const ready = blockers.length === 0;
2132
+ return {
2133
+ plan,
2134
+ lanes,
2135
+ gates,
2136
+ summary: {
2137
+ ready,
2138
+ score,
2139
+ targetCount: plan.targetCount,
2140
+ estimatedCredits: plan.estimatedCredits,
2141
+ builtInLanesReady: builtInLanes.filter((lane) => lane.ready).length,
2142
+ builtInLanesTotal: builtInLanes.length,
2143
+ customRoutesReady: customRoutes.filter((lane) => lane.ready).length,
2144
+ customRoutesTotal: customRoutes.length,
2145
+ approvalGateCount: plan.approvals.length,
2146
+ blockers,
2147
+ warnings: plan.warnings
2148
+ },
2149
+ nextActions: campaignReadinessNextActions(plan, ready, customRoutes.length)
2150
+ };
2151
+ }
2047
2152
  function getMissingCampaignBuilderApprovals(plan, approval) {
2048
2153
  if (approval.planId !== plan.planId) {
2049
2154
  return plan.approvals;
@@ -3307,6 +3412,185 @@ function mapAgentCampaignArtifact(data) {
3307
3412
  createdAt: stringValue(data.created_at) ?? ""
3308
3413
  };
3309
3414
  }
3415
+ function createCampaignBuilderBuildReview(status, rows, artifacts) {
3416
+ const sampledRows = rows.rows.length;
3417
+ const availableRows = rows.count || sampledRows;
3418
+ const qualifiedRows = rows.rows.filter((row) => row.qualified !== false).length;
3419
+ const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
3420
+ const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
3421
+ const artifactCount = artifacts.length || status.artifactCount || 0;
3422
+ const gates = [
3423
+ {
3424
+ id: "build_completed",
3425
+ label: "Build completed",
3426
+ passed: status.status === "completed",
3427
+ detail: status.status === "completed" ? "The campaign build is completed." : `The campaign build is ${status.status}; review again after completion.`
3428
+ },
3429
+ {
3430
+ id: "rows_available",
3431
+ label: "Rows available",
3432
+ passed: sampledRows > 0,
3433
+ detail: sampledRows > 0 ? `${sampledRows} sampled row(s) are available for review.` : "No rows were returned for review."
3434
+ },
3435
+ {
3436
+ id: "qualified_sample",
3437
+ label: "Sample rows qualified",
3438
+ passed: sampledRows > 0 && qualifiedRows === sampledRows,
3439
+ detail: sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
3440
+ },
3441
+ {
3442
+ id: "email_coverage",
3443
+ label: "Email coverage",
3444
+ passed: sampledRows > 0 && rowsWithEmail === sampledRows,
3445
+ detail: sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
3446
+ },
3447
+ {
3448
+ id: "artifact_available",
3449
+ label: "Artifact available",
3450
+ passed: artifactCount > 0,
3451
+ detail: artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
3452
+ },
3453
+ {
3454
+ id: "no_failed_rows",
3455
+ label: "No failed rows",
3456
+ passed: status.recordsFailed === 0,
3457
+ detail: status.recordsFailed === 0 ? "The build reports zero failed rows." : `${status.recordsFailed} row(s) failed during the build.`
3458
+ }
3459
+ ];
3460
+ const blockedReasons = gates.filter((gate) => !gate.passed).map((gate) => gate.detail);
3461
+ const warnings = [
3462
+ rows.hasMore ? "This review is based on a row sample; page through remaining rows before final approval." : void 0,
3463
+ sampledRows > 0 && copyReadyRows < sampledRows ? `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.` : void 0,
3464
+ ...status.warnings
3465
+ ].filter((item) => Boolean(item));
3466
+ const readyForDeliveryApproval = blockedReasons.length === 0;
3467
+ return {
3468
+ campaignBuildId: status.campaignBuildId || rows.campaignBuildId,
3469
+ status,
3470
+ rows,
3471
+ artifacts,
3472
+ gates,
3473
+ summary: {
3474
+ status: status.status,
3475
+ phase: status.currentPhase,
3476
+ sampledRows,
3477
+ availableRows,
3478
+ qualifiedRows,
3479
+ rowsWithEmail,
3480
+ copyReadyRows,
3481
+ artifactCount,
3482
+ readyForDeliveryApproval,
3483
+ blockedReasons,
3484
+ warnings
3485
+ },
3486
+ nextActions: campaignReviewNextActions(status, readyForDeliveryApproval)
3487
+ };
3488
+ }
3489
+ function campaignReviewNextActions(status, readyForDeliveryApproval) {
3490
+ const buildId = status.campaignBuildId;
3491
+ if (!buildId) return [];
3492
+ if (status.status === "completed" && readyForDeliveryApproval) {
3493
+ return [
3494
+ `signaliz campaign-agent approve ${buildId} --destination-type json`,
3495
+ `signaliz campaign-agent artifacts ${buildId}`
3496
+ ];
3497
+ }
3498
+ if (status.status === "completed") {
3499
+ return [
3500
+ `signaliz campaign-agent rows ${buildId} --limit 100`,
3501
+ `signaliz campaign-agent artifacts ${buildId}`
3502
+ ];
3503
+ }
3504
+ if (status.status === "pending_approval") {
3505
+ return [
3506
+ `signaliz campaign-agent rows ${buildId} --limit 100`,
3507
+ `signaliz campaign-agent approve ${buildId} --destination-type json`
3508
+ ];
3509
+ }
3510
+ if ((status.status === "queued" || status.status === "running") && status.triggerRunId) {
3511
+ return [`signaliz ops run-status ${status.triggerRunId} --watch`];
3512
+ }
3513
+ return [`signaliz campaign-agent status ${buildId}`];
3514
+ }
3515
+ function campaignReviewRowHasEmail(row) {
3516
+ const data = asRecord(row.data);
3517
+ return Boolean(
3518
+ stringValue(data.email) || stringValue(data.work_email) || stringValue(data.workEmail)
3519
+ );
3520
+ }
3521
+ function campaignReviewRowHasCopy(row) {
3522
+ const data = asRecord(row.data);
3523
+ const copy = asRecord(data.copy);
3524
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
3525
+ return row.status === "copy_ready" || data.copy_ready === true || Boolean(
3526
+ stringValue(data.subject) || stringValue(data.subject_line) || stringValue(data.subjectLine) || stringValue(data.opener) || stringValue(data.body) || stringValue(data.cta) || stringValue(copy.subject) || stringValue(copy.body) || stringValue(rawCopy.subject) || stringValue(rawCopy.body)
3527
+ );
3528
+ }
3529
+ function createCampaignBuilderReadinessLane(route, plan) {
3530
+ const builtIn = campaignBuilderBuiltInForRoute(route);
3531
+ const customRoute = route.provider !== "signaliz" && route.provider !== "csv" && builtIn === void 0;
3532
+ const prepareStep = plan.mcpFlow.find(
3533
+ (step) => step.tool === "gtm_provider_recipe_prepare" && step.arguments.provider_id === route.provider
3534
+ );
3535
+ const activateStep = plan.mcpFlow.find(
3536
+ (step) => step.tool === "gtm_provider_route_activate" && step.arguments.provider_id === route.provider
3537
+ );
3538
+ const readOnlySetup = !customRoute || prepareStep?.readOnly === true && activateStep?.readOnly === true && activateStep.arguments.dry_run === true && activateStep.arguments.confirm === false;
3539
+ const blockers = [
3540
+ customRoute && !prepareStep ? `${route.label ?? route.provider} is missing provider recipe setup.` : void 0,
3541
+ customRoute && !activateStep ? `${route.label ?? route.provider} is missing provider route dry-run setup.` : void 0,
3542
+ customRoute && !readOnlySetup ? `${route.label ?? route.provider} route setup must stay read-only before approval.` : void 0,
3543
+ route.mode === "required" && !route.toolName && customRoute ? `${route.label ?? route.provider} is required but has no tool name.` : void 0
3544
+ ].filter((item) => Boolean(item));
3545
+ return {
3546
+ id: route.id ?? `${route.provider}-${route.layer}`,
3547
+ label: route.label ?? readinessLaneLabel(route, builtIn),
3548
+ layer: route.layer,
3549
+ gtmLayers: gtmLayersForRoute(route),
3550
+ provider: route.provider,
3551
+ toolName: route.toolName,
3552
+ mode: route.mode ?? "if_available",
3553
+ builtIn,
3554
+ approvalRequired: route.approvalRequired === true || plan.approvals.some((approval) => approval.routeId === route.id),
3555
+ customRoute,
3556
+ readOnlySetup,
3557
+ ready: blockers.length === 0,
3558
+ blockers,
3559
+ nextAction: customRoute ? `Review ${route.provider} route setup before launch approval.` : void 0
3560
+ };
3561
+ }
3562
+ function campaignBuilderBuiltInForRoute(route) {
3563
+ return Object.entries(CAMPAIGN_BUILDER_BUILT_IN_ROUTE_IDS).find(([, routeId]) => route.id === routeId)?.[0];
3564
+ }
3565
+ function readinessLaneLabel(route, builtIn) {
3566
+ if (builtIn) {
3567
+ const labels = {
3568
+ lead_generation: "Signaliz lead generation",
3569
+ local_leads: "Signaliz local leads",
3570
+ email_finding: "Signaliz email finding",
3571
+ email_verification: "Signaliz email verification",
3572
+ signals: "Signaliz signals"
3573
+ };
3574
+ return labels[builtIn];
3575
+ }
3576
+ return `${route.provider} ${route.layer}`;
3577
+ }
3578
+ function campaignReadinessNextActions(plan, ready, customRouteCount) {
3579
+ const requestHint = "--request-file campaign-request.json";
3580
+ if (ready) {
3581
+ return [
3582
+ `signaliz campaign-agent proof ${requestHint} --json`,
3583
+ `signaliz campaign-agent commit-plan ${requestHint} --json`,
3584
+ `signaliz campaign-agent build-approved ${requestHint} --confirm-launch --approve-all --approved-by operator@example.com --spend-limit ${plan.estimatedCredits}`
3585
+ ];
3586
+ }
3587
+ const actions = [
3588
+ plan.strategyMemoryStatus && asRecord(plan.strategyMemoryStatus).ready === false ? "signaliz gtm strategy-memory --strategy-template <strategy_template> --include-samples --json" : void 0,
3589
+ customRouteCount > 0 ? "Review BYO provider recipe and route dry-run steps in the readiness packet." : void 0,
3590
+ `signaliz campaign-agent plan ${requestHint} --json`
3591
+ ];
3592
+ return actions.filter((item) => Boolean(item));
3593
+ }
3310
3594
  function diagnosticMessages2(value) {
3311
3595
  if (!Array.isArray(value)) return [];
3312
3596
  return value.map((item) => {
@@ -6680,17 +6964,18 @@ async function handleCampaignAgentCommand(signaliz, argv) {
6680
6964
  }
6681
6965
  return;
6682
6966
  }
6683
- if (["status", "rows", "artifacts", "approve", "approve-delivery"].includes(subcommand)) {
6967
+ if (["review", "status", "rows", "artifacts", "approve", "approve-delivery"].includes(subcommand)) {
6684
6968
  await handleCampaignAgentReviewCommand(signaliz, subcommand, argv.slice(1));
6685
6969
  return;
6686
6970
  }
6687
- if (!["plan", "proof", "smoke", "commit-plan", "dry-run", "build-approved", "launch-approved"].includes(subcommand)) {
6971
+ if (!["plan", "doctor", "readiness", "preflight", "proof", "smoke", "commit-plan", "dry-run", "build-approved", "launch-approved"].includes(subcommand)) {
6688
6972
  printCampaignAgentHelp();
6689
6973
  return;
6690
6974
  }
6691
6975
  const flags = parseFlags(argv.slice(1));
6976
+ const isDoctor = subcommand === "doctor" || subcommand === "readiness" || subcommand === "preflight";
6692
6977
  const requestFromFile = campaignAgentRequestFileFromFlags(flags);
6693
- const goal = stringFlag(flags, "goal", "brief") ?? stringOrUndefined(requestFromFile?.goal);
6978
+ const goal = stringFlag(flags, "goal", "brief") ?? stringOrUndefined(requestFromFile?.goal) ?? (isDoctor ? "Build a strategy-template campaign for the target ICP." : void 0);
6694
6979
  if (!goal) throw new Error(`campaign-agent ${subcommand} requires --goal or a request file with goal`);
6695
6980
  const isApprovedBuild = subcommand === "build-approved" || subcommand === "launch-approved";
6696
6981
  const isCommitPlan = subcommand === "commit-plan";
@@ -6709,13 +6994,24 @@ async function handleCampaignAgentCommand(signaliz, argv) {
6709
6994
  requestFromFile,
6710
6995
  campaignAgentRequestFromFlags(goal, flags, { includeDefaults: !requestFromFile })
6711
6996
  );
6712
- const plan = await signaliz.campaignBuilderAgent.createPlan(request, {
6997
+ const planOptions = {
6713
6998
  discoverCapabilities: !booleanFlag(flags, "skip-discovery"),
6714
6999
  scopeCampaign: !booleanFlag(flags, "skip-scope"),
6715
7000
  includeStrategyMemoryStatus: !booleanFlag(flags, "no-strategy-memory"),
6716
7001
  includeKernelPlan: !booleanFlag(flags, "no-kernel-plan"),
6717
7002
  includeDeliveryRisk: !booleanFlag(flags, "no-delivery-risk")
6718
- });
7003
+ };
7004
+ if (isDoctor) {
7005
+ const readiness = await signaliz.campaignBuilderAgent.readiness(request, planOptions);
7006
+ if (booleanFlag(flags, "json")) {
7007
+ printJson(readiness);
7008
+ } else {
7009
+ printCampaignAgentReadiness(readiness);
7010
+ }
7011
+ if (!readiness.summary.ready) process.exitCode = 1;
7012
+ return;
7013
+ }
7014
+ const plan = await signaliz.campaignBuilderAgent.createPlan(request, planOptions);
6719
7015
  if (subcommand === "proof" || subcommand === "smoke") {
6720
7016
  const result = await signaliz.campaignBuilderAgent.dryRunPlan(plan, {
6721
7017
  idempotencyKey: stringFlag(flags, "idempotency-key")
@@ -6787,6 +7083,28 @@ async function handleCampaignAgentReviewCommand(signaliz, subcommand, argv) {
6787
7083
  const flags = parseFlags(argv);
6788
7084
  const buildId = argv[0] && !argv[0].startsWith("--") ? argv[0] : stringFlag(flags, "campaign-build-id", "build-id");
6789
7085
  if (!buildId) throw new Error(`campaign-agent ${subcommand} requires <campaign_build_id> or --campaign-build-id`);
7086
+ if (subcommand === "review") {
7087
+ const review = await signaliz.campaignBuilderAgent.reviewBuild(buildId, {
7088
+ limit: numberFlag(flags, "limit") ?? 100,
7089
+ cursor: stringFlag(flags, "cursor"),
7090
+ segment: stringFlag(flags, "segment"),
7091
+ rowStatus: stringFlag(flags, "row-status"),
7092
+ qualified: booleanFlag(flags, "qualified") ? true : booleanFlag(flags, "disqualified") ? false : void 0
7093
+ });
7094
+ const operatorReview = {
7095
+ ...review,
7096
+ rows: {
7097
+ ...review.rows,
7098
+ rows: formatCampaignAgentRows(review.rows.rows)
7099
+ }
7100
+ };
7101
+ if (booleanFlag(flags, "json")) {
7102
+ printJson(operatorReview);
7103
+ } else {
7104
+ printCampaignAgentBuildReview(operatorReview);
7105
+ }
7106
+ return;
7107
+ }
6790
7108
  if (subcommand === "status") {
6791
7109
  const status = await signaliz.campaignBuilderAgent.getBuildStatus(buildId);
6792
7110
  if (booleanFlag(flags, "json")) {
@@ -7181,6 +7499,39 @@ function printCampaignAgentExecution(payload, flags) {
7181
7499
  console.log("\nResult:");
7182
7500
  printJson(payload.result);
7183
7501
  }
7502
+ function printCampaignAgentReadiness(readiness) {
7503
+ const summary = asRecord4(readiness.summary);
7504
+ const plan = readiness.plan;
7505
+ console.log(`Readiness: ${summary.ready === true ? "ready" : "blocked"} (${summary.score ?? 0}%)`);
7506
+ if (plan?.campaignName) console.log(`Campaign: ${plan.campaignName}`);
7507
+ if (summary.targetCount !== void 0) console.log(`Target: ${summary.targetCount}`);
7508
+ if (summary.estimatedCredits !== void 0) console.log(`Estimated credits: ${summary.estimatedCredits}`);
7509
+ console.log(`Built-ins: ${summary.builtInLanesReady ?? 0}/${summary.builtInLanesTotal ?? 0}`);
7510
+ console.log(`BYO routes: ${summary.customRoutesReady ?? 0}/${summary.customRoutesTotal ?? 0}`);
7511
+ console.log(`Approval gates: ${summary.approvalGateCount ?? 0}`);
7512
+ if (Array.isArray(readiness.gates) && readiness.gates.length > 0) {
7513
+ console.log("\nGates:");
7514
+ for (const gate of readiness.gates) {
7515
+ console.log(`- ${gate.passed ? "PASS" : "BLOCKED"} ${gate.label}: ${gate.detail}`);
7516
+ }
7517
+ }
7518
+ if (Array.isArray(readiness.lanes) && readiness.lanes.length > 0) {
7519
+ console.log("\nLanes:");
7520
+ for (const lane of readiness.lanes) {
7521
+ console.log(`- ${lane.ready ? "READY" : "BLOCKED"} ${lane.label}: ${lane.toolName || lane.provider}`);
7522
+ }
7523
+ }
7524
+ const blockers = Array.isArray(summary.blockers) ? summary.blockers : [];
7525
+ if (blockers.length > 0) {
7526
+ console.log("\nBlockers:");
7527
+ for (const blocker of blockers.slice(0, 8)) console.log(`- ${blocker}`);
7528
+ }
7529
+ const nextActions = Array.isArray(readiness.nextActions) ? readiness.nextActions : [];
7530
+ if (nextActions.length > 0) {
7531
+ console.log("\nNext:");
7532
+ for (const action of nextActions) console.log(`- ${action}`);
7533
+ }
7534
+ }
7184
7535
  function printCampaignAgentBuildStatus(status) {
7185
7536
  console.log(`Campaign: ${status.name || status.campaignBuildId}`);
7186
7537
  if (status.campaignId) console.log(`Campaign ID: ${status.campaignId}`);
@@ -7201,6 +7552,34 @@ function printCampaignAgentBuildStatus(status) {
7201
7552
  console.log(`Next: signaliz campaign-agent approve ${status.campaignBuildId} --destination-type webhook`);
7202
7553
  }
7203
7554
  }
7555
+ function printCampaignAgentBuildReview(review) {
7556
+ const summary = asRecord4(review.summary);
7557
+ console.log(`Campaign: ${review.status?.name || review.campaignBuildId}`);
7558
+ console.log(`Status: ${summary.status || review.status?.status}`);
7559
+ console.log(`Phase: ${summary.phase || review.status?.currentPhase || "N/A"}`);
7560
+ console.log(`Rows: ${summary.sampledRows || 0} sampled, ${summary.availableRows || 0} available`);
7561
+ console.log(`Qualified: ${summary.qualifiedRows || 0}`);
7562
+ console.log(`Emails: ${summary.rowsWithEmail || 0}`);
7563
+ console.log(`Copy ready: ${summary.copyReadyRows || 0}`);
7564
+ console.log(`Artifacts: ${summary.artifactCount || 0}`);
7565
+ console.log(`Delivery approval ready: ${summary.readyForDeliveryApproval === true ? "yes" : "no"}`);
7566
+ if (Array.isArray(review.gates) && review.gates.length > 0) {
7567
+ console.log("\nGates:");
7568
+ for (const gate of review.gates) {
7569
+ console.log(`- ${gate.passed ? "PASS" : "BLOCKED"} ${gate.label}: ${gate.detail}`);
7570
+ }
7571
+ }
7572
+ const warnings = Array.isArray(summary.warnings) ? summary.warnings : [];
7573
+ if (warnings.length > 0) {
7574
+ console.log("\nWarnings:");
7575
+ for (const warning of warnings.slice(0, 5)) console.log(`- ${warning}`);
7576
+ }
7577
+ const nextActions = Array.isArray(review.nextActions) ? review.nextActions : [];
7578
+ if (nextActions.length > 0) {
7579
+ console.log("\nNext:");
7580
+ for (const action of nextActions) console.log(`- ${action}`);
7581
+ }
7582
+ }
7204
7583
  function formatCampaignAgentRows(rows) {
7205
7584
  return rows.map((row) => {
7206
7585
  const data = asRecord4(row.data);
@@ -7403,6 +7782,7 @@ Usage:
7403
7782
  signaliz gtm memory search --query "proof-first vertical gate" --json
7404
7783
  signaliz gtm plan --brief "Build 500 verified leads for..." --strategy-template non-medical-home-care --lead-count 500 --json
7405
7784
  signaliz campaign-agent init --strategy-template non-medical-home-care --target-count 250 --use-local-leads --json > campaign-request.json
7785
+ signaliz campaign-agent doctor --request-file campaign-request.json --json
7406
7786
  signaliz campaign-agent plan --goal "Build a strategy-template campaign..." --strategy-template industrial-ot-resilience --target-count 500 --builtins lead_generation,email_finding,email_verification,signals
7407
7787
  signaliz campaign-agent proof --goal "Build a strategy-template campaign..." --strategy-template non-medical-home-care --target-count 100 --json
7408
7788
  signaliz campaign-agent plan --request-file examples/campaign-builder-agent-request.json --target-count 250 --json
@@ -7410,6 +7790,7 @@ Usage:
7410
7790
  signaliz campaign-agent dry-run --goal "Build a strategy-template campaign..." --target-count 500 --use-local-leads --json
7411
7791
  signaliz campaign-agent build-approved --goal "Build a strategy-template campaign..." --confirm-launch --approve-all --approved-by you@example.com --spend-limit 500
7412
7792
  signaliz campaign-agent build-approved --request-file campaign-request.json --confirm-launch --approve-all --approved-by you@example.com --spend-limit 500 --wait --approve-delivery --delivery-destination-type json
7793
+ signaliz campaign-agent review <campaign_build_id> --limit 100 --json
7413
7794
  signaliz campaign-agent status <campaign_build_id> --json
7414
7795
  signaliz campaign-agent rows <campaign_build_id> --limit 100 --json
7415
7796
 
@@ -7438,17 +7819,19 @@ function printCampaignAgentHelp() {
7438
7819
  Signaliz campaign-agent commands:
7439
7820
 
7440
7821
  signaliz campaign-agent init [--goal TEXT] [--strategy-template ID] [--target-count N] [--use-local-leads] [--with-byo-placeholder] [--output-file FILE] [--json]
7822
+ signaliz campaign-agent doctor [--goal TEXT | --request-file FILE] [--strategy-template ID] [--target-count N] [--builtins lead_generation,email_finding,email_verification,signals,local_leads] [--custom-tool provider:layer:tool[:mcp]] [--json]
7441
7823
  signaliz campaign-agent plan (--goal TEXT | --request-file FILE) [--campaign-name NAME] [--workspace-label NAME] [--strategy-template industrial-ot-resilience|non-medical-home-care|agency-founder-led|cloud-infrastructure-displacement] [--operating-playbooks proof-first-vertical-gate,signal-led-copy-approval] [--target-count N] [--icp JSON] [--builtins lead_generation,email_finding,email_verification,signals,local_leads] [--use-local-leads] [--custom-tool provider:layer:tool[:mcp]] [--preferred-providers signaliz_native,octave] [--partners clay,instantly,nango] [--no-strategy-memory] [--no-kernel-plan] [--no-delivery-risk] [--json]
7442
7824
  signaliz campaign-agent proof (--goal TEXT | --request-file FILE) [--strategy-template ID] [--target-count N] [--use-local-leads] [--custom-tool provider:layer:tool[:mcp]] [--idempotency-key KEY] [--json]
7443
7825
  signaliz campaign-agent commit-plan (--goal TEXT | --request-file FILE) [--confirm-write --approved-by EMAIL] [--idempotency-key KEY] [--json]
7444
7826
  signaliz campaign-agent dry-run (--goal TEXT | --request-file FILE) [--gtm-campaign-id ID] [--delivery-risk JSON] [--idempotency-key KEY] [--json]
7445
7827
  signaliz campaign-agent build-approved (--goal TEXT | --request-file FILE) --confirm-launch --approved-by EMAIL (--approve-all | --approved-types a,b) [--commit-before-launch] [--spend-limit N] [--approved-route-ids a,b] [--wait] [--approve-delivery] [--delivery-destination-type json|csv|webhook] [--idempotency-key KEY] [--json]
7828
+ signaliz campaign-agent review <campaign_build_id> [--limit N] [--qualified|--disqualified] [--json]
7446
7829
  signaliz campaign-agent status <campaign_build_id> [--json]
7447
7830
  signaliz campaign-agent rows <campaign_build_id> [--limit N] [--cursor CURSOR] [--qualified|--disqualified] [--json]
7448
7831
  signaliz campaign-agent artifacts <campaign_build_id> [--json]
7449
7832
  signaliz campaign-agent approve <campaign_build_id> --destination-type json|csv|webhook [--destination-config JSON] [--json]
7450
7833
 
7451
- The init command emits a reusable JSON CampaignBuilderAgentRequest. The plan command composes an approval-gated strategy-template MCP flow. Proof runs the same plan path plus build_campaign dry_run=true and returns a no-spend receipt. Use --request-file to load a reusable request, then override fields such as --target-count or --strategy-template from the command line. Use --strategy-template to merge a private-safe strategy pattern into the plan without exposing private account labels, and --operating-playbooks to add reusable build-loop gates such as proof-first vertical qualification, net-new suppression, cache-first inventory, and signal-led copy approval. Built-ins map to Signaliz-native tools: lead_generation -> generate_leads, local_leads -> generate_local_leads, email_finding -> find_and_verify_emails, email_verification -> verify_emails, signals -> enrich_company_signals. Add BYO tools with --custom-tool provider:layer:tool[:mcp] or key=value pairs such as provider=apollo,layer=source,tool=apollo_search,mcp=apollo. Use --no-strategy-patterns or --no-workflow-patterns to disable default private strategy memory hints. Commit-plan dry-runs by default and writes only with --confirm-write plus approval identity. Dry-run executes build_campaign with dry_run=true. Approved build requires explicit launch confirmation and approval metadata; add --commit-before-launch to persist the reviewed plan as a GTM campaign before launch, --wait to poll until completion or pending approval, and --approve-delivery to approve reviewed webhook delivery after pending_approval. Use status, rows, and artifacts to inspect the finished campaign-agent build before routing rows to external tools.
7834
+ The init command emits a reusable JSON CampaignBuilderAgentRequest. Doctor returns a readiness packet for built-in lanes, BYO routes, Memory, Brain, Kernel planning, and approval gates. The plan command composes an approval-gated strategy-template MCP flow. Proof runs the same plan path plus build_campaign dry_run=true and returns a no-spend receipt. Use --request-file to load a reusable request, then override fields such as --target-count or --strategy-template from the command line. Use --strategy-template to merge a private-safe strategy pattern into the plan without exposing private account labels, and --operating-playbooks to add reusable build-loop gates such as proof-first vertical qualification, net-new suppression, cache-first inventory, and signal-led copy approval. Built-ins map to Signaliz-native tools: lead_generation -> generate_leads, local_leads -> generate_local_leads, email_finding -> find_and_verify_emails, email_verification -> verify_emails, signals -> enrich_company_signals. Add BYO tools with --custom-tool provider:layer:tool[:mcp] or key=value pairs such as provider=apollo,layer=source,tool=apollo_search,mcp=apollo. Use --no-strategy-patterns or --no-workflow-patterns to disable default private strategy memory hints. Commit-plan dry-runs by default and writes only with --confirm-write plus approval identity. Dry-run executes build_campaign with dry_run=true. Approved build requires explicit launch confirmation and approval metadata; add --commit-before-launch to persist the reviewed plan as a GTM campaign before launch, --wait to poll until completion or pending approval, and --approve-delivery to approve reviewed webhook delivery after pending_approval. Use review to inspect status, sampled rows, artifacts, and delivery-readiness gates before approving delivery or routing rows to external tools.
7452
7835
  `);
7453
7836
  }
7454
7837
  main().catch((e) => {