@signaliz/sdk 1.0.11 → 1.0.12

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/README.md CHANGED
@@ -100,12 +100,16 @@ console.log(plan.mcpFlow.filter((step) =>
100
100
  ));
101
101
 
102
102
  // After an approved launch, keep review and delivery in the agent surface.
103
+ const review = await signaliz.campaignBuilderAgent.reviewBuild('campaign_build_id', {
104
+ limit: 100,
105
+ });
103
106
  const finalStatus = await signaliz.campaignBuilderAgent.getBuildStatus('campaign_build_id');
104
107
  const reviewRows = await signaliz.campaignBuilderAgent.getBuildRows('campaign_build_id', {
105
108
  limit: 100,
106
109
  qualified: true,
107
110
  });
108
111
  const artifacts = await signaliz.campaignBuilderAgent.listBuildArtifacts('campaign_build_id');
112
+ console.log(review.summary.readyForDeliveryApproval);
109
113
  ```
110
114
 
111
115
  The CLI can run the same request shape from a reusable JSON file, with flags
@@ -125,6 +129,7 @@ npx @signaliz/sdk campaign-agent plan \
125
129
  --json
126
130
 
127
131
  npx @signaliz/sdk campaign-agent status campaign_build_id --json
132
+ npx @signaliz/sdk campaign-agent review campaign_build_id --limit 100 --json
128
133
  npx @signaliz/sdk campaign-agent rows campaign_build_id --limit 100 --json
129
134
  ```
130
135
 
