@signaliz/cli 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.
Files changed (2) hide show
  1. package/dist/bin.js +344 -5
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -1071,12 +1071,15 @@ Campaign Builder:
1071
1071
  Merge a private-safe strategy template
1072
1072
  --builtins a,b lead_generation,local_leads,email_finding,email_verification,signals
1073
1073
  --custom-tool ROUTE BYO route: provider:layer:tool[:mcp]
1074
+ campaign-agent doctor Inspect built-in lanes, BYO routes, Memory, Brain, and approvals
1074
1075
  campaign-agent proof Prove memory, plan, approvals, and build dry-run without spend
1075
1076
  campaign-agent commit-plan
1076
1077
  Dry-run or write a reviewed GTM campaign object
1077
1078
  campaign-agent dry-run Execute build_campaign with dry_run=true
1078
1079
  campaign-agent build-approved
1079
1080
  Launch only after explicit approval metadata
1081
+ campaign-agent review <id>
1082
+ Review rows, artifacts, and delivery gates
1080
1083
  campaign-agent status <id>
1081
1084
  Inspect a campaign-agent build
1082
1085
  campaign-agent rows <id>
@@ -2789,10 +2792,12 @@ var CAMPAIGN_AGENT_HELP = `signaliz campaign-agent <command> [options]
2789
2792
  Strategy-template campaign builder:
2790
2793
  init Emit reusable CampaignBuilderAgentRequest JSON
2791
2794
  plan Compose an approval-gated strategy-template MCP flow
2795
+ doctor Inspect built-in lanes, BYO routes, Memory, Brain, and approvals
2792
2796
  proof Prove memory, plan, approvals, and build dry-run without spend
2793
2797
  commit-plan Dry-run or write a reviewed GTM campaign object
2794
2798
  dry-run Execute build_campaign with dry_run=true
2795
2799
  build-approved Launch only after explicit approval metadata
2800
+ review Review status, rows, artifacts, and delivery gates
2796
2801
  status Inspect a campaign-agent build
2797
2802
  rows Retrieve reviewable campaign-agent rows
2798
2803
  artifacts List generated artifacts
@@ -2800,12 +2805,14 @@ Strategy-template campaign builder:
2800
2805
 
2801
2806
  Examples:
2802
2807
  signaliz campaign-agent init --strategy-template non-medical-home-care --target-count 250 --use-local-leads --json > campaign-request.json
2808
+ signaliz campaign-agent doctor --request-file campaign-request.json --json
2803
2809
  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
2804
2810
  signaliz campaign-agent proof --goal "Build a strategy-template campaign for my ICP" --strategy-template non-medical-home-care --target-count 100 --json
2805
2811
  signaliz campaign-agent plan --request-file campaign-builder-agent-request.json --target-count 250 --json
2806
2812
  signaliz campaign-agent dry-run --goal "Build a local services campaign" --target-count 250 --use-local-leads --json
2807
2813
  signaliz campaign-agent build-approved --goal "Build a strategy-template campaign" --confirm-launch --approve-all --approved-by operator@example.com --spend-limit 500
2808
2814
  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
2815
+ signaliz campaign-agent review <campaign_build_id> --limit 100 --json
2809
2816
  signaliz campaign-agent status <campaign_build_id> --json
2810
2817
  signaliz campaign-agent rows <campaign_build_id> --limit 100 --json
2811
2818
 
@@ -2860,11 +2867,11 @@ async function campaignAgent(sub, rest) {
2860
2867
  process.stdout.write(CAMPAIGN_AGENT_HELP);
2861
2868
  return;
2862
2869
  }
2863
- if (["status", "rows", "artifacts", "approve", "approve-delivery"].includes(sub)) {
2870
+ if (["review", "status", "rows", "artifacts", "approve", "approve-delivery"].includes(sub)) {
2864
2871
  await campaignAgentReview(sub, rest);
2865
2872
  return;
2866
2873
  }
2867
- if (!["init", "template", "request-template", "new", "plan", "proof", "smoke", "commit-plan", "dry-run", "build-approved", "launch-approved"].includes(sub)) {
2874
+ if (!["init", "template", "request-template", "new", "plan", "doctor", "readiness", "preflight", "proof", "smoke", "commit-plan", "dry-run", "build-approved", "launch-approved"].includes(sub)) {
2868
2875
  process.stdout.write(CAMPAIGN_AGENT_HELP);
2869
2876
  return;
2870
2877
  }
@@ -2882,7 +2889,8 @@ async function campaignAgent(sub, rest) {
2882
2889
  return;
2883
2890
  }
2884
2891
  const requestFromFile = campaignAgentRequestFileFromFlags(flags);
2885
- const goal = commandStringFlag(flags, "goal", "brief") || promptArg(rest) || campaignAgentStringValue(requestFromFile?.goal);
2892
+ const isDoctor = sub === "doctor" || sub === "readiness" || sub === "preflight";
2893
+ const goal = commandStringFlag(flags, "goal", "brief") || promptArg(rest) || campaignAgentStringValue(requestFromFile?.goal) || (isDoctor ? "Build a strategy-template campaign for the target ICP." : void 0);
2886
2894
  if (!goal) die(`campaign-agent ${sub} requires --goal or a request file with goal`);
2887
2895
  const isApprovedBuild = sub === "build-approved" || sub === "launch-approved";
2888
2896
  const isCommitPlan = sub === "commit-plan";
@@ -2901,13 +2909,19 @@ async function campaignAgent(sub, rest) {
2901
2909
  requestFromFile,
2902
2910
  campaignAgentRequestFromFlags(goal, flags, { includeDefaults: !requestFromFile })
2903
2911
  );
2904
- const plan = await agent.createPlan(request, {
2912
+ const planOptions = {
2905
2913
  discoverCapabilities: !commandBooleanFlag(flags, "skip-discovery"),
2906
2914
  scopeCampaign: !commandBooleanFlag(flags, "skip-scope"),
2907
2915
  includeStrategyMemoryStatus: !commandBooleanFlag(flags, "no-strategy-memory"),
2908
2916
  includeKernelPlan: !commandBooleanFlag(flags, "no-kernel-plan"),
2909
2917
  includeDeliveryRisk: !commandBooleanFlag(flags, "no-delivery-risk")
2910
- });
2918
+ };
2919
+ if (isDoctor) {
2920
+ const readiness = await agent.readiness?.(request, planOptions) || createCampaignAgentReadiness(await agent.createPlan(request, planOptions));
2921
+ if (!readiness.summary?.ready) process.exitCode = 1;
2922
+ return output(readiness, () => printCampaignAgentReadiness(readiness));
2923
+ }
2924
+ const plan = await agent.createPlan(request, planOptions);
2911
2925
  if (sub === "proof" || sub === "smoke") {
2912
2926
  const result = await agent.dryRunPlan(plan, {
2913
2927
  idempotencyKey: commandStringFlag(flags, "idempotency-key")
@@ -2974,6 +2988,24 @@ async function campaignAgentReview(sub, rest) {
2974
2988
  const sdk = getSdk();
2975
2989
  const agent = sdk.campaignBuilderAgent;
2976
2990
  const campaigns = sdk.campaigns || {};
2991
+ if (sub === "review") {
2992
+ const rowOptions = {
2993
+ limit: commandNumberFlag(flags, "limit") ?? 100,
2994
+ cursor: commandStringFlag(flags, "cursor"),
2995
+ segment: commandStringFlag(flags, "segment"),
2996
+ rowStatus: commandStringFlag(flags, "row-status"),
2997
+ qualified: commandBooleanFlag(flags, "qualified") ? true : commandBooleanFlag(flags, "disqualified") ? false : void 0
2998
+ };
2999
+ const review = await agent.reviewBuild?.(buildId, rowOptions) || await campaignAgentBuildReviewFromCampaignApis(agent, campaigns, buildId, rowOptions);
3000
+ const operatorReview = {
3001
+ ...review,
3002
+ rows: {
3003
+ ...review.rows,
3004
+ rows: formatCampaignRowsForOperator(review.rows?.rows || [])
3005
+ }
3006
+ };
3007
+ return output(operatorReview, () => printCampaignAgentBuildReview(operatorReview));
3008
+ }
2977
3009
  if (sub === "status") {
2978
3010
  const status = await (agent.getBuildStatus?.bind(agent) || campaigns.status?.bind(campaigns) || campaigns.getCampaignBuildStatus?.bind(campaigns))?.(buildId);
2979
3011
  if (!status) die("campaign-agent status requires a compatible @signaliz/sdk campaign status API");
@@ -3025,6 +3057,13 @@ async function campaignAgentReview(sub, rest) {
3025
3057
  if (result.message) console.log(result.message);
3026
3058
  });
3027
3059
  }
3060
+ async function campaignAgentBuildReviewFromCampaignApis(agent, campaigns, buildId, rowOptions) {
3061
+ const status = await (agent.getBuildStatus?.bind(agent) || campaigns.status?.bind(campaigns) || campaigns.getCampaignBuildStatus?.bind(campaigns))?.(buildId);
3062
+ const rows = await (agent.getBuildRows?.bind(agent) || campaigns.rows?.bind(campaigns) || campaigns.getCampaignBuildRows?.bind(campaigns))?.(buildId, rowOptions);
3063
+ const artifacts = await (agent.listBuildArtifacts?.bind(agent) || campaigns.artifacts?.bind(campaigns) || campaigns.listCampaignBuildArtifacts?.bind(campaigns))?.(buildId);
3064
+ if (!status || !rows || !artifacts) die("campaign-agent review requires compatible @signaliz/sdk build status, rows, and artifacts APIs");
3065
+ return createCampaignAgentBuildReview(status, rows, artifacts);
3066
+ }
3028
3067
  function campaignAgentRequestTemplateFromFlags(flags, rest) {
3029
3068
  const goal = commandStringFlag(flags, "goal", "brief") || promptArg(rest) || "Build a strategy-template campaign for mature operators in the target ICP.";
3030
3069
  const request = campaignAgentRequestFromFlags(goal, flags, { includeDefaults: true });
@@ -3305,6 +3344,172 @@ function createCampaignAgentApproval(plan, input) {
3305
3344
  approvedAt: (/* @__PURE__ */ new Date()).toISOString()
3306
3345
  };
3307
3346
  }
3347
+ function createCampaignAgentReadiness(plan) {
3348
+ const lanes = firstArray(plan?.routes).map((route) => {
3349
+ const builtIn = campaignAgentBuiltInForRoute(route);
3350
+ const customRoute = route?.provider !== "signaliz" && route?.provider !== "csv" && !builtIn;
3351
+ const prepareStep = firstArray(plan?.mcpFlow).find(
3352
+ (step) => step?.tool === "gtm_provider_recipe_prepare" && record(step?.arguments).provider_id === route?.provider
3353
+ );
3354
+ const activateStep = firstArray(plan?.mcpFlow).find(
3355
+ (step) => step?.tool === "gtm_provider_route_activate" && record(step?.arguments).provider_id === route?.provider
3356
+ );
3357
+ const activationArgs = record(activateStep?.arguments);
3358
+ const readOnlySetup = !customRoute || prepareStep?.readOnly === true && activateStep?.readOnly === true && activationArgs.dry_run === true && activationArgs.confirm === false;
3359
+ const blockers2 = [
3360
+ customRoute && !prepareStep ? `${route?.label || route?.provider} is missing provider recipe setup.` : void 0,
3361
+ customRoute && !activateStep ? `${route?.label || route?.provider} is missing provider route dry-run setup.` : void 0,
3362
+ customRoute && !readOnlySetup ? `${route?.label || route?.provider} route setup must stay read-only before approval.` : void 0,
3363
+ route?.mode === "required" && !route?.toolName && customRoute ? `${route?.label || route?.provider} is required but has no tool name.` : void 0
3364
+ ].filter(Boolean);
3365
+ return {
3366
+ id: route?.id || `${route?.provider}-${route?.layer}`,
3367
+ label: route?.label || campaignAgentReadinessLaneLabel(route, builtIn),
3368
+ layer: route?.layer,
3369
+ gtmLayers: firstArray(route?.gtmLayers).length ? route.gtmLayers : [route?.gtmLayer].filter(Boolean),
3370
+ provider: route?.provider,
3371
+ toolName: route?.toolName,
3372
+ mode: route?.mode || "if_available",
3373
+ builtIn,
3374
+ approvalRequired: route?.approvalRequired === true || firstArray(plan?.approvals).some((approval) => approval?.routeId === route?.id),
3375
+ customRoute,
3376
+ readOnlySetup,
3377
+ ready: blockers2.length === 0,
3378
+ blockers: blockers2,
3379
+ nextAction: customRoute ? `Review ${route?.provider} route setup before launch approval.` : void 0
3380
+ };
3381
+ });
3382
+ const builtInLanes = lanes.filter((lane) => lane.builtIn);
3383
+ const customRoutes = lanes.filter((lane) => lane.customRoute);
3384
+ const strategyMemory = record(plan?.strategyMemoryStatus);
3385
+ const deliveryRisk = record(record(plan?.buildRequest).deliveryRisk);
3386
+ const risk = record(deliveryRisk.risk);
3387
+ const riskLevel = String(risk.risk_level || risk.riskLevel || "").toLowerCase();
3388
+ const riskBlockers = firstArray(deliveryRisk.blockers);
3389
+ const gates = [
3390
+ {
3391
+ id: "campaign_plan_created",
3392
+ label: "Campaign plan created",
3393
+ passed: Boolean(plan?.planId && firstArray(plan?.mcpFlow).length),
3394
+ detail: plan?.planId ? `Plan ${plan.planId} has ${firstArray(plan?.mcpFlow).length} MCP step(s).` : "No campaign-builder plan was created."
3395
+ },
3396
+ {
3397
+ id: "built_in_lanes_ready",
3398
+ label: "Built-in lanes ready",
3399
+ passed: builtInLanes.length > 0 && builtInLanes.every((lane) => lane.ready),
3400
+ detail: `${builtInLanes.filter((lane) => lane.ready).length}/${builtInLanes.length} Signaliz built-in lane(s) are present.`
3401
+ },
3402
+ {
3403
+ id: "strategy_memory_ready",
3404
+ label: "Strategy memory ready",
3405
+ passed: !plan?.strategyMemoryStatus || strategyMemory.ready !== false,
3406
+ detail: plan?.strategyMemoryStatus ? "Strategy memory readiness was checked." : "Strategy memory readiness was not requested."
3407
+ },
3408
+ {
3409
+ id: "kernel_plan_attached",
3410
+ label: "Kernel plan attached",
3411
+ passed: Object.keys(record(plan?.kernelPlan)).length > 0,
3412
+ detail: Object.keys(record(plan?.kernelPlan)).length > 0 ? "Kernel planner evidence is attached." : "Kernel planner evidence is not attached."
3413
+ },
3414
+ {
3415
+ id: "delivery_risk_clear",
3416
+ label: "Delivery risk clear",
3417
+ passed: Object.keys(deliveryRisk).length === 0 || riskBlockers.length === 0 && !["high", "critical", "blocked"].includes(riskLevel),
3418
+ detail: Object.keys(deliveryRisk).length === 0 ? "Delivery risk preflight was not requested." : `Delivery risk is ${riskLevel || "available"} with ${riskBlockers.length} blocker(s).`
3419
+ },
3420
+ {
3421
+ id: "custom_routes_prepared",
3422
+ label: "Custom routes prepared",
3423
+ passed: customRoutes.every((lane) => lane.ready),
3424
+ 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.`
3425
+ },
3426
+ {
3427
+ id: "approval_gates_present",
3428
+ label: "Approval gates present",
3429
+ passed: firstArray(plan?.approvals).length > 0,
3430
+ detail: firstArray(plan?.approvals).length > 0 ? `${firstArray(plan?.approvals).length} approval gate(s) are attached.` : "No approval gates are attached."
3431
+ }
3432
+ ];
3433
+ const blockers = [
3434
+ ...gates.filter((gate) => !gate.passed).map((gate) => gate.detail),
3435
+ ...lanes.flatMap((lane) => lane.blockers || [])
3436
+ ];
3437
+ const ready = blockers.length === 0;
3438
+ return {
3439
+ plan,
3440
+ lanes,
3441
+ gates,
3442
+ summary: {
3443
+ ready,
3444
+ score: Math.round(gates.filter((gate) => gate.passed).length / gates.length * 100),
3445
+ targetCount: plan?.targetCount,
3446
+ estimatedCredits: plan?.estimatedCredits,
3447
+ builtInLanesReady: builtInLanes.filter((lane) => lane.ready).length,
3448
+ builtInLanesTotal: builtInLanes.length,
3449
+ customRoutesReady: customRoutes.filter((lane) => lane.ready).length,
3450
+ customRoutesTotal: customRoutes.length,
3451
+ approvalGateCount: firstArray(plan?.approvals).length,
3452
+ blockers,
3453
+ warnings: firstArray(plan?.warnings)
3454
+ },
3455
+ nextActions: ready ? [
3456
+ "signaliz campaign-agent proof --request-file campaign-request.json --json",
3457
+ "signaliz campaign-agent commit-plan --request-file campaign-request.json --json",
3458
+ `signaliz campaign-agent build-approved --request-file campaign-request.json --confirm-launch --approve-all --approved-by operator@example.com --spend-limit ${plan?.estimatedCredits || 0}`
3459
+ ] : ["signaliz campaign-agent plan --request-file campaign-request.json --json"]
3460
+ };
3461
+ }
3462
+ function campaignAgentBuiltInForRoute(route) {
3463
+ const map = {
3464
+ "signaliz-lead-generation": "lead_generation",
3465
+ "signaliz-local-leads": "local_leads",
3466
+ "signaliz-email-finding": "email_finding",
3467
+ "signaliz-email-verification": "email_verification",
3468
+ "signaliz-signals": "signals"
3469
+ };
3470
+ return map[String(route?.id || "")];
3471
+ }
3472
+ function campaignAgentReadinessLaneLabel(route, builtIn) {
3473
+ const labels = {
3474
+ lead_generation: "Signaliz lead generation",
3475
+ local_leads: "Signaliz local leads",
3476
+ email_finding: "Signaliz email finding",
3477
+ email_verification: "Signaliz email verification",
3478
+ signals: "Signaliz signals"
3479
+ };
3480
+ return builtIn ? labels[builtIn] : `${route?.provider || "custom"} ${route?.layer || "route"}`;
3481
+ }
3482
+ function printCampaignAgentReadiness(readiness) {
3483
+ const summary = record(readiness.summary);
3484
+ const plan = record(readiness.plan);
3485
+ console.log(`Readiness: ${summary.ready === true ? "ready" : "blocked"} (${summary.score || 0}%)`);
3486
+ if (plan.campaignName) console.log(`Campaign: ${plan.campaignName}`);
3487
+ if (summary.targetCount !== void 0) console.log(`Target: ${summary.targetCount}`);
3488
+ if (summary.estimatedCredits !== void 0) console.log(`Estimated credits: ${summary.estimatedCredits}`);
3489
+ console.log(`Built-ins: ${summary.builtInLanesReady || 0}/${summary.builtInLanesTotal || 0}`);
3490
+ console.log(`BYO routes: ${summary.customRoutesReady || 0}/${summary.customRoutesTotal || 0}`);
3491
+ console.log(`Approval gates: ${summary.approvalGateCount || 0}`);
3492
+ const gates = firstArray(readiness.gates);
3493
+ if (gates.length) {
3494
+ console.log("\nGates:");
3495
+ for (const gate of gates) console.log(`- ${gate.passed ? "PASS" : "BLOCKED"} ${gate.label}: ${gate.detail}`);
3496
+ }
3497
+ const lanes = firstArray(readiness.lanes);
3498
+ if (lanes.length) {
3499
+ console.log("\nLanes:");
3500
+ for (const lane of lanes) console.log(`- ${lane.ready ? "READY" : "BLOCKED"} ${lane.label}: ${lane.toolName || lane.provider}`);
3501
+ }
3502
+ const blockers = firstArray(summary.blockers);
3503
+ if (blockers.length) {
3504
+ console.log("\nBlockers:");
3505
+ for (const blocker of blockers.slice(0, 8)) console.log(`- ${blocker}`);
3506
+ }
3507
+ const nextActions = firstArray(readiness.nextActions);
3508
+ if (nextActions.length) {
3509
+ console.log("\nNext:");
3510
+ for (const action of nextActions) console.log(`- ${action}`);
3511
+ }
3512
+ }
3308
3513
  function printCampaignAgentPlan(plan) {
3309
3514
  console.log(`Plan: ${plan.campaignName || plan.goal || plan.planId}`);
3310
3515
  if (plan.planId) console.log(`Plan ID: ${plan.planId}`);
@@ -3357,6 +3562,140 @@ function printCampaignAgentBuildStatus(status) {
3357
3562
  hint(`signaliz ops run-status ${status.triggerRunId} --watch`);
3358
3563
  }
3359
3564
  }
