fullstackgtm 0.50.0 → 0.50.1

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/CHANGELOG.md CHANGED
@@ -7,6 +7,30 @@ The path to 1.0 is planned in [docs/roadmap-to-1.0.md](https://github.com/fullst
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.50.1] — 2026-07-10
11
+
12
+ ### Added
13
+
14
+ - Decision-first CLI presentation across plan previews, plan queues, apply
15
+ outcomes, doctor, schedules, signals, ICP, market, TAM, and call analysis.
16
+ Human defaults use compact cards and ranked summaries; `--verbose` restores
17
+ full audit detail and `--json` retains the stable machine contract.
18
+
19
+ ### Fixed
20
+
21
+ - HubSpot 429/5xx responses now retry with bounded backoff, honor
22
+ `Retry-After`, and report accurate live throttle feedback.
23
+ - Multi-line progress boards no longer scroll intermediate paint frames into
24
+ terminal history.
25
+ - `plans sync` reconciles historical workspaces concurrently with visible
26
+ progress, skips absent legacy mirrors, and avoids echoing hosted-origin
27
+ receipts back to hosted.
28
+ - CLI apply reports only the exact approved receipt subset to hosted while
29
+ retaining excluded rows in the complete local audit trail.
30
+ - Plan and apply output now distinguish approved, applied, excluded, skipped,
31
+ and failed operations without exposing the immutable document's stale
32
+ creation-time status as the current lifecycle state.
33
+
10
34
  ## [0.50.0] — 2026-07-10
11
35
 
12
36
  ### Added
package/README.md CHANGED
@@ -243,6 +243,13 @@ replica learns what happened rather than inferring it from plan status. A
243
243
  conflicting plan body or approval revision is surfaced for review and is never
244
244
  merged by expanding authority.
245
245
 
246
+ Interactive command output is decision-first by default: compact cards show
247
+ status, selected scope, expected effect, per-record outcomes, hosted links, and
248
+ the next safe command. Use `--verbose` on supported human-facing commands for
249
+ the complete findings/evidence/payload document; use `--json` for the stable
250
+ machine-readable contract. Progress and warnings remain on stderr, while JSON
251
+ and requested report data remain clean on stdout.
252
+
246
253
  Local signing keys and HMAC digests never upload. Operation before/after values
247
254
  do upload to the paired organization because they are required for informed
248
255
  review and hosted execution.
package/dist/cli/audit.js CHANGED
@@ -11,6 +11,7 @@ import { auditReportToHtml, auditReportToMarkdown } from "../report.js";
11
11
  import { reportCounts, reportCrm, reportFindings } from "../runReport.js";
12
12
  import { connectorFor, numericOption, option, readSnapshot, saveRequested, selectedRules } from "./shared.js";
13
13
  import { colorEnabled, createChecklist, formatCount, paint, scoreColor, sparkline, stylizePlanMarkdown, table } from "./ui.js";