@@ -1874,6 +1874,15 @@ var CampaignBuilderAgent = class {
1874
1874
  async approveDelivery(campaignBuildId, destinationType, options) {
1875
1875
  return this.approveCampaignDelivery(campaignBuildId, destinationType, options);
1876
1876
  }
1877
+ async reviewBuild(campaignBuildId, options = {}) {
1878
+ const status = await this.getBuildStatus(campaignBuildId);
1879
+ const rows = await this.getBuildRows(campaignBuildId, {
1880
+ ...options,
1881
+ limit: options.limit ?? 100
1882
+ });
1883
+ const artifacts = await this.listBuildArtifacts(campaignBuildId);
1884
+ return createCampaignBuilderBuildReview(status, rows, artifacts);
1885
+ }
1877
1886
  async getCampaignBuildStatus(campaignBuildId) {
1878
1887
  const data = await this.callMcp("get_campaign_build_status", {
1879
1888
  campaign_build_id: campaignBuildId
@@ -3301,6 +3310,120 @@ function mapAgentCampaignArtifact(data) {
3301
3310
  createdAt: stringValue(data.created_at) ?? ""
3302
3311
  };
3303
3312
  }
3313
+ function createCampaignBuilderBuildReview(status, rows, artifacts) {
3314
+ const sampledRows = rows.rows.length;
3315
+ const availableRows = rows.count || sampledRows;
3316
+ const qualifiedRows = rows.rows.filter((row) => row.qualified !== false).length;
3317
+ const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
3318
+ const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
3319
+ const artifactCount = artifacts.length || status.artifactCount || 0;
3320
+ const gates = [
3321
+ {
3322
+ id: "build_completed",
3323
+ label: "Build completed",
3324
+ passed: status.status === "completed",
3325
+ detail: status.status === "completed" ? "The campaign build is completed." : `The campaign build is ${status.status}; review again after completion.`
3326
+ },
3327
+ {
3328
+ id: "rows_available",
3329
+ label: "Rows available",
3330
+ passed: sampledRows > 0,
3331
+ detail: sampledRows > 0 ? `${sampledRows} sampled row(s) are available for review.` : "No rows were returned for review."
3332
+ },
3333
+ {
3334
+ id: "qualified_sample",
3335
+ label: "Sample rows qualified",
3336
+ passed: sampledRows > 0 && qualifiedRows === sampledRows,
3337
+ detail: sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
3338
+ },
3339
+ {
3340
+ id: "email_coverage",
3341
+ label: "Email coverage",
3342
+ passed: sampledRows > 0 && rowsWithEmail === sampledRows,
3343
+ detail: sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
3344
+ },
3345
+ {
3346
+ id: "artifact_available",
3347
+ label: "Artifact available",
3348
+ passed: artifactCount > 0,
3349
+ detail: artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
3350
+ },
3351
+ {
3352
+ id: "no_failed_rows",
3353
+ label: "No failed rows",
3354
+ passed: status.recordsFailed === 0,
3355
+ detail: status.recordsFailed === 0 ? "The build reports zero failed rows." : `${status.recordsFailed} row(s) failed during the build.`
3356
+ }
3357
+ ];
3358
+ const blockedReasons = gates.filter((gate) => !gate.passed).map((gate) => gate.detail);
3359
+ const warnings = [
3360
+ rows.hasMore ? "This review is based on a row sample; page through remaining rows before final approval." : void 0,
3361
+ sampledRows > 0 && copyReadyRows < sampledRows ? `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.` : void 0,
3362
+ ...status.warnings
3363
+ ].filter((item) => Boolean(item));
3364
+ const readyForDeliveryApproval = blockedReasons.length === 0;
3365
+ return {
3366
+ campaignBuildId: status.campaignBuildId || rows.campaignBuildId,
3367
+ status,
3368
+ rows,
3369
+ artifacts,
3370
+ gates,
3371
+ summary: {
3372
+ status: status.status,
3373
+ phase: status.currentPhase,
3374
+ sampledRows,
3375
+ availableRows,
3376
+ qualifiedRows,
3377
+ rowsWithEmail,
3378
+ copyReadyRows,
3379
+ artifactCount,
3380
+ readyForDeliveryApproval,
3381
+ blockedReasons,
3382
+ warnings
3383
+ },
3384
+ nextActions: campaignReviewNextActions(status, readyForDeliveryApproval)
3385
+ };
3386
+ }
3387
+ function campaignReviewNextActions(status, readyForDeliveryApproval) {
3388
+ const buildId = status.campaignBuildId;
3389
+ if (!buildId) return [];
3390
+ if (status.status === "completed" && readyForDeliveryApproval) {
3391
+ return [
3392
+ `signaliz campaign-agent approve ${buildId} --destination-type json`,
3393
+ `signaliz campaign-agent artifacts ${buildId}`
3394
+ ];
3395
+ }
3396
+ if (status.status === "completed") {
3397
+ return [
3398
+ `signaliz campaign-agent rows ${buildId} --limit 100`,
3399
+ `signaliz campaign-agent artifacts ${buildId}`
3400
+ ];
3401
+ }
3402
+ if (status.status === "pending_approval") {
3403
+ return [
3404
+ `signaliz campaign-agent rows ${buildId} --limit 100`,
3405
+ `signaliz campaign-agent approve ${buildId} --destination-type json`
3406
+ ];
3407
+ }
3408
+ if ((status.status === "queued" || status.status === "running") && status.triggerRunId) {
3409
+ return [`signaliz ops run-status ${status.triggerRunId} --watch`];
3410
+ }
3411
+ return [`signaliz campaign-agent status ${buildId}`];
3412
+ }
3413
+ function campaignReviewRowHasEmail(row) {
3414
+ const data = asRecord(row.data);
3415
+ return Boolean(
3416
+ stringValue(data.email) || stringValue(data.work_email) || stringValue(data.workEmail)
3417
+ );
3418
+ }
3419
+ function campaignReviewRowHasCopy(row) {
3420
+ const data = asRecord(row.data);
3421
+ const copy = asRecord(data.copy);
3422
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
3423
+ return row.status === "copy_ready" || data.copy_ready === true || Boolean(
3424
+ 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)
3425
+ );
3426
+ }
3304
3427
  function diagnosticMessages2(value) {
3305
3428
  if (!Array.isArray(value)) return [];
3306
3429
  return value.map((item) => {
package/dist/cli.js CHANGED
@@ -1880,6 +1880,15 @@ var CampaignBuilderAgent = class {
1880
1880
  async approveDelivery(campaignBuildId, destinationType, options) {
1881
1881
  return this.approveCampaignDelivery(campaignBuildId, destinationType, options);
1882
1882
  }
1883
+ async reviewBuild(campaignBuildId, options = {}) {
1884
+ const status = await this.getBuildStatus(campaignBuildId);
1885
+ const rows = await this.getBuildRows(campaignBuildId, {
1886
+ ...options,
1887
+ limit: options.limit ?? 100
1888
+ });
1889
+ const artifacts = await this.listBuildArtifacts(campaignBuildId);
1890
+ return createCampaignBuilderBuildReview(status, rows, artifacts);
1891
+ }
1883
1892
  async getCampaignBuildStatus(campaignBuildId) {
1884
1893
  const data = await this.callMcp("get_campaign_build_status", {
1885
1894
  campaign_build_id: campaignBuildId
@@ -3307,6 +3316,120 @@ function mapAgentCampaignArtifact(data) {
3307
3316
  createdAt: stringValue(data.created_at) ?? ""
3308
3317
  };
3309
3318
  }
3319
+ function createCampaignBuilderBuildReview(status, rows, artifacts) {
3320
+ const sampledRows = rows.rows.length;
3321
+ const availableRows = rows.count || sampledRows;
3322
+ const qualifiedRows = rows.rows.filter((row) => row.qualified !== false).length;
3323
+ const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
3324
+ const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
3325
+ const artifactCount = artifacts.length || status.artifactCount || 0;
3326
+ const gates = [
3327
+ {
3328
+ id: "build_completed",
3329
+ label: "Build completed",
3330
+ passed: status.status === "completed",
3331
+ detail: status.status === "completed" ? "The campaign build is completed." : `The campaign build is ${status.status}; review again after completion.`
3332
+ },
3333
+ {
3334
+ id: "rows_available",
3335
+ label: "Rows available",
3336
+ passed: sampledRows > 0,
3337
+ detail: sampledRows > 0 ? `${sampledRows} sampled row(s) are available for review.` : "No rows were returned for review."
3338
+ },
3339
+ {
3340
+ id: "qualified_sample",
3341
+ label: "Sample rows qualified",
3342
+ passed: sampledRows > 0 && qualifiedRows === sampledRows,
3343
+ detail: sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
3344
+ },
3345
+ {
3346
+ id: "email_coverage",
3347
+ label: "Email coverage",
3348
+ passed: sampledRows > 0 && rowsWithEmail === sampledRows,
3349
+ detail: sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
3350
+ },
3351
+ {
3352
+ id: "artifact_available",
3353
+ label: "Artifact available",
3354
+ passed: artifactCount > 0,
3355
+ detail: artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
3356
+ },
3357
+ {
3358
+ id: "no_failed_rows",
3359
+ label: "No failed rows",
3360
+ passed: status.recordsFailed === 0,
3361
+ detail: status.recordsFailed === 0 ? "The build reports zero failed rows." : `${status.recordsFailed} row(s) failed during the build.`
3362
+ }
3363
+ ];
3364
+ const blockedReasons = gates.filter((gate) => !gate.passed).map((gate) => gate.detail);
3365
+ const warnings = [
3366
+ rows.hasMore ? "This review is based on a row sample; page through remaining rows before final approval." : void 0,
3367
+ sampledRows > 0 && copyReadyRows < sampledRows ? `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.` : void 0,
3368
+ ...status.warnings
3369
+ ].filter((item) => Boolean(item));
3370
+ const readyForDeliveryApproval = blockedReasons.length === 0;
3371
+ return {
3372
+ campaignBuildId: status.campaignBuildId || rows.campaignBuildId,
3373
+ status,
3374
+ rows,
3375
+ artifacts,
3376
+ gates,
3377
+ summary: {
3378
+ status: status.status,
3379
+ phase: status.currentPhase,
3380
+ sampledRows,
3381
+ availableRows,
3382
+ qualifiedRows,
3383
+ rowsWithEmail,
3384
+ copyReadyRows,
3385
+ artifactCount,
3386
+ readyForDeliveryApproval,
3387
+ blockedReasons,
3388
+ warnings
3389
+ },
3390
+ nextActions: campaignReviewNextActions(status, readyForDeliveryApproval)
3391
+ };
3392
+ }
3393
+ function campaignReviewNextActions(status, readyForDeliveryApproval) {
3394
+ const buildId = status.campaignBuildId;
3395
+ if (!buildId) return [];
3396
+ if (status.status === "completed" && readyForDeliveryApproval) {
3397
+ return [
3398
+ `signaliz campaign-agent approve ${buildId} --destination-type json`,
3399
+ `signaliz campaign-agent artifacts ${buildId}`
3400
+ ];
3401
+ }
3402
+ if (status.status === "completed") {
3403
+ return [
3404
+ `signaliz campaign-agent rows ${buildId} --limit 100`,
3405
+ `signaliz campaign-agent artifacts ${buildId}`
3406
+ ];
3407
+ }
3408
+ if (status.status === "pending_approval") {
3409
+ return [
3410
+ `signaliz campaign-agent rows ${buildId} --limit 100`,
3411
+ `signaliz campaign-agent approve ${buildId} --destination-type json`
3412
+ ];
3413
+ }
3414
+ if ((status.status === "queued" || status.status === "running") && status.triggerRunId) {
3415
+ return [`signaliz ops run-status ${status.triggerRunId} --watch`];
3416
+ }
3417
+ return [`signaliz campaign-agent status ${buildId}`];
3418
+ }
3419
+ function campaignReviewRowHasEmail(row) {
3420
+ const data = asRecord(row.data);
3421
+ return Boolean(
3422
+ stringValue(data.email) || stringValue(data.work_email) || stringValue(data.workEmail)
3423
+ );
3424
+ }
3425
+ function campaignReviewRowHasCopy(row) {
3426
+ const data = asRecord(row.data);
3427
+ const copy = asRecord(data.copy);
3428
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
3429
+ return row.status === "copy_ready" || data.copy_ready === true || Boolean(
3430
+ 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
+ );
3432
+ }
3310
3433
  function diagnosticMessages2(value) {
3311
3434
  if (!Array.isArray(value)) return [];
3312
3435
  return value.map((item) => {
@@ -6680,7 +6803,7 @@ async function handleCampaignAgentCommand(signaliz, argv) {
6680
6803
  }
6681
6804
  return;
6682
6805
  }
6683
- if (["status", "rows", "artifacts", "approve", "approve-delivery"].includes(subcommand)) {
6806
+ if (["review", "status", "rows", "artifacts", "approve", "approve-delivery"].includes(subcommand)) {
6684
6807
  await handleCampaignAgentReviewCommand(signaliz, subcommand, argv.slice(1));
6685
6808
  return;
6686
6809
  }
@@ -6787,6 +6910,28 @@ async function handleCampaignAgentReviewCommand(signaliz, subcommand, argv) {
6787
6910
  const flags = parseFlags(argv);
6788
6911
  const buildId = argv[0] && !argv[0].startsWith("--") ? argv[0] : stringFlag(flags, "campaign-build-id", "build-id");
6789
6912
  if (!buildId) throw new Error(`campaign-agent ${subcommand} requires <campaign_build_id> or --campaign-build-id`);
6913
+ if (subcommand === "review") {
6914
+ const review = await signaliz.campaignBuilderAgent.reviewBuild(buildId, {
6915
+ limit: numberFlag(flags, "limit") ?? 100,
6916
+ cursor: stringFlag(flags, "cursor"),
6917
+ segment: stringFlag(flags, "segment"),
6918
+ rowStatus: stringFlag(flags, "row-status"),
6919
+ qualified: booleanFlag(flags, "qualified") ? true : booleanFlag(flags, "disqualified") ? false : void 0
6920
+ });
6921
+ const operatorReview = {
6922
+ ...review,
6923
+ rows: {
6924
+ ...review.rows,
6925
+ rows: formatCampaignAgentRows(review.rows.rows)
6926
+ }
6927
+ };
6928
+ if (booleanFlag(flags, "json")) {
6929
+ printJson(operatorReview);
6930
+ } else {
6931
+ printCampaignAgentBuildReview(operatorReview);
6932
+ }
6933
+ return;
6934
+ }
6790
6935
  if (subcommand === "status") {
6791
6936
  const status = await signaliz.campaignBuilderAgent.getBuildStatus(buildId);
6792
6937
  if (booleanFlag(flags, "json")) {
@@ -7201,6 +7346,34 @@ function printCampaignAgentBuildStatus(status) {
7201
7346
  console.log(`Next: signaliz campaign-agent approve ${status.campaignBuildId} --destination-type webhook`);
7202
7347
  }
7203
7348
  }
7349
+ function printCampaignAgentBuildReview(review) {
7350
+ const summary = asRecord4(review.summary);
7351
+ console.log(`Campaign: ${review.status?.name || review.campaignBuildId}`);
7352
+ console.log(`Status: ${summary.status || review.status?.status}`);
7353
+ console.log(`Phase: ${summary.phase || review.status?.currentPhase || "N/A"}`);
7354
+ console.log(`Rows: ${summary.sampledRows || 0} sampled, ${summary.availableRows || 0} available`);
7355
+ console.log(`Qualified: ${summary.qualifiedRows || 0}`);
7356
+ console.log(`Emails: ${summary.rowsWithEmail || 0}`);
7357
+ console.log(`Copy ready: ${summary.copyReadyRows || 0}`);
7358
+ console.log(`Artifacts: ${summary.artifactCount || 0}`);
7359
+ console.log(`Delivery approval ready: ${summary.readyForDeliveryApproval === true ? "yes" : "no"}`);
7360
+ if (Array.isArray(review.gates) && review.gates.length > 0) {
7361
+ console.log("\nGates:");
7362
+ for (const gate of review.gates) {
7363
+ console.log(`- ${gate.passed ? "PASS" : "BLOCKED"} ${gate.label}: ${gate.detail}`);
7364
+ }
7365
+ }
7366
+ const warnings = Array.isArray(summary.warnings) ? summary.warnings : [];
7367
+ if (warnings.length > 0) {
7368
+ console.log("\nWarnings:");
7369
+ for (const warning of warnings.slice(0, 5)) console.log(`- ${warning}`);
7370
+ }
7371
+ const nextActions = Array.isArray(review.nextActions) ? review.nextActions : [];
7372
+ if (nextActions.length > 0) {
7373
+ console.log("\nNext:");
7374
+ for (const action of nextActions) console.log(`- ${action}`);
7375
+ }
7376
+ }
7204
7377
  function formatCampaignAgentRows(rows) {
7205
7378
  return rows.map((row) => {
7206
7379
  const data = asRecord4(row.data);
@@ -7410,6 +7583,7 @@ Usage:
7410
7583
  signaliz campaign-agent dry-run --goal "Build a strategy-template campaign..." --target-count 500 --use-local-leads --json
7411
7584
  signaliz campaign-agent build-approved --goal "Build a strategy-template campaign..." --confirm-launch --approve-all --approved-by you@example.com --spend-limit 500
7412
7585
  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
7586
+ signaliz campaign-agent review <campaign_build_id> --limit 100 --json
7413
7587
  signaliz campaign-agent status <campaign_build_id> --json
7414
7588
  signaliz campaign-agent rows <campaign_build_id> --limit 100 --json
7415
7589
 
@@ -7443,12 +7617,13 @@ Signaliz campaign-agent commands:
7443
7617
  signaliz campaign-agent commit-plan (--goal TEXT | --request-file FILE) [--confirm-write --approved-by EMAIL] [--idempotency-key KEY] [--json]
7444
7618
  signaliz campaign-agent dry-run (--goal TEXT | --request-file FILE) [--gtm-campaign-id ID] [--delivery-risk JSON] [--idempotency-key KEY] [--json]
7445
7619
  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]
7620
+ signaliz campaign-agent review <campaign_build_id> [--limit N] [--qualified|--disqualified] [--json]
7446
7621
  signaliz campaign-agent status <campaign_build_id> [--json]
7447
7622
  signaliz campaign-agent rows <campaign_build_id> [--limit N] [--cursor CURSOR] [--qualified|--disqualified] [--json]
7448
7623
  signaliz campaign-agent artifacts <campaign_build_id> [--json]
7449
7624
  signaliz campaign-agent approve <campaign_build_id> --destination-type json|csv|webhook [--destination-config JSON] [--json]
7450
7625
 
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.
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.
7452
7627
  `);
7453
7628
  }
7454
7629
  main().catch((e) => {
package/dist/cli.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  Signaliz,
4
4
  createCampaignBuilderAgentRequestTemplate,
5
5
  createCampaignBuilderApproval
6
- } from "./chunk-QQW6DZQ4.mjs";
6
+ } from "./chunk-XJQ6KGNQ.mjs";
7
7
 
8
8
  // src/cli.ts
9
9
  import { readFileSync, writeFileSync } from "fs";
@@ -379,7 +379,7 @@ async function handleCampaignAgentCommand(signaliz, argv) {
379
379
  }
380
380
  return;
381
381
  }
382
- if (["status", "rows", "artifacts", "approve", "approve-delivery"].includes(subcommand)) {
382
+ if (["review", "status", "rows", "artifacts", "approve", "approve-delivery"].includes(subcommand)) {
383
383
  await handleCampaignAgentReviewCommand(signaliz, subcommand, argv.slice(1));
384
384
  return;
385
385
  }
@@ -486,6 +486,28 @@ async function handleCampaignAgentReviewCommand(signaliz, subcommand, argv) {
486
486
  const flags = parseFlags(argv);
487
487
  const buildId = argv[0] && !argv[0].startsWith("--") ? argv[0] : stringFlag(flags, "campaign-build-id", "build-id");
488
488
  if (!buildId) throw new Error(`campaign-agent ${subcommand} requires <campaign_build_id> or --campaign-build-id`);
489
+ if (subcommand === "review") {
490
+ const review = await signaliz.campaignBuilderAgent.reviewBuild(buildId, {
491
+ limit: numberFlag(flags, "limit") ?? 100,
492
+ cursor: stringFlag(flags, "cursor"),
493
+ segment: stringFlag(flags, "segment"),
494
+ rowStatus: stringFlag(flags, "row-status"),
495
+ qualified: booleanFlag(flags, "qualified") ? true : booleanFlag(flags, "disqualified") ? false : void 0
496
+ });
497
+ const operatorReview = {
498
+ ...review,
499
+ rows: {
500
+ ...review.rows,
501
+ rows: formatCampaignAgentRows(review.rows.rows)
502
+ }
503
+ };
504
+ if (booleanFlag(flags, "json")) {
505
+ printJson(operatorReview);
506
+ } else {
507
+ printCampaignAgentBuildReview(operatorReview);
508
+ }
509
+ return;
510
+ }
489
511
  if (subcommand === "status") {
490
512
  const status = await signaliz.campaignBuilderAgent.getBuildStatus(buildId);
491
513
  if (booleanFlag(flags, "json")) {
@@ -900,6 +922,34 @@ function printCampaignAgentBuildStatus(status) {
900
922
  console.log(`Next: signaliz campaign-agent approve ${status.campaignBuildId} --destination-type webhook`);
901
923
  }
902
924
  }
925
+ function printCampaignAgentBuildReview(review) {
926
+ const summary = asRecord(review.summary);
927
+ console.log(`Campaign: ${review.status?.name || review.campaignBuildId}`);
928
+ console.log(`Status: ${summary.status || review.status?.status}`);
929
+ console.log(`Phase: ${summary.phase || review.status?.currentPhase || "N/A"}`);
930
+ console.log(`Rows: ${summary.sampledRows || 0} sampled, ${summary.availableRows || 0} available`);
931
+ console.log(`Qualified: ${summary.qualifiedRows || 0}`);
932
+ console.log(`Emails: ${summary.rowsWithEmail || 0}`);
933
+ console.log(`Copy ready: ${summary.copyReadyRows || 0}`);
934
+ console.log(`Artifacts: ${summary.artifactCount || 0}`);
935
+ console.log(`Delivery approval ready: ${summary.readyForDeliveryApproval === true ? "yes" : "no"}`);
936
+ if (Array.isArray(review.gates) && review.gates.length > 0) {
937
+ console.log("\nGates:");
938
+ for (const gate of review.gates) {
939
+ console.log(`- ${gate.passed ? "PASS" : "BLOCKED"} ${gate.label}: ${gate.detail}`);
940
+ }
941
+ }
942
+ const warnings = Array.isArray(summary.warnings) ? summary.warnings : [];
943
+ if (warnings.length > 0) {
944
+ console.log("\nWarnings:");
945
+ for (const warning of warnings.slice(0, 5)) console.log(`- ${warning}`);
946
+ }
947
+ const nextActions = Array.isArray(review.nextActions) ? review.nextActions : [];
948
+ if (nextActions.length > 0) {
949
+ console.log("\nNext:");
950
+ for (const action of nextActions) console.log(`- ${action}`);
951
+ }
952
+ }
903
953
  function formatCampaignAgentRows(rows) {
904
954
  return rows.map((row) => {
905
955
  const data = asRecord(row.data);
@@ -1109,6 +1159,7 @@ Usage:
1109
1159
  signaliz campaign-agent dry-run --goal "Build a strategy-template campaign..." --target-count 500 --use-local-leads --json
1110
1160
  signaliz campaign-agent build-approved --goal "Build a strategy-template campaign..." --confirm-launch --approve-all --approved-by you@example.com --spend-limit 500
1111
1161
  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
1162
+ signaliz campaign-agent review <campaign_build_id> --limit 100 --json
1112
1163
  signaliz campaign-agent status <campaign_build_id> --json
1113
1164
  signaliz campaign-agent rows <campaign_build_id> --limit 100 --json
1114
1165
 
@@ -1142,12 +1193,13 @@ Signaliz campaign-agent commands:
1142
1193
  signaliz campaign-agent commit-plan (--goal TEXT | --request-file FILE) [--confirm-write --approved-by EMAIL] [--idempotency-key KEY] [--json]
1143
1194
  signaliz campaign-agent dry-run (--goal TEXT | --request-file FILE) [--gtm-campaign-id ID] [--delivery-risk JSON] [--idempotency-key KEY] [--json]
1144
1195
  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]
1196
+ signaliz campaign-agent review <campaign_build_id> [--limit N] [--qualified|--disqualified] [--json]
1145
1197
  signaliz campaign-agent status <campaign_build_id> [--json]
1146
1198
  signaliz campaign-agent rows <campaign_build_id> [--limit N] [--cursor CURSOR] [--qualified|--disqualified] [--json]
1147
1199
  signaliz campaign-agent artifacts <campaign_build_id> [--json]
1148
1200
  signaliz campaign-agent approve <campaign_build_id> --destination-type json|csv|webhook [--destination-config JSON] [--json]
1149
1201
 
1150
- 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.
1202
+ 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.
1151
1203
  `);
1152
1204
  }
1153
1205
  main().catch((e) => {
package/dist/index.d.mts CHANGED
@@ -1094,6 +1094,38 @@ interface CampaignBuilderApprovedRunResult {
1094
1094
  finalStatus?: CampaignBuildStatus;
1095
1095
  deliveryApproval?: ApproveDeliveryResult;
1096
1096
  }
1097
+ interface CampaignBuilderBuildReviewOptions extends CampaignRowsOptions {
1098
+ /** Number of rows to sample for review. Defaults to 100. */
1099
+ limit?: number;
1100
+ }
1101
+ interface CampaignBuilderBuildReviewGate {
1102
+ id: string;
1103
+ label: string;
1104
+ passed: boolean;
1105
+ detail: string;
1106
+ }
1107
+ interface CampaignBuilderBuildReviewSummary {
1108
+ status: string;
1109
+ phase: string | null;
1110
+ sampledRows: number;
1111
+ availableRows: number;
1112
+ qualifiedRows: number;
1113
+ rowsWithEmail: number;
1114
+ copyReadyRows: number;
1115
+ artifactCount: number;
1116
+ readyForDeliveryApproval: boolean;
1117
+ blockedReasons: string[];
1118
+ warnings: string[];
1119
+ }
1120
+ interface CampaignBuilderBuildReview {
1121
+ campaignBuildId: string;
1122
+ status: CampaignBuildStatus;
1123
+ rows: CampaignRowsResult;
1124
+ artifacts: CampaignArtifact[];
1125
+ gates: CampaignBuilderBuildReviewGate[];
1126
+ summary: CampaignBuilderBuildReviewSummary;
1127
+ nextActions: string[];
1128
+ }
1097
1129
  declare const DEFAULT_SIGNALIZ_CAMPAIGN_BUILDER_DEFAULTS: Required<Pick<SignalizCampaignBuilderDefaults, 'requireVerifiedEmail' | 'dedupKeys' | 'allowDownscale'>> & SignalizCampaignBuilderDefaults;
1098
1130
  declare const DEFAULT_CAMPAIGN_BUILDER_BUILT_INS: CampaignBuilderBuiltInTool[];
1099
1131
  declare const DEFAULT_CAMPAIGN_BUILDER_APPROVAL_TYPES: CampaignBuilderApprovalType[];
@@ -1114,6 +1146,7 @@ declare class CampaignBuilderAgent {
1114
1146
  destinationId?: string;
1115
1147
  destinationConfig?: UnknownRecord$1;
1116
1148
  }): Promise<ApproveDeliveryResult>;
1149
+ reviewBuild(campaignBuildId: string, options?: CampaignBuilderBuildReviewOptions): Promise<CampaignBuilderBuildReview>;
1117
1150
  private getCampaignBuildStatus;
1118
1151
  private waitForCampaignBuildStatus;
1119
1152
  private approveCampaignDelivery;
package/dist/index.d.ts CHANGED
@@ -1094,6 +1094,38 @@ interface CampaignBuilderApprovedRunResult {
1094
1094
  finalStatus?: CampaignBuildStatus;
1095
1095
  deliveryApproval?: ApproveDeliveryResult;
1096
1096
  }
1097
+ interface CampaignBuilderBuildReviewOptions extends CampaignRowsOptions {
1098
+ /** Number of rows to sample for review. Defaults to 100. */
1099
+ limit?: number;
1100
+ }
1101
+ interface CampaignBuilderBuildReviewGate {
1102
+ id: string;
1103
+ label: string;
1104
+ passed: boolean;
1105
+ detail: string;
1106
+ }
1107
+ interface CampaignBuilderBuildReviewSummary {
1108
+ status: string;
1109
+ phase: string | null;
1110
+ sampledRows: number;
1111
+ availableRows: number;
1112
+ qualifiedRows: number;
1113
+ rowsWithEmail: number;
1114
+ copyReadyRows: number;
1115
+ artifactCount: number;
1116
+ readyForDeliveryApproval: boolean;
1117
+ blockedReasons: string[];
1118
+ warnings: string[];
1119
+ }
1120
+ interface CampaignBuilderBuildReview {
1121
+ campaignBuildId: string;
1122
+ status: CampaignBuildStatus;
1123
+ rows: CampaignRowsResult;
1124
+ artifacts: CampaignArtifact[];
1125
+ gates: CampaignBuilderBuildReviewGate[];
1126
+ summary: CampaignBuilderBuildReviewSummary;
1127
+ nextActions: string[];
1128
+ }
1097
1129
  declare const DEFAULT_SIGNALIZ_CAMPAIGN_BUILDER_DEFAULTS: Required<Pick<SignalizCampaignBuilderDefaults, 'requireVerifiedEmail' | 'dedupKeys' | 'allowDownscale'>> & SignalizCampaignBuilderDefaults;
1098
1130
  declare const DEFAULT_CAMPAIGN_BUILDER_BUILT_INS: CampaignBuilderBuiltInTool[];
1099
1131
  declare const DEFAULT_CAMPAIGN_BUILDER_APPROVAL_TYPES: CampaignBuilderApprovalType[];
@@ -1114,6 +1146,7 @@ declare class CampaignBuilderAgent {
1114
1146
  destinationId?: string;
1115
1147
  destinationConfig?: UnknownRecord$1;
1116
1148
  }): Promise<ApproveDeliveryResult>;
1149
+ reviewBuild(campaignBuildId: string, options?: CampaignBuilderBuildReviewOptions): Promise<CampaignBuilderBuildReview>;
1117
1150
  private getCampaignBuildStatus;
1118
1151
  private waitForCampaignBuildStatus;
1119
1152
  private approveCampaignDelivery;
package/dist/index.js CHANGED
@@ -1923,6 +1923,15 @@ var CampaignBuilderAgent = class {
1923
1923
  async approveDelivery(campaignBuildId, destinationType, options) {
1924
1924
  return this.approveCampaignDelivery(campaignBuildId, destinationType, options);
1925
1925
  }
1926
+ async reviewBuild(campaignBuildId, options = {}) {
1927
+ const status = await this.getBuildStatus(campaignBuildId);
1928
+ const rows = await this.getBuildRows(campaignBuildId, {
1929
+ ...options,
1930
+ limit: options.limit ?? 100
1931
+ });
1932
+ const artifacts = await this.listBuildArtifacts(campaignBuildId);
1933
+ return createCampaignBuilderBuildReview(status, rows, artifacts);
1934
+ }
1926
1935
  async getCampaignBuildStatus(campaignBuildId) {
1927
1936
  const data = await this.callMcp("get_campaign_build_status", {
1928
1937
  campaign_build_id: campaignBuildId
@@ -3350,6 +3359,120 @@ function mapAgentCampaignArtifact(data) {
3350
3359
  createdAt: stringValue(data.created_at) ?? ""
3351
3360
  };
3352
3361
  }
3362
+ function createCampaignBuilderBuildReview(status, rows, artifacts) {
3363
+ const sampledRows = rows.rows.length;
3364
+ const availableRows = rows.count || sampledRows;
3365
+ const qualifiedRows = rows.rows.filter((row) => row.qualified !== false).length;
3366
+ const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
3367
+ const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
3368
+ const artifactCount = artifacts.length || status.artifactCount || 0;
3369
+ const gates = [
3370
+ {
3371
+ id: "build_completed",
3372
+ label: "Build completed",
3373
+ passed: status.status === "completed",
3374
+ detail: status.status === "completed" ? "The campaign build is completed." : `The campaign build is ${status.status}; review again after completion.`
3375
+ },
3376
+ {
3377
+ id: "rows_available",
3378
+ label: "Rows available",
3379
+ passed: sampledRows > 0,
3380
+ detail: sampledRows > 0 ? `${sampledRows} sampled row(s) are available for review.` : "No rows were returned for review."
3381
+ },
3382
+ {
3383
+ id: "qualified_sample",
3384
+ label: "Sample rows qualified",
3385
+ passed: sampledRows > 0 && qualifiedRows === sampledRows,
3386
+ detail: sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
3387
+ },
3388
+ {
3389
+ id: "email_coverage",
3390
+ label: "Email coverage",
3391
+ passed: sampledRows > 0 && rowsWithEmail === sampledRows,
3392
+ detail: sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
3393
+ },
3394
+ {
3395
+ id: "artifact_available",
3396
+ label: "Artifact available",
3397
+ passed: artifactCount > 0,
3398
+ detail: artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
3399
+ },
3400
+ {
3401
+ id: "no_failed_rows",
3402
+ label: "No failed rows",
3403
+ passed: status.recordsFailed === 0,
3404
+ detail: status.recordsFailed === 0 ? "The build reports zero failed rows." : `${status.recordsFailed} row(s) failed during the build.`
3405
+ }
3406
+ ];
3407
+ const blockedReasons = gates.filter((gate) => !gate.passed).map((gate) => gate.detail);
3408
+ const warnings = [
3409
+ rows.hasMore ? "This review is based on a row sample; page through remaining rows before final approval." : void 0,
3410
+ sampledRows > 0 && copyReadyRows < sampledRows ? `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.` : void 0,
3411
+ ...status.warnings
3412
+ ].filter((item) => Boolean(item));
3413
+ const readyForDeliveryApproval = blockedReasons.length === 0;
3414
+ return {
3415
+ campaignBuildId: status.campaignBuildId || rows.campaignBuildId,
3416
+ status,
3417
+ rows,
3418
+ artifacts,
3419
+ gates,
3420
+ summary: {
3421
+ status: status.status,
3422
+ phase: status.currentPhase,
3423
+ sampledRows,
3424
+ availableRows,
3425
+ qualifiedRows,
3426
+ rowsWithEmail,
3427
+ copyReadyRows,
3428
+ artifactCount,
3429
+ readyForDeliveryApproval,
3430
+ blockedReasons,
3431
+ warnings
3432
+ },
3433
+ nextActions: campaignReviewNextActions(status, readyForDeliveryApproval)
3434
+ };
3435
+ }
3436
+ function campaignReviewNextActions(status, readyForDeliveryApproval) {
3437
+ const buildId = status.campaignBuildId;
3438
+ if (!buildId) return [];
3439
+ if (status.status === "completed" && readyForDeliveryApproval) {
3440
+ return [
3441
+ `signaliz campaign-agent approve ${buildId} --destination-type json`,
3442
+ `signaliz campaign-agent artifacts ${buildId}`
3443
+ ];
3444
+ }
3445
+ if (status.status === "completed") {
3446
+ return [
3447
+ `signaliz campaign-agent rows ${buildId} --limit 100`,
3448
+ `signaliz campaign-agent artifacts ${buildId}`
3449
+ ];
3450
+ }
3451
+ if (status.status === "pending_approval") {
3452
+ return [
3453
+ `signaliz campaign-agent rows ${buildId} --limit 100`,
3454
+ `signaliz campaign-agent approve ${buildId} --destination-type json`
3455
+ ];
3456
+ }
3457
+ if ((status.status === "queued" || status.status === "running") && status.triggerRunId) {
3458
+ return [`signaliz ops run-status ${status.triggerRunId} --watch`];
3459
+ }
3460
+ return [`signaliz campaign-agent status ${buildId}`];
3461
+ }
3462
+ function campaignReviewRowHasEmail(row) {
3463
+ const data = asRecord(row.data);
3464
+ return Boolean(
3465
+ stringValue(data.email) || stringValue(data.work_email) || stringValue(data.workEmail)
3466
+ );
3467
+ }
3468
+ function campaignReviewRowHasCopy(row) {
3469
+ const data = asRecord(row.data);
3470
+ const copy = asRecord(data.copy);
3471
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
3472
+ return row.status === "copy_ready" || data.copy_ready === true || Boolean(
3473
+ 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)
3474
+ );
3475
+ }
3353
3476
  function diagnosticMessages2(value) {
3354
3477
  if (!Array.isArray(value)) return [];
3355
3478
  return value.map((item) => {
package/dist/index.mjs CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  getCampaignBuilderStrategyTemplate,
24
24
  getMissingCampaignBuilderApprovals,
25
25
  normalizeExecutionReference
26
- } from "./chunk-QQW6DZQ4.mjs";
26
+ } from "./chunk-XJQ6KGNQ.mjs";
27
27
  export {
28
28
  Ai,
29
29
  CAMPAIGN_BUILDER_OPERATING_PLAYBOOKS,
@@ -1897,6 +1897,15 @@ var CampaignBuilderAgent = class {
1897
1897
  async approveDelivery(campaignBuildId, destinationType, options) {
1898
1898
  return this.approveCampaignDelivery(campaignBuildId, destinationType, options);
1899
1899
  }
1900
+ async reviewBuild(campaignBuildId, options = {}) {
1901
+ const status = await this.getBuildStatus(campaignBuildId);
1902
+ const rows = await this.getBuildRows(campaignBuildId, {
1903
+ ...options,
1904
+ limit: options.limit ?? 100
1905
+ });
1906
+ const artifacts = await this.listBuildArtifacts(campaignBuildId);
1907
+ return createCampaignBuilderBuildReview(status, rows, artifacts);
1908
+ }
1900
1909
  async getCampaignBuildStatus(campaignBuildId) {
1901
1910
  const data = await this.callMcp("get_campaign_build_status", {
1902
1911
  campaign_build_id: campaignBuildId
@@ -3223,6 +3232,120 @@ function mapAgentCampaignArtifact(data) {
3223
3232
  createdAt: stringValue(data.created_at) ?? ""
3224
3233
  };
3225
3234
  }
3235
+ function createCampaignBuilderBuildReview(status, rows, artifacts) {
3236
+ const sampledRows = rows.rows.length;
3237
+ const availableRows = rows.count || sampledRows;
3238
+ const qualifiedRows = rows.rows.filter((row) => row.qualified !== false).length;
3239
+ const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
3240
+ const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
3241
+ const artifactCount = artifacts.length || status.artifactCount || 0;
3242
+ const gates = [
3243
+ {
3244
+ id: "build_completed",
3245
+ label: "Build completed",
3246
+ passed: status.status === "completed",
3247
+ detail: status.status === "completed" ? "The campaign build is completed." : `The campaign build is ${status.status}; review again after completion.`
3248
+ },
3249
+ {
3250
+ id: "rows_available",
3251
+ label: "Rows available",
3252
+ passed: sampledRows > 0,
3253
+ detail: sampledRows > 0 ? `${sampledRows} sampled row(s) are available for review.` : "No rows were returned for review."
3254
+ },
3255
+ {
3256
+ id: "qualified_sample",
3257
+ label: "Sample rows qualified",
3258
+ passed: sampledRows > 0 && qualifiedRows === sampledRows,
3259
+ detail: sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
3260
+ },
3261
+ {
3262
+ id: "email_coverage",
3263
+ label: "Email coverage",
3264
+ passed: sampledRows > 0 && rowsWithEmail === sampledRows,
3265
+ detail: sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
3266
+ },
3267
+ {
3268
+ id: "artifact_available",
3269
+ label: "Artifact available",
3270
+ passed: artifactCount > 0,
3271
+ detail: artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
3272
+ },
3273
+ {
3274
+ id: "no_failed_rows",
3275
+ label: "No failed rows",
3276
+ passed: status.recordsFailed === 0,
3277
+ detail: status.recordsFailed === 0 ? "The build reports zero failed rows." : `${status.recordsFailed} row(s) failed during the build.`
3278
+ }
3279
+ ];
3280
+ const blockedReasons = gates.filter((gate) => !gate.passed).map((gate) => gate.detail);
3281
+ const warnings = [
3282
+ rows.hasMore ? "This review is based on a row sample; page through remaining rows before final approval." : void 0,
3283
+ sampledRows > 0 && copyReadyRows < sampledRows ? `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.` : void 0,
3284
+ ...status.warnings
3285
+ ].filter((item) => Boolean(item));
3286
+ const readyForDeliveryApproval = blockedReasons.length === 0;
3287
+ return {
3288
+ campaignBuildId: status.campaignBuildId || rows.campaignBuildId,
3289
+ status,
3290
+ rows,
3291
+ artifacts,
3292
+ gates,
3293
+ summary: {
3294
+ status: status.status,
3295
+ phase: status.currentPhase,
3296
+ sampledRows,
3297
+ availableRows,
3298
+ qualifiedRows,
3299
+ rowsWithEmail,
3300
+ copyReadyRows,
3301
+ artifactCount,
3302
+ readyForDeliveryApproval,
3303
+ blockedReasons,
3304
+ warnings
3305
+ },
3306
+ nextActions: campaignReviewNextActions(status, readyForDeliveryApproval)
3307
+ };
3308
+ }
3309
+ function campaignReviewNextActions(status, readyForDeliveryApproval) {
3310
+ const buildId = status.campaignBuildId;
3311
+ if (!buildId) return [];
3312
+ if (status.status === "completed" && readyForDeliveryApproval) {
3313
+ return [
3314
+ `signaliz campaign-agent approve ${buildId} --destination-type json`,
3315
+ `signaliz campaign-agent artifacts ${buildId}`
3316
+ ];
3317
+ }
3318
+ if (status.status === "completed") {
3319
+ return [
3320
+ `signaliz campaign-agent rows ${buildId} --limit 100`,
3321
+ `signaliz campaign-agent artifacts ${buildId}`
3322
+ ];
3323
+ }
3324
+ if (status.status === "pending_approval") {
3325
+ return [
3326
+ `signaliz campaign-agent rows ${buildId} --limit 100`,
3327
+ `signaliz campaign-agent approve ${buildId} --destination-type json`
3328
+ ];
3329
+ }
3330
+ if ((status.status === "queued" || status.status === "running") && status.triggerRunId) {
3331
+ return [`signaliz ops run-status ${status.triggerRunId} --watch`];
3332
+ }
3333
+ return [`signaliz campaign-agent status ${buildId}`];
3334
+ }
3335
+ function campaignReviewRowHasEmail(row) {
3336
+ const data = asRecord(row.data);
3337
+ return Boolean(
3338
+ stringValue(data.email) || stringValue(data.work_email) || stringValue(data.workEmail)
3339
+ );
3340
+ }
3341
+ function campaignReviewRowHasCopy(row) {
3342
+ const data = asRecord(row.data);
3343
+ const copy = asRecord(data.copy);
3344
+ const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
3345
+ return row.status === "copy_ready" || data.copy_ready === true || Boolean(
3346
+ 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)
3347
+ );
3348
+ }
3226
3349
  function diagnosticMessages2(value) {
3227
3350
  if (!Array.isArray(value)) return [];
3228
3351
  return value.map((item) => {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  Signaliz
4
- } from "./chunk-QQW6DZQ4.mjs";
4
+ } from "./chunk-XJQ6KGNQ.mjs";
5
5
 
6
6
  // src/mcp-config.ts
7
7
  import * as fs from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaliz/sdk",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "Signaliz SDK — GTM Kernel, Nango routes, MCP, and enrichment for AI agents",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",