3565
+ function printCampaignAgentBuildReview(review) {
3566
+ const summary = record(review.summary);
3567
+ const status = record(review.status);
3568
+ console.log(`Campaign: ${status.name || review.campaignBuildId}`);
3569
+ console.log(`Status: ${summary.status || status.status}`);
3570
+ console.log(`Phase: ${summary.phase || status.currentPhase || "N/A"}`);
3571
+ console.log(`Rows: ${summary.sampledRows || 0} sampled, ${summary.availableRows || 0} available`);
3572
+ console.log(`Qualified: ${summary.qualifiedRows || 0}`);
3573
+ console.log(`Emails: ${summary.rowsWithEmail || 0}`);
3574
+ console.log(`Copy ready: ${summary.copyReadyRows || 0}`);
3575
+ console.log(`Artifacts: ${summary.artifactCount || 0}`);
3576
+ console.log(`Delivery approval ready: ${summary.readyForDeliveryApproval === true ? "yes" : "no"}`);
3577
+ const gates = Array.isArray(review.gates) ? review.gates : [];
3578
+ if (gates.length) {
3579
+ console.log("\nGates:");
3580
+ for (const gate of gates) {
3581
+ console.log(`- ${gate.passed ? "PASS" : "BLOCKED"} ${gate.label}: ${gate.detail}`);
3582
+ }
3583
+ }
3584
+ const warnings = Array.isArray(summary.warnings) ? summary.warnings : [];
3585
+ if (warnings.length) {
3586
+ console.log("\nWarnings:");
3587
+ for (const warning of warnings.slice(0, 5)) console.log(`- ${warning}`);
3588
+ }
3589
+ const nextActions = Array.isArray(review.nextActions) ? review.nextActions : [];
3590
+ if (nextActions.length) {
3591
+ console.log("\nNext:");
3592
+ for (const action of nextActions) console.log(`- ${action}`);
3593
+ }
3594
+ }
3595
+ function createCampaignAgentBuildReview(status, rows, artifacts) {
3596
+ const rowList = Array.isArray(rows?.rows) ? rows.rows : [];
3597
+ const sampledRows = rowList.length;
3598
+ const availableRows = Number(rows?.count || sampledRows);
3599
+ const qualifiedRows = rowList.filter((row) => row?.qualified !== false).length;
3600
+ const operatorRows = formatCampaignRowsForOperator(rowList);
3601
+ const rowsWithEmail = operatorRows.filter((row) => Boolean(row.email)).length;
3602
+ const copyReadyRows = operatorRows.filter((row) => Boolean(row.has_copy || row.status === "copy_ready")).length;
3603
+ const artifactCount = artifacts.length || Number(status?.artifactCount || status?.artifact_count || 0);
3604
+ const buildStatus = String(status?.status || "unknown");
3605
+ const gates = [
3606
+ {
3607
+ id: "build_completed",
3608
+ label: "Build completed",
3609
+ passed: buildStatus === "completed",
3610
+ detail: buildStatus === "completed" ? "The campaign build is completed." : `The campaign build is ${buildStatus}; review again after completion.`
3611
+ },
3612
+ {
3613
+ id: "rows_available",
3614
+ label: "Rows available",
3615
+ passed: sampledRows > 0,
3616
+ detail: sampledRows > 0 ? `${sampledRows} sampled row(s) are available for review.` : "No rows were returned for review."
3617
+ },
3618
+ {
3619
+ id: "qualified_sample",
3620
+ label: "Sample rows qualified",
3621
+ passed: sampledRows > 0 && qualifiedRows === sampledRows,
3622
+ detail: sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
3623
+ },
3624
+ {
3625
+ id: "email_coverage",
3626
+ label: "Email coverage",
3627
+ passed: sampledRows > 0 && rowsWithEmail === sampledRows,
3628
+ detail: sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
3629
+ },
3630
+ {
3631
+ id: "artifact_available",
3632
+ label: "Artifact available",
3633
+ passed: artifactCount > 0,
3634
+ detail: artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
3635
+ },
3636
+ {
3637
+ id: "no_failed_rows",
3638
+ label: "No failed rows",
3639
+ passed: Number(status?.recordsFailed || status?.records_failed || 0) === 0,
3640
+ detail: Number(status?.recordsFailed || status?.records_failed || 0) === 0 ? "The build reports zero failed rows." : `${status.recordsFailed || status.records_failed} row(s) failed during the build.`
3641
+ }
3642
+ ];
3643
+ const blockedReasons = gates.filter((gate) => !gate.passed).map((gate) => gate.detail);
3644
+ const warnings = [
3645
+ rows?.hasMore || rows?.has_more ? "This review is based on a row sample; page through remaining rows before final approval." : void 0,
3646
+ sampledRows > 0 && copyReadyRows < sampledRows ? `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.` : void 0,
3647
+ ...Array.isArray(status?.warnings) ? status.warnings : []
3648
+ ].filter(Boolean);
3649
+ const readyForDeliveryApproval = blockedReasons.length === 0;
3650
+ return {
3651
+ campaignBuildId: status.campaignBuildId || status.campaign_build_id || rows.campaignBuildId || rows.campaign_build_id || "",
3652
+ status,
3653
+ rows,
3654
+ artifacts,
3655
+ gates,
3656
+ summary: {
3657
+ status: buildStatus,
3658
+ phase: status.currentPhase || status.current_phase || null,
3659
+ sampledRows,
3660
+ availableRows,
3661
+ qualifiedRows,
3662
+ rowsWithEmail,
3663
+ copyReadyRows,
3664
+ artifactCount,
3665
+ readyForDeliveryApproval,
3666
+ blockedReasons,
3667
+ warnings
3668
+ },
3669
+ nextActions: campaignAgentReviewNextActions(status, readyForDeliveryApproval)
3670
+ };
3671
+ }
3672
+ function campaignAgentReviewNextActions(status, readyForDeliveryApproval) {
3673
+ const buildId = status.campaignBuildId || status.campaign_build_id;
3674
+ const buildStatus = String(status.status || "unknown");
3675
+ if (!buildId) return [];
3676
+ if (buildStatus === "completed" && readyForDeliveryApproval) {
3677
+ return [
3678
+ `signaliz campaign-agent approve ${buildId} --destination-type json`,
3679
+ `signaliz campaign-agent artifacts ${buildId}`
3680
+ ];
3681
+ }
3682
+ if (buildStatus === "completed") {
3683
+ return [
3684
+ `signaliz campaign-agent rows ${buildId} --limit 100`,
3685
+ `signaliz campaign-agent artifacts ${buildId}`
3686
+ ];
3687
+ }
3688
+ if (buildStatus === "pending_approval") {
3689
+ return [
3690
+ `signaliz campaign-agent rows ${buildId} --limit 100`,
3691
+ `signaliz campaign-agent approve ${buildId} --destination-type json`
3692
+ ];
3693
+ }
3694
+ if ((buildStatus === "queued" || buildStatus === "running") && status.triggerRunId) {
3695
+ return [`signaliz ops run-status ${status.triggerRunId} --watch`];
3696
+ }
3697
+ return [`signaliz campaign-agent status ${buildId}`];
3698
+ }
3360
3699
  function printCampaignAgentRows(result) {
3361
3700
  if (!Array.isArray(result.rows) || result.rows.length === 0) {
3362
3701
  console.log("No rows found.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaliz/cli",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "Signaliz CLI — GTM Kernel, Nango routes, MCP, and enrichment operations.",
5
5
  "bin": {
6
6
  "signaliz": "dist/bin.js"