@sonnechasser/ntrp 2.2.1 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1584,7 +1584,7 @@ function printGuideCatalogLine(slide) {
1584
1584
  function printDeepdiveHint(metricId, label) {
1585
1585
  const name = label ?? metricId;
1586
1586
  console.log(
1587
- " " + chalk3.dim("How this number works: ") + paint("accent", `/tour ${metricId}`) + chalk3.dim(` \u2014 ${name}`)
1587
+ " " + chalk3.dim(`Ask what ${name.toLowerCase()} means \u2014 or `) + paint("accent", `/tour ${metricId}`) + chalk3.dim(" for the card")
1588
1588
  );
1589
1589
  console.log();
1590
1590
  }
@@ -1987,6 +1987,23 @@ function printHealthSummary(result, _pipelineMetrics) {
1987
1987
  VITAL_SIGN_LABELS[result.gating_vital_sign]
1988
1988
  );
1989
1989
  }
1990
+ function printHealthLine(result) {
1991
+ const score = `${chalk4.bold(String(Math.round(result.overall_score)))}${chalk4.dim("/100")}`;
1992
+ const gloss = voiceHealthGloss(result.overall_status);
1993
+ const parts = [
1994
+ `${chalk4.dim("Health")} ${score} ${statusBadge(result.overall_status)}${gloss ? chalk4.dim(` ${gloss}`) : ""}`,
1995
+ `${chalk4.dim("held back by")} ${paint("accent", VITAL_SIGN_LABELS[result.gating_vital_sign])}`
1996
+ ];
1997
+ if (result.total_value_at_risk != null && result.total_value_at_risk > 0) {
1998
+ parts.push(`${paint("success", formatCurrency(result.total_value_at_risk))} ${chalk4.dim("total at risk")}`);
1999
+ }
2000
+ console.log();
2001
+ console.log(" " + parts.join(chalk4.dim(" \xB7 ")));
2002
+ printDeepdiveHint(
2003
+ result.gating_vital_sign,
2004
+ VITAL_SIGN_LABELS[result.gating_vital_sign]
2005
+ );
2006
+ }
1990
2007
  function printVitalSigns(vitals) {
1991
2008
  console.log();
1992
2009
  printHeading("Vital Signs");
@@ -16931,7 +16948,7 @@ function consumeOrientEmptyEnterCoach(ctx) {
16931
16948
  if (!hasValidLicense()) {
16932
16949
  return "Type /activate to paste a key. Type /checkout if you need to sign up.";
16933
16950
  }
16934
- return "Type a question, type use demo data, or type /tour. Enter alone does not start a step here.";
16951
+ return "Type a question, or type use demo data. Enter alone does not start a step here.";
16935
16952
  }
16936
16953
  function formatPhaseLabel(phase) {
16937
16954
  switch (phase) {
@@ -17555,7 +17572,10 @@ function buildExploreTeachingGhosts(ctx, staticExplore) {
17555
17572
  if (cached2 && cached2.length > 0) {
17556
17573
  const ghosts = asksToGhostHints(cached2);
17557
17574
  const gating = ctx.snapshot.computeResult?.aggregate.gating_vital_sign;
17558
- if (gating) ghosts.push(`try /tour ${gating}`);
17575
+ if (gating) {
17576
+ const label = (VITAL_SIGN_LABELS[gating] ?? gating).toLowerCase();
17577
+ ghosts.push(`try "why is ${label} red?"`);
17578
+ }
17559
17579
  ghosts.push('try "how should we fix this?"');
17560
17580
  return dedupeGhosts(ghosts);
17561
17581
  }
@@ -17566,7 +17586,11 @@ function buildExploreTeachingGhosts(ctx, staticExplore) {
17566
17586
  });
17567
17587
  return dedupeGhosts([
17568
17588
  ...asksToGhostHints(asks),
17569
- `try /tour ${health.gating_vital_sign}`,
17589
+ (() => {
17590
+ const g = health.gating_vital_sign;
17591
+ const label = (VITAL_SIGN_LABELS[g] ?? g).toLowerCase();
17592
+ return `try "why is ${label} red?"`;
17593
+ })(),
17570
17594
  'try "how should we fix this?"'
17571
17595
  ]);
17572
17596
  }
@@ -18345,21 +18369,21 @@ var init_metric_definitions = __esm({
18345
18369
  kind: "vital",
18346
18370
  label: "Freshness",
18347
18371
  group: "Vital Signs",
18348
- tagline: "Is the CRM still talking about live work \u2014 or a museum of old deals?",
18349
- how_computed: "NTRP computes a weighted average of people, organizations, and opportunities with recent activity. Open opportunities must also not be past-due. Default windows: people and organizations 90 days, opportunities 30 days. Weights are 35 / 30 / 35.",
18372
+ tagline: "Is the CRM talking about live deals \u2014 or a museum of old ones?",
18373
+ how_computed: "We look at how recently people, companies, and deals got a touch. Deals need a fresher pulse than people or companies, and past-due deals count as stale. The formula lines on this card show the exact mix and windows.",
18350
18374
  formula_lines: [
18351
18375
  "freshness = people%\xD70.35 + orgs%\xD70.30 + opps%\xD70.35",
18352
18376
  "people/orgs fresh if activity within 90d",
18353
18377
  "opps fresh if activity within 30d AND not past-due"
18354
18378
  ],
18355
- meaning: 'We treat Freshness as a truth check on the pipeline number. Quiet days mean different things by motion: in enterprise, 30 quiet days can be normal cadence; in a velocity book, that same silence often means the deal is already dead. Stale open amount is why we show "pipeline at risk."',
18356
- expert_read: `A useful first cut is by owner and by stage \u2014 reds tend to clump on people or process, not sprinkle evenly. A sudden cliff is usually a broken integration or a departed rep, not gradual decay. Watch for the false positive too: bulk-imported records nobody has touched yet can look "stale" when they're just new.`,
18379
+ meaning: `Freshness asks whether the pipeline number is still true. Quiet deals pad the forecast. Like milk dates on a shelf \u2014 past the date, don't trust the carton. Stale open dollars show as "pipeline at risk."`,
18380
+ expert_read: "Cut by owner and stage first \u2014 reds usually clump, they don't sprinkle. A sudden cliff is often a broken sync or a departed rep, not slow decay. New bulk imports can look stale when they're just new.",
18357
18381
  deepdive: [
18358
- "Status bands: green 80 or more, yellow 60 or more, red below 60. Motion presets can change the windows.",
18359
- 'Dollar translation: sum of amount on stale open opportunities \u2192 "pipeline at risk".',
18360
- "Layer 1 of the gating stack. A red score here limits trust in later layers.",
18361
- "Trigger play: Clean Dead Pipeline (clean-dead-pipeline) when the score is below 60.",
18362
- "Levers: stale-deal alert at N quiet days. Weekly hygiene scrub. Enrichment refresh on quiet records. Signal-triggered reactivation for paid dormant accounts."
18382
+ "Green 80+, yellow 60+, red below 60. Motion presets can change the windows.",
18383
+ 'Dollars: sum of amount on stale open deals \u2192 "pipeline at risk".',
18384
+ "Read this first. If Freshness is red, later vitals are harder to trust.",
18385
+ "Weak score \u2192 play Clean Dead Pipeline.",
18386
+ "Useful moves: stale-deal alert, weekly hygiene scrub, enrichment on quiet records."
18363
18387
  ],
18364
18388
  visual: {
18365
18389
  kind: "bars",
@@ -18383,21 +18407,21 @@ var init_metric_definitions = __esm({
18383
18407
  kind: "vital",
18384
18408
  label: "Flow Rate",
18385
18409
  group: "Vital Signs",
18386
- tagline: "Where does deal motion slow \u2014 and what's stuck behind that bottleneck?",
18387
- how_computed: "NTRP sets a base score from average open-deal age versus max_days. It then applies a penalty of up to 20 for the share of stuck deals. A deal is stuck when it has no update beyond stuck_days, or a past-due close. Status uses average open age, not the score alone.",
18410
+ tagline: "Where do deals stop moving \u2014 and what's piled up behind that stage?",
18411
+ how_computed: "We start from how old open deals are versus a max window, then shave points for the share that look stuck. A deal is stuck when it goes quiet too long or its close date is already past. The formula lines show the base, the penalty, and the stuck rules.",
18388
18412
  formula_lines: [
18389
18413
  "base = 100 \xD7 (1 \u2212 avgOpenAge / max_days)",
18390
18414
  "score = base \u2212 stuckSharePenalty (\u226420)",
18391
18415
  "stuck = no update > stuck_days OR past-due close"
18392
18416
  ],
18393
- meaning: 'We think of Flow Rate as velocity risk with a dollar tag. When deals stop advancing \u2014 and especially when close dates slip past due \u2014 next quarter starts to feel optimistic on paper. The dollar line is the amount still "stuck in pipeline."',
18394
- expert_read: "We prefer stage age over deal age alone: find the stage where advancement collapses \u2014 it's usually one stage, not everywhere. Compare stuck age to this company's own median cycle rather than a generic norm. Stuck deals plus past-due closes are often a forecast-credibility problem before they're a revenue miss.",
18417
+ meaning: 'Flow Rate asks whether deals are still advancing or sitting still. Stuck deals pad next quarter on paper. Like traffic behind a closed lane \u2014 the jam is one stage, not the whole highway. Dollars on stuck deals show as "stuck in pipeline."',
18418
+ expert_read: "Find the stage where advancement collapses first \u2014 it's usually one stage, not everywhere. Compare stuck age to this company's own median cycle rather than a generic norm. Stuck deals plus past-due closes are often a forecast-credibility problem before they're a revenue miss.",
18395
18419
  deepdive: [
18396
- "Status uses average open age. Green is 45 days or less. Yellow is 90 days or less. Else red. Defaults: max_days 120, stuck_days 60.",
18397
- 'Dollar translation: sum of amount on stuck deals \u2192 "stuck in pipeline".',
18398
- "Layer 2 of the gating stack, with Drop Rate.",
18399
- "Trigger play: Unstick the Pipeline (unstick-pipeline) when the score is weak.",
18400
- "Levers: stage-age report. Past-due close cleanup. Progression plans on stuck deals. Forecast hygiene on optimistic close dates."
18420
+ "Status uses average open age. Green \u226445 days, yellow \u226490, else red. Defaults: max_days 120, stuck_days 60.",
18421
+ 'Dollars: sum of amount on stuck deals \u2192 "stuck in pipeline".',
18422
+ "Read with Drop Rate \u2014 stuck deals and handoff leaks often show up together.",
18423
+ "Weak score \u2192 play Unstick the Pipeline.",
18424
+ "Useful moves: stage-age report, past-due close cleanup, progression plans on stuck deals, forecast hygiene on optimistic close dates."
18401
18425
  ],
18402
18426
  visual: {
18403
18427
  kind: "funnel",
@@ -18423,21 +18447,21 @@ var init_metric_definitions = __esm({
18423
18447
  kind: "vital",
18424
18448
  label: "Drop Rate",
18425
18449
  group: "Vital Signs",
18426
- tagline: "Where does paid demand fall between systems before anyone works it?",
18427
- how_computed: "NTRP blends cross-system retention and opportunity retention. Cross-system retention is marketing people also present in sales. Opportunity retention is open opportunities that are not abandoned. Default weights: cross-system 60 percent, opportunity retention 40 percent. Abandoned means open opportunities with no activity in 30 days.",
18450
+ tagline: "Where does paid demand fall between systems before a rep works it?",
18451
+ how_computed: "We blend two checks: do marketing people also show up in sales CRM, and are open deals still being worked (not abandoned). The formula lines show the weights and the 30-day abandon window.",
18428
18452
  formula_lines: [
18429
18453
  "score = crossSystemRetention\xD70.6 + oppRetention\xD70.4",
18430
18454
  "cross-system = marketing people also in sales CRM",
18431
18455
  "abandoned = open opps with no activity in 30d"
18432
18456
  ],
18433
- meaning: 'We read Drop Rate as the price of the handoff leak \u2014 budget already spent on leads that never reach a working rep. The dollar estimate is droppedCount \xD7 conversion \xD7 avg deal size: "est. lost at handoff."',
18434
- expert_read: 'In practice this usually looks like a systems story: routing rules, unassigned territories, dead rep queues, or a sync gap between marketing and CRM \u2014 more often than "lazy reps." A useful first cut is by lead source; the leak tends to concentrate in one or two sources. The cheapest pipeline a business can buy is often the leads it already paid for.',
18457
+ meaning: 'Drop Rate prices the handoff leak \u2014 money you already spent on leads that never reach a working rep. Like buying groceries and leaving half the bags in the parking lot. The dollar line is "est. lost at handoff."',
18458
+ expert_read: "A useful first cut is by lead source \u2014 the leak usually concentrates in one or two, not everywhere. Routing rules, unassigned territories, dead queues, or a sync gap between marketing and CRM show up more often than lazy reps. The cheapest pipeline you can buy is often the leads you already paid for.",
18435
18459
  deepdive: [
18436
- "Status bands: green 80 or more, yellow 60 or more, red below 60.",
18437
- 'Dollar translation: dropped \xD7 conversion \xD7 avg deal (fallback: drop% \xD7 open pipeline) \u2192 "est. lost at handoff".',
18438
- "Layer 2 of the gating stack, with Flow Rate.",
18439
- "Trigger play: Fix the Handoff Gap (fix-handoff-gap) when drop is high.",
18440
- "Levers: source-level handoff audit. Routing and sync repair. Time-to-first-touch SLA. Weekly marketing-only-leads report."
18460
+ "Green 80+, yellow 60+, red below 60.",
18461
+ 'Dollars: dropped \xD7 conversion \xD7 avg deal (fallback: drop% \xD7 open pipeline) \u2192 "est. lost at handoff".',
18462
+ "Read with Flow Rate \u2014 handoff leaks and stuck deals often show up together.",
18463
+ "Weak score \u2192 play Fix the Handoff Gap.",
18464
+ "Useful moves: source-level handoff audit, routing and sync repair, time-to-first-touch SLA, weekly marketing-only-leads report."
18441
18465
  ],
18442
18466
  visual: {
18443
18467
  kind: "funnel",
@@ -18462,21 +18486,21 @@ var init_metric_definitions = __esm({
18462
18486
  kind: "vital",
18463
18487
  label: "Signal:Noise",
18464
18488
  group: "Vital Signs",
18465
- tagline: "How much of the team's effort still aims at deals that can close?",
18466
- how_computed: "Over a 90-day lookback, NTRP computes (signal activities / all activities) \xD7 100. Signal is activity linked to an open opportunity, a pipeline person, or a pipeline organization.",
18489
+ tagline: "How much effort still aims at accounts that can close \u2014 versus dead ends?",
18490
+ how_computed: "We look at the last 90 days of activity and ask what share is signal \u2014 linked to an open deal, a pipeline person, or a pipeline company. The formula lines show the ratio and what counts as signal.",
18467
18491
  formula_lines: [
18468
18492
  "score = (signalCount / activityCount) \xD7 100",
18469
18493
  "signal = linked to open opp / pipeline person / pipeline org",
18470
18494
  "lookback = trailing 90 days"
18471
18495
  ],
18472
- meaning: `We treat Signal:Noise as activity efficiency with a capacity price. Noise hours \xD7 hours_per_activity \xD7 rep_hourly_cost becomes "misdirected effort" \u2014 energy aimed at accounts that can't still close.`,
18496
+ meaning: 'Signal:Noise asks whether activity still points at deals that can close. Noise hours are capacity spent on dead ends. Like a radio stuck between stations \u2014 lots of static, little signal. Those hours show as "misdirected effort."',
18473
18497
  expert_read: "A useful cut is by rep and by account status. Persistent noise often means reps keep working dead accounts they already know \u2014 stale targeting or coverage models more than a coaching gap. Also check whether activity is logged against closed or unlinked records; that can be a hygiene artifact pretending to be effort.",
18474
18498
  deepdive: [
18475
- "Status bands: green 65 or more, yellow 40 or more, red below 40.",
18476
- "Dollar defaults: 0.25 hours/activity \xD7 $75/hr (config: hours_per_activity, rep_hourly_cost).",
18477
- "Layer 3 of the gating stack. Trust Freshness, Flow Rate, and Drop Rate before you read activity efficiency.",
18478
- "Trigger play: Retarget Misdirected Effort (retarget-effort) when the score is low.",
18479
- "Levers: refresh account lists. Signal-based targeting. Stop logging against closed or unlinked records. Coverage-model redesign."
18499
+ "Green 65+, yellow 40+, red below 40.",
18500
+ 'Dollar defaults: 0.25 hours/activity \xD7 $75/hr (config: hours_per_activity, rep_hourly_cost) \u2192 "misdirected effort".',
18501
+ "Read Freshness, Flow Rate, and Drop Rate before you trust this score.",
18502
+ "Weak score \u2192 play Retarget Misdirected Effort.",
18503
+ "Useful moves: refresh account lists, signal-based targeting, stop logging against closed or unlinked records, coverage-model redesign."
18480
18504
  ],
18481
18505
  visual: {
18482
18506
  kind: "split",
@@ -18499,21 +18523,21 @@ var init_metric_definitions = __esm({
18499
18523
  kind: "vital",
18500
18524
  label: "Thread Depth",
18501
18525
  group: "Vital Signs",
18502
- tagline: "How fragile is revenue if one champion changes jobs?",
18503
- how_computed: "Percent of open deals with at least multi_thread_threshold distinct people active in the last 90 days. The default threshold is 2. People include opportunity-direct contacts and same-organization activity.",
18526
+ tagline: "How fragile is a deal if one champion changes jobs?",
18527
+ how_computed: "We ask what share of open deals have at least two people active in the last 90 days. Contacts on the deal and activity at the same company both count. The formula lines show the threshold.",
18504
18528
  formula_lines: [
18505
18529
  "score = % open deals with \u22652 active people (90d)",
18506
18530
  "people counted via opp contacts + same-org activity",
18507
18531
  "threshold configurable (default 2)"
18508
18532
  ],
18509
- meaning: "We read Thread Depth as resilience risk. One single-threaded mega-deal can outweigh ten small ones \u2014 and late-cycle single-threading is especially scary because champions change jobs. The dollar line sums amount on single-threaded deals.",
18510
- expert_read: "Weight by deal size when you triage. Single-threading late in the cycle tends to matter more than early. In enterprise motions, thin threads are often a leading indicator of slipped quarters: the champion leaves and there's no second contact warm enough to carry the deal.",
18533
+ meaning: 'Thread Depth asks how many people keep each deal warm. One contact is a single point of failure \u2014 if they leave, the deal often dies with them. Like a bridge with one cable. Dollars on thin deals show as "single-threaded."',
18534
+ expert_read: "Weight by deal size when you triage \u2014 one thin mega-deal can outweigh ten small ones. Late-cycle single-threading tends to matter more than early. In enterprise motions, thin threads often lead to slipped quarters: the champion leaves and nobody else is warm enough to carry the deal.",
18511
18535
  deepdive: [
18512
- "Status bands: green 65 or more, yellow 40 or more, red below 40.",
18513
- 'Dollar translation: sum of amount on single-threaded deals \u2192 "single-threaded".',
18514
- "Layer 4 of the gating stack. Read this last, after the upstream vital signs.",
18515
- "Trigger play: Multi-Thread Your Deals (multi-thread-deals) when depth is low.",
18516
- "Levers: buying-committee map. Warm internal referral first. CRM contact roles. Mid-stage single-thread alerts. Champion job-change signals."
18536
+ "Green 65+, yellow 40+, red below 40.",
18537
+ 'Dollars: sum of amount on single-threaded deals \u2192 "single-threaded".',
18538
+ "Read this last, after Freshness, Flow Rate, Drop Rate, and Signal:Noise.",
18539
+ "Weak score \u2192 play Multi-Thread Your Deals.",
18540
+ "Useful moves: buying-committee map, warm internal referral first, CRM contact roles, mid-stage single-thread alerts, champion job-change signals."
18517
18541
  ],
18518
18542
  visual: {
18519
18543
  kind: "bars",
@@ -25903,8 +25927,8 @@ var init_ghost_hints = __esm({
25903
25927
  PHASE_GHOST_HINTS = {
25904
25928
  orient: [
25905
25929
  'try "pipeline health"',
25906
- "try /tour",
25907
- "try /tour guide",
25930
+ 'try "how healthy is the pipeline?"',
25931
+ 'try "what should I do next?"',
25908
25932
  'try "is our retention real for the board?"',
25909
25933
  'try "what is the most expensive problem to solve?"',
25910
25934
  'try "board deck on Q3"'
@@ -25921,7 +25945,7 @@ var init_ghost_hints = __esm({
25921
25945
  'try "how should we fix this?"',
25922
25946
  'try "which segment is weakest?"',
25923
25947
  'try "what is ARR?"',
25924
- "try /tour freshness",
25948
+ 'try "why is freshness red?"',
25925
25949
  'try "ship a board deck"'
25926
25950
  ]
25927
25951
  };
@@ -36716,7 +36740,7 @@ async function handler19(args, ctx) {
36716
36740
  console.log(chalk51.dim(` Trigger: ${triggerLabel} \u2014 ${play.trigger_condition}`));
36717
36741
  if (play.trigger_vital_sign) {
36718
36742
  console.log(
36719
- chalk51.dim(" How this number works: ") + paint("accent", `/tour ${play.trigger_vital_sign}`)
36743
+ chalk51.dim(" Ask what ") + chalk51.dim(play.trigger_vital_sign.replace(/_/g, " ")) + chalk51.dim(" means \u2014 or ") + paint("accent", `/tour ${play.trigger_vital_sign}`) + chalk51.dim(" for the card")
36720
36744
  );
36721
36745
  }
36722
36746
  console.log();
@@ -40446,7 +40470,8 @@ function getDeepdiveNudge(ctx) {
40446
40470
  if (hasCompletedMetricsTour() || hasSeenDeepdiveHomeNudge()) return null;
40447
40471
  if (!hasAnalysisForDeepdiveNudge(ctx)) return null;
40448
40472
  return {
40449
- text: "Tour \u2014 how we think about the numbers, and how to use NTRP",
40473
+ text: "Ask how healthy the pipeline looks",
40474
+ tip: "Or /tour for the numbers deck",
40450
40475
  command: "/tour"
40451
40476
  };
40452
40477
  }
@@ -45677,12 +45702,69 @@ var init_explore_expand = __esm({
45677
45702
  }
45678
45703
  });
45679
45704
 
45705
+ // src/conversation/health-chat.ts
45706
+ var health_chat_exports = {};
45707
+ __export(health_chat_exports, {
45708
+ isHealthChatPhase: () => isHealthChatPhase,
45709
+ isHealthIntent: () => isHealthIntent,
45710
+ tryHealthChatReadout: () => tryHealthChatReadout
45711
+ });
45712
+ import chalk94 from "chalk";
45713
+ function isHealthIntent(line) {
45714
+ const text = line.trim();
45715
+ if (!text) return false;
45716
+ if (/^(tour|\/tour|deepdive|\/deepdive)\b/i.test(text)) return false;
45717
+ return HEALTH_INTENT_RE.test(text);
45718
+ }
45719
+ function isHealthChatPhase(ctx) {
45720
+ const phase = resolveConversationPhase(ctx);
45721
+ return phase === "explore" || phase === "orient" && isAnalysisReady(ctx);
45722
+ }
45723
+ function tryHealthChatReadout(ctx, line) {
45724
+ if (!isHealthIntent(line)) return false;
45725
+ if (!isHealthChatPhase(ctx)) return false;
45726
+ const aggregate = ctx.snapshot.computeResult?.aggregate;
45727
+ if (!aggregate) {
45728
+ console.log();
45729
+ console.log(
45730
+ " " + chalk94.dim(
45731
+ "No health reading in this session yet. Run analysis first, then ask again."
45732
+ )
45733
+ );
45734
+ console.log();
45735
+ recordMessage(ctx, "user", line);
45736
+ recordMessage(ctx, "agent", "No health reading yet");
45737
+ saveSessionState(ctx);
45738
+ return true;
45739
+ }
45740
+ printHealthLine(aggregate);
45741
+ printVitalSigns(aggregate.vital_signs);
45742
+ recordMessage(ctx, "user", line);
45743
+ recordMessage(
45744
+ ctx,
45745
+ "agent",
45746
+ `Health ${Math.round(aggregate.overall_score)}/100 \xB7 gating ${aggregate.gating_vital_sign}`
45747
+ );
45748
+ saveSessionState(ctx);
45749
+ return true;
45750
+ }
45751
+ var HEALTH_INTENT_RE;
45752
+ var init_health_chat = __esm({
45753
+ "src/conversation/health-chat.ts"() {
45754
+ "use strict";
45755
+ init_context2();
45756
+ init_terminal();
45757
+ init_phase();
45758
+ HEALTH_INTENT_RE = /\b((how|how'?s|how is)\s+(healthy|the\s+pipeline|our\s+pipeline|pipeline\s+health)|pipeline\s+health|show\s+vitals|vital\s+signs?|where\s+are\s+we\s+red|where'?s\s+the\s+red|health\s+check|how\s+healthy)\b/i;
45759
+ }
45760
+ });
45761
+
45680
45762
  // src/conversation/router.ts
45681
45763
  var router_exports = {};
45682
45764
  __export(router_exports, {
45683
45765
  conversationRouter: () => conversationRouter
45684
45766
  });
45685
- import chalk94 from "chalk";
45767
+ import chalk95 from "chalk";
45686
45768
  function popModalState(ctx) {
45687
45769
  const popped = [];
45688
45770
  if (ctx.strategistState) {
@@ -45715,7 +45797,7 @@ function handleBackNavigation(ctx, line) {
45715
45797
  }
45716
45798
  if (phase === "scope") {
45717
45799
  console.log();
45718
- console.log(" " + chalk94.dim("What should we focus on instead?"));
45800
+ console.log(" " + chalk95.dim("What should we focus on instead?"));
45719
45801
  ctx.scope = void 0;
45720
45802
  clearPendingAsk(ctx);
45721
45803
  saveSessionState(ctx);
@@ -45735,7 +45817,7 @@ function handleBackNavigation(ctx, line) {
45735
45817
  printScopeProposal(ctx);
45736
45818
  if (hadData || hadAnalysis) {
45737
45819
  console.log(
45738
- " " + chalk94.dim(
45820
+ " " + chalk95.dim(
45739
45821
  "Focus gate rewound \u2014 loaded data and any analysis stay. `b` does not unload demo data or undo compute."
45740
45822
  )
45741
45823
  );
@@ -45751,7 +45833,7 @@ function handleBackNavigation(ctx, line) {
45751
45833
  recordMessage(ctx, "user", line);
45752
45834
  recordMessage(ctx, "agent", "Strategy objective adjust (back)");
45753
45835
  console.log();
45754
- console.log(" " + chalk94.dim("What is the objective? State a finish line."));
45836
+ console.log(" " + chalk95.dim("What is the objective? State a finish line."));
45755
45837
  console.log();
45756
45838
  return { handled: true, summary: "Awaiting objective" };
45757
45839
  }
@@ -45763,7 +45845,7 @@ function handleBackNavigation(ctx, line) {
45763
45845
  const backTo = formatPhaseLabel(resolveConversationPhase(ctx));
45764
45846
  console.log();
45765
45847
  console.log(
45766
- " " + chalk94.dim(`Back \u2014 dropped ${popped.join(" and ")}. At `) + chalk94.cyan(backTo) + chalk94.dim(".")
45848
+ " " + chalk95.dim(`Back \u2014 dropped ${popped.join(" and ")}. At `) + chalk95.cyan(backTo) + chalk95.dim(".")
45767
45849
  );
45768
45850
  console.log();
45769
45851
  return { handled: true, summary: "Back" };
@@ -45777,7 +45859,7 @@ function handleBackNavigation(ctx, line) {
45777
45859
  const backTo = formatPhaseLabel(resolveConversationPhase(ctx));
45778
45860
  console.log();
45779
45861
  console.log(
45780
- " " + chalk94.dim(`Back \u2014 dropped ${popped.join(" and ")}. At `) + chalk94.cyan(backTo) + chalk94.dim(".")
45862
+ " " + chalk95.dim(`Back \u2014 dropped ${popped.join(" and ")}. At `) + chalk95.cyan(backTo) + chalk95.dim(".")
45781
45863
  );
45782
45864
  console.log();
45783
45865
  return { handled: true, summary: "Back" };
@@ -45821,7 +45903,7 @@ async function conversationRouter(input, ctx) {
45821
45903
  if (!ok) {
45822
45904
  console.log();
45823
45905
  console.log(
45824
- " " + chalk94.dim("Staying on this session. Type ") + chalk94.cyan("/home") + chalk94.dim(" for the dashboard.")
45906
+ " " + chalk95.dim("Staying on this session. Type ") + chalk95.cyan("/home") + chalk95.dim(" for the dashboard.")
45825
45907
  );
45826
45908
  console.log();
45827
45909
  return { handled: true };
@@ -45836,7 +45918,7 @@ async function conversationRouter(input, ctx) {
45836
45918
  }
45837
45919
  console.log();
45838
45920
  console.log(
45839
- " " + chalk94.dim("Start a fresh analysis with ") + chalk94.cyan("/new") + chalk94.dim(" \u2014 or ") + chalk94.cyan("/home") + chalk94.dim(" for the dashboard.")
45921
+ " " + chalk95.dim("Start a fresh analysis with ") + chalk95.cyan("/new") + chalk95.dim(" \u2014 or ") + chalk95.cyan("/home") + chalk95.dim(" for the dashboard.")
45840
45922
  );
45841
45923
  console.log();
45842
45924
  return { handled: true };
@@ -45850,7 +45932,7 @@ async function conversationRouter(input, ctx) {
45850
45932
  const backTo = formatPhaseLabel(resolveConversationPhase(ctx));
45851
45933
  console.log();
45852
45934
  console.log(
45853
- " " + chalk94.dim(`Cancelled \u2014 dropped ${popped.join(" and ")}. Back to `) + chalk94.cyan(backTo) + chalk94.dim(".")
45935
+ " " + chalk95.dim(`Cancelled \u2014 dropped ${popped.join(" and ")}. Back to `) + chalk95.cyan(backTo) + chalk95.dim(".")
45854
45936
  );
45855
45937
  console.log();
45856
45938
  return { handled: true, summary: "Cancelled" };
@@ -45894,6 +45976,12 @@ async function conversationRouter(input, ctx) {
45894
45976
  const summary = await handleKeepGoingLine(ctx, line) ?? void 0;
45895
45977
  return { handled: true, summary };
45896
45978
  }
45979
+ if (phase === "explore" || phase === "orient" && isAnalysisReady(ctx)) {
45980
+ const { tryHealthChatReadout: tryHealthChatReadout2 } = await Promise.resolve().then(() => (init_health_chat(), health_chat_exports));
45981
+ if (tryHealthChatReadout2(ctx, line)) {
45982
+ return { handled: true, summary: "Pipeline health" };
45983
+ }
45984
+ }
45897
45985
  if (phase === "think") {
45898
45986
  const summary = await handleThinkFlow(line, ctx) ?? void 0;
45899
45987
  return { handled: true, summary };
@@ -45932,7 +46020,7 @@ async function conversationRouter(input, ctx) {
45932
46020
  }
45933
46021
  if (phase === "compute") {
45934
46022
  console.log();
45935
- console.log(" " + chalk94.dim("Analysis running \u2014 wait for it to finish before typing another question."));
46023
+ console.log(" " + chalk95.dim("Analysis running \u2014 wait for it to finish before typing another question."));
45936
46024
  console.log();
45937
46025
  return { handled: true };
45938
46026
  }
@@ -45988,7 +46076,7 @@ __export(dispatch_exports, {
45988
46076
  dispatch: () => dispatch,
45989
46077
  replayPendingBlockedLine: () => replayPendingBlockedLine
45990
46078
  });
45991
- import chalk95 from "chalk";
46079
+ import chalk96 from "chalk";
45992
46080
  function printLicenseRequired(command) {
45993
46081
  printLicenseBlocked(command);
45994
46082
  }
@@ -46006,7 +46094,7 @@ async function replayPendingBlockedLine(ctx) {
46006
46094
  if (!hasValidLicense()) return false;
46007
46095
  ctx.pendingBlockedLine = void 0;
46008
46096
  console.log();
46009
- console.log(" " + chalk95.dim("Picking up where you left off\u2026"));
46097
+ console.log(" " + chalk96.dim("Picking up where you left off\u2026"));
46010
46098
  console.log();
46011
46099
  await dispatch(line, ctx);
46012
46100
  return true;
@@ -46076,7 +46164,7 @@ async function dispatch(input, ctx) {
46076
46164
  if (tokens.length === 1) {
46077
46165
  if (/^\d$/.test(first)) {
46078
46166
  console.log(
46079
- " " + chalk95.dim("Looks like a menu pick \u2014 run ") + paint("accent", "/new") + chalk95.dim(" to start (pick Demo, then choose your analysis type).")
46167
+ " " + chalk96.dim("Looks like a menu pick \u2014 run ") + paint("accent", "/new") + chalk96.dim(" to start (pick Demo, then choose your analysis type).")
46080
46168
  );
46081
46169
  return { kind: "handled" };
46082
46170
  }
@@ -46099,19 +46187,19 @@ async function dispatch(input, ctx) {
46099
46187
  return { kind: "handled", summary };
46100
46188
  }
46101
46189
  console.log(
46102
- " " + chalk95.dim("Not in Q&A yet. Confirm the scope, load data, and run analysis first. Type ") + paint("accent", "/home") + chalk95.dim(" for status.")
46190
+ " " + chalk96.dim("Not in Q&A yet. Confirm the scope, load data, and run analysis first. Type ") + paint("accent", "/home") + chalk96.dim(" for status.")
46103
46191
  );
46104
46192
  return { kind: "handled" };
46105
46193
  }
46106
46194
  console.log(
46107
- " " + chalk95.dim("Natural-language questions run inside ntrp. Start with ") + paint("accent", "ntrp") + chalk95.dim(" and type a question after analysis.")
46195
+ " " + chalk96.dim("Natural-language questions run inside ntrp. Start with ") + paint("accent", "ntrp") + chalk96.dim(" and type a question after analysis.")
46108
46196
  );
46109
46197
  return { kind: "handled" };
46110
46198
  }
46111
46199
  async function runSlashCommand(name, args, ctx) {
46112
46200
  const handler54 = await resolveHandler(name);
46113
46201
  if (!handler54) {
46114
- console.error(chalk95.red(` Unknown command: /${name}`));
46202
+ console.error(chalk96.red(` Unknown command: /${name}`));
46115
46203
  return void 0;
46116
46204
  }
46117
46205
  const result = await handler54(args, ctx);
@@ -46148,7 +46236,7 @@ var init_inline_suggestion = __esm({
46148
46236
  });
46149
46237
 
46150
46238
  // src/conversation/loop-guard.ts
46151
- import chalk96 from "chalk";
46239
+ import chalk97 from "chalk";
46152
46240
  function createLoopGuardState() {
46153
46241
  return { phase: null, stuckTurns: 0 };
46154
46242
  }
@@ -46180,10 +46268,10 @@ function printLoopEscalation(phase) {
46180
46268
  if (!guide) return;
46181
46269
  console.log();
46182
46270
  console.log(
46183
- " " + chalk96.yellow("We seem to be going in circles \u2014 you're in ") + paint("accent", guide.mode) + chalk96.yellow(" mode.")
46271
+ " " + chalk97.yellow("We seem to be going in circles \u2014 you're in ") + paint("accent", guide.mode) + chalk97.yellow(" mode.")
46184
46272
  );
46185
- console.log(" " + chalk96.dim("Right now I can only accept: ") + guide.accepts);
46186
- console.log(" " + chalk96.dim("To leave: ") + guide.leave);
46273
+ console.log(" " + chalk97.dim("Right now I can only accept: ") + guide.accepts);
46274
+ console.log(" " + chalk97.dim("To leave: ") + guide.leave);
46187
46275
  console.log();
46188
46276
  }
46189
46277
  var LOOP_GUARD_THRESHOLD, MODAL_PHASES, PHASE_GUIDES;
@@ -46227,7 +46315,7 @@ __export(welcome_exports, {
46227
46315
  resolveWelcomeNextAction: () => resolveWelcomeNextAction,
46228
46316
  staleProcessHomeNotice: () => staleProcessHomeNotice
46229
46317
  });
46230
- import chalk97 from "chalk";
46318
+ import chalk98 from "chalk";
46231
46319
  function formatHomeEntityCounts(counts) {
46232
46320
  const parts = [];
46233
46321
  const people = counts.people ?? 0;
@@ -46241,11 +46329,11 @@ function formatHomeEntityCounts(counts) {
46241
46329
  return parts.join(" \xB7 ");
46242
46330
  }
46243
46331
  function formatEmptyDataHomeHint(savedSessionCount, opts = {}) {
46244
- const onboard = opts.includeOnboard === true ? chalk97.dim(", or ") + paint("accent", "/onboard") + chalk97.dim(" to calibrate") : "";
46332
+ const onboard = opts.includeOnboard === true ? chalk98.dim(", or ") + paint("accent", "/onboard") + chalk98.dim(" to calibrate") : "";
46245
46333
  if (savedSessionCount > 0) {
46246
- return chalk97.dim("Drop a CSV or type ") + paint("accent", "use demo data") + chalk97.dim(" to load a sample") + onboard + chalk97.dim(". Type ") + paint("accent", "/session") + chalk97.dim(" to open a saved session");
46334
+ return chalk98.dim("Drop a CSV or type ") + paint("accent", "use demo data") + chalk98.dim(" to load a sample") + onboard + chalk98.dim(". Type ") + paint("accent", "/session") + chalk98.dim(" to open a saved session");
46247
46335
  }
46248
- return chalk97.dim("Drop a CSV or type ") + paint("accent", "use demo data") + chalk97.dim(" to load a sample pipeline") + onboard;
46336
+ return chalk98.dim("Drop a CSV or type ") + paint("accent", "use demo data") + chalk98.dim(" to load a sample pipeline") + onboard;
46249
46337
  }
46250
46338
  function staleProcessHomeNotice() {
46251
46339
  const running = getInstalledVersion();
@@ -46287,19 +46375,19 @@ function sessionSummaryText(s) {
46287
46375
  summary: s.summary,
46288
46376
  dataset: s.dataset
46289
46377
  });
46290
- return summary === NO_SUMMARY ? chalk97.dim(summary) : summary;
46378
+ return summary === NO_SUMMARY ? chalk98.dim(summary) : summary;
46291
46379
  }
46292
46380
  function formatLastSessionLine(s, colW, ctx, opts) {
46293
46381
  const phase = sessionPhaseLabel(s, ctx);
46294
- const current = opts?.markCurrent && s.id === ctx?.sessionId ? chalk97.dim(" \xB7 current") : "";
46295
- const meta = `${formatSessionId(s.id, s.name)} ${chalk97.dim("\xB7")} ${chalk97.dim(lensBadgeLabel(s.analysis))} ${chalk97.dim("\xB7")} ${paint("accent", phase)} ${chalk97.dim("\xB7")} ${sessionSummaryText(s)}${current}`;
46382
+ const current = opts?.markCurrent && s.id === ctx?.sessionId ? chalk98.dim(" \xB7 current") : "";
46383
+ const meta = `${formatSessionId(s.id, s.name)} ${chalk98.dim("\xB7")} ${chalk98.dim(lensBadgeLabel(s.analysis))} ${chalk98.dim("\xB7")} ${paint("accent", phase)} ${chalk98.dim("\xB7")} ${sessionSummaryText(s)}${current}`;
46296
46384
  return truncateVisible(` ${meta}`, colW);
46297
46385
  }
46298
46386
  function formatActiveSessionLine(s, colW, ctx, opts) {
46299
46387
  const indent = " ";
46300
46388
  const idPart = formatSessionId(s.id, s.name);
46301
- const status = chalk97.dim(` \xB7 ${sessionStatusSuffix(s)}`);
46302
- const current = opts?.markCurrent && s.id === ctx?.sessionId ? chalk97.dim(" \xB7 current") : "";
46389
+ const status = chalk98.dim(` \xB7 ${sessionStatusSuffix(s)}`);
46390
+ const current = opts?.markCurrent && s.id === ctx?.sessionId ? chalk98.dim(" \xB7 current") : "";
46303
46391
  const suffix = `${status}${current}`;
46304
46392
  const summaryBudget = Math.max(8, colW - visibleWidth(indent) - visibleWidth(idPart) - visibleWidth(suffix) - 2);
46305
46393
  const summaryPart = truncateVisible(sessionSummaryText(s), summaryBudget);
@@ -46340,13 +46428,13 @@ function buildSystemLines(colW, statusRows, recent) {
46340
46428
  lines.push(sectionHeading("System"));
46341
46429
  const labelW = Math.max(...statusRows.map((r) => r.label.length), "last used".length);
46342
46430
  for (const item of statusRows) {
46343
- const label = chalk97.dim(padRight(item.label, labelW));
46431
+ const label = chalk98.dim(padRight(item.label, labelW));
46344
46432
  const state2 = padRight(item.state, 10);
46345
46433
  const detailW = Math.max(1, colW - labelW - 13);
46346
- lines.push(`${label} ${state2} ${chalk97.dim(truncateVisible(item.detail, detailW))}`);
46434
+ lines.push(`${label} ${state2} ${chalk98.dim(truncateVisible(item.detail, detailW))}`);
46347
46435
  }
46348
46436
  if (recent) {
46349
- lines.push(`${chalk97.dim(padRight("last used", labelW))} ${chalk97.dim(recent)}`);
46437
+ lines.push(`${chalk98.dim(padRight("last used", labelW))} ${chalk98.dim(recent)}`);
46350
46438
  }
46351
46439
  return lines;
46352
46440
  }
@@ -46354,10 +46442,10 @@ function buildLastSessionLines(colW, ctx, lastSession, nextAction, emptyDataHint
46354
46442
  const lines = [""];
46355
46443
  lines.push(sectionHeading("Last Session"));
46356
46444
  if (!lastSession) {
46357
- lines.push(` ${chalk97.dim("(none)")}`);
46445
+ lines.push(` ${chalk98.dim("(none)")}`);
46358
46446
  lines.push(
46359
46447
  truncateVisible(
46360
- ` ${nextAction.command ? actionHint(nextAction.label, nextAction.command, nextAction.detail) : `${chalk97.dim(nextAction.label)} ${chalk97.dim(nextAction.detail)}`}`,
46448
+ ` ${nextAction.command ? actionHint(nextAction.label, nextAction.command, nextAction.detail) : `${chalk98.dim(nextAction.label)} ${chalk98.dim(nextAction.detail)}`}`,
46361
46449
  colW
46362
46450
  )
46363
46451
  );
@@ -46371,7 +46459,7 @@ function buildLastSessionLines(colW, ctx, lastSession, nextAction, emptyDataHint
46371
46459
  if (!isCurrent) {
46372
46460
  lines.push(
46373
46461
  truncateVisible(
46374
- ` ${chalk97.dim("Resume:")} ${paint("accent", `/session ${lastSession.id.slice(-4)}`)}`,
46462
+ ` ${chalk98.dim("Resume:")} ${paint("accent", `/session ${lastSession.id.slice(-4)}`)}`,
46375
46463
  colW
46376
46464
  )
46377
46465
  );
@@ -46380,7 +46468,7 @@ function buildLastSessionLines(colW, ctx, lastSession, nextAction, emptyDataHint
46380
46468
  truncateVisible(` ${actionHint(nextAction.label, nextAction.command, nextAction.detail)}`, colW)
46381
46469
  );
46382
46470
  } else {
46383
- lines.push(truncateVisible(` ${chalk97.dim(nextAction.label)} ${chalk97.dim(nextAction.detail)}`, colW));
46471
+ lines.push(truncateVisible(` ${chalk98.dim(nextAction.label)} ${chalk98.dim(nextAction.detail)}`, colW));
46384
46472
  }
46385
46473
  if (isCurrent && emptyDataHint) {
46386
46474
  lines.push(truncateVisible(` ${emptyDataHint}`, colW));
@@ -46391,14 +46479,14 @@ function buildActiveSessionsLines(colW, ctx, activeSessions) {
46391
46479
  const lines = [""];
46392
46480
  lines.push(sectionHeading("Active Sessions"));
46393
46481
  if (activeSessions.length === 0) {
46394
- lines.push(` ${chalk97.dim("(none in progress)")}`);
46482
+ lines.push(` ${chalk98.dim("(none in progress)")}`);
46395
46483
  return lines;
46396
46484
  }
46397
46485
  for (const s of activeSessions.slice(0, 5)) {
46398
46486
  lines.push(formatActiveSessionLine(s, colW, ctx, { markCurrent: true }));
46399
46487
  }
46400
46488
  if (activeSessions.length > 5) {
46401
- lines.push(` ${chalk97.dim(`+${activeSessions.length - 5} more \xB7 `)}${paint("accent", "/session")}`);
46489
+ lines.push(` ${chalk98.dim(`+${activeSessions.length - 5} more \xB7 `)}${paint("accent", "/session")}`);
46402
46490
  }
46403
46491
  return lines;
46404
46492
  }
@@ -46486,7 +46574,7 @@ async function printWelcome(ctx, version) {
46486
46574
  const gap = Math.max(0, innerW - versionTag.length);
46487
46575
  const gapL = Math.floor(gap / 2);
46488
46576
  push(
46489
- border(`\u256D${"\u2500".repeat(gapL)}`) + chalk97.dim(versionTag) + border(`${"\u2500".repeat(gap - gapL)}\u256E`)
46577
+ border(`\u256D${"\u2500".repeat(gapL)}`) + chalk98.dim(versionTag) + border(`${"\u2500".repeat(gap - gapL)}\u256E`)
46490
46578
  );
46491
46579
  if (useWideLayout) {
46492
46580
  const leftLines = systemLines;
@@ -46513,7 +46601,7 @@ async function printWelcome(ctx, version) {
46513
46601
  push(border(`\u2570${"\u2500".repeat(innerW)}\u256F`));
46514
46602
  push(
46515
46603
  truncateVisible(
46516
- license.valid ? ` ${paint("accent", "/help")}${chalk97.dim(" commands \xB7 ")}${paint("accent", "/tour")}${chalk97.dim(" tour \xB7 ")}${paint("accent", "/progress")}${chalk97.dim(" hours \xB7 ")}${paint("accent", "/session")}${chalk97.dim(" resume")}` : ` ${paint("accent", "/help")}${chalk97.dim(" commands \xB7 ")}${paint("accent", "/activate")}${chalk97.dim(" paste a key \xB7 ")}${paint("accent", "/checkout")}${chalk97.dim(" signup \xB7 ")}${paint("accent", "/progress")}${chalk97.dim(" hours")}`,
46604
+ license.valid ? ` ${paint("accent", "/help")}${chalk98.dim(" commands \xB7 ")}${paint("accent", "/tour")}${chalk98.dim(" tour \xB7 ")}${paint("accent", "/progress")}${chalk98.dim(" hours \xB7 ")}${paint("accent", "/session")}${chalk98.dim(" resume")}` : ` ${paint("accent", "/help")}${chalk98.dim(" commands \xB7 ")}${paint("accent", "/activate")}${chalk98.dim(" paste a key \xB7 ")}${paint("accent", "/checkout")}${chalk98.dim(" signup \xB7 ")}${paint("accent", "/progress")}${chalk98.dim(" hours")}`,
46517
46605
  cardW
46518
46606
  )
46519
46607
  );
@@ -46524,14 +46612,15 @@ async function printWelcome(ctx, version) {
46524
46612
  if (strategyNudge) {
46525
46613
  push(
46526
46614
  truncateVisible(
46527
- ` ${paint("warning", "\u2691")} ${chalk97.dim(strategyNudge.text)} ${chalk97.dim("\xB7")} ${paint("accent", strategyNudge.command)}`,
46615
+ ` ${paint("warning", "\u2691")} ${chalk98.dim(strategyNudge.text)} ${chalk98.dim("\xB7")} ${paint("accent", strategyNudge.command)}`,
46528
46616
  cardW
46529
46617
  )
46530
46618
  );
46531
46619
  } else if (deepdiveNudge) {
46620
+ const tip = deepdiveNudge.tip ?? deepdiveNudge.command;
46532
46621
  push(
46533
46622
  truncateVisible(
46534
- ` ${paint("warning", "\u2691")} ${chalk97.dim(deepdiveNudge.text)} ${chalk97.dim("\xB7")} ${paint("accent", deepdiveNudge.command)}`,
46623
+ tip ? ` ${paint("warning", "\u2691")} ${chalk98.dim(deepdiveNudge.text)} ${chalk98.dim("\xB7")} ${chalk98.dim(tip)}` : ` ${paint("warning", "\u2691")} ${chalk98.dim(deepdiveNudge.text)}`,
46535
46624
  cardW
46536
46625
  )
46537
46626
  );
@@ -46539,7 +46628,7 @@ async function printWelcome(ctx, version) {
46539
46628
  } else if (voiceNudge) {
46540
46629
  push(
46541
46630
  truncateVisible(
46542
- ` ${paint("warning", "\u2691")} ${chalk97.dim(voiceNudge.text)} ${chalk97.dim("\xB7")} ${paint("accent", voiceNudge.command)}`,
46631
+ ` ${paint("warning", "\u2691")} ${chalk98.dim(voiceNudge.text)} ${chalk98.dim("\xB7")} ${paint("accent", voiceNudge.command)}`,
46543
46632
  cardW
46544
46633
  )
46545
46634
  );
@@ -46727,7 +46816,7 @@ __export(repl_exports, {
46727
46816
  });
46728
46817
  import { createInterface as createInterface2 } from "readline/promises";
46729
46818
  import { clearLine as clearLine2, cursorTo as cursorTo2 } from "readline";
46730
- import chalk98 from "chalk";
46819
+ import chalk99 from "chalk";
46731
46820
  import { join as join42 } from "path";
46732
46821
  function buildPrompt(ctx) {
46733
46822
  return buildConversationPrompt(ctx);
@@ -46819,12 +46908,12 @@ function renderInlineSuggestion(rl, prompt, ctx) {
46819
46908
  suggestionPainted = suffix !== null;
46820
46909
  clearLine2(process.stdout, 0);
46821
46910
  cursorTo2(process.stdout, 0);
46822
- process.stdout.write(prompt + line + (suffix ? chalk98.dim(suffix) : ""));
46911
+ process.stdout.write(prompt + line + (suffix ? chalk99.dim(suffix) : ""));
46823
46912
  cursorTo2(process.stdout, promptWidth + cursor);
46824
46913
  }
46825
46914
  function appendTurnLine(current, promptLabel, currentSummary) {
46826
- const currentLine = currentSummary ? `${promptLabel} ${current} ${chalk98.white("\u2192")} ${currentSummary}` : `${promptLabel} ${current}`;
46827
- console.log(" " + chalk98.dim(currentLine));
46915
+ const currentLine = currentSummary ? `${promptLabel} ${current} ${chalk99.white("\u2192")} ${currentSummary}` : `${promptLabel} ${current}`;
46916
+ console.log(" " + chalk99.dim(currentLine));
46828
46917
  console.log();
46829
46918
  }
46830
46919
  async function goHome(ctx, version, history, opts) {
@@ -46834,7 +46923,7 @@ async function goHome(ctx, version, history, opts) {
46834
46923
  clearTerminalHome();
46835
46924
  if (opts?.banner) {
46836
46925
  console.log();
46837
- console.log(" " + paint("accent", "\u2713") + " " + chalk98.dim(opts.banner));
46926
+ console.log(" " + paint("accent", "\u2713") + " " + chalk99.dim(opts.banner));
46838
46927
  }
46839
46928
  await printWelcome(ctx, version);
46840
46929
  }
@@ -46857,11 +46946,11 @@ async function handleDispatchResult(result, ctx, version, history) {
46857
46946
  case "unknown":
46858
46947
  if (result.suggestion) {
46859
46948
  console.log(
46860
- " " + chalk98.red(`Unknown command: ${result.token}.`) + chalk98.dim(" Did you mean ") + paint("accent", result.suggestion) + chalk98.dim("?")
46949
+ " " + chalk99.red(`Unknown command: ${result.token}.`) + chalk99.dim(" Did you mean ") + paint("accent", result.suggestion) + chalk99.dim("?")
46861
46950
  );
46862
46951
  } else {
46863
46952
  console.log(
46864
- " " + chalk98.red(`Unknown command: ${result.token}`) + chalk98.dim(" Type ") + paint("accent", "/help") + chalk98.dim(" to see available commands.")
46953
+ " " + chalk99.red(`Unknown command: ${result.token}`) + chalk99.dim(" Type ") + paint("accent", "/help") + chalk99.dim(" to see available commands.")
46865
46954
  );
46866
46955
  }
46867
46956
  break;
@@ -46885,7 +46974,7 @@ async function runRepl(ctx, version) {
46885
46974
  });
46886
46975
  ctx.rl = rl;
46887
46976
  console.log();
46888
- console.log(" " + chalk98.dim("What do you want to look at?"));
46977
+ console.log(" " + chalk99.dim("What do you want to look at?"));
46889
46978
  console.log();
46890
46979
  if (ctx.pendingUpdateCheck) {
46891
46980
  void ctx.pendingUpdateCheck.then((result) => {
@@ -46927,7 +47016,7 @@ async function runRepl(ctx, version) {
46927
47016
  return;
46928
47017
  }
46929
47018
  sigintPrimed = true;
46930
- console.log("\n " + chalk98.dim("Type /exit to quit, or press Ctrl+C again."));
47019
+ console.log("\n " + chalk99.dim("Type /exit to quit, or press Ctrl+C again."));
46931
47020
  };
46932
47021
  rl.on("SIGINT", sigintHandler);
46933
47022
  function shutdownRepl() {
@@ -46964,7 +47053,7 @@ async function runRepl(ctx, version) {
46964
47053
  if (!typed && !enterAction) {
46965
47054
  clearGhostRowAfterSubmit();
46966
47055
  const coach = consumeOrientEmptyEnterCoach(ctx);
46967
- if (coach) console.log(" " + chalk98.dim(coach));
47056
+ if (coach) console.log(" " + chalk99.dim(coach));
46968
47057
  continue;
46969
47058
  }
46970
47059
  const line = typed || enterAction.submit;
@@ -47015,11 +47104,11 @@ async function runRepl(ctx, version) {
47015
47104
  ctx.wizardDepth = 0;
47016
47105
  ctx.secretInputActive = false;
47017
47106
  if (err instanceof Error && err.message === "Cancelled") {
47018
- console.log(" " + chalk98.dim("Cancelled."));
47107
+ console.log(" " + chalk99.dim("Cancelled."));
47019
47108
  } else {
47020
47109
  const { tryRecoverLlmAuthFailure: tryRecoverLlmAuthFailure2 } = await Promise.resolve().then(() => (init_auth_recovery(), auth_recovery_exports));
47021
47110
  if (!await tryRecoverLlmAuthFailure2(ctx, err)) {
47022
- console.error(" " + chalk98.red("Error: " + String(err.message ?? err)));
47111
+ console.error(" " + chalk99.red("Error: " + String(err.message ?? err)));
47023
47112
  }
47024
47113
  }
47025
47114
  }
@@ -47046,10 +47135,10 @@ async function runRepl(ctx, version) {
47046
47135
  await closeSession(ctx);
47047
47136
  }
47048
47137
  if (isTranscriptActive(ctx.sessionId)) {
47049
- console.log(" " + chalk98.dim("Transcript: ") + chalk98.dim(transcriptPathForSession(ctx.sessionId)));
47050
- console.log(" " + chalk98.dim("Context brief: ") + chalk98.dim(contextDocPathForSession(ctx.sessionId)));
47138
+ console.log(" " + chalk99.dim("Transcript: ") + chalk99.dim(transcriptPathForSession(ctx.sessionId)));
47139
+ console.log(" " + chalk99.dim("Context brief: ") + chalk99.dim(contextDocPathForSession(ctx.sessionId)));
47051
47140
  }
47052
- console.log(" " + chalk98.dim(randomGoodbye()));
47141
+ console.log(" " + chalk99.dim(randomGoodbye()));
47053
47142
  }
47054
47143
  function printHelpOneShot() {
47055
47144
  printHelp();
@@ -47057,43 +47146,43 @@ function printHelpOneShot() {
47057
47146
  function printHelp() {
47058
47147
  console.log();
47059
47148
  console.log(" " + sectionHeading("Conversation"));
47060
- console.log(" " + chalk98.dim("Type the question. You do not need a slash command."));
47061
- console.log(" " + chalk98.dim("Paste a CSV path or type ") + paint("accent", '"use demo data"') + chalk98.dim(" to load data."));
47062
- console.log(" " + chalk98.dim("After analysis, type questions in English."));
47063
- console.log(" " + chalk98.dim("Type ") + paint("accent", '"how should we fix this?"') + chalk98.dim(" to make a strategy. Type ") + paint("accent", "keep going") + chalk98.dim(" at confirm to keep working until the plan is ready."));
47064
- console.log(" " + chalk98.dim("Type ") + paint("accent", '"ship a board deck"') + chalk98.dim(" to write a handoff."));
47065
- console.log(" " + chalk98.dim("The ") + paint("accent", "\u203A") + chalk98.dim(" prompt shows brief or deep after analysis. Brief is the default."));
47149
+ console.log(" " + chalk99.dim("Type the question. You do not need a slash command."));
47150
+ console.log(" " + chalk99.dim("Paste a CSV path or type ") + paint("accent", '"use demo data"') + chalk99.dim(" to load data."));
47151
+ console.log(" " + chalk99.dim("After analysis, type questions in English."));
47152
+ console.log(" " + chalk99.dim("Type ") + paint("accent", '"how should we fix this?"') + chalk99.dim(" to make a strategy. Type ") + paint("accent", "keep going") + chalk99.dim(" at confirm to keep working until the plan is ready."));
47153
+ console.log(" " + chalk99.dim("Type ") + paint("accent", '"ship a board deck"') + chalk99.dim(" to write a handoff."));
47154
+ console.log(" " + chalk99.dim("The ") + paint("accent", "\u203A") + chalk99.dim(" prompt shows brief or deep after analysis. Brief is the default."));
47066
47155
  console.log();
47067
47156
  console.log(" " + sectionHeading("Keys"));
47068
47157
  console.log(
47069
- " " + paint("accent", "\u23CE") + chalk98.dim(
47158
+ " " + paint("accent", "\u23CE") + chalk99.dim(
47070
47159
  " Accepts the default. At the main prompt it runs the armed action (yes, use demo data, go ahead, /connect, /update, keep going)."
47071
47160
  )
47072
47161
  );
47073
47162
  console.log(
47074
- " " + chalk98.dim(
47163
+ " " + chalk99.dim(
47075
47164
  " On yes/no questions and menus it accepts yes (or the recommended option). Type something else to decline or pick another path."
47076
47165
  )
47077
47166
  );
47078
47167
  console.log(
47079
- " " + paint("accent", "b") + chalk98.dim(" / ") + paint("accent", "back") + chalk98.dim(" Steps back one confirm gate, or leaves a modal. Does not unload data or undo compute.")
47168
+ " " + paint("accent", "b") + chalk99.dim(" / ") + paint("accent", "back") + chalk99.dim(" Steps back one confirm gate, or leaves a modal. Does not unload data or undo compute.")
47080
47169
  );
47081
47170
  console.log(
47082
- " " + chalk98.dim("On ") + paint("accent", "/settings") + chalk98.dim(" menus, ") + paint("accent", "b") + chalk98.dim(" leaves the hub (same as Done).")
47171
+ " " + chalk99.dim("On ") + paint("accent", "/settings") + chalk99.dim(" menus, ") + paint("accent", "b") + chalk99.dim(" leaves the hub (same as Done).")
47083
47172
  );
47084
47173
  console.log(
47085
- " " + paint("accent", "more") + chalk98.dim(" Shows the full strategy brief after the short summary.")
47174
+ " " + paint("accent", "more") + chalk99.dim(" Shows the full strategy brief after the short summary.")
47086
47175
  );
47087
47176
  console.log(
47088
- " " + paint("accent", "expand") + chalk98.dim(
47177
+ " " + paint("accent", "expand") + chalk99.dim(
47089
47178
  " After a short Q&A answer, shows more reasoning for that same answer. With no last answer, expands the strategy brief when one is on screen."
47090
47179
  )
47091
47180
  );
47092
47181
  console.log(
47093
- " " + paint("accent", "cancel") + chalk98.dim(" Drops the current modal overlay (strategy, think, ship, unconfirmed scope).")
47182
+ " " + paint("accent", "cancel") + chalk99.dim(" Drops the current modal overlay (strategy, think, ship, unconfirmed scope).")
47094
47183
  );
47095
47184
  console.log(
47096
- " " + chalk98.dim("On ") + paint("accent", "/tour") + chalk98.dim(" slides, ") + paint("accent", "b") + chalk98.dim(" moves to the previous slide.")
47185
+ " " + chalk99.dim("On ") + paint("accent", "/tour") + chalk99.dim(" slides, ") + paint("accent", "b") + chalk99.dim(" moves to the previous slide.")
47097
47186
  );
47098
47187
  console.log();
47099
47188
  console.log(" " + sectionHeading("Shortcuts"));
@@ -47121,11 +47210,11 @@ function printHelp() {
47121
47210
  ];
47122
47211
  const maxW = Math.max(...shortcuts.map(([c]) => c.length)) + 2;
47123
47212
  for (const [cmd, desc] of shortcuts) {
47124
- console.log(` ${paint("accent", padRight(cmd, maxW))} ${chalk98.dim(desc)}`);
47213
+ console.log(` ${paint("accent", padRight(cmd, maxW))} ${chalk99.dim(desc)}`);
47125
47214
  }
47126
47215
  console.log();
47127
47216
  console.log(" " + sectionHeading("Teach NTRP"));
47128
- console.log(" " + chalk98.dim("NTRP learns your business over time. There are three ways to teach it:"));
47217
+ console.log(" " + chalk99.dim("NTRP learns your business over time. There are three ways to teach it:"));
47129
47218
  const teach = [
47130
47219
  ["/remember <fact>", "Store a fact, a decision, or a preference"],
47131
47220
  ["/recall [topic]", "Show what NTRP stores about your business"],
@@ -47134,13 +47223,13 @@ function printHelp() {
47134
47223
  ];
47135
47224
  const teachMaxW = Math.max(...teach.map(([c]) => c.length)) + 2;
47136
47225
  for (const [cmd, desc] of teach) {
47137
- console.log(` ${paint("accent", padRight(cmd, teachMaxW))} ${chalk98.dim(desc)}`);
47226
+ console.log(` ${paint("accent", padRight(cmd, teachMaxW))} ${chalk99.dim(desc)}`);
47138
47227
  }
47139
- console.log(" " + chalk98.dim("When a key is connected, NTRP stores a small number of facts when you close a session."));
47228
+ console.log(" " + chalk99.dim("When a key is connected, NTRP stores a small number of facts when you close a session."));
47140
47229
  console.log();
47141
- console.log(" " + chalk98.dim("Factory reset: type ") + paint("accent", "/scratch") + chalk98.dim("."));
47230
+ console.log(" " + chalk99.dim("Factory reset: type ") + paint("accent", "/scratch") + chalk99.dim("."));
47142
47231
  console.log();
47143
- console.log(" " + chalk98.dim("These commands also work: ") + paint("accent", "/new") + chalk98.dim(", ") + paint("accent", "/diagnose") + chalk98.dim(", ") + paint("accent", "/metrics") + chalk98.dim(", ") + paint("accent", "/session") + chalk98.dim("."));
47232
+ console.log(" " + chalk99.dim("These commands also work: ") + paint("accent", "/new") + chalk99.dim(", ") + paint("accent", "/diagnose") + chalk99.dim(", ") + paint("accent", "/metrics") + chalk99.dim(", ") + paint("accent", "/session") + chalk99.dim("."));
47144
47233
  console.log();
47145
47234
  }
47146
47235
  var REPL_BUILTINS, ANSI_PATTERN, GOODBYES, GHOST_HINTS, ghostHintTurn, activeGhostHint, suggestionPainted;
@@ -47240,7 +47329,7 @@ init_errors();
47240
47329
  init_diagnostics();
47241
47330
  init_types();
47242
47331
  init_version();
47243
- import chalk99 from "chalk";
47332
+ import chalk100 from "chalk";
47244
47333
  var VERSION = getInstalledVersion();
47245
47334
  function exitIfVersionFlag(argv) {
47246
47335
  const rest = argv.slice(2).filter((a) => a === "--version" || a === "-v");
@@ -47278,7 +47367,7 @@ async function main() {
47278
47367
  debug: args.globals.debug
47279
47368
  });
47280
47369
  if (!ctx.execution.color) {
47281
- chalk99.level = 0;
47370
+ chalk100.level = 0;
47282
47371
  }
47283
47372
  fatalReporting = {
47284
47373
  command: firstToken(args.input) || "ntrp",
@@ -47295,16 +47384,16 @@ async function main() {
47295
47384
  if (isStructuredOutput(ctx.execution)) {
47296
47385
  emitError(cmd || "ntrp", new NtrpError("license_invalid", lic.message, 3 /* Auth */));
47297
47386
  }
47298
- console.error(chalk99.red(`
47387
+ console.error(chalk100.red(`
47299
47388
  ${lic.message}`));
47300
- console.error(chalk99.dim(" Trial's over \u2014 /upgrade and paste your key.\n"));
47389
+ console.error(chalk100.dim(" Trial's over \u2014 /upgrade and paste your key.\n"));
47301
47390
  process.exit(1);
47302
47391
  }
47303
47392
  }
47304
47393
  const PROFILE_HINT_SKIP = /* @__PURE__ */ new Set(["onboard", "setup", "config", "connect", "activate", "help", "home", "exit", "quit", "clear", "profile", "progress", "deepdive", "tour", "settings", "voice", "logs"]);
47305
47394
  if (!isProfileConfigured() && !PROFILE_HINT_SKIP.has(cmd) && !ctx.execution.quiet) {
47306
47395
  console.error(
47307
- " " + chalk99.dim("Tip: run ") + paint("accent", "ntrp") + chalk99.dim(" interactively to set up your company profile for richer answers.")
47396
+ " " + chalk100.dim("Tip: run ") + paint("accent", "ntrp") + chalk100.dim(" interactively to set up your company profile for richer answers.")
47308
47397
  );
47309
47398
  }
47310
47399
  const result = await dispatch(args.input, ctx);
@@ -47316,12 +47405,12 @@ async function main() {
47316
47405
  }
47317
47406
  if (result.suggestion) {
47318
47407
  console.error(
47319
- chalk99.red(` Unknown command: ${result.token}.`) + chalk99.dim(" Did you mean ") + paint("accent", result.suggestion) + chalk99.dim("?")
47408
+ chalk100.red(` Unknown command: ${result.token}.`) + chalk100.dim(" Did you mean ") + paint("accent", result.suggestion) + chalk100.dim("?")
47320
47409
  );
47321
47410
  } else {
47322
- console.error(chalk99.red(` Unknown command: ${result.token}`));
47411
+ console.error(chalk100.red(` Unknown command: ${result.token}`));
47323
47412
  }
47324
- console.error(chalk99.dim(" Run 'ntrp' for the interactive prompt."));
47413
+ console.error(chalk100.dim(" Run 'ntrp' for the interactive prompt."));
47325
47414
  process.exit(1);
47326
47415
  break;
47327
47416
  case "help":
@@ -47374,7 +47463,7 @@ async function main() {
47374
47463
  if (justUpdated) {
47375
47464
  clearTerminalHome();
47376
47465
  console.log();
47377
- console.log(" " + paint("accent", "\u2713") + " " + chalk99.dim(`Now running v${justUpdated.to}`));
47466
+ console.log(" " + paint("accent", "\u2713") + " " + chalk100.dim(`Now running v${justUpdated.to}`));
47378
47467
  }
47379
47468
  await printWelcome2(ctx, VERSION);
47380
47469
  await runRepl(ctx, VERSION);
@@ -47388,9 +47477,9 @@ async function die(err, scope) {
47388
47477
  if (fatalReporting.structured) {
47389
47478
  emitError(fatalReporting.command, err);
47390
47479
  }
47391
- console.error(chalk99.red(` ${describeError(err)}`));
47480
+ console.error(chalk100.red(` ${describeError(err)}`));
47392
47481
  if (!(err instanceof NtrpError) && err instanceof Error && err.stack) {
47393
- console.error(chalk99.dim(err.stack.split("\n").slice(1).join("\n")));
47482
+ console.error(chalk100.dim(err.stack.split("\n").slice(1).join("\n")));
47394
47483
  }
47395
47484
  process.exit(err instanceof NtrpError ? err.exitCode : 1 /* RuntimeError */);
47396
47485
  }
@@ -1088,7 +1088,7 @@ import chalk3 from "chalk";
1088
1088
  function printDeepdiveHint(metricId, label) {
1089
1089
  const name = label ?? metricId;
1090
1090
  console.log(
1091
- " " + chalk3.dim("How this number works: ") + paint("accent", `/tour ${metricId}`) + chalk3.dim(` \u2014 ${name}`)
1091
+ " " + chalk3.dim(`Ask what ${name.toLowerCase()} means \u2014 or `) + paint("accent", `/tour ${metricId}`) + chalk3.dim(" for the card")
1092
1092
  );
1093
1093
  console.log();
1094
1094
  }
@@ -12989,21 +12989,21 @@ var init_metric_definitions = __esm({
12989
12989
  kind: "vital",
12990
12990
  label: "Freshness",
12991
12991
  group: "Vital Signs",
12992
- tagline: "Is the CRM still talking about live work \u2014 or a museum of old deals?",
12993
- how_computed: "NTRP computes a weighted average of people, organizations, and opportunities with recent activity. Open opportunities must also not be past-due. Default windows: people and organizations 90 days, opportunities 30 days. Weights are 35 / 30 / 35.",
12992
+ tagline: "Is the CRM talking about live deals \u2014 or a museum of old ones?",
12993
+ how_computed: "We look at how recently people, companies, and deals got a touch. Deals need a fresher pulse than people or companies, and past-due deals count as stale. The formula lines on this card show the exact mix and windows.",
12994
12994
  formula_lines: [
12995
12995
  "freshness = people%\xD70.35 + orgs%\xD70.30 + opps%\xD70.35",
12996
12996
  "people/orgs fresh if activity within 90d",
12997
12997
  "opps fresh if activity within 30d AND not past-due"
12998
12998
  ],
12999
- meaning: 'We treat Freshness as a truth check on the pipeline number. Quiet days mean different things by motion: in enterprise, 30 quiet days can be normal cadence; in a velocity book, that same silence often means the deal is already dead. Stale open amount is why we show "pipeline at risk."',
13000
- expert_read: `A useful first cut is by owner and by stage \u2014 reds tend to clump on people or process, not sprinkle evenly. A sudden cliff is usually a broken integration or a departed rep, not gradual decay. Watch for the false positive too: bulk-imported records nobody has touched yet can look "stale" when they're just new.`,
12999
+ meaning: `Freshness asks whether the pipeline number is still true. Quiet deals pad the forecast. Like milk dates on a shelf \u2014 past the date, don't trust the carton. Stale open dollars show as "pipeline at risk."`,
13000
+ expert_read: "Cut by owner and stage first \u2014 reds usually clump, they don't sprinkle. A sudden cliff is often a broken sync or a departed rep, not slow decay. New bulk imports can look stale when they're just new.",
13001
13001
  deepdive: [
13002
- "Status bands: green 80 or more, yellow 60 or more, red below 60. Motion presets can change the windows.",
13003
- 'Dollar translation: sum of amount on stale open opportunities \u2192 "pipeline at risk".',
13004
- "Layer 1 of the gating stack. A red score here limits trust in later layers.",
13005
- "Trigger play: Clean Dead Pipeline (clean-dead-pipeline) when the score is below 60.",
13006
- "Levers: stale-deal alert at N quiet days. Weekly hygiene scrub. Enrichment refresh on quiet records. Signal-triggered reactivation for paid dormant accounts."
13002
+ "Green 80+, yellow 60+, red below 60. Motion presets can change the windows.",
13003
+ 'Dollars: sum of amount on stale open deals \u2192 "pipeline at risk".',
13004
+ "Read this first. If Freshness is red, later vitals are harder to trust.",
13005
+ "Weak score \u2192 play Clean Dead Pipeline.",
13006
+ "Useful moves: stale-deal alert, weekly hygiene scrub, enrichment on quiet records."
13007
13007
  ],
13008
13008
  visual: {
13009
13009
  kind: "bars",
@@ -13027,21 +13027,21 @@ var init_metric_definitions = __esm({
13027
13027
  kind: "vital",
13028
13028
  label: "Flow Rate",
13029
13029
  group: "Vital Signs",
13030
- tagline: "Where does deal motion slow \u2014 and what's stuck behind that bottleneck?",
13031
- how_computed: "NTRP sets a base score from average open-deal age versus max_days. It then applies a penalty of up to 20 for the share of stuck deals. A deal is stuck when it has no update beyond stuck_days, or a past-due close. Status uses average open age, not the score alone.",
13030
+ tagline: "Where do deals stop moving \u2014 and what's piled up behind that stage?",
13031
+ how_computed: "We start from how old open deals are versus a max window, then shave points for the share that look stuck. A deal is stuck when it goes quiet too long or its close date is already past. The formula lines show the base, the penalty, and the stuck rules.",
13032
13032
  formula_lines: [
13033
13033
  "base = 100 \xD7 (1 \u2212 avgOpenAge / max_days)",
13034
13034
  "score = base \u2212 stuckSharePenalty (\u226420)",
13035
13035
  "stuck = no update > stuck_days OR past-due close"
13036
13036
  ],
13037
- meaning: 'We think of Flow Rate as velocity risk with a dollar tag. When deals stop advancing \u2014 and especially when close dates slip past due \u2014 next quarter starts to feel optimistic on paper. The dollar line is the amount still "stuck in pipeline."',
13038
- expert_read: "We prefer stage age over deal age alone: find the stage where advancement collapses \u2014 it's usually one stage, not everywhere. Compare stuck age to this company's own median cycle rather than a generic norm. Stuck deals plus past-due closes are often a forecast-credibility problem before they're a revenue miss.",
13037
+ meaning: 'Flow Rate asks whether deals are still advancing or sitting still. Stuck deals pad next quarter on paper. Like traffic behind a closed lane \u2014 the jam is one stage, not the whole highway. Dollars on stuck deals show as "stuck in pipeline."',
13038
+ expert_read: "Find the stage where advancement collapses first \u2014 it's usually one stage, not everywhere. Compare stuck age to this company's own median cycle rather than a generic norm. Stuck deals plus past-due closes are often a forecast-credibility problem before they're a revenue miss.",
13039
13039
  deepdive: [
13040
- "Status uses average open age. Green is 45 days or less. Yellow is 90 days or less. Else red. Defaults: max_days 120, stuck_days 60.",
13041
- 'Dollar translation: sum of amount on stuck deals \u2192 "stuck in pipeline".',
13042
- "Layer 2 of the gating stack, with Drop Rate.",
13043
- "Trigger play: Unstick the Pipeline (unstick-pipeline) when the score is weak.",
13044
- "Levers: stage-age report. Past-due close cleanup. Progression plans on stuck deals. Forecast hygiene on optimistic close dates."
13040
+ "Status uses average open age. Green \u226445 days, yellow \u226490, else red. Defaults: max_days 120, stuck_days 60.",
13041
+ 'Dollars: sum of amount on stuck deals \u2192 "stuck in pipeline".',
13042
+ "Read with Drop Rate \u2014 stuck deals and handoff leaks often show up together.",
13043
+ "Weak score \u2192 play Unstick the Pipeline.",
13044
+ "Useful moves: stage-age report, past-due close cleanup, progression plans on stuck deals, forecast hygiene on optimistic close dates."
13045
13045
  ],
13046
13046
  visual: {
13047
13047
  kind: "funnel",
@@ -13067,21 +13067,21 @@ var init_metric_definitions = __esm({
13067
13067
  kind: "vital",
13068
13068
  label: "Drop Rate",
13069
13069
  group: "Vital Signs",
13070
- tagline: "Where does paid demand fall between systems before anyone works it?",
13071
- how_computed: "NTRP blends cross-system retention and opportunity retention. Cross-system retention is marketing people also present in sales. Opportunity retention is open opportunities that are not abandoned. Default weights: cross-system 60 percent, opportunity retention 40 percent. Abandoned means open opportunities with no activity in 30 days.",
13070
+ tagline: "Where does paid demand fall between systems before a rep works it?",
13071
+ how_computed: "We blend two checks: do marketing people also show up in sales CRM, and are open deals still being worked (not abandoned). The formula lines show the weights and the 30-day abandon window.",
13072
13072
  formula_lines: [
13073
13073
  "score = crossSystemRetention\xD70.6 + oppRetention\xD70.4",
13074
13074
  "cross-system = marketing people also in sales CRM",
13075
13075
  "abandoned = open opps with no activity in 30d"
13076
13076
  ],
13077
- meaning: 'We read Drop Rate as the price of the handoff leak \u2014 budget already spent on leads that never reach a working rep. The dollar estimate is droppedCount \xD7 conversion \xD7 avg deal size: "est. lost at handoff."',
13078
- expert_read: 'In practice this usually looks like a systems story: routing rules, unassigned territories, dead rep queues, or a sync gap between marketing and CRM \u2014 more often than "lazy reps." A useful first cut is by lead source; the leak tends to concentrate in one or two sources. The cheapest pipeline a business can buy is often the leads it already paid for.',
13077
+ meaning: 'Drop Rate prices the handoff leak \u2014 money you already spent on leads that never reach a working rep. Like buying groceries and leaving half the bags in the parking lot. The dollar line is "est. lost at handoff."',
13078
+ expert_read: "A useful first cut is by lead source \u2014 the leak usually concentrates in one or two, not everywhere. Routing rules, unassigned territories, dead queues, or a sync gap between marketing and CRM show up more often than lazy reps. The cheapest pipeline you can buy is often the leads you already paid for.",
13079
13079
  deepdive: [
13080
- "Status bands: green 80 or more, yellow 60 or more, red below 60.",
13081
- 'Dollar translation: dropped \xD7 conversion \xD7 avg deal (fallback: drop% \xD7 open pipeline) \u2192 "est. lost at handoff".',
13082
- "Layer 2 of the gating stack, with Flow Rate.",
13083
- "Trigger play: Fix the Handoff Gap (fix-handoff-gap) when drop is high.",
13084
- "Levers: source-level handoff audit. Routing and sync repair. Time-to-first-touch SLA. Weekly marketing-only-leads report."
13080
+ "Green 80+, yellow 60+, red below 60.",
13081
+ 'Dollars: dropped \xD7 conversion \xD7 avg deal (fallback: drop% \xD7 open pipeline) \u2192 "est. lost at handoff".',
13082
+ "Read with Flow Rate \u2014 handoff leaks and stuck deals often show up together.",
13083
+ "Weak score \u2192 play Fix the Handoff Gap.",
13084
+ "Useful moves: source-level handoff audit, routing and sync repair, time-to-first-touch SLA, weekly marketing-only-leads report."
13085
13085
  ],
13086
13086
  visual: {
13087
13087
  kind: "funnel",
@@ -13106,21 +13106,21 @@ var init_metric_definitions = __esm({
13106
13106
  kind: "vital",
13107
13107
  label: "Signal:Noise",
13108
13108
  group: "Vital Signs",
13109
- tagline: "How much of the team's effort still aims at deals that can close?",
13110
- how_computed: "Over a 90-day lookback, NTRP computes (signal activities / all activities) \xD7 100. Signal is activity linked to an open opportunity, a pipeline person, or a pipeline organization.",
13109
+ tagline: "How much effort still aims at accounts that can close \u2014 versus dead ends?",
13110
+ how_computed: "We look at the last 90 days of activity and ask what share is signal \u2014 linked to an open deal, a pipeline person, or a pipeline company. The formula lines show the ratio and what counts as signal.",
13111
13111
  formula_lines: [
13112
13112
  "score = (signalCount / activityCount) \xD7 100",
13113
13113
  "signal = linked to open opp / pipeline person / pipeline org",
13114
13114
  "lookback = trailing 90 days"
13115
13115
  ],
13116
- meaning: `We treat Signal:Noise as activity efficiency with a capacity price. Noise hours \xD7 hours_per_activity \xD7 rep_hourly_cost becomes "misdirected effort" \u2014 energy aimed at accounts that can't still close.`,
13116
+ meaning: 'Signal:Noise asks whether activity still points at deals that can close. Noise hours are capacity spent on dead ends. Like a radio stuck between stations \u2014 lots of static, little signal. Those hours show as "misdirected effort."',
13117
13117
  expert_read: "A useful cut is by rep and by account status. Persistent noise often means reps keep working dead accounts they already know \u2014 stale targeting or coverage models more than a coaching gap. Also check whether activity is logged against closed or unlinked records; that can be a hygiene artifact pretending to be effort.",
13118
13118
  deepdive: [
13119
- "Status bands: green 65 or more, yellow 40 or more, red below 40.",
13120
- "Dollar defaults: 0.25 hours/activity \xD7 $75/hr (config: hours_per_activity, rep_hourly_cost).",
13121
- "Layer 3 of the gating stack. Trust Freshness, Flow Rate, and Drop Rate before you read activity efficiency.",
13122
- "Trigger play: Retarget Misdirected Effort (retarget-effort) when the score is low.",
13123
- "Levers: refresh account lists. Signal-based targeting. Stop logging against closed or unlinked records. Coverage-model redesign."
13119
+ "Green 65+, yellow 40+, red below 40.",
13120
+ 'Dollar defaults: 0.25 hours/activity \xD7 $75/hr (config: hours_per_activity, rep_hourly_cost) \u2192 "misdirected effort".',
13121
+ "Read Freshness, Flow Rate, and Drop Rate before you trust this score.",
13122
+ "Weak score \u2192 play Retarget Misdirected Effort.",
13123
+ "Useful moves: refresh account lists, signal-based targeting, stop logging against closed or unlinked records, coverage-model redesign."
13124
13124
  ],
13125
13125
  visual: {
13126
13126
  kind: "split",
@@ -13143,21 +13143,21 @@ var init_metric_definitions = __esm({
13143
13143
  kind: "vital",
13144
13144
  label: "Thread Depth",
13145
13145
  group: "Vital Signs",
13146
- tagline: "How fragile is revenue if one champion changes jobs?",
13147
- how_computed: "Percent of open deals with at least multi_thread_threshold distinct people active in the last 90 days. The default threshold is 2. People include opportunity-direct contacts and same-organization activity.",
13146
+ tagline: "How fragile is a deal if one champion changes jobs?",
13147
+ how_computed: "We ask what share of open deals have at least two people active in the last 90 days. Contacts on the deal and activity at the same company both count. The formula lines show the threshold.",
13148
13148
  formula_lines: [
13149
13149
  "score = % open deals with \u22652 active people (90d)",
13150
13150
  "people counted via opp contacts + same-org activity",
13151
13151
  "threshold configurable (default 2)"
13152
13152
  ],
13153
- meaning: "We read Thread Depth as resilience risk. One single-threaded mega-deal can outweigh ten small ones \u2014 and late-cycle single-threading is especially scary because champions change jobs. The dollar line sums amount on single-threaded deals.",
13154
- expert_read: "Weight by deal size when you triage. Single-threading late in the cycle tends to matter more than early. In enterprise motions, thin threads are often a leading indicator of slipped quarters: the champion leaves and there's no second contact warm enough to carry the deal.",
13153
+ meaning: 'Thread Depth asks how many people keep each deal warm. One contact is a single point of failure \u2014 if they leave, the deal often dies with them. Like a bridge with one cable. Dollars on thin deals show as "single-threaded."',
13154
+ expert_read: "Weight by deal size when you triage \u2014 one thin mega-deal can outweigh ten small ones. Late-cycle single-threading tends to matter more than early. In enterprise motions, thin threads often lead to slipped quarters: the champion leaves and nobody else is warm enough to carry the deal.",
13155
13155
  deepdive: [
13156
- "Status bands: green 65 or more, yellow 40 or more, red below 40.",
13157
- 'Dollar translation: sum of amount on single-threaded deals \u2192 "single-threaded".',
13158
- "Layer 4 of the gating stack. Read this last, after the upstream vital signs.",
13159
- "Trigger play: Multi-Thread Your Deals (multi-thread-deals) when depth is low.",
13160
- "Levers: buying-committee map. Warm internal referral first. CRM contact roles. Mid-stage single-thread alerts. Champion job-change signals."
13156
+ "Green 65+, yellow 40+, red below 40.",
13157
+ 'Dollars: sum of amount on single-threaded deals \u2192 "single-threaded".',
13158
+ "Read this last, after Freshness, Flow Rate, Drop Rate, and Signal:Noise.",
13159
+ "Weak score \u2192 play Multi-Thread Your Deals.",
13160
+ "Useful moves: buying-committee map, warm internal referral first, CRM contact roles, mid-stage single-thread alerts, champion job-change signals."
13161
13161
  ],
13162
13162
  visual: {
13163
13163
  kind: "bars",
@@ -18778,7 +18778,7 @@ function consumeOrientEmptyEnterCoach(ctx) {
18778
18778
  if (!hasValidLicense()) {
18779
18779
  return "Type /activate to paste a key. Type /checkout if you need to sign up.";
18780
18780
  }
18781
- return "Type a question, type use demo data, or type /tour. Enter alone does not start a step here.";
18781
+ return "Type a question, or type use demo data. Enter alone does not start a step here.";
18782
18782
  }
18783
18783
  function formatPhaseLabel(phase) {
18784
18784
  switch (phase) {
@@ -19698,7 +19698,10 @@ function buildExploreTeachingGhosts(ctx, staticExplore) {
19698
19698
  if (cached2 && cached2.length > 0) {
19699
19699
  const ghosts = asksToGhostHints(cached2);
19700
19700
  const gating = ctx.snapshot.computeResult?.aggregate.gating_vital_sign;
19701
- if (gating) ghosts.push(`try /tour ${gating}`);
19701
+ if (gating) {
19702
+ const label = (VITAL_SIGN_LABELS[gating] ?? gating).toLowerCase();
19703
+ ghosts.push(`try "why is ${label} red?"`);
19704
+ }
19702
19705
  ghosts.push('try "how should we fix this?"');
19703
19706
  return dedupeGhosts(ghosts);
19704
19707
  }
@@ -19709,7 +19712,11 @@ function buildExploreTeachingGhosts(ctx, staticExplore) {
19709
19712
  });
19710
19713
  return dedupeGhosts([
19711
19714
  ...asksToGhostHints(asks),
19712
- `try /tour ${health.gating_vital_sign}`,
19715
+ (() => {
19716
+ const g = health.gating_vital_sign;
19717
+ const label = (VITAL_SIGN_LABELS[g] ?? g).toLowerCase();
19718
+ return `try "why is ${label} red?"`;
19719
+ })(),
19713
19720
  'try "how should we fix this?"'
19714
19721
  ]);
19715
19722
  }
@@ -24648,8 +24655,8 @@ var init_ghost_hints = __esm({
24648
24655
  PHASE_GHOST_HINTS = {
24649
24656
  orient: [
24650
24657
  'try "pipeline health"',
24651
- "try /tour",
24652
- "try /tour guide",
24658
+ 'try "how healthy is the pipeline?"',
24659
+ 'try "what should I do next?"',
24653
24660
  'try "is our retention real for the board?"',
24654
24661
  'try "what is the most expensive problem to solve?"',
24655
24662
  'try "board deck on Q3"'
@@ -24666,7 +24673,7 @@ var init_ghost_hints = __esm({
24666
24673
  'try "how should we fix this?"',
24667
24674
  'try "which segment is weakest?"',
24668
24675
  'try "what is ARR?"',
24669
- "try /tour freshness",
24676
+ 'try "why is freshness red?"',
24670
24677
  'try "ship a board deck"'
24671
24678
  ]
24672
24679
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonnechasser/ntrp",
3
- "version": "2.2.1",
3
+ "version": "2.3.2",
4
4
  "description": "GTM Health Diagnostic CLI — local pipeline analysis tool",
5
5
  "homepage": "https://ntrp.sonnechasser.com",
6
6
  "repository": {