14
+ import { compactPlan, verbosePlanRequested } from "./planOutput.js";
14
15
  const SEVERITY_RANK = {
15
16
  info: 0,
16
17
  warning: 1,
@@ -241,11 +242,15 @@ export async function audit(args) {
241
242
  if (args.includes("--json")) {
242
243
  console.log(JSON.stringify(plan, null, 2));
243
244
  }
245
+ else if (!verbosePlanRequested(args)) {
246
+ console.log(compactPlan(plan, { saved: saveRequested(args) }));
247
+ console.error(`\n${auditNextStep(args, plan)}`);
248
+ }
244
249
  else {
245
250
  // Default to the summary view (rule table + counts); the full per-operation
246
251
  // dump is opt-in via --full or, for a deliverable, `report`. (dx-punch-list #3)
247
252
  // Interactive terminals get the styled rendering; piped output is unchanged.
248
- console.log(stylizePlanMarkdown(patchPlanToMarkdown(plan, { summary: !args.includes("--full") }), paint(colorEnabled(process.stdout))));
253
+ console.log(stylizePlanMarkdown(patchPlanToMarkdown(plan, { summary: false }), paint(colorEnabled(process.stdout))));
249
254
  console.error(`\n${auditNextStep(args, plan)}`);
250
255
  }
251
256
  if (threshold &&
package/dist/cli/auth.js CHANGED
@@ -579,6 +579,31 @@ export async function doctorCommand(args) {
579
579
  const healthLine = workspace.auditCount === 0
580
580
  ? p.dim("no saved audits yet (fullstackgtm audit --provider <name> --save starts the timeline)")
581
581
  : `${scoreColor(workspace.healthScore ?? 0, p)}/100${delta} — ${workspace.auditCount} audit(s) saved, last ${workspace.lastAuditAt}${healthSparkline(workspace.profile, p.enabled)}`;
582
+ const connectedProviders = Object.entries(report.providers).filter(([, provider]) => provider.source !== "none");
583
+ const blockers = [
584
+ ...(!report.node.ok ? [`Node v${report.node.version} is unsupported; install ${report.node.required}.`] : []),
585
+ ...(connectedProviders.length === 0 ? ["No CRM connected."] : []),
586
+ ...(workspace.pendingPlans.length > 0 ? [`${workspace.pendingPlans.length} plan${workspace.pendingPlans.length === 1 ? "" : "s"} awaiting approval.`] : []),
587
+ ];
588
+ if (!args.includes("--verbose")) {
589
+ const ready = report.node.ok && connectedProviders.length > 0;
590
+ const compact = [
591
+ `${ready ? p.green("Ready") : p.yellow("Setup needed")} · ${report.package.name} ${report.package.version} · profile ${report.profile}`,
592
+ `CRM ${connectedProviders.length > 0 ? connectedProviders.map(([name]) => name).join(", ") : "not connected"}`,
593
+ `Health ${healthLine}`,
594
+ `Plans ${workspace.pendingPlans.length === 0 ? "none awaiting approval" : `${workspace.pendingPlans.length} awaiting approval · ${workspace.pendingPlans[0].id}`}`,
595
+ ...(blockers.length > 0 ? ["", "Attention", ...blockers.map((blocker) => ` ${blocker}`)] : []),
596
+ "",
597
+ "Next",
598
+ ...nextSteps.map((step) => ` ${step}`),
599
+ "",
600
+ "Run `fullstackgtm doctor --verbose` for credential paths and optional tooling checks.",
601
+ ];
602
+ console.log(p.enabled ? box(compact, p, "Workspace readiness").join("\n") : compact.join("\n"));
603
+ if (!report.node.ok)
604
+ process.exitCode = 1;
605
+ return;
606
+ }
582
607
  const lines = [
583
608
  `Package: ${p.bold(`${report.package.name} ${report.package.version}`)}`,
584
609
  `Node: v${report.node.version} (${report.node.required} required) ${mark(report.node.ok)}`,
@@ -14,6 +14,7 @@ import { progressReporter } from "../runReport.js";
14
14
  import { unknownSubcommandError } from "./suggest.js";
15
15
  import { option, readSnapshot, saveRequested } from "./shared.js";
16
16
  import { colorEnabled, createProgressRenderer, paint, stylizePlanMarkdown, table } from "./ui.js";
17
+ import { compactPlan, verbosePlanRequested } from "./planOutput.js";
17
18
  /** STRIPE_SECRET_KEY env ∨ stored `login stripe` credential (same ladder as `--provider stripe`). */
18
19
  function resolveStripeKey() {
19
20
  const key = process.env.STRIPE_SECRET_KEY ?? getCredential("stripe")?.accessToken;
@@ -73,6 +74,9 @@ export async function backfillCommand(args) {
73
74
  if (rest.includes("--json")) {
74
75
  console.log(JSON.stringify({ plan: result.plan, counts: result.counts, unmatched: result.unmatched, proposedAccounts: result.proposedAccounts }, null, 2));
75
76
  }
77
+ else if (!verbosePlanRequested(rest)) {
78
+ console.log(compactPlan(result.plan, { saved: save && result.plan.operations.length > 0 }));
79
+ }
76
80
  else {
77
81
  // TTY-only styling; piped output stays byte-identical plain text.
78
82
  console.log(stylizePlanMarkdown(patchPlanToMarkdown(result.plan), paint(colorEnabled(process.stdout))));
package/dist/cli/call.js CHANGED
@@ -161,11 +161,13 @@ score always needs a key (scoring is LLM work).`);
161
161
  console.log(JSON.stringify(parsed, null, 2));
162
162
  return;
163
163
  }
164
- console.log(`Call ${parsed.id}${parsed.title ? ` — ${parsed.title}` : ""} (${parsed.sourceSystem})`);
165
- console.log(`${parsed.segments.length} segments · ${parsed.insights.length} insights (${parsed.summary.highImportance} high-importance)\n`);
166
- for (const insight of parsed.insights) {
167
- console.log(`[${insight.type}] (importance ${insight.importance}) ${insight.text}`);
168
- }
164
+ console.log(`Call ${parsed.id}${parsed.title ? ` — ${parsed.title}` : ""}`);
165
+ console.log(`${parsed.segments.length} segments · ${parsed.insights.length} insights · ${parsed.summary.highImportance} high priority`);
166
+ const visible = rest.includes("--verbose") ? parsed.insights : parsed.insights.filter((insight) => insight.importance >= 3);
167
+ for (const insight of visible)
168
+ console.log(`\n${insight.type.replaceAll("_", " ")} · priority ${insight.importance}\n ${insight.text.replace(/\s+/g, " ").trim()}`);
169
+ if (!rest.includes("--verbose") && visible.length < parsed.insights.length)
170
+ console.log(`\n${parsed.insights.length - visible.length} lower-priority insight(s) hidden; use --verbose to show all.`);
169
171
  return;
170
172
  }
171
173
  if (subcommand === "link") {
@@ -284,11 +286,31 @@ score always needs a key (scoring is LLM work).`);
284
286
  console.log(JSON.stringify(scorecard, null, 2));
285
287
  return;
286
288
  }
287
- console.log(renderScorecard(scorecard, title));
289
+ console.log(rest.includes("--verbose") ? renderScorecard(scorecard, title) : renderCompactScorecard(scorecard, title));
288
290
  return;
289
291
  }
290
292
  throw new Error(`call supports: parse, classify, link, plan, score (got ${subcommand ?? "nothing"})`);
291
293
  }
294
+ function renderCompactScorecard(scorecard, title) {
295
+ const lines = [
296
+ `${title ?? "Call"} · ${scorecard.overallScore}/${scorecard.scale}${scorecard.band ? ` · ${scorecard.band.label}` : ""}`,
297
+ scorecard.rubricName ? `${scorecard.rubricName}${scorecard.callType ? ` (${scorecard.callType})` : ""}` : "",
298
+ "",
299
+ ].filter((line, index) => line || index === 2);
300
+ for (const dimension of scorecard.dimensions) {
301
+ lines.push(`${dimension.score}/${dimension.maxScore} ${dimension.name}`);
302
+ lines.push(` ${dimension.coachingNote.replace(/\s+/g, " ").trim()}`);
303
+ }
304
+ if (scorecard.missedItems.length) {
305
+ lines.push("", "Focus next");
306
+ for (const item of scorecard.missedItems)
307
+ lines.push(` ${item}`);
308
+ }
309
+ if (scorecard.highlights.length)
310
+ lines.push("", `Strengths: ${scorecard.highlights.join(" · ")}`);
311
+ lines.push("", "Use --verbose for the full coaching scorecard.");
312
+ return lines.join("\n");
313
+ }
292
314
  function renderScorecard(scorecard, title) {
293
315
  const bandText = scorecard.band ? ` — ${scorecard.band.label}` : "";
294
316
  const rubricLine = scorecard.rubricName ? `Rubric: ${scorecard.rubricName}${scorecard.callType ? ` (${scorecard.callType})` : ""}` : "";
package/dist/cli/draft.js CHANGED
@@ -7,6 +7,7 @@ import { createFileSignalStore } from "../signals.js";
7
7
  import { createFileJudgeStore } from "../judge.js";
8
8
  import { authorOpeners, DEFAULT_DRAFT_PROMPT, DRAFT_CHANNELS, draft } from "../draft.js";
9
9
  import { numericOption, option, resolveLlmBaseUrls, saveRequested } from "./shared.js";
10
+ import { compactPlan, verbosePlanRequested } from "./planOutput.js";
10
11
  /**
11
12
  * `draft` — author ONE trigger-grounded opener per hot judge decision as a
12
13
  * governed create_task plan. Structurally a proposal: never sends, never writes
@@ -68,7 +69,16 @@ LLM key it emits a clearly-labeled stub rather than fake authored copy.`);
68
69
  channel,
69
70
  openers,
70
71
  });
71
- console.log(JSON.stringify({ drafts, rejected }, null, 2));
72
+ if (args.includes("--json")) {
73
+ console.log(JSON.stringify({ drafts, rejected }, null, 2));
74
+ }
75
+ else if (verbosePlanRequested(args)) {
76
+ console.log(JSON.stringify({ drafts, rejected }, null, 2));
77
+ console.log(compactPlan(plan, { saved: save }));
78
+ }
79
+ else {
80
+ console.log(compactPlan(plan, { saved: save }));
81
+ }
72
82
  const stale = drafts.filter((d) => d.staleTrigger);
73
83
  console.error(`${drafts.length} opener(s) staged as create_task proposals (channel ${channel}, min score ${minScore})` +
74
84
  `${rejected.length ? `; ${rejected.length} rejected (ungrounded first line)` : ""}` +
@@ -22,6 +22,7 @@ import { isOptionValue, loadIcp, numericOption, option, readSnapshot, saveReques
22
22
  import { providerKey } from "./tam.js";
23
23
  import { unknownSubcommandError } from "./suggest.js";
24
24
  import { colorEnabled, createProgressRenderer, createStatusLine, formatBar, formatDuration, paint } from "./ui.js";
25
+ import { compactPlan, verbosePlanRequested } from "./planOutput.js";
25
26
  /**
26
27
  * The enrich layer: governed append/refresh of third-party data (Apollo pull,
27
28
  * Clay ingest) into the CRM through the normal dry-run → approval → apply
@@ -267,7 +268,7 @@ are phase 2. Recurring execution is the scheduler's job; enrich has no cron.`);
267
268
  console.log(JSON.stringify({ plan: result.plan, counts: result.counts, estCostUsd: result.estCostUsd, meter: headroom }, null, 2));
268
269
  }
269
270
  else {
270
- console.log(patchPlanToMarkdown(result.plan));
271
+ console.log(verbosePlanRequested(rest) ? patchPlanToMarkdown(result.plan) : compactPlan(result.plan));
271
272
  console.log(meterLine);
272
273
  if (gaugeLine)
273
274
  console.log(gaugeLine);
@@ -460,7 +461,7 @@ are phase 2. Recurring execution is the scheduler's job; enrich has no cron.`);
460
461
  console.log(JSON.stringify(result.plan, null, 2));
461
462
  }
462
463
  else {
463
- console.log(patchPlanToMarkdown(result.plan));
464
+ console.log(verbosePlanRequested(rest) ? patchPlanToMarkdown(result.plan) : compactPlan(result.plan));
464
465
  console.log(formatEnrichCounts(result.counts, result.ambiguities.length));
465
466
  console.log("\nDry run — nothing written. Re-run with --save to persist the plan and the run record.");
466
467
  }
package/dist/cli/fix.js CHANGED
@@ -20,6 +20,7 @@ import { APPLY_STAGES, composeListeners, createProgressEmitter } from "../progre
20
20
  import { progressReporter } from "../runReport.js";
21
21
  import { confirmRequested, connectorFor, isOptionValue, numericOption, option, readSnapshot, repeatedOption, saveRequested } from "./shared.js";
22
22
  import { box, colorEnabled, createProgressRenderer, paint } from "./ui.js";
23
+ import { compactPlan, verbosePlanRequested } from "./planOutput.js";
23
24
  /**
24
25
  * The resolve gate: exit 0 = safe to create, exit 2 = match found (exists or
25
26
  * ambiguous — do NOT blind-create), exit 1 = error. Built for sync jobs and
@@ -101,9 +102,12 @@ async function emitPlan(plan, args) {
101
102
  if (args.includes("--json")) {
102
103
  console.log(JSON.stringify(plan, null, 2));
103
104
  }
104
- else {
105
+ else if (verbosePlanRequested(args)) {
105
106
  console.log(patchPlanToMarkdown(plan));
106
107
  }
108
+ else {
109
+ console.log(compactPlan(plan, { saved: saveRequested(args) }));
110
+ }
107
111
  }
108
112
  /**
109
113
  * Governed duplicate cleanup: group by a normalized identity key, propose one
@@ -170,8 +174,7 @@ export async function reassignCommand(args) {
170
174
  for (const plan of plans) {
171
175
  if (store)
172
176
  await store.save(plan);
173
- console.log(`${plan.id} ${String(plan.operations.length).padStart(3)} operation(s) ${plan.title}`);
174
- console.log(` ${plan.summary}`);
177
+ console.log(verbosePlanRequested(args) ? patchPlanToMarkdown(plan) : compactPlan(plan, { saved: Boolean(store) }));
175
178
  }
176
179
  if (store) {
177
180
  console.log(`\nSaved ${plans.length} plan(s). For each: \`fullstackgtm plans show <id>\`, \`fullstackgtm plans approve <id> --operations <ids|all>\`, then \`fullstackgtm apply --plan-id <id> --provider <name>\`.`);
package/dist/cli/help.js CHANGED
@@ -176,11 +176,11 @@ Usage:
176
176
  fullstackgtm suggest --plan-id <id> | --plan <path> [source options] [--json] [--out <path>]
177
177
  derive values for requires_human_* placeholders
178
178
  from snapshot evidence, with confidence + reasons
179
- fullstackgtm plans list [--status <s>] | show <id> | sync | reject <id>
179
+ fullstackgtm plans list [--status <s>] | show <id> [--verbose] | sync | reject <id>
180
180
  fullstackgtm plans approve <id> --operations <ids|all> [--value <opId>=<v>]
181
181
  fullstackgtm plans approve <id> --values-from <suggestions.json> [--min-confidence high|low] [--include-creates]
182
182
  fullstackgtm plans recover <id> --acknowledge-uncertain-writes
183
- fullstackgtm apply --plan-id <id> --provider <name>
183
+ fullstackgtm apply --plan-id <id> --provider <name> [--verbose|--json]
184
184
  fullstackgtm apply --plan-id <id> --channel outbox (render approved openers; transmits nothing)
185
185
  fullstackgtm apply --plan <path> --provider <name> --approve <ids|all> [options]
186
186
  fullstackgtm audit-log export [--out <path>] | verify --in <path> tamper-evident apply-run record
@@ -502,7 +502,7 @@ export const HELP = {
502
502
  summary: "replicated plan lifecycle: list / show / sync / approve / reject",
503
503
  phase: "Govern",
504
504
  synopsis: [
505
- "fullstackgtm plans list [--status <s>] | show <id> | sync | reject <id>",
505
+ "fullstackgtm plans list [--status <s>] | show <id> [--verbose] | sync | reject <id>",
506
506
  "fullstackgtm plans approve <id> --operations <ids|all> [--value <opId>=<v>]",
507
507
  "fullstackgtm plans approve <id> --values-from <suggestions.json> [--min-confidence high|low]",
508
508
  "fullstackgtm plans recover <id> --acknowledge-uncertain-writes",
@@ -514,11 +514,11 @@ export const HELP = {
514
514
  summary: "write ONLY explicitly approved operations to a provider",
515
515
  phase: "Govern / Verify",
516
516
  synopsis: [
517
- "fullstackgtm apply --plan-id <id> --provider <name>",
517
+ "fullstackgtm apply --plan-id <id> --provider <name> [--verbose|--json]",
518
518
  "fullstackgtm apply --plan-id <id> --channel outbox (render approved drafted openers to the outbox; transmits nothing)",
519
519
  "fullstackgtm apply --plan <path> --provider <name> --approve <ids|all> [--value <opId>=<v>]",
520
520
  ],
521
- detail: "The CLI CRM-write verb. Writes only operations approved locally or imported from the shared hosted replica, with an online execution claim when available, stable operation ids, compare-and-set, resolve-before-create, and readback. Hosted may execute the same synchronized plan when it has a compatible CRM connection; its exact operation receipts are imported on the next CLI check-in. Never writes requires_human_* placeholders without a --value override.",
521
+ detail: "The CLI CRM-write verb. Writes only operations approved locally or imported from the shared hosted replica, with an online execution claim when available, stable operation ids, compare-and-set, resolve-before-create, and readback. Default output is a compact outcome card; --verbose prints the full run document and --json preserves structured output. Hosted may execute the same synchronized plan when it has a compatible CRM connection; its exact operation receipts are imported on the next CLI check-in. Never writes requires_human_* placeholders without a --value override.",
522
522
  seeAlso: ["plans", "suggest", "audit-log"],
523
523
  },
524
524
  "audit-log": {
@@ -597,7 +597,7 @@ export const BESPOKE_HELP = ["init", "call", "market", "tam", "enrich", "schedul
597
597
  export const GLOBAL_FLAGS = ["--help", "--full"];
598
598
  export const GLOBAL_SHORT_FLAGS = ["-h"];
599
599
  export const SOURCE_FLAGS = ["--provider", "--token-env", "--input", "--demo", "--sample", "--seed", "--today"];
600
- export const AUDIT_FLAGS = ["--config", "--allow-plugins", "--no-plugins", "--rules", "--stale-days", "--fail-on", "--save", "--dry-run", "--json", "--out", "--full"];
600
+ export const AUDIT_FLAGS = ["--config", "--allow-plugins", "--no-plugins", "--rules", "--stale-days", "--fail-on", "--save", "--dry-run", "--json", "--out", "--full", "--verbose"];
601
601
  // Complete per-command flag registry used by runCli's fail-closed flag
602
602
  // validation. Keep this next to HELP so focused help, machine capabilities,
603
603
  // and the parser safety gate have one command inventory to reconcile against.
@@ -605,7 +605,7 @@ export const COMMAND_FLAGS = {
605
605
  init: ["--source", "--provider", "--out", "--force"],
606
606
  login: ["--via", "--hosted", "--private-token", "--token", "--key", "--api-key", "--client-id", "--client-secret", "--scopes", "--port", "--oauth", "--device", "--instance-url", "--login-url", "--no-validate"],
607
607
  logout: [],
608
- doctor: ["--json"],
608
+ doctor: ["--verbose", "--json"],
609
609
  capabilities: ["--json"],
610
610
  "robot-docs": [],
611
611
  profiles: ["--json"],
@@ -619,24 +619,24 @@ export const COMMAND_FLAGS = {
619
619
  hierarchy: [...SOURCE_FLAGS, "--json", "--out"],
620
620
  relationships: [...SOURCE_FLAGS, "--account-id", "--domain", "--json", "--out"],
621
621
  route: [...SOURCE_FLAGS, "--match", "--no-inherit-owner", "--reassign-owned", "--policy", "--reason", "--max-operations", "--save", "--dry-run", "--json", "--out"],
622
- fix: [...SOURCE_FLAGS, "--config", "--allow-plugins", "--no-plugins", "--rule", "--provider", "--min-confidence", "--include-creates", "--yes", "--confirm", "--dry-run"],
623
- "bulk-update": [...SOURCE_FLAGS, "--where", "--set", "--guard", "--require", "--create-task", "--archive", "--force-archive-duplicates", "--reason", "--max-operations", "--save", "--dry-run", "--json", "--out"],
624
- dedupe: [...SOURCE_FLAGS, "--key", "--keep", "--reason", "--max-operations", "--save", "--dry-run", "--json", "--out"],
625
- reassign: [...SOURCE_FLAGS, "--from", "--assign-unowned", "--to", "--objects", "--where", "--except-deal-stage", "--include-closed-deals", "--reason", "--max-operations", "--save", "--dry-run", "--json", "--out"],
626
- backfill: [...SOURCE_FLAGS, "--since", "--pipeline", "--match-property", "--skip-unmatched", "--save", "--dry-run", "--json"],
627
- enrich: [...SOURCE_FLAGS, "--config", "--source", "--icp", "--input", "--provider", "--list", "--stale-days", "--assign-owner", "--objects", "--max", "--scan-limit", "--staged-run", "--run-label", "--label", "--runs", "--save", "--dry-run", "--json", "--out"],
628
- call: [...SOURCE_FLAGS, "--transcript", "--title", "--source", "--model", "--deterministic", "--heuristics", "--llm", "--json", "--ndjson", "--out", "--call", "--call-type", "--rubric", "--list", "--list-rubrics", "--attendees", "--domain", "--deal", "--save", "--dry-run"],
622
+ fix: [...SOURCE_FLAGS, "--config", "--allow-plugins", "--no-plugins", "--rule", "--provider", "--min-confidence", "--include-creates", "--yes", "--confirm", "--dry-run", "--verbose"],
623
+ "bulk-update": [...SOURCE_FLAGS, "--where", "--set", "--guard", "--require", "--create-task", "--archive", "--force-archive-duplicates", "--reason", "--max-operations", "--save", "--dry-run", "--verbose", "--json", "--out"],
624
+ dedupe: [...SOURCE_FLAGS, "--key", "--keep", "--reason", "--max-operations", "--save", "--dry-run", "--verbose", "--json", "--out"],
625
+ reassign: [...SOURCE_FLAGS, "--from", "--assign-unowned", "--to", "--objects", "--where", "--except-deal-stage", "--include-closed-deals", "--reason", "--max-operations", "--save", "--dry-run", "--verbose", "--json", "--out"],
626
+ backfill: [...SOURCE_FLAGS, "--since", "--pipeline", "--match-property", "--skip-unmatched", "--save", "--dry-run", "--verbose", "--json"],
627
+ enrich: [...SOURCE_FLAGS, "--config", "--source", "--icp", "--input", "--provider", "--list", "--stale-days", "--assign-owner", "--objects", "--max", "--scan-limit", "--staged-run", "--run-label", "--label", "--runs", "--save", "--dry-run", "--verbose", "--json", "--out"],
628
+ call: [...SOURCE_FLAGS, "--transcript", "--title", "--source", "--model", "--deterministic", "--heuristics", "--llm", "--verbose", "--json", "--ndjson", "--out", "--call", "--call-type", "--rubric", "--list", "--list-rubrics", "--attendees", "--domain", "--deal", "--save", "--dry-run"],
629
629
  suggest: [...SOURCE_FLAGS, "--plan-id", "--plan", "--json", "--out"],
630
- plans: ["--status", "--operations", "--value", "--values-from", "--min-confidence", "--include-creates", "--acknowledge-uncertain-writes", "--json"],
631
- apply: ["--plan", "--plan-id", "--provider", "--channel", "--token-env", "--approve", "--value", "--json", "--config"],
630
+ plans: ["--status", "--operations", "--value", "--values-from", "--min-confidence", "--include-creates", "--acknowledge-uncertain-writes", "--verbose", "--json"],
631
+ apply: ["--plan", "--plan-id", "--provider", "--channel", "--token-env", "--approve", "--value", "--verbose", "--json", "--config"],
632
632
  "audit-log": ["--in", "--out", "--json"],
633
633
  merge: ["--input", "--out", "--json"],
634
- market: ["--category", "--config", "--vendor", "--snapshot", "--task-account", "--task-deal", "--capture-run", "--run", "--prior-run", "--diff", "--from", "--calls", "--anchor", "--min-mentions", "--max-claims", "--promote-lift", "--model", "--format", "--auto", "--unverified", "--save", "--dry-run", "--json", "--out"],
635
- tam: [...SOURCE_FLAGS, "--source", "--name", "--icp", "--accounts", "--acv", "--acv-basis", "--acv-from-crm", "--deal-period", "--buyers-per-account", "--cross-checks", "--max", "--max-credits", "--usd-per-credit", "--dry-run", "--confirm", "--cron", "--label", "--save", "--json", "--out"],
636
- icp: [...SOURCE_FLAGS, "--name", "--signals-from", "--with-history", "--prompt", "--min-score", "--from-judge", "--golden", "--against-outcomes", "--min-accuracy", "--model", "--label", "--save", "--json", "--out"],
637
- signals: ["--bucket", "--source", "--connector", "--connector-opt", "--watchlist", "--keywords", "--from", "--label", "--since", "--account", "--contact", "--unjudged", "--touch", "--result", "--save", "--json", "--explain"],
638
- draft: ["--from-judge", "--min-score", "--prompt", "--model", "--channel", "--save", "--dry-run", "--json", "--out"],
639
- schedule: ["--cron", "--label", "--provider", "--trigger", "--timer", "--runs", "--json"],
634
+ market: ["--category", "--config", "--vendor", "--snapshot", "--task-account", "--task-deal", "--capture-run", "--run", "--prior-run", "--diff", "--from", "--calls", "--anchor", "--min-mentions", "--max-claims", "--promote-lift", "--model", "--format", "--auto", "--unverified", "--save", "--dry-run", "--verbose", "--json", "--out"],
635
+ tam: [...SOURCE_FLAGS, "--source", "--name", "--icp", "--accounts", "--acv", "--acv-basis", "--acv-from-crm", "--deal-period", "--buyers-per-account", "--cross-checks", "--max", "--max-credits", "--usd-per-credit", "--dry-run", "--confirm", "--cron", "--label", "--save", "--verbose", "--json", "--out"],
636
+ icp: [...SOURCE_FLAGS, "--name", "--signals-from", "--with-history", "--prompt", "--min-score", "--from-judge", "--golden", "--against-outcomes", "--min-accuracy", "--model", "--label", "--save", "--verbose", "--json", "--out"],
637
+ signals: ["--bucket", "--source", "--connector", "--connector-opt", "--watchlist", "--keywords", "--from", "--label", "--since", "--account", "--contact", "--unjudged", "--touch", "--result", "--save", "--verbose", "--json", "--explain"],
638
+ draft: ["--from-judge", "--min-score", "--prompt", "--model", "--channel", "--save", "--dry-run", "--verbose", "--json", "--out"],
639
+ schedule: ["--cron", "--label", "--provider", "--trigger", "--timer", "--runs", "--verbose", "--json"],
640
640
  };
641
641
  export const FLAGS_WITH_VALUES = new Set([
642
642
  "--account", "--account-id", "--accounts", "--acv", "--acv-basis", "--after", "--anchor", "--api-key", "--approve", "--archive", "--assign-owner", "--attendees", "--before", "--bucket", "--buyers-per-account", "--call", "--call-type", "--calls", "--capture-run", "--category", "--channel", "--client", "--client-id", "--client-secret", "--config", "--connector", "--connector-opt", "--contact", "--cron", "--cross-checks", "--deal", "--deal-period", "--diff", "--domain", "--email", "--except-deal-stage", "--format", "--from", "--from-judge", "--golden", "--guard", "--icp", "--in", "--input", "--instance-url", "--keep", "--key", "--keywords", "--label", "--list", "--login-url", "--match", "--match-property", "--max", "--max-claims", "--max-credits", "--max-examples", "--max-operations", "--min-accuracy", "--min-confidence", "--min-mentions", "--min-score", "--model", "--name", "--objects", "--operations", "--out", "--pipeline", "--plan", "--plan-id", "--policy", "--port", "--prepared-by", "--prior-run", "--profile", "--promote-lift", "--prompt", "--provider", "--reason", "--require", "--result", "--rubric", "--rule", "--rules", "--run", "--run-label", "--runs", "--scan-limit", "--scopes", "--seed", "--set", "--signals-from", "--since", "--snapshot", "--source", "--staged-run", "--stale-days", "--status", "--task-account", "--task-deal", "--timer", "--title", "--to", "--today", "--token", "--token-env", "--touch", "--transcript", "--trigger", "--usd-per-credit", "--value", "--values-from", "--vendor", "--via", "--watchlist", "--where",
package/dist/cli/icp.js CHANGED
@@ -9,6 +9,20 @@ import { DEFAULT_GOLDEN_NOW_ISO, DEFAULT_GOLDEN_SET, DEFAULT_MIN_ACCURACY, defau
9
9
  import { loadIcp, numericOption, option, readSnapshot, resolveLlmBaseUrls, saveRequested } from "./shared.js";
10
10
  import { createStatusLine } from "./ui.js";
11
11
  import { unknownSubcommandError } from "./suggest.js";
12
+ function renderJudgeDecisions(decisions) {
13
+ if (decisions.length === 0)
14
+ return "No accounts cleared the score threshold.";
15
+ const lines = [`ICP decisions (${decisions.length})`, ""];
16
+ for (const decision of decisions) {
17
+ const target = decision.contact?.email || decision.contact?.title;
18
+ lines.push(`${decision.decision.toUpperCase().padEnd(7)} ${String(decision.score).padStart(3)} ${decision.accountDomain}${target ? ` · ${target}` : ""}`);
19
+ if (decision.whyNow)
20
+ lines.push(` ${decision.whyNow.replace(/\s+/g, " ").trim()}`);
21
+ if (decision.play)
22
+ lines.push(` Next: ${decision.play.replace(/\s+/g, " ").trim()}`);
23
+ }
24
+ return lines.join("\n");
25
+ }
12
26
  /**
13
27
  * `icp` — develop and inspect the Ideal Customer Profile that targets acquire.
14
28
  * The CLI can't run AskUserQuestion itself; `icp interview` emits the question
@@ -133,7 +147,7 @@ ops. Develop one by interview, then \`enrich acquire\` picks up ./icp.json.
133
147
  }
134
148
  decisions = decisions.filter((d) => d.score >= minScore);
135
149
  // 5) Ranked decisions to stdout (JSON), guidance to stderr.
136
- console.log(JSON.stringify(decisions, null, 2));
150
+ console.log(rest.includes("--json") || rest.includes("--verbose") ? JSON.stringify(decisions, null, 2) : renderJudgeDecisions(decisions));
137
151
  console.error(`Judged ${unjudged.length} signal(s) across ${new Set(decisions.map((d) => d.accountDomain)).size} account(s): ` +
138
152
  `${decisions.filter((d) => d.decision === "send").length} send, ` +
139
153
  `${decisions.filter((d) => d.decision === "nurture").length} nurture, ` +
@@ -481,9 +481,22 @@ Rules:
481
481
  writeFileSync(resolve(process.cwd(), outPath), output);
482
482
  console.log(`Wrote ${outPath}`);
483
483
  }
484
- else {
484
+ else if (rest.includes("--verbose") || option(rest, "--format")) {
485
485
  console.log(output);
486
486
  }
487
+ else {
488
+ const fronts = computeFrontStates(config, set);
489
+ const counts = new Map();
490
+ for (const front of fronts)
491
+ counts.set(front.state, (counts.get(front.state) ?? 0) + 1);
492
+ console.log(`${config.category} market · ${set.runLabel}`);
493
+ console.log(`${fronts.length} claim fronts · ${["open", "contested", "owned", "saturated", "vacant"].filter((state) => counts.has(state)).map((state) => `${counts.get(state)} ${state}`).join(" · ")}`);
494
+ for (const front of fronts) {
495
+ const owner = front.loudVendorIds.length ? ` · ${front.loudVendorIds.join(", ")}` : "";
496
+ console.log(`\n${front.state.toUpperCase()} ${front.claimId}${owner}`);
497
+ }
498
+ console.log("\nUse --verbose for the full evidence report, or --out <path> to write it.");
499
+ }
487
500
  return;
488
501
  }
489
502
  if (subcommand === "axes") {
@@ -0,0 +1,5 @@
1
+ import type { PatchPlan } from "../types.ts";
2
+ export declare function compactPlan(plan: PatchPlan, options?: {
3
+ saved?: boolean;
4
+ }): string;
5
+ export declare function verbosePlanRequested(args: readonly string[]): boolean;
@@ -0,0 +1,27 @@
1
+ import { box, colorEnabled, paint, truncateToWidth } from "./ui.js";
2
+ export function compactPlan(plan, options = {}) {
3
+ const counts = new Map();
4
+ for (const operation of plan.operations) {
5
+ const action = operation.operation || operation.field || "change";
6
+ counts.set(action, (counts.get(action) ?? 0) + 1);
7
+ }
8
+ const mix = [...counts]
9
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
10
+ .slice(0, 4)
11
+ .map(([action, count]) => `${count} ${action}`)
12
+ .join(" · ");
13
+ const lines = [
14
+ truncateToWidth(plan.title, 100),
15
+ `Status ${options.saved ? "SAVED · NEEDS APPROVAL" : "DRY RUN · NOTHING WRITTEN"}`,
16
+ truncateToWidth(`Scope ${plan.operations.length} operation${plan.operations.length === 1 ? "" : "s"}${mix ? ` · ${mix}` : ""}`, 100),
17
+ truncateToWidth(`Effect ${plan.summary}`, 100),
18
+ options.saved ? `Next fullstackgtm plans show ${plan.id}` : "Next re-run with --save to stage this plan for approval",
19
+ ];
20
+ return [
21
+ ...box(lines, paint(colorEnabled(process.stdout)), "Plan preview"),
22
+ `Details: re-run with --verbose${options.saved ? `; JSON: fullstackgtm plans show ${plan.id} --json` : "; machine output: --json"}`,
23
+ ].join("\n");
24
+ }
25
+ export function verbosePlanRequested(args) {
26
+ return args.includes("--verbose") || args.includes("--full");
27
+ }