@signaliz/sdk 1.0.12 → 1.0.14

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,17 @@ 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
+ }
1707
+ async proof(request, options = {}) {
1708
+ const plan = await this.createPlan(request, options);
1709
+ const result = await this.dryRunPlan(plan, {
1710
+ idempotencyKey: options.idempotencyKey
1711
+ });
1712
+ return createCampaignBuilderProofReceipt(plan, result);
1713
+ }
1696
1714
  async commitPlan(plan, options = {}) {
1697
1715
  const writeMode = options.confirm === true && options.dryRun === false;
1698
1716
  if (writeMode && !options.approvedBy) {
@@ -2053,6 +2071,157 @@ function createCampaignBuilderAgentPlan(request, context = {}) {
2053
2071
  warnings: context.warnings ?? []
2054
2072
  };
2055
2073
  }
2074
+ function createCampaignBuilderReadiness(plan) {
2075
+ const lanes = plan.routes.map((route) => createCampaignBuilderReadinessLane(route, plan));
2076
+ const builtInLanes = lanes.filter((lane) => lane.builtIn);
2077
+ const customRoutes = lanes.filter((lane) => lane.customRoute);
2078
+ const strategyMemory = asRecord(plan.strategyMemoryStatus);
2079
+ const deliveryRisk = asRecord(plan.buildRequest.deliveryRisk);
2080
+ const deliveryRiskBlockers = arrayOfStrings(deliveryRisk.blockers) ?? [];
2081
+ const deliveryRiskValue = asRecord(deliveryRisk.risk);
2082
+ const deliveryRiskLevel = stringValue(deliveryRiskValue.risk_level ?? deliveryRiskValue.riskLevel);
2083
+ const memoryReady = !plan.strategyMemoryStatus || strategyMemory.ready !== false;
2084
+ const kernelPlanReady = Object.keys(asRecord(plan.kernelPlan)).length > 0;
2085
+ const deliveryRiskReady = !plan.buildRequest.deliveryRisk || deliveryRiskBlockers.length === 0 && !["high", "critical", "blocked"].includes(String(deliveryRiskLevel ?? "").toLowerCase());
2086
+ const builtInsReady = builtInLanes.length > 0 && builtInLanes.every((lane) => lane.ready);
2087
+ const customRoutesReady = customRoutes.every((lane) => lane.ready);
2088
+ const approvalGatesReady = plan.approvals.length > 0;
2089
+ const gates = [
2090
+ {
2091
+ id: "campaign_plan_created",
2092
+ label: "Campaign plan created",
2093
+ passed: Boolean(plan.planId && plan.mcpFlow.length > 0),
2094
+ detail: plan.planId ? `Plan ${plan.planId} has ${plan.mcpFlow.length} MCP step(s).` : "No campaign-builder plan was created."
2095
+ },
2096
+ {
2097
+ id: "built_in_lanes_ready",
2098
+ label: "Built-in lanes ready",
2099
+ passed: builtInsReady,
2100
+ detail: `${builtInLanes.filter((lane) => lane.ready).length}/${builtInLanes.length} Signaliz built-in lane(s) are present.`
2101
+ },
2102
+ {
2103
+ id: "strategy_memory_ready",
2104
+ label: "Strategy memory ready",
2105
+ passed: memoryReady,
2106
+ detail: plan.strategyMemoryStatus ? "Strategy memory readiness was checked." : "Strategy memory readiness was not requested."
2107
+ },
2108
+ {
2109
+ id: "kernel_plan_attached",
2110
+ label: "Kernel plan attached",
2111
+ passed: kernelPlanReady,
2112
+ detail: kernelPlanReady ? "Kernel planner evidence is attached." : "Kernel planner evidence is not attached."
2113
+ },
2114
+ {
2115
+ id: "delivery_risk_clear",
2116
+ label: "Delivery risk clear",
2117
+ passed: deliveryRiskReady,
2118
+ detail: plan.buildRequest.deliveryRisk ? `Delivery risk is ${deliveryRiskLevel || "available"} with ${deliveryRiskBlockers.length} blocker(s).` : "Delivery risk preflight was not requested."
2119
+ },
2120
+ {
2121
+ id: "custom_routes_prepared",
2122
+ label: "Custom routes prepared",
2123
+ passed: customRoutesReady,
2124
+ 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.`
2125
+ },
2126
+ {
2127
+ id: "approval_gates_present",
2128
+ label: "Approval gates present",
2129
+ passed: approvalGatesReady,
2130
+ detail: approvalGatesReady ? `${plan.approvals.length} approval gate(s) are attached.` : "No approval gates are attached."
2131
+ }
2132
+ ];
2133
+ const blockers = [
2134
+ ...gates.filter((gate) => !gate.passed).map((gate) => gate.detail),
2135
+ ...lanes.flatMap((lane) => lane.blockers)
2136
+ ];
2137
+ const score = gates.length === 0 ? 0 : Math.round(gates.filter((gate) => gate.passed).length / gates.length * 100);
2138
+ const ready = blockers.length === 0;
2139
+ return {
2140
+ plan,
2141
+ lanes,
2142
+ gates,
2143
+ summary: {
2144
+ ready,
2145
+ score,
2146
+ targetCount: plan.targetCount,
2147
+ estimatedCredits: plan.estimatedCredits,
2148
+ builtInLanesReady: builtInLanes.filter((lane) => lane.ready).length,
2149
+ builtInLanesTotal: builtInLanes.length,
2150
+ customRoutesReady: customRoutes.filter((lane) => lane.ready).length,
2151
+ customRoutesTotal: customRoutes.length,
2152
+ approvalGateCount: plan.approvals.length,
2153
+ blockers,
2154
+ warnings: plan.warnings
2155
+ },
2156
+ nextActions: campaignReadinessNextActions(plan, ready, customRoutes.length)
2157
+ };
2158
+ }
2159
+ function createCampaignBuilderProofReceipt(plan, result) {
2160
+ const strategyMemory = asRecord(plan.strategyMemoryStatus);
2161
+ const dryRunResult = asRecord(result);
2162
+ const memoryReady = plan.strategyMemoryStatus ? strategyMemory.ready !== false : false;
2163
+ const dryRunCompleted = dryRunResult.dryRun === true || dryRunResult.dry_run === true || dryRunResult.status === "dry_run";
2164
+ const noLaunch = dryRunCompleted && !dryRunResult.campaignBuildId && !dryRunResult.campaign_build_id;
2165
+ const gates = [
2166
+ {
2167
+ id: "strategy_memory_ready",
2168
+ passed: memoryReady,
2169
+ ready: strategyMemory.ready ?? null,
2170
+ blocker_count: Array.isArray(strategyMemory.blockers) ? strategyMemory.blockers.length : 0
2171
+ },
2172
+ {
2173
+ id: "campaign_plan_ready",
2174
+ passed: Boolean(plan.planId && plan.mcpFlow.length > 0),
2175
+ plan_id: plan.planId,
2176
+ flow_steps: plan.mcpFlow.length
2177
+ },
2178
+ {
2179
+ id: "approval_gates_present",
2180
+ passed: plan.approvals.length > 0,
2181
+ approval_types: plan.approvals.map((approval) => approval.type)
2182
+ },
2183
+ {
2184
+ id: "dry_run_completed",
2185
+ passed: dryRunCompleted,
2186
+ status: dryRunResult.status ?? null,
2187
+ current_phase: dryRunResult.currentPhase ?? dryRunResult.current_phase ?? null
2188
+ },
2189
+ {
2190
+ id: "no_spendful_launch",
2191
+ passed: noLaunch,
2192
+ campaign_build_id: dryRunResult.campaignBuildId ?? dryRunResult.campaign_build_id ?? null
2193
+ }
2194
+ ];
2195
+ return {
2196
+ receipt_version: "campaign-agent-proof.v1",
2197
+ source: "signaliz.campaignBuilderAgent.proof",
2198
+ success: gates.every((gate) => gate.passed),
2199
+ safety: {
2200
+ spendful_tools_called: false,
2201
+ external_writes_called: false,
2202
+ sender_loads_called: false,
2203
+ email_sends_called: false,
2204
+ dry_run_only: true
2205
+ },
2206
+ campaign: {
2207
+ plan_id: plan.planId,
2208
+ campaign_name: plan.campaignName,
2209
+ strategy_template: campaignBuilderProofStrategyTemplate(plan),
2210
+ target_count: plan.targetCount,
2211
+ estimated_credits: plan.estimatedCredits,
2212
+ operating_playbooks: plan.operatingPlaybooks.map((playbook) => playbook.slug)
2213
+ },
2214
+ gates,
2215
+ strategy_memory_status: summarizeCampaignBuilderProofStrategyMemory(strategyMemory),
2216
+ dry_run_result: summarizeCampaignBuilderProofDryRun(dryRunResult),
2217
+ recommended_next_actions: [
2218
+ "Review the plan, approvals, and dry-run result.",
2219
+ "Use campaign-agent commit-plan --confirm-write only after review.",
2220
+ "Use campaign-agent build-approved only with --confirm-launch, approval identity, approval types, and spend limit.",
2221
+ "When an approved build reaches pending_approval, review rows and approve delivery through the Campaigns SDK."
2222
+ ]
2223
+ };
2224
+ }
2056
2225
  function getMissingCampaignBuilderApprovals(plan, approval) {
2057
2226
  if (approval.planId !== plan.planId) {
2058
2227
  return plan.approvals;
@@ -3430,6 +3599,137 @@ function campaignReviewRowHasCopy(row) {
3430
3599
  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)
3431
3600
  );
3432
3601
  }
3602
+ function createCampaignBuilderReadinessLane(route, plan) {
3603
+ const builtIn = campaignBuilderBuiltInForRoute(route);
3604
+ const customRoute = route.provider !== "signaliz" && route.provider !== "csv" && builtIn === void 0;
3605
+ const prepareStep = plan.mcpFlow.find(
3606
+ (step) => step.tool === "gtm_provider_recipe_prepare" && step.arguments.provider_id === route.provider
3607
+ );
3608
+ const activateStep = plan.mcpFlow.find(
3609
+ (step) => step.tool === "gtm_provider_route_activate" && step.arguments.provider_id === route.provider
3610
+ );
3611
+ const readOnlySetup = !customRoute || prepareStep?.readOnly === true && activateStep?.readOnly === true && activateStep.arguments.dry_run === true && activateStep.arguments.confirm === false;
3612
+ const blockers = [
3613
+ customRoute && !prepareStep ? `${route.label ?? route.provider} is missing provider recipe setup.` : void 0,
3614
+ customRoute && !activateStep ? `${route.label ?? route.provider} is missing provider route dry-run setup.` : void 0,
3615
+ customRoute && !readOnlySetup ? `${route.label ?? route.provider} route setup must stay read-only before approval.` : void 0,
3616
+ route.mode === "required" && !route.toolName && customRoute ? `${route.label ?? route.provider} is required but has no tool name.` : void 0
3617
+ ].filter((item) => Boolean(item));
3618
+ return {
3619
+ id: route.id ?? `${route.provider}-${route.layer}`,
3620
+ label: route.label ?? readinessLaneLabel(route, builtIn),
3621
+ layer: route.layer,
3622
+ gtmLayers: gtmLayersForRoute(route),
3623
+ provider: route.provider,
3624
+ toolName: route.toolName,
3625
+ mode: route.mode ?? "if_available",
3626
+ builtIn,
3627
+ approvalRequired: route.approvalRequired === true || plan.approvals.some((approval) => approval.routeId === route.id),
3628
+ customRoute,
3629
+ readOnlySetup,
3630
+ ready: blockers.length === 0,
3631
+ blockers,
3632
+ nextAction: customRoute ? `Review ${route.provider} route setup before launch approval.` : void 0
3633
+ };
3634
+ }
3635
+ function campaignBuilderBuiltInForRoute(route) {
3636
+ return Object.entries(CAMPAIGN_BUILDER_BUILT_IN_ROUTE_IDS).find(([, routeId]) => route.id === routeId)?.[0];
3637
+ }
3638
+ function readinessLaneLabel(route, builtIn) {
3639
+ if (builtIn) {
3640
+ const labels = {
3641
+ lead_generation: "Signaliz lead generation",
3642
+ local_leads: "Signaliz local leads",
3643
+ email_finding: "Signaliz email finding",
3644
+ email_verification: "Signaliz email verification",
3645
+ signals: "Signaliz signals"
3646
+ };
3647
+ return labels[builtIn];
3648
+ }
3649
+ return `${route.provider} ${route.layer}`;
3650
+ }
3651
+ function campaignReadinessNextActions(plan, ready, customRouteCount) {
3652
+ const requestHint = "--request-file campaign-request.json";
3653
+ if (ready) {
3654
+ return [
3655
+ `signaliz campaign-agent proof ${requestHint} --json`,
3656
+ `signaliz campaign-agent commit-plan ${requestHint} --json`,
3657
+ `signaliz campaign-agent build-approved ${requestHint} --confirm-launch --approve-all --approved-by operator@example.com --spend-limit ${plan.estimatedCredits}`
3658
+ ];
3659
+ }
3660
+ const actions = [
3661
+ plan.strategyMemoryStatus && asRecord(plan.strategyMemoryStatus).ready === false ? "signaliz gtm strategy-memory --strategy-template <strategy_template> --include-samples --json" : void 0,
3662
+ customRouteCount > 0 ? "Review BYO provider recipe and route dry-run steps in the readiness packet." : void 0,
3663
+ `signaliz campaign-agent plan ${requestHint} --json`
3664
+ ];
3665
+ return actions.filter((item) => Boolean(item));
3666
+ }
3667
+ function campaignBuilderProofStrategyTemplate(plan) {
3668
+ const buildRequest = asRecord(plan.buildRequest);
3669
+ const strategyMemory = asRecord(plan.strategyMemoryStatus);
3670
+ const statusTemplate = strategyMemory.strategy_template ?? strategyMemory.strategyTemplate;
3671
+ const statusTemplateRecord = asRecord(statusTemplate);
3672
+ const flowTemplate = plan.mcpFlow.map((step) => asRecord(step.arguments).strategy_template ?? asRecord(step.arguments).strategyTemplate).find((value) => typeof value === "string" && value.trim());
3673
+ return firstNonEmptyString(
3674
+ buildRequest.strategyTemplate,
3675
+ buildRequest.strategy_template,
3676
+ statusTemplate,
3677
+ statusTemplateRecord.slug,
3678
+ statusTemplateRecord.id,
3679
+ flowTemplate
3680
+ );
3681
+ }
3682
+ function summarizeCampaignBuilderProofStrategyMemory(strategyMemory) {
3683
+ if (Object.keys(strategyMemory).length === 0) return { ready: false };
3684
+ const template = strategyMemory.strategy_template ?? strategyMemory.strategyTemplate;
3685
+ const templateRecord = asRecord(template);
3686
+ const coverage = asRecord(strategyMemory.coverage);
3687
+ const rawSourceGroups = Array.isArray(strategyMemory.source_groups) ? strategyMemory.source_groups : Array.isArray(strategyMemory.sourceGroups) ? strategyMemory.sourceGroups : [];
3688
+ const sourceGroups = rawSourceGroups.map((group) => {
3689
+ const sourceGroup = asRecord(group);
3690
+ return {
3691
+ id: sourceGroup.id ?? null,
3692
+ required: sourceGroup.required ?? null,
3693
+ ready: sourceGroup.ready ?? null,
3694
+ counts: asRecord(sourceGroup.counts)
3695
+ };
3696
+ });
3697
+ return {
3698
+ ready: strategyMemory.ready ?? null,
3699
+ read_only: strategyMemory.read_only ?? strategyMemory.readOnly ?? null,
3700
+ source_tool: firstNonEmptyString(strategyMemory.source_tool, strategyMemory.sourceTool),
3701
+ strategy_template: {
3702
+ slug: firstNonEmptyString(template, templateRecord.slug, templateRecord.id),
3703
+ label: firstNonEmptyString(templateRecord.label)
3704
+ },
3705
+ coverage: {
3706
+ required_groups_ready: coverage.required_groups_ready ?? coverage.requiredGroupsReady ?? null,
3707
+ required_groups_total: coverage.required_groups_total ?? coverage.requiredGroupsTotal ?? null,
3708
+ source_groups_ready: coverage.source_groups_ready ?? coverage.sourceGroupsReady ?? null,
3709
+ source_groups_total: coverage.source_groups_total ?? coverage.sourceGroupsTotal ?? null,
3710
+ knowledge_docs: coverage.knowledge_docs ?? coverage.knowledgeDocs ?? null,
3711
+ memories: coverage.memories ?? null
3712
+ },
3713
+ source_groups: sourceGroups,
3714
+ blocker_count: Array.isArray(strategyMemory.blockers) ? strategyMemory.blockers.length : 0,
3715
+ warning_count: Array.isArray(strategyMemory.warnings) ? strategyMemory.warnings.length : 0
3716
+ };
3717
+ }
3718
+ function summarizeCampaignBuilderProofDryRun(dryRunResult) {
3719
+ return {
3720
+ dryRun: dryRunResult.dryRun ?? dryRunResult.dry_run ?? null,
3721
+ status: dryRunResult.status ?? null,
3722
+ currentPhase: dryRunResult.currentPhase ?? dryRunResult.current_phase ?? null,
3723
+ campaignBuildId: dryRunResult.campaignBuildId ?? dryRunResult.campaign_build_id ?? null,
3724
+ plannedTargetCount: dryRunResult.plannedTargetCount ?? dryRunResult.planned_target_count ?? null
3725
+ };
3726
+ }
3727
+ function firstNonEmptyString(...values) {
3728
+ for (const value of values) {
3729
+ if (typeof value === "string" && value.trim()) return value;
3730
+ }
3731
+ return null;
3732
+ }
3433
3733
  function diagnosticMessages2(value) {
3434
3734
  if (!Array.isArray(value)) return [];
3435
3735
  return value.map((item) => {
@@ -6807,13 +7107,14 @@ async function handleCampaignAgentCommand(signaliz, argv) {
6807
7107
  await handleCampaignAgentReviewCommand(signaliz, subcommand, argv.slice(1));
6808
7108
  return;
6809
7109
  }
6810
- if (!["plan", "proof", "smoke", "commit-plan", "dry-run", "build-approved", "launch-approved"].includes(subcommand)) {
7110
+ if (!["plan", "doctor", "readiness", "preflight", "proof", "smoke", "commit-plan", "dry-run", "build-approved", "launch-approved"].includes(subcommand)) {
6811
7111
  printCampaignAgentHelp();
6812
7112
  return;
6813
7113
  }
6814
7114
  const flags = parseFlags(argv.slice(1));
7115
+ const isDoctor = subcommand === "doctor" || subcommand === "readiness" || subcommand === "preflight";
6815
7116
  const requestFromFile = campaignAgentRequestFileFromFlags(flags);
6816
- const goal = stringFlag(flags, "goal", "brief") ?? stringOrUndefined(requestFromFile?.goal);
7117
+ const goal = stringFlag(flags, "goal", "brief") ?? stringOrUndefined(requestFromFile?.goal) ?? (isDoctor ? "Build a strategy-template campaign for the target ICP." : void 0);
6817
7118
  if (!goal) throw new Error(`campaign-agent ${subcommand} requires --goal or a request file with goal`);
6818
7119
  const isApprovedBuild = subcommand === "build-approved" || subcommand === "launch-approved";
6819
7120
  const isCommitPlan = subcommand === "commit-plan";
@@ -6832,18 +7133,28 @@ async function handleCampaignAgentCommand(signaliz, argv) {
6832
7133
  requestFromFile,
6833
7134
  campaignAgentRequestFromFlags(goal, flags, { includeDefaults: !requestFromFile })
6834
7135
  );
6835
- const plan = await signaliz.campaignBuilderAgent.createPlan(request, {
7136
+ const planOptions = {
6836
7137
  discoverCapabilities: !booleanFlag(flags, "skip-discovery"),
6837
7138
  scopeCampaign: !booleanFlag(flags, "skip-scope"),
6838
7139
  includeStrategyMemoryStatus: !booleanFlag(flags, "no-strategy-memory"),
6839
7140
  includeKernelPlan: !booleanFlag(flags, "no-kernel-plan"),
6840
7141
  includeDeliveryRisk: !booleanFlag(flags, "no-delivery-risk")
6841
- });
7142
+ };
7143
+ if (isDoctor) {
7144
+ const readiness = await signaliz.campaignBuilderAgent.readiness(request, planOptions);
7145
+ if (booleanFlag(flags, "json")) {
7146
+ printJson(readiness);
7147
+ } else {
7148
+ printCampaignAgentReadiness(readiness);
7149
+ }
7150
+ if (!readiness.summary.ready) process.exitCode = 1;
7151
+ return;
7152
+ }
6842
7153
  if (subcommand === "proof" || subcommand === "smoke") {
6843
- const result = await signaliz.campaignBuilderAgent.dryRunPlan(plan, {
7154
+ const proof = await signaliz.campaignBuilderAgent.proof(request, {
7155
+ ...planOptions,
6844
7156
  idempotencyKey: stringFlag(flags, "idempotency-key")
6845
7157
  });
6846
- const proof = createCampaignAgentProofReceipt(plan, result);
6847
7158
  if (booleanFlag(flags, "json")) {
6848
7159
  printJson(proof);
6849
7160
  } else {
@@ -6852,6 +7163,7 @@ async function handleCampaignAgentCommand(signaliz, argv) {
6852
7163
  if (!proof.success) process.exitCode = 1;
6853
7164
  return;
6854
7165
  }
7166
+ const plan = await signaliz.campaignBuilderAgent.createPlan(request, planOptions);
6855
7167
  if (isCommitPlan) {
6856
7168
  const result = await signaliz.campaignBuilderAgent.commitPlan(plan, {
6857
7169
  confirm: isConfirmedCommit,
@@ -7326,6 +7638,39 @@ function printCampaignAgentExecution(payload, flags) {
7326
7638
  console.log("\nResult:");
7327
7639
  printJson(payload.result);
7328
7640
  }
7641
+ function printCampaignAgentReadiness(readiness) {
7642
+ const summary = asRecord4(readiness.summary);
7643
+ const plan = readiness.plan;
7644
+ console.log(`Readiness: ${summary.ready === true ? "ready" : "blocked"} (${summary.score ?? 0}%)`);
7645
+ if (plan?.campaignName) console.log(`Campaign: ${plan.campaignName}`);
7646
+ if (summary.targetCount !== void 0) console.log(`Target: ${summary.targetCount}`);
7647
+ if (summary.estimatedCredits !== void 0) console.log(`Estimated credits: ${summary.estimatedCredits}`);
7648
+ console.log(`Built-ins: ${summary.builtInLanesReady ?? 0}/${summary.builtInLanesTotal ?? 0}`);
7649
+ console.log(`BYO routes: ${summary.customRoutesReady ?? 0}/${summary.customRoutesTotal ?? 0}`);
7650
+ console.log(`Approval gates: ${summary.approvalGateCount ?? 0}`);
7651
+ if (Array.isArray(readiness.gates) && readiness.gates.length > 0) {
7652
+ console.log("\nGates:");
7653
+ for (const gate of readiness.gates) {
7654
+ console.log(`- ${gate.passed ? "PASS" : "BLOCKED"} ${gate.label}: ${gate.detail}`);
7655
+ }
7656
+ }
7657
+ if (Array.isArray(readiness.lanes) && readiness.lanes.length > 0) {
7658
+ console.log("\nLanes:");
7659
+ for (const lane of readiness.lanes) {
7660
+ console.log(`- ${lane.ready ? "READY" : "BLOCKED"} ${lane.label}: ${lane.toolName || lane.provider}`);
7661
+ }
7662
+ }
7663
+ const blockers = Array.isArray(summary.blockers) ? summary.blockers : [];
7664
+ if (blockers.length > 0) {
7665
+ console.log("\nBlockers:");
7666
+ for (const blocker of blockers.slice(0, 8)) console.log(`- ${blocker}`);
7667
+ }
7668
+ const nextActions = Array.isArray(readiness.nextActions) ? readiness.nextActions : [];
7669
+ if (nextActions.length > 0) {
7670
+ console.log("\nNext:");
7671
+ for (const action of nextActions) console.log(`- ${action}`);
7672
+ }
7673
+ }
7329
7674
  function printCampaignAgentBuildStatus(status) {
7330
7675
  console.log(`Campaign: ${status.name || status.campaignBuildId}`);
7331
7676
  if (status.campaignId) console.log(`Campaign ID: ${status.campaignId}`);
@@ -7410,138 +7755,6 @@ function printCampaignAgentRows(result) {
7410
7755
  function asRecord4(value) {
7411
7756
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
7412
7757
  }
7413
- function createCampaignAgentProofReceipt(plan, result) {
7414
- const strategyMemory = asRecord4(plan.strategyMemoryStatus);
7415
- const dryRunResult = asRecord4(result);
7416
- const memoryReady = plan.strategyMemoryStatus ? strategyMemory.ready !== false : false;
7417
- const dryRunCompleted = dryRunResult.dryRun === true || dryRunResult.dry_run === true || dryRunResult.status === "dry_run";
7418
- const noLaunch = dryRunCompleted && !dryRunResult.campaignBuildId && !dryRunResult.campaign_build_id;
7419
- const gates = [
7420
- {
7421
- id: "strategy_memory_ready",
7422
- passed: memoryReady,
7423
- ready: strategyMemory.ready ?? null,
7424
- blocker_count: Array.isArray(strategyMemory.blockers) ? strategyMemory.blockers.length : 0
7425
- },
7426
- {
7427
- id: "campaign_plan_ready",
7428
- passed: Boolean(plan.planId && plan.mcpFlow.length > 0),
7429
- plan_id: plan.planId,
7430
- flow_steps: plan.mcpFlow.length
7431
- },
7432
- {
7433
- id: "approval_gates_present",
7434
- passed: plan.approvals.length > 0,
7435
- approval_types: plan.approvals.map((approval) => approval.type)
7436
- },
7437
- {
7438
- id: "dry_run_completed",
7439
- passed: dryRunCompleted,
7440
- status: dryRunResult.status ?? null,
7441
- current_phase: dryRunResult.currentPhase ?? dryRunResult.current_phase ?? null
7442
- },
7443
- {
7444
- id: "no_spendful_launch",
7445
- passed: noLaunch,
7446
- campaign_build_id: dryRunResult.campaignBuildId ?? dryRunResult.campaign_build_id ?? null
7447
- }
7448
- ];
7449
- return {
7450
- receipt_version: "campaign-agent-proof.v1",
7451
- source: "signaliz campaign-agent proof",
7452
- success: gates.every((gate) => gate.passed),
7453
- safety: {
7454
- spendful_tools_called: false,
7455
- external_writes_called: false,
7456
- sender_loads_called: false,
7457
- email_sends_called: false,
7458
- dry_run_only: true
7459
- },
7460
- campaign: {
7461
- plan_id: plan.planId,
7462
- campaign_name: plan.campaignName,
7463
- strategy_template: campaignAgentStrategyTemplate(plan),
7464
- target_count: plan.targetCount,
7465
- estimated_credits: plan.estimatedCredits,
7466
- operating_playbooks: plan.operatingPlaybooks.map((playbook) => playbook.slug)
7467
- },
7468
- gates,
7469
- strategy_memory_status: summarizeCampaignAgentStrategyMemory(strategyMemory),
7470
- dry_run_result: summarizeCampaignAgentDryRun(dryRunResult),
7471
- recommended_next_actions: [
7472
- "Review the plan, approvals, and dry-run result.",
7473
- "Use campaign-agent commit-plan --confirm-write only after review.",
7474
- "Use campaign-agent build-approved only with --confirm-launch, approval identity, approval types, and spend limit.",
7475
- "When an approved build reaches pending_approval, review rows and approve delivery through the Campaigns SDK."
7476
- ]
7477
- };
7478
- }
7479
- function campaignAgentString(...values) {
7480
- for (const value of values) {
7481
- if (typeof value === "string" && value.trim()) return value;
7482
- }
7483
- return null;
7484
- }
7485
- function campaignAgentStrategyTemplate(plan) {
7486
- const buildRequest = asRecord4(plan.buildRequest);
7487
- const strategyMemory = asRecord4(plan.strategyMemoryStatus);
7488
- const statusTemplate = strategyMemory.strategy_template ?? strategyMemory.strategyTemplate;
7489
- const statusTemplateRecord = asRecord4(statusTemplate);
7490
- const flowTemplate = plan.mcpFlow.map((step) => asRecord4(step.arguments).strategy_template ?? asRecord4(step.arguments).strategyTemplate).find((value) => typeof value === "string" && value.trim());
7491
- return campaignAgentString(
7492
- buildRequest.strategyTemplate,
7493
- buildRequest.strategy_template,
7494
- statusTemplate,
7495
- statusTemplateRecord.slug,
7496
- statusTemplateRecord.id,
7497
- flowTemplate
7498
- );
7499
- }
7500
- function summarizeCampaignAgentStrategyMemory(strategyMemory) {
7501
- if (Object.keys(strategyMemory).length === 0) return { ready: false };
7502
- const template = strategyMemory.strategy_template ?? strategyMemory.strategyTemplate;
7503
- const templateRecord = asRecord4(template);
7504
- const coverage = asRecord4(strategyMemory.coverage);
7505
- const rawSourceGroups = Array.isArray(strategyMemory.source_groups) ? strategyMemory.source_groups : Array.isArray(strategyMemory.sourceGroups) ? strategyMemory.sourceGroups : [];
7506
- const sourceGroups = rawSourceGroups.map((group) => {
7507
- const sourceGroup = asRecord4(group);
7508
- return {
7509
- id: sourceGroup.id ?? null,
7510
- required: sourceGroup.required ?? null,
7511
- ready: sourceGroup.ready ?? null,
7512
- counts: asRecord4(sourceGroup.counts)
7513
- };
7514
- });
7515
- return {
7516
- ready: strategyMemory.ready ?? null,
7517
- read_only: strategyMemory.read_only ?? strategyMemory.readOnly ?? null,
7518
- source_tool: campaignAgentString(strategyMemory.source_tool, strategyMemory.sourceTool),
7519
- strategy_template: {
7520
- slug: campaignAgentString(template, templateRecord.slug, templateRecord.id),
7521
- label: campaignAgentString(templateRecord.label)
7522
- },
7523
- coverage: {
7524
- required_groups_ready: coverage.required_groups_ready ?? coverage.requiredGroupsReady ?? null,
7525
- required_groups_total: coverage.required_groups_total ?? coverage.requiredGroupsTotal ?? null,
7526
- source_groups_ready: coverage.source_groups_ready ?? coverage.sourceGroupsReady ?? null,
7527
- source_groups_total: coverage.source_groups_total ?? coverage.sourceGroupsTotal ?? null,
7528
- knowledge_docs: coverage.knowledge_docs ?? coverage.knowledgeDocs ?? null,
7529
- memories: coverage.memories ?? null
7530
- },
7531
- source_groups: sourceGroups,
7532
- blocker_count: Array.isArray(strategyMemory.blockers) ? strategyMemory.blockers.length : 0,
7533
- warning_count: Array.isArray(strategyMemory.warnings) ? strategyMemory.warnings.length : 0
7534
- };
7535
- }
7536
- function summarizeCampaignAgentDryRun(dryRunResult) {
7537
- return {
7538
- dryRun: dryRunResult.dryRun ?? dryRunResult.dry_run ?? null,
7539
- status: dryRunResult.status ?? null,
7540
- currentPhase: dryRunResult.currentPhase ?? dryRunResult.current_phase ?? null,
7541
- campaignBuildId: dryRunResult.campaignBuildId ?? dryRunResult.campaign_build_id ?? null,
7542
- plannedTargetCount: dryRunResult.plannedTargetCount ?? dryRunResult.planned_target_count ?? null
7543
- };
7544
- }
7545
7758
  function printCampaignAgentProof(proof) {
7546
7759
  console.log(`Campaign-agent proof: ${proof.success ? "passed" : "failed"}`);
7547
7760
  const campaign = asRecord4(proof.campaign);
@@ -7576,6 +7789,7 @@ Usage:
7576
7789
  signaliz gtm memory search --query "proof-first vertical gate" --json
7577
7790
  signaliz gtm plan --brief "Build 500 verified leads for..." --strategy-template non-medical-home-care --lead-count 500 --json
7578
7791
  signaliz campaign-agent init --strategy-template non-medical-home-care --target-count 250 --use-local-leads --json > campaign-request.json
7792
+ signaliz campaign-agent doctor --request-file campaign-request.json --json
7579
7793
  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
7580
7794
  signaliz campaign-agent proof --goal "Build a strategy-template campaign..." --strategy-template non-medical-home-care --target-count 100 --json
7581
7795
  signaliz campaign-agent plan --request-file examples/campaign-builder-agent-request.json --target-count 250 --json
@@ -7612,6 +7826,7 @@ function printCampaignAgentHelp() {
7612
7826
  Signaliz campaign-agent commands:
7613
7827
 
7614
7828
  signaliz campaign-agent init [--goal TEXT] [--strategy-template ID] [--target-count N] [--use-local-leads] [--with-byo-placeholder] [--output-file FILE] [--json]
7829
+ 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]
7615
7830
  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]
7616
7831
  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]
7617
7832
  signaliz campaign-agent commit-plan (--goal TEXT | --request-file FILE) [--confirm-write --approved-by EMAIL] [--idempotency-key KEY] [--json]
@@ -7623,7 +7838,7 @@ Signaliz campaign-agent commands:
7623
7838
  signaliz campaign-agent artifacts <campaign_build_id> [--json]
7624
7839
  signaliz campaign-agent approve <campaign_build_id> --destination-type json|csv|webhook [--destination-config JSON] [--json]
7625
7840
 
7626
- 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 review to inspect status, sampled rows, artifacts, and delivery-readiness gates before approving delivery or routing rows to external tools.
7841
+ 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.
7627
7842
  `);
7628
7843
  }
7629
7844
  main().catch((e) => {