@sonnechasser/ntrp 1.4.3 → 1.4.6
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 +629 -239
- package/dist/mcp/server.js +325 -98
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6279,6 +6279,15 @@ var init_pseudonymize = __esm({
|
|
|
6279
6279
|
});
|
|
6280
6280
|
|
|
6281
6281
|
// src/config/profile.ts
|
|
6282
|
+
var profile_exports = {};
|
|
6283
|
+
__export(profile_exports, {
|
|
6284
|
+
isProfileConfigured: () => isProfileConfigured,
|
|
6285
|
+
loadProfile: () => loadProfile,
|
|
6286
|
+
profileExists: () => profileExists,
|
|
6287
|
+
profilePath: () => profilePath,
|
|
6288
|
+
saveProfile: () => saveProfile,
|
|
6289
|
+
updateProfile: () => updateProfile
|
|
6290
|
+
});
|
|
6282
6291
|
import { readFileSync as readFileSync11, writeFileSync as writeFileSync10, existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
|
|
6283
6292
|
import { join as join12 } from "path";
|
|
6284
6293
|
function ensureDir5() {
|
|
@@ -10125,7 +10134,7 @@ function buildSlideContent(explainer, opts = {}) {
|
|
|
10125
10134
|
lines.push(...visLines);
|
|
10126
10135
|
lines.push("");
|
|
10127
10136
|
}
|
|
10128
|
-
lines.push(sectionHeading("
|
|
10137
|
+
lines.push(sectionHeading("How we think about it"));
|
|
10129
10138
|
pushWrapped(lines, explainer.meaning, inner, " ");
|
|
10130
10139
|
lines.push("");
|
|
10131
10140
|
if (!opts.skipFormula) {
|
|
@@ -10137,7 +10146,7 @@ function buildSlideContent(explainer, opts = {}) {
|
|
|
10137
10146
|
lines.push("");
|
|
10138
10147
|
}
|
|
10139
10148
|
if (opts.deepdive) {
|
|
10140
|
-
lines.push(sectionHeading("
|
|
10149
|
+
lines.push(sectionHeading("How to read it"));
|
|
10141
10150
|
pushWrapped(lines, explainer.expert_read, inner, " ");
|
|
10142
10151
|
lines.push("");
|
|
10143
10152
|
for (const bullet of explainer.deepdive) {
|
|
@@ -11406,15 +11415,15 @@ var init_metric_definitions = __esm({
|
|
|
11406
11415
|
kind: "vital",
|
|
11407
11416
|
label: "Freshness",
|
|
11408
11417
|
group: "Vital Signs",
|
|
11409
|
-
tagline: "
|
|
11418
|
+
tagline: "Is the CRM still talking about live work \u2014 or a museum of old deals?",
|
|
11410
11419
|
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.",
|
|
11411
11420
|
formula_lines: [
|
|
11412
11421
|
"freshness = people%\xD70.35 + orgs%\xD70.30 + opps%\xD70.35",
|
|
11413
11422
|
"people/orgs fresh if activity within 90d",
|
|
11414
11423
|
"opps fresh if activity within 30d AND not past-due"
|
|
11415
11424
|
],
|
|
11416
|
-
meaning:
|
|
11417
|
-
expert_read:
|
|
11425
|
+
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."',
|
|
11426
|
+
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.`,
|
|
11418
11427
|
deepdive: [
|
|
11419
11428
|
"Status bands: green 80 or more, yellow 60 or more, red below 60. Motion presets can change the windows.",
|
|
11420
11429
|
'Dollar translation: sum of amount on stale open opportunities \u2192 "pipeline at risk".',
|
|
@@ -11444,15 +11453,15 @@ var init_metric_definitions = __esm({
|
|
|
11444
11453
|
kind: "vital",
|
|
11445
11454
|
label: "Flow Rate",
|
|
11446
11455
|
group: "Vital Signs",
|
|
11447
|
-
tagline: "
|
|
11456
|
+
tagline: "Where does deal motion slow \u2014 and what's stuck behind that bottleneck?",
|
|
11448
11457
|
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.",
|
|
11449
11458
|
formula_lines: [
|
|
11450
11459
|
"base = 100 \xD7 (1 \u2212 avgOpenAge / max_days)",
|
|
11451
11460
|
"score = base \u2212 stuckSharePenalty (\u226420)",
|
|
11452
11461
|
"stuck = no update > stuck_days OR past-due close"
|
|
11453
11462
|
],
|
|
11454
|
-
meaning:
|
|
11455
|
-
expert_read: "
|
|
11463
|
+
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."',
|
|
11464
|
+
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.",
|
|
11456
11465
|
deepdive: [
|
|
11457
11466
|
"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.",
|
|
11458
11467
|
'Dollar translation: sum of amount on stuck deals \u2192 "stuck in pipeline".',
|
|
@@ -11484,15 +11493,15 @@ var init_metric_definitions = __esm({
|
|
|
11484
11493
|
kind: "vital",
|
|
11485
11494
|
label: "Drop Rate",
|
|
11486
11495
|
group: "Vital Signs",
|
|
11487
|
-
tagline: "Where
|
|
11496
|
+
tagline: "Where does paid demand fall between systems before anyone works it?",
|
|
11488
11497
|
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.",
|
|
11489
11498
|
formula_lines: [
|
|
11490
11499
|
"score = crossSystemRetention\xD70.6 + oppRetention\xD70.4",
|
|
11491
11500
|
"cross-system = marketing people also in sales CRM",
|
|
11492
11501
|
"abandoned = open opps with no activity in 30d"
|
|
11493
11502
|
],
|
|
11494
|
-
meaning:
|
|
11495
|
-
expert_read:
|
|
11503
|
+
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."',
|
|
11504
|
+
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.',
|
|
11496
11505
|
deepdive: [
|
|
11497
11506
|
"Status bands: green 80 or more, yellow 60 or more, red below 60.",
|
|
11498
11507
|
'Dollar translation: dropped \xD7 conversion \xD7 avg deal (fallback: drop% \xD7 open pipeline) \u2192 "est. lost at handoff".',
|
|
@@ -11523,15 +11532,15 @@ var init_metric_definitions = __esm({
|
|
|
11523
11532
|
kind: "vital",
|
|
11524
11533
|
label: "Signal:Noise",
|
|
11525
11534
|
group: "Vital Signs",
|
|
11526
|
-
tagline: "How much
|
|
11535
|
+
tagline: "How much of the team's effort still aims at deals that can close?",
|
|
11527
11536
|
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.",
|
|
11528
11537
|
formula_lines: [
|
|
11529
11538
|
"score = (signalCount / activityCount) \xD7 100",
|
|
11530
11539
|
"signal = linked to open opp / pipeline person / pipeline org",
|
|
11531
11540
|
"lookback = trailing 90 days"
|
|
11532
11541
|
],
|
|
11533
|
-
meaning:
|
|
11534
|
-
expert_read: "
|
|
11542
|
+
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.`,
|
|
11543
|
+
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.",
|
|
11535
11544
|
deepdive: [
|
|
11536
11545
|
"Status bands: green 65 or more, yellow 40 or more, red below 40.",
|
|
11537
11546
|
"Dollar defaults: 0.25 hours/activity \xD7 $75/hr (config: hours_per_activity, rep_hourly_cost).",
|
|
@@ -11560,15 +11569,15 @@ var init_metric_definitions = __esm({
|
|
|
11560
11569
|
kind: "vital",
|
|
11561
11570
|
label: "Thread Depth",
|
|
11562
11571
|
group: "Vital Signs",
|
|
11563
|
-
tagline: "How fragile is
|
|
11572
|
+
tagline: "How fragile is revenue if one champion changes jobs?",
|
|
11564
11573
|
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.",
|
|
11565
11574
|
formula_lines: [
|
|
11566
11575
|
"score = % open deals with \u22652 active people (90d)",
|
|
11567
11576
|
"people counted via opp contacts + same-org activity",
|
|
11568
11577
|
"threshold configurable (default 2)"
|
|
11569
11578
|
],
|
|
11570
|
-
meaning: "
|
|
11571
|
-
expert_read: "Weight by deal size
|
|
11579
|
+
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.",
|
|
11580
|
+
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.",
|
|
11572
11581
|
deepdive: [
|
|
11573
11582
|
"Status bands: green 65 or more, yellow 40 or more, red below 40.",
|
|
11574
11583
|
'Dollar translation: sum of amount on single-threaded deals \u2192 "single-threaded".',
|
|
@@ -11600,14 +11609,14 @@ var init_metric_definitions = __esm({
|
|
|
11600
11609
|
kind: "saas",
|
|
11601
11610
|
label: "ARR",
|
|
11602
11611
|
group: "Revenue",
|
|
11603
|
-
tagline: "How big is the
|
|
11612
|
+
tagline: "How big is the engine \u2014 and what's the mix that got it there?",
|
|
11604
11613
|
how_computed: "Sum of amount on closed-won opportunities in the dataset. This is pipeline-inferred ARR when a pure subscription ledger is not available.",
|
|
11605
11614
|
formula_lines: [
|
|
11606
11615
|
"ARR \u2248 \u03A3 amount on closed-won opportunities",
|
|
11607
11616
|
"New + Expansion = growth \xB7 Churned + Contraction = leakage"
|
|
11608
11617
|
],
|
|
11609
|
-
meaning: "
|
|
11610
|
-
expert_read: "Always decompose growth into new versus expansion
|
|
11618
|
+
meaning: "We treat ARR as the size of the revenue engine \u2014 but the interesting story is the mix. New versus expansion growth, and how much leakage (churn + contraction) ate into it. From CRM exports we often infer this from closed-won amounts when a pure subscription ledger isn't available.",
|
|
11619
|
+
expert_read: "Always decompose growth into new versus expansion before you brief anyone \u2014 the mix is the story. Prefer this company's own trailing history over an external prior. When a number sits below its reliability_gate, we treat it as a hypothesis, not a fact.",
|
|
11611
11620
|
deepdive: [
|
|
11612
11621
|
"Companion metrics: New ARR, Expansion ARR, Churned ARR, Contraction ARR.",
|
|
11613
11622
|
"Estimation method may be ledger, pipeline_inferred, or snapshot. Read confidence and reliability_gate.",
|
|
@@ -11760,15 +11769,15 @@ var init_metric_definitions = __esm({
|
|
|
11760
11769
|
kind: "saas",
|
|
11761
11770
|
label: "Net Revenue Retention",
|
|
11762
11771
|
group: "Retention",
|
|
11763
|
-
tagline: "Would
|
|
11772
|
+
tagline: "Would the base still grow if new logo sales paused?",
|
|
11764
11773
|
how_computed: "startingArr = ARR + Churned + Contraction \u2212 Expansion. NRR = ((starting \u2212 Churned \u2212 Contraction + Expansion) / starting) \xD7 100.",
|
|
11765
11774
|
formula_lines: [
|
|
11766
11775
|
"starting = ARR + churned + contraction \u2212 expansion",
|
|
11767
11776
|
"NRR = (starting \u2212 churned \u2212 contraction + expansion) / starting \xD7 100",
|
|
11768
11777
|
"NRR = 100% + expansion% \u2212 contraction% \u2212 churn%"
|
|
11769
11778
|
],
|
|
11770
|
-
meaning: "
|
|
11771
|
-
expert_read: "Decompose before you judge
|
|
11779
|
+
meaning: "We love NRR because it answers a sharp question: would this business still grow if sales stopped selling new logos? Above 100% means the installed base compounds. From CRM we reconstruct the walk (+expansion \u2212contraction \u2212churn) and stay humble when the data is pipeline-inferred.",
|
|
11780
|
+
expert_read: "Decompose before you judge \u2014 the same 95% can be a churn/PMF problem or a no-expansion/packaging problem, and those have different owners. Rough priors by segment sit around 97% SMB, 108% mid-market, 118% enterprise medians; 110%+ is a strong signal at any stage.",
|
|
11772
11781
|
deepdive: [
|
|
11773
11782
|
"Always show the waterfall: +expansion \u2212contraction \u2212churn.",
|
|
11774
11783
|
"GRR is the floor. NRR adds expansion on top.",
|
|
@@ -11796,14 +11805,14 @@ var init_metric_definitions = __esm({
|
|
|
11796
11805
|
kind: "saas",
|
|
11797
11806
|
label: "Gross Revenue Retention",
|
|
11798
11807
|
group: "Retention",
|
|
11799
|
-
tagline: "How leaky is the bucket before expansion
|
|
11808
|
+
tagline: "How leaky is the bucket before expansion can hide it?",
|
|
11800
11809
|
how_computed: "GRR = ((startingArr \u2212 Churned \u2212 Contraction) / startingArr) \xD7 100. Expansion is excluded on purpose.",
|
|
11801
11810
|
formula_lines: [
|
|
11802
11811
|
"starting = ARR + churned + contraction \u2212 expansion",
|
|
11803
11812
|
"GRR = (starting \u2212 churned \u2212 contraction) / starting \xD7 100"
|
|
11804
11813
|
],
|
|
11805
|
-
meaning: "
|
|
11806
|
-
expert_read: "GRR
|
|
11814
|
+
meaning: "We treat GRR as the honesty metric \u2014 churn plus contraction only, with expansion deliberately left out. That way a shiny NRR can't paper over a leaky bucket. Rough priors: above 90% feels healthy; above 95% is strong for enterprise.",
|
|
11815
|
+
expert_read: "Always read GRR next to NRR. Expansion can make net retention look fine while the floor erodes. Owners tend to split: product/CS for logo churn, packaging for contraction.",
|
|
11807
11816
|
deepdive: [
|
|
11808
11817
|
"GRR never includes Expansion. That is the point.",
|
|
11809
11818
|
"Owners: product and CS for churn. Packaging for contraction."
|
|
@@ -11826,14 +11835,14 @@ var init_metric_definitions = __esm({
|
|
|
11826
11835
|
kind: "saas",
|
|
11827
11836
|
label: "Pipeline Coverage",
|
|
11828
11837
|
group: "Pipeline",
|
|
11829
|
-
tagline: "
|
|
11838
|
+
tagline: "Does next quarter already have enough real pipeline?",
|
|
11830
11839
|
how_computed: "Open pipeline amount divided by trailing-90-day closed-won amount.",
|
|
11831
11840
|
formula_lines: [
|
|
11832
11841
|
"Coverage = openPipeline / trailing_90d_won",
|
|
11833
11842
|
"required \u2248 1 / win_rate (discount for time left)"
|
|
11834
11843
|
],
|
|
11835
|
-
meaning:
|
|
11836
|
-
expert_read: "
|
|
11844
|
+
meaning: `We read coverage as an early "is next quarter already thin?" check. Priors scale with cycle length \u2014 roughly 3x for velocity/SMB, 4\u20135x for enterprise \u2014 but coverage without win rate is half a story. We also cross-check Freshness so zombies don't fake a healthy multiple.`,
|
|
11845
|
+
expert_read: "Required coverage is roughly 1 / win rate, discounted for time left in the period. Inflated stages and stale deals can manufacture coverage that isn't there. Pair with Win Rate and Freshness before you trust the brief.",
|
|
11837
11846
|
deepdive: [
|
|
11838
11847
|
"Always pair with Win Rate and Freshness.",
|
|
11839
11848
|
"Weighted Pipeline is the credibility-adjusted companion."
|
|
@@ -11918,14 +11927,14 @@ var init_metric_definitions = __esm({
|
|
|
11918
11927
|
kind: "saas",
|
|
11919
11928
|
label: "Pipeline Velocity",
|
|
11920
11929
|
group: "Pipeline",
|
|
11921
|
-
tagline: "
|
|
11930
|
+
tagline: "Four levers, one throughput number \u2014 which lever moved?",
|
|
11922
11931
|
how_computed: "(openOpps \xD7 avgDeal \xD7 winRate) / avgCycleDays. Requires 3 or more dated closed-won deals. Unit: $/day.",
|
|
11923
11932
|
formula_lines: [
|
|
11924
11933
|
"Velocity = (openOpps \xD7 avgDeal \xD7 winRate) / avgCycleDays",
|
|
11925
11934
|
"four levers: #opps \xB7 deal size \xB7 win rate \xB7 cycle days"
|
|
11926
11935
|
],
|
|
11927
|
-
meaning: "
|
|
11928
|
-
expert_read:
|
|
11936
|
+
meaning: "We like velocity because it's decision-ready: revenue throughput per day with four named levers (volume, size, win rate, cycle). When throughput changes, the interesting question is which lever moved \u2014 not just whether the number went up.",
|
|
11937
|
+
expert_read: 'Name the lever. A win-rate rise on falling opportunity volume often means qualification tightened, not that selling got "better." Needs a handful of dated wins; otherwise we leave it unavailable rather than invent a story.',
|
|
11929
11938
|
deepdive: [
|
|
11930
11939
|
"Needs 3 or more dated wins. Otherwise this metric is unavailable.",
|
|
11931
11940
|
"Pairs with Flow Rate (cycle) and Win Rate (conversion)."
|
|
@@ -11947,11 +11956,11 @@ var init_metric_definitions = __esm({
|
|
|
11947
11956
|
kind: "saas",
|
|
11948
11957
|
label: "Win Rate",
|
|
11949
11958
|
group: "Sales Efficiency",
|
|
11950
|
-
tagline: "Of
|
|
11959
|
+
tagline: "Of the deals we decide, how often do we win?",
|
|
11951
11960
|
how_computed: "closed-won / (won + lost) \xD7 100.",
|
|
11952
11961
|
formula_lines: ["Win Rate = won / (won + lost) \xD7 100"],
|
|
11953
|
-
meaning: "
|
|
11954
|
-
expert_read: "A rising win rate on falling opportunity volume
|
|
11962
|
+
meaning: "We read win rate as conversion of decided deals \u2014 are we winning the pipeline we create? Rough priors: ~25\u201335% SMB, ~18\u201325% mid-market, ~12\u201318% enterprise on qualified opportunities. It also sets how much coverage you need (~1 / win rate).",
|
|
11963
|
+
expert_read: "Watch the denominator. A rising win rate on falling opportunity volume often means qualification tightened, not that the team suddenly sells better. Cut by segment or source before company-wide coaching.",
|
|
11955
11964
|
deepdive: [
|
|
11956
11965
|
"Required coverage is about 1 / win rate.",
|
|
11957
11966
|
"Cut by segment or source before company-wide coaching."
|
|
@@ -12291,10 +12300,10 @@ var init_guide_slides = __esm({
|
|
|
12291
12300
|
TALK = {
|
|
12292
12301
|
id: "talk",
|
|
12293
12302
|
label: "Talk to NTRP",
|
|
12294
|
-
tagline: "
|
|
12303
|
+
tagline: "Start with the question you care about \u2014 we'll mirror it back before we compute.",
|
|
12295
12304
|
visual: {
|
|
12296
12305
|
kind: "funnel",
|
|
12297
|
-
caption: "
|
|
12306
|
+
caption: "When a dim \u23CE hint shows, bare Enter submits it. No hint means Enter does nothing.",
|
|
12298
12307
|
funnel: [
|
|
12299
12308
|
{ label: "Type a question", widthPct: 100 },
|
|
12300
12309
|
{ label: "\u23CE yes (scope)", widthPct: 78 },
|
|
@@ -12303,25 +12312,23 @@ var init_guide_slides = __esm({
|
|
|
12303
12312
|
]
|
|
12304
12313
|
},
|
|
12305
12314
|
lines: [
|
|
12306
|
-
|
|
12315
|
+
`We designed the conversation so you don't need a slash to start. Type the question you actually need \u2014 "is our retention real for the board?" or "pipeline health" \u2014 and NTRP restates it as a scope card so you can confirm we're answering the right thing.`,
|
|
12307
12316
|
"",
|
|
12308
|
-
"
|
|
12317
|
+
"Empty dataset? Load a sample with \u23CE use demo data, paste a CSV path, or /ingest. When the gap card says the formulas can compute, \u23CE go ahead runs the local math.",
|
|
12309
12318
|
"",
|
|
12310
|
-
"
|
|
12311
|
-
"",
|
|
12312
|
-
"Vital signs and SaaS metrics compute without an AI key. Every score that has a dollar translation shows it."
|
|
12319
|
+
"Vital signs and SaaS metrics compute without an AI key. Every score that has a dollar translation shows it \u2014 that's the point of the stethoscope."
|
|
12313
12320
|
],
|
|
12314
12321
|
deepdive: [
|
|
12315
12322
|
"Power-user slash commands still work. They stay hidden from /help: /new, /diagnose, /metrics, /ingest, /session.",
|
|
12316
|
-
'
|
|
12317
|
-
'After compute, the prompt
|
|
12318
|
-
"
|
|
12323
|
+
'"use demo data" loads a fitted sample book. Company profile is optional. In scripts, /demo --no-profile.',
|
|
12324
|
+
'After compute, the prompt returns to \u203A. Brief is the default depth; type "go deep" for the long read.',
|
|
12325
|
+
"/home shows phase status. /help lists conversation shortcuts \u2014 not every command."
|
|
12319
12326
|
]
|
|
12320
12327
|
};
|
|
12321
12328
|
ASK = {
|
|
12322
12329
|
id: "ask",
|
|
12323
12330
|
label: "After the numbers",
|
|
12324
|
-
tagline: '
|
|
12331
|
+
tagline: 'Two kinds of questions: glossary (free) versus "our" numbers (needs data \u2014 and often a key).',
|
|
12325
12332
|
visual: {
|
|
12326
12333
|
kind: "split",
|
|
12327
12334
|
caption: "Possessives such as our, my, and the team's skip the glossary. They go to compute or scope.",
|
|
@@ -12331,25 +12338,23 @@ var init_guide_slides = __esm({
|
|
|
12331
12338
|
]
|
|
12332
12339
|
},
|
|
12333
12340
|
lines: [
|
|
12334
|
-
|
|
12335
|
-
"",
|
|
12336
|
-
'"what is our ARR?" and "why is this red?" need a loaded dataset. AI findings and /ask need a stored key. Type /connect and paste any provider key. NTRP detects it.',
|
|
12341
|
+
`After compute, we think in two lanes. "what is ARR?" / "how is freshness calculated?" answer from the built-in glossary \u2014 no key, no data. That's so you can learn the lens without wiring anything up.`,
|
|
12337
12342
|
"",
|
|
12338
|
-
"
|
|
12343
|
+
'"what is our ARR?" or "why is this red?" need a loaded dataset. Narrative findings and /ask need a stored key \u2014 /connect pastes any provider key and we detect it.',
|
|
12339
12344
|
"",
|
|
12340
|
-
"
|
|
12345
|
+
"Replay this tour with /deepdive, jump to one slide with /deepdive freshness (or nrr, arr, \u2026), or /deepdive guide for this how-to section. When the prompt shows a dim \u23CE hint (yes, use demo data, go ahead, /connect), bare Enter submits it."
|
|
12341
12346
|
],
|
|
12342
12347
|
deepdive: [
|
|
12343
|
-
"
|
|
12344
|
-
"
|
|
12345
|
-
"
|
|
12348
|
+
"/connect ollama for a keyless local model. /connect --base-url <url> --id <name> for any OpenAI-compatible endpoint.",
|
|
12349
|
+
"/model refresh re-discovers models. A retired model self-heals on the first 404.",
|
|
12350
|
+
"Skipping this tour on first-run does not complete it \u2014 a home \u2691 chip can bring you back after the first analysis.",
|
|
12346
12351
|
"Tab completes /deepdive <metric>. Finding cards and playbook triggers also link here."
|
|
12347
12352
|
]
|
|
12348
12353
|
};
|
|
12349
12354
|
HANDOFF = {
|
|
12350
12355
|
id: "handoff",
|
|
12351
12356
|
label: "Ship work to your AI",
|
|
12352
|
-
tagline: "Teach the inbox once
|
|
12357
|
+
tagline: "Teach the inbox once \u2014 then your desktop AI can pick up what we write.",
|
|
12353
12358
|
visual: {
|
|
12354
12359
|
kind: "layer_stack",
|
|
12355
12360
|
caption: "You and your AI find and open the file. NTRP only writes.",
|
|
@@ -12361,28 +12366,26 @@ var init_guide_slides = __esm({
|
|
|
12361
12366
|
]
|
|
12362
12367
|
},
|
|
12363
12368
|
lines: [
|
|
12364
|
-
'
|
|
12369
|
+
'We think of handoff as a quiet bridge: NTRP writes a board deck or brief into a folder you choose; your desktop AI (Claude, ChatGPT, Cursor, \u2026) opens it. You teach that AI a standing finder once \u2014 then later you just say "pick up the latest NTRP handoff."',
|
|
12365
12370
|
"",
|
|
12366
|
-
"
|
|
12371
|
+
'"ship a board deck" or /handoff lands files in ~/Documents/ntrp-inbox by default (/inbox set to change). During demo or company setup \u2014 or anytime \u2014 /inbox skill prints the finder to paste once. After that, each /handoff prints "Handoff ready" without a paste block every write.',
|
|
12367
12372
|
"",
|
|
12368
|
-
|
|
12369
|
-
"",
|
|
12370
|
-
"If you skipped this step, NTRP asks once when you load your own data. Skip then, and type /inbox set ~/Documents/ntrp-inbox. Then type /inbox skill. Type /handoff skill to reprint the same finder."
|
|
12373
|
+
"Skipped earlier? We ask once when you load your own data. Or /inbox set ~/Documents/ntrp-inbox, then /inbox skill. /handoff skill reprints the same finder."
|
|
12371
12374
|
],
|
|
12372
12375
|
deepdive: [
|
|
12373
12376
|
"Inbox copies: latest-handoff.md, latest-pickup.md, SKILL.md, INDEX.md. Dated files also live under ~/.ntrp/exports/.",
|
|
12374
|
-
"
|
|
12375
|
-
"Audience-framed Metric definitions append to decks and reports
|
|
12376
|
-
"
|
|
12377
|
+
"/exports lists writes. /inbox show prints the folder and latest pointer. /handoff --print shows the prompt body in the terminal.",
|
|
12378
|
+
"Audience-framed Metric definitions append to decks and reports \u2014 your AI gets the same glossary you walked.",
|
|
12379
|
+
"/end closes a session and writes a transcript plus a 1-page context brief under ~/.ntrp/sessions/."
|
|
12377
12380
|
]
|
|
12378
12381
|
};
|
|
12379
12382
|
LOOP = {
|
|
12380
12383
|
id: "loop",
|
|
12381
12384
|
label: "Stay in the loop",
|
|
12382
|
-
tagline: "
|
|
12385
|
+
tagline: "Listen \u2192 plan \u2192 review \u2192 remember. We learn your business over time.",
|
|
12383
12386
|
visual: {
|
|
12384
12387
|
kind: "layer_stack",
|
|
12385
|
-
caption: "Stethoscope, not hospital
|
|
12388
|
+
caption: "Stethoscope, not hospital \u2014 we observe and recommend; you decide the surgery.",
|
|
12386
12389
|
layers: [
|
|
12387
12390
|
{ label: "Listen (/diagnose, /metrics)" },
|
|
12388
12391
|
{ label: 'Plan ("how should we fix this?" / /strategy)', highlight: true },
|
|
@@ -12391,18 +12394,16 @@ var init_guide_slides = __esm({
|
|
|
12391
12394
|
]
|
|
12392
12395
|
},
|
|
12393
12396
|
lines: [
|
|
12394
|
-
'
|
|
12395
|
-
"",
|
|
12396
|
-
"When a vital sign is red, type /playbook. NTRP names the matching play. Outcomes from reviews annotate the catalog with what hit here.",
|
|
12397
|
+
'The learning loop is how we stay useful after the first diagnose. "how should we fix this?" or /strategy builds a measurable plan with milestones and dollar-anchored ranges; /strategy review checks those against later vital signs.',
|
|
12397
12398
|
"",
|
|
12398
|
-
"
|
|
12399
|
+
"When a vital is red, /playbook names a matching play \u2014 and outcomes from reviews annotate the catalog with what actually hit here. /remember stores a durable fact; /rate bad <reason> writes a calibration. Optional ~/.ntrp/ANALYST.md is standing voice and priorities (it never overrides safety rules).",
|
|
12399
12400
|
"",
|
|
12400
|
-
"
|
|
12401
|
+
"Handy later: /sessions show, /session <id> pickup, /end (transcript + brief), /home, /progress, /help."
|
|
12401
12402
|
],
|
|
12402
12403
|
deepdive: [
|
|
12403
|
-
'
|
|
12404
|
+
'"build me a game plan" before analysis queues the strategist and resumes after compute.',
|
|
12404
12405
|
"Interactive sessions distill 5 or fewer durable facts on close when an LLM key is stored. One-shot commands do not bank hours or distill.",
|
|
12405
|
-
"
|
|
12406
|
+
"/scratch factory-resets data and config. progress.json and install.json survive unless you pass --include-progress.",
|
|
12406
12407
|
"Credits in /progress accrue in interactive ntrp only."
|
|
12407
12408
|
]
|
|
12408
12409
|
};
|
|
@@ -12419,6 +12420,11 @@ var init_guide_slides = __esm({
|
|
|
12419
12420
|
var metric_tour_exports = {};
|
|
12420
12421
|
__export(metric_tour_exports, {
|
|
12421
12422
|
METRICS_TOUR_MILESTONE_ID: () => METRICS_TOUR_MILESTONE_ID,
|
|
12423
|
+
TOUR_CLOSE_LINES: () => TOUR_CLOSE_LINES,
|
|
12424
|
+
TOUR_CLOSE_TITLE: () => TOUR_CLOSE_TITLE,
|
|
12425
|
+
TOUR_INTRO_LINES: () => TOUR_INTRO_LINES,
|
|
12426
|
+
TOUR_INTRO_TITLE: () => TOUR_INTRO_TITLE,
|
|
12427
|
+
TOUR_OFFER_BLURB: () => TOUR_OFFER_BLURB,
|
|
12422
12428
|
describeTourDeck: () => describeTourDeck,
|
|
12423
12429
|
firstRunTourDefaultChoice: () => firstRunTourDefaultChoice,
|
|
12424
12430
|
getDeepdiveNudge: () => getDeepdiveNudge,
|
|
@@ -12472,7 +12478,7 @@ function getDeepdiveNudge(ctx) {
|
|
|
12472
12478
|
if (hasCompletedMetricsTour() || hasSeenDeepdiveHomeNudge()) return null;
|
|
12473
12479
|
if (!hasAnalysisForDeepdiveNudge(ctx)) return null;
|
|
12474
12480
|
return {
|
|
12475
|
-
text: "Tour \u2014
|
|
12481
|
+
text: "Tour \u2014 how we think about the numbers, and how to use NTRP",
|
|
12476
12482
|
command: "/deepdive"
|
|
12477
12483
|
};
|
|
12478
12484
|
}
|
|
@@ -12558,19 +12564,13 @@ function paintIntro(opts) {
|
|
|
12558
12564
|
index: opts.index,
|
|
12559
12565
|
total: opts.total,
|
|
12560
12566
|
motion: opts.motion,
|
|
12561
|
-
titleOverride:
|
|
12567
|
+
titleOverride: TOUR_INTRO_TITLE,
|
|
12562
12568
|
visualOverride: GATING_LAYER_VISUAL,
|
|
12563
12569
|
skipFormula: true,
|
|
12564
12570
|
extraLines: [
|
|
12565
|
-
|
|
12566
|
-
"",
|
|
12567
|
-
"SaaS metrics (next) \u2014 ARR, NRR, coverage, win rate, velocity \u2014 the board already knows these. Quick refresher on how NTRP computes them from CRM exports.",
|
|
12568
|
-
"",
|
|
12569
|
-
"Vital signs (after) \u2014 Freshness, Flow Rate, Drop Rate, Signal:Noise, Thread Depth \u2014 NTRP's own ontology. Each has a dollar translation. Scores gate in layer order: first red wins.",
|
|
12571
|
+
...TOUR_INTRO_LINES,
|
|
12570
12572
|
"",
|
|
12571
|
-
"
|
|
12572
|
-
"",
|
|
12573
|
-
chalk10.dim("No AI key needed for this tour. Press Enter to advance; type /deepdive (or d) on any slide for more.")
|
|
12573
|
+
chalk10.dim("No AI key needed for this tour. Enter advances; /deepdive (or d) opens more on any slide.")
|
|
12574
12574
|
],
|
|
12575
12575
|
footer: `${progressDots(opts.index, opts.total)} \u23CE next \xB7 q skip`
|
|
12576
12576
|
});
|
|
@@ -12581,19 +12581,19 @@ function paintClose(opts) {
|
|
|
12581
12581
|
index: opts.index,
|
|
12582
12582
|
total: opts.total,
|
|
12583
12583
|
motion: opts.motion,
|
|
12584
|
-
titleOverride:
|
|
12584
|
+
titleOverride: TOUR_CLOSE_TITLE,
|
|
12585
12585
|
skipFormula: true,
|
|
12586
12586
|
extraLines: [
|
|
12587
|
-
|
|
12588
|
-
` ${paint("accent", "/diagnose")} \u2014 five vital signs
|
|
12587
|
+
TOUR_CLOSE_LINES[0],
|
|
12588
|
+
` ${paint("accent", "/diagnose")} \u2014 five vital signs with dollars attached`,
|
|
12589
12589
|
` ${paint("accent", "/metrics")} \u2014 SaaS scorecard (ARR, NRR, coverage\u2026)`,
|
|
12590
12590
|
` ${paint("accent", "/deepdive")} \u2014 replay this tour \xB7 ${paint("accent", "/deepdive guide")} how-to only`,
|
|
12591
12591
|
` ${paint("accent", "/handoff")} \u2014 ship a file; teach the inbox once (/inbox skill)`,
|
|
12592
12592
|
` ${paint("accent", "/strategy")} \u2014 measurable plan \xB7 ${paint("accent", "/help")} shortcuts`,
|
|
12593
12593
|
"",
|
|
12594
|
-
|
|
12594
|
+
TOUR_CLOSE_LINES[7],
|
|
12595
12595
|
"",
|
|
12596
|
-
|
|
12596
|
+
TOUR_CLOSE_LINES[9]
|
|
12597
12597
|
],
|
|
12598
12598
|
footer: `${progressDots(opts.index, opts.total)} \u23CE done \xB7 q quit`
|
|
12599
12599
|
});
|
|
@@ -12619,7 +12619,7 @@ function guideExtraLines(slide, deepdive) {
|
|
|
12619
12619
|
];
|
|
12620
12620
|
if (deepdive && slide.deepdive.length > 0) {
|
|
12621
12621
|
lines.push("");
|
|
12622
|
-
lines.push(sectionHeading("
|
|
12622
|
+
lines.push(sectionHeading("How to read it"));
|
|
12623
12623
|
for (const bullet of slide.deepdive) {
|
|
12624
12624
|
lines.push(`\xB7 ${bullet}`);
|
|
12625
12625
|
}
|
|
@@ -12754,10 +12754,8 @@ function firstRunTourDefaultChoice() {
|
|
|
12754
12754
|
async function offerFirstRunTour(ctx) {
|
|
12755
12755
|
if (hasCompletedMetricsTour()) return false;
|
|
12756
12756
|
console.log();
|
|
12757
|
-
console.log(" " + bold("
|
|
12758
|
-
console.log(
|
|
12759
|
-
" " + chalk10.dim("SaaS numbers, five vitals, then how to talk to NTRP and ship work to your AI.")
|
|
12760
|
-
);
|
|
12757
|
+
console.log(" " + bold("A short tour") + chalk10.dim(" \u2014 ~3 minutes, no AI key required"));
|
|
12758
|
+
console.log(" " + chalk10.dim(TOUR_OFFER_BLURB));
|
|
12761
12759
|
const tourDefault = firstRunTourDefaultChoice();
|
|
12762
12760
|
if (tourDefault === "skip") {
|
|
12763
12761
|
console.log(" " + chalk10.dim("You have taken this tour before."));
|
|
@@ -12796,7 +12794,7 @@ async function offerFirstRunTour(ctx) {
|
|
|
12796
12794
|
await runMetricTour(ctx, { live: false });
|
|
12797
12795
|
return true;
|
|
12798
12796
|
}
|
|
12799
|
-
var TOUR_COMPLETED_KEY, TOUR_SKIPPED_KEY, HOME_NUDGE_SEEN_KEY, METRICS_TOUR_MILESTONE_ID;
|
|
12797
|
+
var TOUR_COMPLETED_KEY, TOUR_SKIPPED_KEY, HOME_NUDGE_SEEN_KEY, METRICS_TOUR_MILESTONE_ID, TOUR_INTRO_TITLE, TOUR_CLOSE_TITLE, TOUR_INTRO_LINES, TOUR_CLOSE_LINES, TOUR_OFFER_BLURB;
|
|
12800
12798
|
var init_metric_tour = __esm({
|
|
12801
12799
|
"src/conversation/metric-tour.ts"() {
|
|
12802
12800
|
"use strict";
|
|
@@ -12814,6 +12812,30 @@ var init_metric_tour = __esm({
|
|
|
12814
12812
|
TOUR_SKIPPED_KEY = "metrics-tour-skipped";
|
|
12815
12813
|
HOME_NUDGE_SEEN_KEY = "metrics-tour-home-nudge-seen";
|
|
12816
12814
|
METRICS_TOUR_MILESTONE_ID = "metrics_tour";
|
|
12815
|
+
TOUR_INTRO_TITLE = "How we listen to a pipeline";
|
|
12816
|
+
TOUR_CLOSE_TITLE = "Curious? Start listening";
|
|
12817
|
+
TOUR_INTRO_LINES = [
|
|
12818
|
+
"We think about GTM health through two lenses \u2014 then we'll show how to talk to NTRP.",
|
|
12819
|
+
"",
|
|
12820
|
+
"SaaS metrics (next) \u2014 ARR, NRR, coverage, win rate, velocity. Boards already know these names; we'll walk how we derive them from CRM exports, and what we trust (or don't) when the data is messy.",
|
|
12821
|
+
"",
|
|
12822
|
+
"Vital signs (after) \u2014 Freshness, Flow Rate, Drop Rate, Signal:Noise, Thread Depth. These are our stethoscope: each score has a dollar translation, and we read them in layer order so an upstream red doesn't get papered over by a prettier downstream number.",
|
|
12823
|
+
"",
|
|
12824
|
+
"How to use NTRP (after vitals) \u2014 ask in English, peek at the glossary without a key, teach your desktop AI an inbox once, then stay in diagnose \u2192 plan \u2192 review."
|
|
12825
|
+
];
|
|
12826
|
+
TOUR_CLOSE_LINES = [
|
|
12827
|
+
"A good first loop: ask something you actually care about \u2192 confirm the scope \u2192 load data (demo is fine) \u2192 let the formulas run. Then poke around:",
|
|
12828
|
+
"/diagnose \u2014 five vital signs with dollars attached",
|
|
12829
|
+
"/metrics \u2014 SaaS scorecard (ARR, NRR, coverage\u2026)",
|
|
12830
|
+
"/deepdive \u2014 replay this tour \xB7 /deepdive guide how-to only",
|
|
12831
|
+
"/handoff \u2014 ship a file; teach the inbox once (/inbox skill)",
|
|
12832
|
+
"/strategy \u2014 measurable plan \xB7 /help shortcuts",
|
|
12833
|
+
"",
|
|
12834
|
+
"Fourteen more SaaS metrics sit behind /deepdive list \u2014 including unit economics that unlock when spend data lands.",
|
|
12835
|
+
"",
|
|
12836
|
+
"We're a stethoscope, not a hospital: we listen hard, translate scores into dollars, and point at plays \u2014 you decide the surgery."
|
|
12837
|
+
];
|
|
12838
|
+
TOUR_OFFER_BLURB = "How we think about SaaS numbers and five vitals \u2014 then how to talk to NTRP and ship work to your AI.";
|
|
12817
12839
|
}
|
|
12818
12840
|
});
|
|
12819
12841
|
|
|
@@ -13082,6 +13104,15 @@ var init_spinner = __esm({
|
|
|
13082
13104
|
});
|
|
13083
13105
|
|
|
13084
13106
|
// src/pipeline/segments.ts
|
|
13107
|
+
function resolveSegmentByName(query, segments) {
|
|
13108
|
+
const lower = query.toLowerCase();
|
|
13109
|
+
const exact = segments.find((s) => s.segment.name.toLowerCase() === lower);
|
|
13110
|
+
if (exact) return { type: "exact", segment: exact };
|
|
13111
|
+
const matches = segments.filter((s) => s.segment.name.toLowerCase().includes(lower));
|
|
13112
|
+
if (matches.length === 1) return { type: "exact", segment: matches[0] };
|
|
13113
|
+
if (matches.length > 1) return { type: "ambiguous", matches };
|
|
13114
|
+
return { type: "none" };
|
|
13115
|
+
}
|
|
13085
13116
|
function resolveSegmentScopeFromSnapshot(segment, snapshot) {
|
|
13086
13117
|
const orgIds = [];
|
|
13087
13118
|
const peopleIds = [];
|
|
@@ -13394,8 +13425,9 @@ function resolveThresholds(salesMotion, computedBaselines) {
|
|
|
13394
13425
|
return resolved;
|
|
13395
13426
|
}
|
|
13396
13427
|
async function getResolvedThresholds() {
|
|
13428
|
+
const { loadProfile: loadProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
|
|
13397
13429
|
const { getConfigValue: getConfigValue2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
13398
|
-
const motion = getConfigValue2("sales-motion");
|
|
13430
|
+
const motion = loadProfile2()?.sales_motion ?? getConfigValue2("sales-motion");
|
|
13399
13431
|
return resolveThresholds(motion ?? null, {});
|
|
13400
13432
|
}
|
|
13401
13433
|
var init_resolve = __esm({
|
|
@@ -15453,7 +15485,6 @@ __export(phase_exports, {
|
|
|
15453
15485
|
buildConversationPrompt: () => buildConversationPrompt,
|
|
15454
15486
|
consumeOrientEmptyEnterCoach: () => consumeOrientEmptyEnterCoach,
|
|
15455
15487
|
formatPhaseLabel: () => formatPhaseLabel,
|
|
15456
|
-
getConversationPhaseBlock: () => getConversationPhaseBlock,
|
|
15457
15488
|
resolveConversationPhase: () => resolveConversationPhase,
|
|
15458
15489
|
sessionHasData: () => sessionHasData
|
|
15459
15490
|
});
|
|
@@ -15493,6 +15524,8 @@ function formatPhaseLabel(phase) {
|
|
|
15493
15524
|
switch (phase) {
|
|
15494
15525
|
case "orient":
|
|
15495
15526
|
return "setup";
|
|
15527
|
+
case "awaiting_data":
|
|
15528
|
+
return "data loading";
|
|
15496
15529
|
case "explore":
|
|
15497
15530
|
return "ready to ask";
|
|
15498
15531
|
case "think":
|
|
@@ -15501,15 +15534,24 @@ function formatPhaseLabel(phase) {
|
|
|
15501
15534
|
return phase.replace(/_/g, " ");
|
|
15502
15535
|
}
|
|
15503
15536
|
}
|
|
15537
|
+
function accentPromptHead(phase, sessionName) {
|
|
15538
|
+
const scope = sessionName ? ` ${sessionName}` : "";
|
|
15539
|
+
if (phase === "orient" || phase === "awaiting_data" || phase === "explore") {
|
|
15540
|
+
if (phase === "explore" && sessionName) {
|
|
15541
|
+
return paint("accent", `\u203A`) + chalk11.dim(`${scope} `);
|
|
15542
|
+
}
|
|
15543
|
+
return paint("accent", `\u203A `);
|
|
15544
|
+
}
|
|
15545
|
+
if (phase === "compute") {
|
|
15546
|
+
return paint("accent", `\u2026 `);
|
|
15547
|
+
}
|
|
15548
|
+
const word = PROMPT_LABELS[phase].replace(" \u203A", "");
|
|
15549
|
+
return paint("accent", `${word}${scope} \u203A `);
|
|
15550
|
+
}
|
|
15504
15551
|
function buildConversationPrompt(ctx) {
|
|
15505
15552
|
const phase = resolveConversationPhase(ctx);
|
|
15506
|
-
const
|
|
15507
|
-
const scope = ctx.sessionName ? ` ${ctx.sessionName}` : "";
|
|
15553
|
+
const sessionName = ctx.sessionName ?? "";
|
|
15508
15554
|
const action = resolveRecommendedAction(ctx);
|
|
15509
|
-
if (phase === "orient") {
|
|
15510
|
-
const enterHint2 = action ? chalk11.dim(`\u23CE ${action.hint} `) : "";
|
|
15511
|
-
return paint("accent", `${label} `) + enterHint2;
|
|
15512
|
-
}
|
|
15513
15555
|
if (phase === "explore") {
|
|
15514
15556
|
const mode = defaultExploreResponseMode(ctx, Math.floor(ctx.messages.length / 2));
|
|
15515
15557
|
const modeTag = mode === "brief" ? "brief" : "deep";
|
|
@@ -15517,34 +15559,15 @@ function buildConversationPrompt(ctx) {
|
|
|
15517
15559
|
const strategyWait = ctx.strategistState?.step === "awaiting_connect" ? chalk11.dim(" \xB7 strategy after /connect") : "";
|
|
15518
15560
|
const thinkWait = ctx.thinkState?.step === "awaiting_connect" ? chalk11.dim(" \xB7 think after /connect") : "";
|
|
15519
15561
|
const enterHint2 = action ? chalk11.dim(` \xB7 \u23CE ${action.hint}`) : "";
|
|
15520
|
-
return
|
|
15562
|
+
return accentPromptHead(phase, sessionName) + chalk11.dim(`${modeTag} \xB7 ${stack}`) + strategyWait + thinkWait + enterHint2 + " ";
|
|
15521
15563
|
}
|
|
15522
15564
|
if (phase === "think") {
|
|
15523
15565
|
const stack = canUseReplAi(ctx) ? formatActiveStackShort(ctx) : "no engine";
|
|
15524
15566
|
const enterHint2 = action ? chalk11.dim(` \xB7 \u23CE ${action.hint}`) : "";
|
|
15525
|
-
return
|
|
15567
|
+
return accentPromptHead(phase, sessionName) + chalk11.dim(stack) + enterHint2 + " ";
|
|
15526
15568
|
}
|
|
15527
15569
|
const enterHint = action ? chalk11.dim(`\u23CE ${action.hint} `) : "";
|
|
15528
|
-
return
|
|
15529
|
-
}
|
|
15530
|
-
function getConversationPhaseBlock(ctx) {
|
|
15531
|
-
const phase = resolveConversationPhase(ctx);
|
|
15532
|
-
const lines = [`Conversation phase: ${formatPhaseLabel(phase)}`];
|
|
15533
|
-
if (ctx.scope) {
|
|
15534
|
-
lines.push(`Intent: ${ctx.scope.intent_summary}`);
|
|
15535
|
-
lines.push(`Primary lens: ${ctx.scope.primary_lens}`);
|
|
15536
|
-
if (ctx.scope.audience) lines.push(`Audience: ${ctx.scope.audience}`);
|
|
15537
|
-
if (ctx.scope.time_horizon) lines.push(`Time horizon: ${ctx.scope.time_horizon}`);
|
|
15538
|
-
if (ctx.scope.confirmed_at) lines.push(`Scope confirmed: ${ctx.scope.confirmed_at}`);
|
|
15539
|
-
}
|
|
15540
|
-
if (ctx.dataset?.label) lines.push(`Dataset: ${ctx.dataset.label}`);
|
|
15541
|
-
if (ctx.gapAudit) {
|
|
15542
|
-
lines.push(`Can compute: ${ctx.gapAudit.can_compute}`);
|
|
15543
|
-
if (ctx.gapAudit.missing.length > 0) {
|
|
15544
|
-
lines.push(`Data gaps: ${ctx.gapAudit.missing.map((m) => m.label).join(", ")}`);
|
|
15545
|
-
}
|
|
15546
|
-
}
|
|
15547
|
-
return lines.join("\n");
|
|
15570
|
+
return accentPromptHead(phase, sessionName) + enterHint;
|
|
15548
15571
|
}
|
|
15549
15572
|
var PROMPT_LABELS;
|
|
15550
15573
|
var init_phase = __esm({
|
|
@@ -15560,9 +15583,9 @@ var init_phase = __esm({
|
|
|
15560
15583
|
PROMPT_LABELS = {
|
|
15561
15584
|
orient: "\u203A",
|
|
15562
15585
|
scope: "scope \u203A",
|
|
15563
|
-
awaiting_data: "
|
|
15586
|
+
awaiting_data: "\u203A",
|
|
15564
15587
|
compute: "\u2026",
|
|
15565
|
-
explore: "
|
|
15588
|
+
explore: "\u203A",
|
|
15566
15589
|
think: "think \u203A",
|
|
15567
15590
|
strategize: "strategy \u203A",
|
|
15568
15591
|
deliver: "ship \u203A"
|
|
@@ -21088,8 +21111,11 @@ function buildInvestigationTools() {
|
|
|
21088
21111
|
if (isWebRetrievalEnabled()) tools.push(WEB_SEARCH_TOOL);
|
|
21089
21112
|
return tools;
|
|
21090
21113
|
}
|
|
21091
|
-
function buildFreshNlTools() {
|
|
21092
|
-
const
|
|
21114
|
+
function buildFreshNlTools(opts = {}) {
|
|
21115
|
+
const conversationTools = opts.analysisReady ? CONVERSATION_TOOLS.filter(
|
|
21116
|
+
(t) => t.name !== "propose_scope" && t.name !== "confirm_scope" && t.name !== "run_compute"
|
|
21117
|
+
) : CONVERSATION_TOOLS;
|
|
21118
|
+
const tools = [...AGENTIC_TOOLS, ...conversationTools];
|
|
21093
21119
|
if (isWebRetrievalEnabled()) tools.push(WEB_SEARCH_TOOL);
|
|
21094
21120
|
return tools;
|
|
21095
21121
|
}
|
|
@@ -22792,12 +22818,12 @@ Cite numbers from here; call tools when you need a new cut or to pressure-test a
|
|
|
22792
22818
|
|
|
22793
22819
|
` : "";
|
|
22794
22820
|
const conversationSection = opts.conversationBlock ? `
|
|
22795
|
-
CONVERSATION STATE:
|
|
22796
22821
|
${opts.conversationBlock}
|
|
22797
22822
|
|
|
22798
22823
|
` : "";
|
|
22799
22824
|
const scratchSection = buildScratchBlock(opts.thinkState);
|
|
22800
22825
|
const socraticCraft = `HOW YOU THINK WITH THE USER (socratic partner \u2014 not a search box, not a strategist):
|
|
22826
|
+
- Pre-explore funnel gates are handled by the CLI; you speak in the think channel after analysis.
|
|
22801
22827
|
- You are a thinking partner. Prefer sharp questions that unlock the user's idea over dumping answers.
|
|
22802
22828
|
- Protect imagination: give "what if" space before the smell-test \u2014 do not kill creativity in the first sentence.
|
|
22803
22829
|
- Steelman the user's position before you attack it; then deliver one hard pushback grounded in evidence when possible.
|
|
@@ -22831,7 +22857,7 @@ ${buildCommandCatalogBlock()}
|
|
|
22831
22857
|
COMMAND SUGGESTION RULES:
|
|
22832
22858
|
- Suggest at most ONE command when it adds capability beyond your answer (e.g. \`/strategy\`, \`/handoff\`).
|
|
22833
22859
|
- Never claim a command was run. Never invent flags.
|
|
22834
|
-
- Type \`done\` or \`cancel\` leaves the think channel back to
|
|
22860
|
+
- Type \`done\` or \`cancel\` leaves the think channel back to \u203A.`;
|
|
22835
22861
|
const stable = `You are a world-class GTM operating partner in a dedicated THINK WITH ME channel. The user wants collaborative exploration \u2014 imagination, pressure-testing, and evidence \u2014 not a slide deck and not a finished strategy plan.
|
|
22836
22862
|
|
|
22837
22863
|
${companyContextSection2()}${operatorSection2()}${socraticCraft}
|
|
@@ -22881,7 +22907,7 @@ ${SAFETY_BLOCK}`;
|
|
|
22881
22907
|
const dynamic = [
|
|
22882
22908
|
"SESSION STATE (current \u2014 changes as the session progresses):",
|
|
22883
22909
|
...dynamicSections,
|
|
22884
|
-
buildRuntimeBlock()
|
|
22910
|
+
buildRuntimeBlock(opts.runtimeFacts ?? {})
|
|
22885
22911
|
].join("\n\n");
|
|
22886
22912
|
return { stable, dynamic };
|
|
22887
22913
|
}
|
|
@@ -22893,6 +22919,170 @@ var init_think_prompt = __esm({
|
|
|
22893
22919
|
}
|
|
22894
22920
|
});
|
|
22895
22921
|
|
|
22922
|
+
// src/conversation/ghost-hints.ts
|
|
22923
|
+
function ghostExamplesForPhase(phase, limit = 2) {
|
|
22924
|
+
const hints = PHASE_GHOST_HINTS[phase] ?? [];
|
|
22925
|
+
return hints.slice(0, limit).map((h) => h.replace(/^try\s+/i, ""));
|
|
22926
|
+
}
|
|
22927
|
+
var PHASE_GHOST_HINTS;
|
|
22928
|
+
var init_ghost_hints = __esm({
|
|
22929
|
+
"src/conversation/ghost-hints.ts"() {
|
|
22930
|
+
"use strict";
|
|
22931
|
+
PHASE_GHOST_HINTS = {
|
|
22932
|
+
orient: [
|
|
22933
|
+
'try "pipeline health"',
|
|
22934
|
+
"try /deepdive",
|
|
22935
|
+
"try /deepdive guide",
|
|
22936
|
+
'try "is our retention real for the board?"',
|
|
22937
|
+
'try "what is the most expensive problem to solve?"',
|
|
22938
|
+
'try "board deck on Q3"'
|
|
22939
|
+
],
|
|
22940
|
+
awaiting_data: [
|
|
22941
|
+
'try "use demo data"',
|
|
22942
|
+
"try a CSV path",
|
|
22943
|
+
"try /ingest",
|
|
22944
|
+
'try "go ahead"'
|
|
22945
|
+
],
|
|
22946
|
+
explore: [
|
|
22947
|
+
'try "what is the most expensive problem?"',
|
|
22948
|
+
'try "how should we fix this?"',
|
|
22949
|
+
'try "which segment is weakest?"',
|
|
22950
|
+
'try "what is ARR?"',
|
|
22951
|
+
"try /deepdive freshness",
|
|
22952
|
+
'try "ship a board deck"'
|
|
22953
|
+
]
|
|
22954
|
+
};
|
|
22955
|
+
}
|
|
22956
|
+
});
|
|
22957
|
+
|
|
22958
|
+
// src/conversation/situation.ts
|
|
22959
|
+
function buildSituationalAwarenessBlock(ctx, opts = {}) {
|
|
22960
|
+
const phase = resolveConversationPhase(ctx);
|
|
22961
|
+
const channel = opts.channel ?? (phase === "think" ? "think" : "explore");
|
|
22962
|
+
const responseMode = opts.responseMode ?? (channel === "think" ? "deep" : defaultExploreResponseMode(ctx, Math.floor(ctx.messages.length / 2)));
|
|
22963
|
+
const analysisReady = isAnalysisReady(ctx);
|
|
22964
|
+
const action = resolveRecommendedAction(ctx);
|
|
22965
|
+
const examples = ghostExamplesForPhase(
|
|
22966
|
+
phase === "think" || phase === "strategize" || phase === "deliver" ? "explore" : phase,
|
|
22967
|
+
2
|
|
22968
|
+
);
|
|
22969
|
+
const lines = [
|
|
22970
|
+
"WHERE YOU ARE IN NTRP:",
|
|
22971
|
+
FUNNEL_SPINE,
|
|
22972
|
+
`Phase: ${phase} (${formatPhaseLabel(phase)}) \u2014 ${PHASE_MEANING[phase]}`
|
|
22973
|
+
];
|
|
22974
|
+
if (channel === "think") {
|
|
22975
|
+
lines.push("Channel: think \u2014 socratic partner; type done/cancel returns to \u203A");
|
|
22976
|
+
lines.push("Response mode: deep (tools on)");
|
|
22977
|
+
} else if (channel === "strategist") {
|
|
22978
|
+
lines.push("Channel: strategist \u2014 measurable plan engine owns sequencing");
|
|
22979
|
+
} else {
|
|
22980
|
+
lines.push(
|
|
22981
|
+
responseMode === "brief" ? "Response mode: brief (tools off; cite COMPLETED SESSION ANALYSIS; ~40\u201390 words)" : "Response mode: deep (tools on; new cuts and drill-downs)"
|
|
22982
|
+
);
|
|
22983
|
+
}
|
|
22984
|
+
if (ctx.scope) {
|
|
22985
|
+
const bits = [`Scope: ${ctx.scope.intent_summary}`, `lens=${ctx.scope.primary_lens}`];
|
|
22986
|
+
if (ctx.scope.audience) bits.push(`audience=${ctx.scope.audience}`);
|
|
22987
|
+
if (ctx.scope.time_horizon) bits.push(`horizon=${ctx.scope.time_horizon}`);
|
|
22988
|
+
lines.push(bits.join(" \xB7 "));
|
|
22989
|
+
if (ctx.scope.confirmed_at) lines.push(`Scope confirmed: ${ctx.scope.confirmed_at}`);
|
|
22990
|
+
}
|
|
22991
|
+
if (ctx.dataset?.label) {
|
|
22992
|
+
lines.push(`Dataset: ${ctx.dataset.label} \xB7 stage=${ctx.stage}`);
|
|
22993
|
+
} else if (sessionHasData(ctx)) {
|
|
22994
|
+
lines.push(`Dataset: loaded \xB7 stage=${ctx.stage}`);
|
|
22995
|
+
} else {
|
|
22996
|
+
lines.push(`Dataset: empty \xB7 stage=${ctx.stage}`);
|
|
22997
|
+
}
|
|
22998
|
+
if (ctx.gapAudit) {
|
|
22999
|
+
lines.push(`Can compute: ${ctx.gapAudit.can_compute}`);
|
|
23000
|
+
if (ctx.gapAudit.missing.length > 0) {
|
|
23001
|
+
lines.push(`Data gaps: ${ctx.gapAudit.missing.map((m) => m.label).join(", ")}`);
|
|
23002
|
+
}
|
|
23003
|
+
}
|
|
23004
|
+
if (analysisReady) {
|
|
23005
|
+
lines.push(
|
|
23006
|
+
"Already done: scope confirmed; compute ran; do not re-propose scope or re-run compute unless the user asks to restart"
|
|
23007
|
+
);
|
|
23008
|
+
lines.push(
|
|
23009
|
+
"Available now: answer from the artifact; suggest at most one of /strategy, /handoff, /deepdive <metric>, /thinkwithme, /playbook"
|
|
23010
|
+
);
|
|
23011
|
+
if (channel === "explore" && responseMode === "deep") {
|
|
23012
|
+
lines.push(
|
|
23013
|
+
"Tools: diagnostics + draft_strategy + draft_handoff (+ audit_data_gaps for re-check). Scope/compute orchestration is off."
|
|
23014
|
+
);
|
|
23015
|
+
}
|
|
23016
|
+
} else if (phase === "awaiting_data") {
|
|
23017
|
+
lines.push("Already done: scope confirmed; analysis not ready");
|
|
23018
|
+
lines.push(
|
|
23019
|
+
'Available now: tell the user to paste a CSV path, type "use demo data", or \u23CE go ahead when can_compute \u2014 you cannot open files'
|
|
23020
|
+
);
|
|
23021
|
+
} else if (phase === "scope") {
|
|
23022
|
+
lines.push("Already done: intent proposed; waiting on confirm");
|
|
23023
|
+
lines.push("Available now: wait for CLI confirm (\u23CE yes) \u2014 do not invent a different question");
|
|
23024
|
+
} else if (phase === "orient") {
|
|
23025
|
+
lines.push("Already done: nothing locked yet");
|
|
23026
|
+
lines.push("Available now: help the user name a focus; CLI will propose scope from their words");
|
|
23027
|
+
} else if (phase === "think") {
|
|
23028
|
+
lines.push("Already done: analysis complete; think channel open");
|
|
23029
|
+
lines.push("Available now: socratic exploration; draft_strategy / draft_handoff when ready to graduate");
|
|
23030
|
+
}
|
|
23031
|
+
lines.push(
|
|
23032
|
+
`Armed user next step: ${action ? `${action.hint} (\u23CE submits "${action.submit}")` : "none"}`
|
|
23033
|
+
);
|
|
23034
|
+
lines.push(`Engine connected: ${canUseReplAi(ctx) ? "yes" : "no"}`);
|
|
23035
|
+
if (examples.length > 0) {
|
|
23036
|
+
lines.push(`User may try: ${examples.join(" \xB7 ")}`);
|
|
23037
|
+
}
|
|
23038
|
+
lines.push(
|
|
23039
|
+
"Your job in this harness: fill the judgment gap after formula compute \u2014 connect dollars, name the expensive problem, do not restate scorecards the user already saw"
|
|
23040
|
+
);
|
|
23041
|
+
lines.push(
|
|
23042
|
+
"Pre-explore funnel gates are handled by the CLI; you speak in explore/think/strategist unless tools explicitly allow a restart"
|
|
23043
|
+
);
|
|
23044
|
+
return lines.join("\n");
|
|
23045
|
+
}
|
|
23046
|
+
function buildSituationalRuntimeFacts(ctx, opts = {}) {
|
|
23047
|
+
const phase = resolveConversationPhase(ctx);
|
|
23048
|
+
const channel = opts.channel ?? (phase === "think" ? "think" : "explore");
|
|
23049
|
+
const responseMode = opts.responseMode ?? (channel === "think" ? "deep" : defaultExploreResponseMode(ctx, Math.floor(ctx.messages.length / 2)));
|
|
23050
|
+
return {
|
|
23051
|
+
phase,
|
|
23052
|
+
mode: responseMode,
|
|
23053
|
+
lens: ctx.scope?.primary_lens,
|
|
23054
|
+
stage: ctx.stage,
|
|
23055
|
+
can_compute: ctx.gapAudit?.can_compute === void 0 ? void 0 : String(ctx.gapAudit.can_compute),
|
|
23056
|
+
engine: canUseReplAi(ctx) ? "connected" : "none"
|
|
23057
|
+
};
|
|
23058
|
+
}
|
|
23059
|
+
function getConversationPhaseBlock(ctx, opts = {}) {
|
|
23060
|
+
return buildSituationalAwarenessBlock(ctx, opts);
|
|
23061
|
+
}
|
|
23062
|
+
var FUNNEL_SPINE, PHASE_MEANING;
|
|
23063
|
+
var init_situation = __esm({
|
|
23064
|
+
"src/conversation/situation.ts"() {
|
|
23065
|
+
"use strict";
|
|
23066
|
+
init_context2();
|
|
23067
|
+
init_explore_mode();
|
|
23068
|
+
init_repl_api();
|
|
23069
|
+
init_ghost_hints();
|
|
23070
|
+
init_recommended_action();
|
|
23071
|
+
init_phase();
|
|
23072
|
+
FUNNEL_SPINE = "Funnel: orient \u2192 scope \u2192 data \u2192 compute \u2192 explore | side doors: think, strategy, ship";
|
|
23073
|
+
PHASE_MEANING = {
|
|
23074
|
+
orient: "setup \u2014 open chat; user has not locked a scope yet",
|
|
23075
|
+
scope: "scope confirm \u2014 CLI owns yes/adjust; do not re-propose unless asked",
|
|
23076
|
+
awaiting_data: "data gate \u2014 load CSV/demo or compute; CLI owns the gate",
|
|
23077
|
+
compute: "compute in progress \u2014 wait",
|
|
23078
|
+
explore: "analysis complete \u2014 free-form Q&A on this session's artifact",
|
|
23079
|
+
think: "socratic think channel \u2014 return to \u203A via done/cancel",
|
|
23080
|
+
strategize: "strategist flow \u2014 objective confirm or plan drafting",
|
|
23081
|
+
deliver: "ship/handoff wizard \u2014 CLI owns write confirms"
|
|
23082
|
+
};
|
|
23083
|
+
}
|
|
23084
|
+
});
|
|
23085
|
+
|
|
22896
23086
|
// src/ai/agentic-loop.ts
|
|
22897
23087
|
function findingsJsonSchemaNudge() {
|
|
22898
23088
|
return `Respond with ONLY a JSON array of finding objects \u2014 no prose, no markdown fences. Each object needs: severity, segment, finding, vital_signs, entity_count, recommended_focus, dollar_value, recommended_plays. Shape:
|
|
@@ -22998,12 +23188,11 @@ The user already received the full report in the terminal. Answer follow-ups by
|
|
|
22998
23188
|
|
|
22999
23189
|
` : "";
|
|
23000
23190
|
const conversationSection = conversationBlock ? `
|
|
23001
|
-
CONVERSATION STATE:
|
|
23002
23191
|
${conversationBlock}
|
|
23003
|
-
Do not run compute until audit_data_gaps reports can_compute. If the user needs to load data, tell them to paste a CSV path or type "use demo data". You cannot open files.
|
|
23004
23192
|
|
|
23005
23193
|
` : "";
|
|
23006
23194
|
const conversationRules = `HOW A GREAT ANALYST CARRIES A CONVERSATION (read carefully \u2014 this is what separates you from a search box):
|
|
23195
|
+
- Pre-explore funnel gates (orient \u2192 scope \u2192 data \u2192 compute) are handled by the CLI. You speak after compute unless tools explicitly allow a restart.
|
|
23007
23196
|
- You have continuity. The conversation so far is provided to you as prior messages. Read it before answering. You remember every angle you have already explored, every number you have already surfaced, and every recommendation you have already made.
|
|
23008
23197
|
- Never repeat or re-recommend an analysis you have already presented in this conversation unless the user explicitly asks you to revisit, recall, or connect it. Repetition reads as forgetfulness and destroys trust.
|
|
23009
23198
|
- Each answer should ADVANCE the discussion: go deeper, introduce a genuinely new angle, challenge an assumption, or synthesize across what you have already found. Move the thinking forward.
|
|
@@ -23012,6 +23201,9 @@ Do not run compute until audit_data_gaps reports can_compute. If the user needs
|
|
|
23012
23201
|
- If you genuinely have nothing new to add on a topic, say so briefly and offer a different, higher-value direction \u2014 never pad with a recycled recommendation.`;
|
|
23013
23202
|
const briefJob = `YOUR JOB (BRIEF MODE \u2014 default after analysis is complete):
|
|
23014
23203
|
- Stay concise \u2014 roughly 40\u201390 words total, not a mini-report. Verdict first: your opening sentence is the headline the user should be able to repeat an hour later.
|
|
23204
|
+
- Lead with the single most expensive or most actionable implication from the completed session analysis \u2014 not a scorecard restatement.
|
|
23205
|
+
- Do not re-list all five vital signs unless the user asked for that inventory.
|
|
23206
|
+
- Prefer naming one playbook play or one next cut over a generic "want me to dig deeper?" close.
|
|
23015
23207
|
- You are at 30,000 ft by default. If they ask for detail or a new data cut, that routes to deep mode automatically \u2014 don't cram the weeds in here.
|
|
23016
23208
|
- Do NOT call tools unless the user explicitly asks for a new data cut (by rep, stage, list of deals, etc.). The completed analysis above is your source of truth.
|
|
23017
23209
|
- Name a playbook play only when the user asks what to do or which action to take.
|
|
@@ -23131,7 +23323,7 @@ ${briefCommandSection}` : "";
|
|
|
23131
23323
|
const dynamic = [
|
|
23132
23324
|
"SESSION STATE (current \u2014 changes as the session progresses):",
|
|
23133
23325
|
...dynamicSections,
|
|
23134
|
-
buildRuntimeBlock()
|
|
23326
|
+
buildRuntimeBlock(promptOptions.runtimeFacts ?? {})
|
|
23135
23327
|
].join("\n\n");
|
|
23136
23328
|
return { stable, dynamic };
|
|
23137
23329
|
}
|
|
@@ -23163,15 +23355,27 @@ async function* agenticFindings(computeResult, divergences, options) {
|
|
|
23163
23355
|
analysisBlock: options.analysisBlock,
|
|
23164
23356
|
conversationBlock: options.conversationBlock,
|
|
23165
23357
|
sessionArtifact: options.sessionArtifact,
|
|
23166
|
-
thinkState: options.ctx.thinkState
|
|
23358
|
+
thinkState: options.ctx.thinkState,
|
|
23359
|
+
runtimeFacts: buildSituationalRuntimeFacts(options.ctx, {
|
|
23360
|
+
channel: "think",
|
|
23361
|
+
responseMode: "deep"
|
|
23362
|
+
})
|
|
23167
23363
|
}) : mode === "fresh" ? buildFreshNlSystemPrompt(
|
|
23168
23364
|
options.sessionContext,
|
|
23169
23365
|
options.memoryBlock,
|
|
23170
23366
|
options.analysisBlock,
|
|
23171
23367
|
options.conversationBlock,
|
|
23172
|
-
{
|
|
23368
|
+
{
|
|
23369
|
+
responseMode,
|
|
23370
|
+
sessionArtifact: options.sessionArtifact,
|
|
23371
|
+
experiment,
|
|
23372
|
+
runtimeFacts: buildSituationalRuntimeFacts(options.ctx, {
|
|
23373
|
+
responseMode
|
|
23374
|
+
})
|
|
23375
|
+
}
|
|
23173
23376
|
) : buildSystemPrompt();
|
|
23174
|
-
const
|
|
23377
|
+
const analysisReady = isAnalysisReady(options.ctx);
|
|
23378
|
+
const tools = useTools ? mode === "think" ? buildThinkTools() : mode === "fresh" ? buildFreshNlTools({ analysisReady }) : buildInvestigationTools() : [];
|
|
23175
23379
|
const toolCtx = { computeResult, divergences };
|
|
23176
23380
|
if (options.includeMetrics) {
|
|
23177
23381
|
try {
|
|
@@ -23340,6 +23544,8 @@ var init_agentic_loop = __esm({
|
|
|
23340
23544
|
init_prompt_parts();
|
|
23341
23545
|
init_think_prompt();
|
|
23342
23546
|
init_prompt();
|
|
23547
|
+
init_context2();
|
|
23548
|
+
init_situation();
|
|
23343
23549
|
MAX_ITERATIONS = 10;
|
|
23344
23550
|
INVESTIGATION_EVIDENCE_NUDGE_AFTER = 5;
|
|
23345
23551
|
BRIEF_MAX_TOKENS = 768;
|
|
@@ -24173,52 +24379,82 @@ OUTPUT FORMAT \u2014 respond with STRICT JSON only, no preamble, no markdown fen
|
|
|
24173
24379
|
});
|
|
24174
24380
|
|
|
24175
24381
|
// src/commands/profile.ts
|
|
24176
|
-
var
|
|
24177
|
-
__export(
|
|
24382
|
+
var profile_exports2 = {};
|
|
24383
|
+
__export(profile_exports2, {
|
|
24178
24384
|
PRESET_DESCRIPTIONS: () => PRESET_DESCRIPTIONS,
|
|
24179
24385
|
PRESET_LABELS: () => PRESET_LABELS,
|
|
24180
24386
|
handler: () => handler4
|
|
24181
24387
|
});
|
|
24182
24388
|
import chalk23 from "chalk";
|
|
24183
|
-
async function handler4(args,
|
|
24389
|
+
async function handler4(args, ctx) {
|
|
24184
24390
|
const { positional } = parseArgs2(args);
|
|
24185
24391
|
const sub = positional[0] ?? "show";
|
|
24186
|
-
|
|
24187
|
-
|
|
24188
|
-
|
|
24189
|
-
|
|
24190
|
-
|
|
24191
|
-
|
|
24192
|
-
|
|
24193
|
-
|
|
24194
|
-
|
|
24195
|
-
|
|
24196
|
-
|
|
24392
|
+
const structured = isStructuredOutput(ctx.execution);
|
|
24393
|
+
try {
|
|
24394
|
+
switch (sub) {
|
|
24395
|
+
case "list":
|
|
24396
|
+
return listProfiles(structured);
|
|
24397
|
+
case "set":
|
|
24398
|
+
return setProfile(positional[1], structured);
|
|
24399
|
+
case "show":
|
|
24400
|
+
return showProfile(structured);
|
|
24401
|
+
default: {
|
|
24402
|
+
const err = new NtrpError("unknown_subcommand", `Unknown subcommand: ${sub}`, 2 /* Usage */);
|
|
24403
|
+
if (structured) emitError("profile", err);
|
|
24404
|
+
console.error(chalk23.red(` Unknown subcommand: ${sub}`));
|
|
24405
|
+
console.log(chalk23.dim(" Usage: /profile <list|set|show> [preset]"));
|
|
24406
|
+
process.exit(1);
|
|
24407
|
+
}
|
|
24197
24408
|
}
|
|
24409
|
+
} catch (err) {
|
|
24410
|
+
if (structured) emitError("profile", err);
|
|
24411
|
+
throw err;
|
|
24198
24412
|
}
|
|
24199
24413
|
}
|
|
24200
|
-
function listProfiles() {
|
|
24414
|
+
function listProfiles(structured) {
|
|
24415
|
+
const current = loadProfile()?.sales_motion ?? getConfigValue("sales-motion");
|
|
24416
|
+
const presets = Object.entries(PRESET_DESCRIPTIONS).map(([id, description]) => ({
|
|
24417
|
+
id,
|
|
24418
|
+
label: PRESET_LABELS[id],
|
|
24419
|
+
description,
|
|
24420
|
+
active: current === id
|
|
24421
|
+
}));
|
|
24422
|
+
if (structured) {
|
|
24423
|
+
emitResult("profile", { action: "list", presets, current_preset: current ?? null });
|
|
24424
|
+
return;
|
|
24425
|
+
}
|
|
24201
24426
|
console.log();
|
|
24202
24427
|
console.log(chalk23.bold(" Sales Motion Presets"));
|
|
24203
24428
|
console.log();
|
|
24204
|
-
const
|
|
24205
|
-
|
|
24206
|
-
|
|
24207
|
-
console.log(` ${
|
|
24208
|
-
console.log(chalk23.dim(` ${"".padEnd(16)} id: ${key}`));
|
|
24429
|
+
for (const p of presets) {
|
|
24430
|
+
const marker2 = p.active ? chalk23.green(" (active)") : "";
|
|
24431
|
+
console.log(` ${chalk23.bold(p.label.padEnd(16))} ${chalk23.dim(p.description)}${marker2}`);
|
|
24432
|
+
console.log(chalk23.dim(` ${"".padEnd(16)} id: ${p.id}`));
|
|
24209
24433
|
}
|
|
24210
24434
|
console.log();
|
|
24211
24435
|
console.log(chalk23.dim(" Run /profile set <preset> to activate a profile."));
|
|
24212
24436
|
console.log();
|
|
24213
24437
|
}
|
|
24214
|
-
function setProfile(preset) {
|
|
24438
|
+
function setProfile(preset, structured) {
|
|
24215
24439
|
if (!preset) {
|
|
24440
|
+
const err = new NtrpError(
|
|
24441
|
+
"preset_required",
|
|
24442
|
+
`/profile set requires a preset. Valid options: ${Object.keys(PROFILE_PRESETS).join(", ")}`,
|
|
24443
|
+
2 /* Usage */
|
|
24444
|
+
);
|
|
24445
|
+
if (structured) emitError("profile", err);
|
|
24216
24446
|
console.error(chalk23.red(" /profile set requires a preset."));
|
|
24217
24447
|
console.error(chalk23.dim(` Valid options: ${Object.keys(PROFILE_PRESETS).join(", ")}`));
|
|
24218
24448
|
process.exit(1);
|
|
24219
24449
|
}
|
|
24220
24450
|
const validPresets = Object.keys(PROFILE_PRESETS);
|
|
24221
24451
|
if (!validPresets.includes(preset)) {
|
|
24452
|
+
const err = new NtrpError(
|
|
24453
|
+
"unknown_preset",
|
|
24454
|
+
`Unknown preset: ${preset}. Valid options: ${validPresets.join(", ")}`,
|
|
24455
|
+
2 /* Usage */
|
|
24456
|
+
);
|
|
24457
|
+
if (structured) emitError("profile", err);
|
|
24222
24458
|
console.error(chalk23.red(`
|
|
24223
24459
|
Unknown preset: ${preset}`));
|
|
24224
24460
|
console.error(chalk23.dim(` Valid options: ${validPresets.join(", ")}
|
|
@@ -24227,26 +24463,76 @@ function setProfile(preset) {
|
|
|
24227
24463
|
}
|
|
24228
24464
|
const motion = preset;
|
|
24229
24465
|
setConfigValue("sales-motion", motion);
|
|
24466
|
+
const hadProfile = Boolean(loadProfile());
|
|
24467
|
+
if (hadProfile) {
|
|
24468
|
+
updateProfile({ sales_motion: motion });
|
|
24469
|
+
}
|
|
24230
24470
|
const resolved = resolveThresholds(motion, {});
|
|
24231
24471
|
const defaults = DEFAULT_THRESHOLDS;
|
|
24232
|
-
|
|
24233
|
-
console.log(chalk23.green(` Profile set to ${PRESET_LABELS[motion]}`));
|
|
24234
|
-
console.log();
|
|
24235
|
-
const changes = [];
|
|
24472
|
+
const threshold_changes = {};
|
|
24236
24473
|
if (resolved.freshness.people_window_days !== defaults.freshness.people_window_days) {
|
|
24237
|
-
|
|
24474
|
+
threshold_changes.freshness_window_days = {
|
|
24475
|
+
from: defaults.freshness.people_window_days,
|
|
24476
|
+
to: resolved.freshness.people_window_days
|
|
24477
|
+
};
|
|
24238
24478
|
}
|
|
24239
24479
|
if (resolved.flow_rate.green_days !== defaults.flow_rate.green_days) {
|
|
24240
|
-
|
|
24480
|
+
threshold_changes.flow_rate_green_days = {
|
|
24481
|
+
from: defaults.flow_rate.green_days,
|
|
24482
|
+
to: resolved.flow_rate.green_days
|
|
24483
|
+
};
|
|
24241
24484
|
}
|
|
24242
24485
|
if (resolved.flow_rate.stuck_days !== defaults.flow_rate.stuck_days) {
|
|
24243
|
-
|
|
24486
|
+
threshold_changes.flow_rate_stuck_days = {
|
|
24487
|
+
from: defaults.flow_rate.stuck_days,
|
|
24488
|
+
to: resolved.flow_rate.stuck_days
|
|
24489
|
+
};
|
|
24244
24490
|
}
|
|
24245
24491
|
if (resolved.signal_to_noise.lookback_days !== defaults.signal_to_noise.lookback_days) {
|
|
24246
|
-
|
|
24492
|
+
threshold_changes.signal_to_noise_lookback_days = {
|
|
24493
|
+
from: defaults.signal_to_noise.lookback_days,
|
|
24494
|
+
to: resolved.signal_to_noise.lookback_days
|
|
24495
|
+
};
|
|
24247
24496
|
}
|
|
24248
24497
|
if (resolved.thread_depth.activity_window_days !== defaults.thread_depth.activity_window_days) {
|
|
24249
|
-
|
|
24498
|
+
threshold_changes.thread_depth_window_days = {
|
|
24499
|
+
from: defaults.thread_depth.activity_window_days,
|
|
24500
|
+
to: resolved.thread_depth.activity_window_days
|
|
24501
|
+
};
|
|
24502
|
+
}
|
|
24503
|
+
if (structured) {
|
|
24504
|
+
emitResult("profile", {
|
|
24505
|
+
action: "set",
|
|
24506
|
+
motion,
|
|
24507
|
+
label: PRESET_LABELS[motion],
|
|
24508
|
+
threshold_changes,
|
|
24509
|
+
profile_synced: hadProfile
|
|
24510
|
+
});
|
|
24511
|
+
return;
|
|
24512
|
+
}
|
|
24513
|
+
console.log();
|
|
24514
|
+
console.log(chalk23.green(` Profile set to ${PRESET_LABELS[motion]}`));
|
|
24515
|
+
console.log();
|
|
24516
|
+
const changes = [];
|
|
24517
|
+
if (threshold_changes.freshness_window_days) {
|
|
24518
|
+
const c = threshold_changes.freshness_window_days;
|
|
24519
|
+
changes.push(` Freshness window: ${c.from}d \u2192 ${c.to}d`);
|
|
24520
|
+
}
|
|
24521
|
+
if (threshold_changes.flow_rate_green_days) {
|
|
24522
|
+
const c = threshold_changes.flow_rate_green_days;
|
|
24523
|
+
changes.push(` Flow rate green: ${c.from}d \u2192 ${c.to}d`);
|
|
24524
|
+
}
|
|
24525
|
+
if (threshold_changes.flow_rate_stuck_days) {
|
|
24526
|
+
const c = threshold_changes.flow_rate_stuck_days;
|
|
24527
|
+
changes.push(` Stuck threshold: ${c.from}d \u2192 ${c.to}d`);
|
|
24528
|
+
}
|
|
24529
|
+
if (threshold_changes.signal_to_noise_lookback_days) {
|
|
24530
|
+
const c = threshold_changes.signal_to_noise_lookback_days;
|
|
24531
|
+
changes.push(` S/N lookback: ${c.from}d \u2192 ${c.to}d`);
|
|
24532
|
+
}
|
|
24533
|
+
if (threshold_changes.thread_depth_window_days) {
|
|
24534
|
+
const c = threshold_changes.thread_depth_window_days;
|
|
24535
|
+
changes.push(` Thread depth window: ${c.from}d \u2192 ${c.to}d`);
|
|
24250
24536
|
}
|
|
24251
24537
|
if (changes.length > 0) {
|
|
24252
24538
|
console.log(chalk23.dim(" Threshold changes:"));
|
|
@@ -24258,9 +24544,27 @@ function setProfile(preset) {
|
|
|
24258
24544
|
console.log(chalk23.dim(" Run /diagnose to see results with the new profile."));
|
|
24259
24545
|
console.log();
|
|
24260
24546
|
}
|
|
24261
|
-
function showProfile() {
|
|
24547
|
+
function showProfile(structured) {
|
|
24262
24548
|
const companyProfile = loadProfile();
|
|
24263
24549
|
const current = companyProfile?.sales_motion ?? getConfigValue("sales-motion");
|
|
24550
|
+
const resolved = resolveThresholds(current ?? null, {});
|
|
24551
|
+
if (structured) {
|
|
24552
|
+
emitResult("profile", {
|
|
24553
|
+
action: "show",
|
|
24554
|
+
motion: current ?? null,
|
|
24555
|
+
label: current && PRESET_LABELS[current] ? PRESET_LABELS[current] : null,
|
|
24556
|
+
company_profile: companyProfile,
|
|
24557
|
+
thresholds: {
|
|
24558
|
+
freshness_window_days: resolved.freshness.people_window_days,
|
|
24559
|
+
flow_rate_green_days: resolved.flow_rate.green_days,
|
|
24560
|
+
flow_rate_stuck_days: resolved.flow_rate.stuck_days,
|
|
24561
|
+
signal_to_noise_lookback_days: resolved.signal_to_noise.lookback_days,
|
|
24562
|
+
thread_depth_window_days: resolved.thread_depth.activity_window_days,
|
|
24563
|
+
multi_thread_min: resolved.thread_depth.multi_thread_threshold
|
|
24564
|
+
}
|
|
24565
|
+
});
|
|
24566
|
+
return;
|
|
24567
|
+
}
|
|
24264
24568
|
if (companyProfile) {
|
|
24265
24569
|
const lines = [];
|
|
24266
24570
|
lines.push(`### ${companyProfile.company_name}`);
|
|
@@ -24290,7 +24594,6 @@ function showProfile() {
|
|
|
24290
24594
|
console.log(chalk23.dim(" No sales motion set. Using default thresholds."));
|
|
24291
24595
|
}
|
|
24292
24596
|
console.log();
|
|
24293
|
-
const resolved = resolveThresholds(current ?? null, {});
|
|
24294
24597
|
console.log(chalk23.dim(" Key Thresholds:"));
|
|
24295
24598
|
console.log(chalk23.dim(` Freshness window: ${resolved.freshness.people_window_days} days`));
|
|
24296
24599
|
console.log(chalk23.dim(` Flow rate (green): ${resolved.flow_rate.green_days} days`));
|
|
@@ -24312,6 +24615,9 @@ var init_profile2 = __esm({
|
|
|
24312
24615
|
init_argparse();
|
|
24313
24616
|
init_markdown();
|
|
24314
24617
|
init_theme();
|
|
24618
|
+
init_emit();
|
|
24619
|
+
init_errors2();
|
|
24620
|
+
init_types2();
|
|
24315
24621
|
PRESET_DESCRIPTIONS = {
|
|
24316
24622
|
plg: "Product-Led Growth \u2014 shorter cycles, high volume, self-serve focus",
|
|
24317
24623
|
smb_velocity: "SMB Velocity \u2014 fast sales cycles, quick close, volume-oriented",
|
|
@@ -25784,22 +26090,19 @@ async function loadReportData(segmentName) {
|
|
|
25784
26090
|
let { health, segments, findings, entityCounts } = diagnosis;
|
|
25785
26091
|
let scopedSegment = null;
|
|
25786
26092
|
if (segmentName) {
|
|
25787
|
-
const
|
|
25788
|
-
|
|
25789
|
-
|
|
25790
|
-
|
|
25791
|
-
|
|
25792
|
-
else if (subs.length > 1) {
|
|
25793
|
-
throw new NtrpError("ambiguous_segment", `"${segmentName}" matches multiple segments.`, 2 /* Usage */, {
|
|
25794
|
-
matches: subs.map((s) => s.segment.name)
|
|
25795
|
-
});
|
|
25796
|
-
}
|
|
26093
|
+
const resolved = resolveSegmentByName(segmentName, segments);
|
|
26094
|
+
if (resolved.type === "ambiguous") {
|
|
26095
|
+
throw new NtrpError("ambiguous_segment", `"${segmentName}" matches multiple segments.`, 2 /* Usage */, {
|
|
26096
|
+
matches: resolved.matches.map((s) => s.segment.name)
|
|
26097
|
+
});
|
|
25797
26098
|
}
|
|
25798
|
-
if (
|
|
26099
|
+
if (resolved.type === "none") {
|
|
25799
26100
|
throw new NtrpError("segment_not_found", `No segment matching "${segmentName}".`, 2 /* Usage */, {
|
|
25800
26101
|
available_segments: segments.map((s) => s.segment.name)
|
|
25801
26102
|
});
|
|
25802
26103
|
}
|
|
26104
|
+
const match = resolved.segment;
|
|
26105
|
+
const lower = segmentName.toLowerCase();
|
|
25803
26106
|
scopedSegment = match;
|
|
25804
26107
|
health = match.result;
|
|
25805
26108
|
segments = [match];
|
|
@@ -25825,6 +26128,7 @@ var init_report = __esm({
|
|
|
25825
26128
|
init_errors2();
|
|
25826
26129
|
init_types2();
|
|
25827
26130
|
init_session_analysis();
|
|
26131
|
+
init_segments();
|
|
25828
26132
|
}
|
|
25829
26133
|
});
|
|
25830
26134
|
|
|
@@ -26188,20 +26492,15 @@ async function handler10(args, _ctx) {
|
|
|
26188
26492
|
let exportFindings = diagnosis.findings;
|
|
26189
26493
|
let exportHealth = diagnosis.health;
|
|
26190
26494
|
if (segmentName) {
|
|
26191
|
-
const
|
|
26192
|
-
|
|
26193
|
-
|
|
26194
|
-
const subs = diagnosis.segments.filter((s) => s.segment.name.toLowerCase().includes(lower));
|
|
26195
|
-
if (subs.length === 1) match = subs[0];
|
|
26196
|
-
else if (subs.length > 1) {
|
|
26197
|
-
console.error(chalk30.yellow(`
|
|
26495
|
+
const resolved = resolveSegmentByName(segmentName, diagnosis.segments);
|
|
26496
|
+
if (resolved.type === "ambiguous") {
|
|
26497
|
+
console.error(chalk30.yellow(`
|
|
26198
26498
|
"${segmentName}" matches multiple segments:`));
|
|
26199
|
-
|
|
26200
|
-
|
|
26201
|
-
|
|
26202
|
-
}
|
|
26499
|
+
for (const s of resolved.matches) console.log(chalk30.dim(` - ${s.segment.name}`));
|
|
26500
|
+
console.log();
|
|
26501
|
+
process.exit(1);
|
|
26203
26502
|
}
|
|
26204
|
-
if (
|
|
26503
|
+
if (resolved.type === "none") {
|
|
26205
26504
|
console.error(chalk30.red(`
|
|
26206
26505
|
No segment matching "${segmentName}".`));
|
|
26207
26506
|
if (diagnosis.segments.length > 0) {
|
|
@@ -26211,6 +26510,8 @@ async function handler10(args, _ctx) {
|
|
|
26211
26510
|
console.log();
|
|
26212
26511
|
process.exit(1);
|
|
26213
26512
|
}
|
|
26513
|
+
const match = resolved.segment;
|
|
26514
|
+
const lower = segmentName.toLowerCase();
|
|
26214
26515
|
exportHealth = match.result;
|
|
26215
26516
|
exportSegments = [match];
|
|
26216
26517
|
exportFindings = diagnosis.findings.filter((f) => f.segment.toLowerCase().includes(lower));
|
|
@@ -26267,6 +26568,7 @@ var init_export = __esm({
|
|
|
26267
26568
|
init_path_safety();
|
|
26268
26569
|
init_argparse();
|
|
26269
26570
|
init_exports_registry();
|
|
26571
|
+
init_segments();
|
|
26270
26572
|
}
|
|
26271
26573
|
});
|
|
26272
26574
|
|
|
@@ -27619,15 +27921,6 @@ __export(segment_exports, {
|
|
|
27619
27921
|
handler: () => handler15
|
|
27620
27922
|
});
|
|
27621
27923
|
import chalk35 from "chalk";
|
|
27622
|
-
function resolveSegmentByName(query, segments) {
|
|
27623
|
-
const lower = query.toLowerCase();
|
|
27624
|
-
const exact = segments.find((s) => s.segment.name.toLowerCase() === lower);
|
|
27625
|
-
if (exact) return { type: "exact", segment: exact };
|
|
27626
|
-
const matches = segments.filter((s) => s.segment.name.toLowerCase().includes(lower));
|
|
27627
|
-
if (matches.length === 1) return { type: "exact", segment: matches[0] };
|
|
27628
|
-
if (matches.length > 1) return { type: "ambiguous", matches };
|
|
27629
|
-
return { type: "none" };
|
|
27630
|
-
}
|
|
27631
27924
|
function handleNoMatch(query, segments) {
|
|
27632
27925
|
console.error(chalk35.red(` No segment matching "${query}".`));
|
|
27633
27926
|
console.log();
|
|
@@ -27951,6 +28244,7 @@ var init_segment = __esm({
|
|
|
27951
28244
|
init_types2();
|
|
27952
28245
|
init_serialize();
|
|
27953
28246
|
init_session_analysis();
|
|
28247
|
+
init_segments();
|
|
27954
28248
|
OPERATOR_ALIASES = {
|
|
27955
28249
|
eq: "equals",
|
|
27956
28250
|
equals: "equals",
|
|
@@ -30565,42 +30859,85 @@ __export(reset_exports, {
|
|
|
30565
30859
|
handler: () => handler20
|
|
30566
30860
|
});
|
|
30567
30861
|
import chalk43 from "chalk";
|
|
30568
|
-
async function
|
|
30862
|
+
async function countRowsByTable() {
|
|
30863
|
+
const counts = {};
|
|
30864
|
+
for (const table of TABLES) {
|
|
30865
|
+
try {
|
|
30866
|
+
const rows = await all(`SELECT COUNT(*) AS cnt FROM ${table}`);
|
|
30867
|
+
counts[table] = Number(rows[0]?.cnt ?? 0);
|
|
30868
|
+
} catch {
|
|
30869
|
+
counts[table] = 0;
|
|
30870
|
+
}
|
|
30871
|
+
}
|
|
30872
|
+
return counts;
|
|
30873
|
+
}
|
|
30874
|
+
async function handler20(args, ctx) {
|
|
30569
30875
|
const { flags } = parseArgs2(args, ["force"]);
|
|
30570
30876
|
const force = getBool(flags, "force");
|
|
30877
|
+
const structured = isStructuredOutput(ctx.execution);
|
|
30571
30878
|
if (!force) {
|
|
30879
|
+
const message = "This will delete ALL data from the local database. Re-run with --force to confirm.";
|
|
30880
|
+
if (structured) {
|
|
30881
|
+
emitError("reset", new NtrpError("force_required", message, 2 /* Usage */));
|
|
30882
|
+
}
|
|
30572
30883
|
console.log(chalk43.yellow("\n This will delete ALL data from the local database."));
|
|
30573
30884
|
console.log(chalk43.dim(" Re-run with --force to confirm.\n"));
|
|
30574
30885
|
process.exit(1);
|
|
30575
30886
|
}
|
|
30576
|
-
const spinner = makeSpinner("Resetting database\u2026");
|
|
30887
|
+
const spinner = structured ? null : makeSpinner("Resetting database\u2026");
|
|
30577
30888
|
try {
|
|
30578
30889
|
await initSchema();
|
|
30890
|
+
const counts_by_table = await countRowsByTable();
|
|
30891
|
+
const total_rows_deleted = Object.values(counts_by_table).reduce((a, b) => a + b, 0);
|
|
30579
30892
|
for (const table of TABLES) {
|
|
30580
30893
|
await run(`DELETE FROM ${table}`);
|
|
30581
30894
|
}
|
|
30582
|
-
spinner.succeed("All data cleared");
|
|
30583
30895
|
const { wipePrivacyStore: wipePrivacyStore3 } = await Promise.resolve().then(() => (init_lexicon_seed(), lexicon_seed_exports));
|
|
30584
30896
|
wipePrivacyStore3();
|
|
30897
|
+
if (structured) {
|
|
30898
|
+
emitResult("reset", {
|
|
30899
|
+
status: "cleared",
|
|
30900
|
+
tables_cleared: TABLES,
|
|
30901
|
+
total_rows_deleted,
|
|
30902
|
+
counts_by_table
|
|
30903
|
+
});
|
|
30904
|
+
return;
|
|
30905
|
+
}
|
|
30906
|
+
spinner.succeed("All data cleared");
|
|
30585
30907
|
console.log(chalk43.dim("\n Run ") + chalk43.cyan("/new") + chalk43.dim(" \u2192 pick Demo, then choose your analysis type.\n"));
|
|
30586
30908
|
} catch (err) {
|
|
30587
30909
|
if (force && isClosedConnectionError(err)) {
|
|
30588
30910
|
try {
|
|
30589
|
-
spinner.text = "Recreating database\u2026";
|
|
30911
|
+
if (spinner) spinner.text = "Recreating database\u2026";
|
|
30590
30912
|
await recreateDatabaseFile();
|
|
30591
30913
|
await initSchema();
|
|
30592
|
-
spinner.succeed("Database recreated");
|
|
30593
30914
|
const { wipePrivacyStore: wipePrivacyStore3 } = await Promise.resolve().then(() => (init_lexicon_seed(), lexicon_seed_exports));
|
|
30594
30915
|
wipePrivacyStore3();
|
|
30916
|
+
if (structured) {
|
|
30917
|
+
emitResult("reset", {
|
|
30918
|
+
status: "recreated",
|
|
30919
|
+
tables_cleared: TABLES,
|
|
30920
|
+
total_rows_deleted: 0,
|
|
30921
|
+
counts_by_table: Object.fromEntries(TABLES.map((t) => [t, 0]))
|
|
30922
|
+
});
|
|
30923
|
+
return;
|
|
30924
|
+
}
|
|
30925
|
+
spinner.succeed("Database recreated");
|
|
30595
30926
|
console.log(chalk43.dim("\n Run ") + chalk43.cyan("/new") + chalk43.dim(" \u2192 pick Demo, then choose your analysis type.\n"));
|
|
30596
30927
|
return;
|
|
30597
30928
|
} catch (recreateErr) {
|
|
30598
|
-
|
|
30929
|
+
if (structured) {
|
|
30930
|
+
emitError("reset", recreateErr instanceof NtrpError ? recreateErr : new NtrpError("reset_failed", String(recreateErr), 1 /* RuntimeError */));
|
|
30931
|
+
}
|
|
30932
|
+
spinner?.fail("Reset failed");
|
|
30599
30933
|
console.error(chalk43.red(String(recreateErr)));
|
|
30600
30934
|
process.exit(1);
|
|
30601
30935
|
}
|
|
30602
30936
|
}
|
|
30603
|
-
|
|
30937
|
+
if (structured) {
|
|
30938
|
+
emitError("reset", err instanceof NtrpError ? err : new NtrpError("reset_failed", String(err), 1 /* RuntimeError */));
|
|
30939
|
+
}
|
|
30940
|
+
spinner?.fail("Reset failed");
|
|
30604
30941
|
console.error(chalk43.red(String(err)));
|
|
30605
30942
|
process.exit(1);
|
|
30606
30943
|
}
|
|
@@ -30613,6 +30950,9 @@ var init_reset = __esm({
|
|
|
30613
30950
|
init_connection();
|
|
30614
30951
|
init_schema();
|
|
30615
30952
|
init_argparse();
|
|
30953
|
+
init_emit();
|
|
30954
|
+
init_errors2();
|
|
30955
|
+
init_types2();
|
|
30616
30956
|
TABLES = [
|
|
30617
30957
|
"findings",
|
|
30618
30958
|
"health_readings",
|
|
@@ -30893,7 +31233,10 @@ function usage() {
|
|
|
30893
31233
|
console.log(chalk45.dim(" Tip: ") + paint("accent", "/config set api-key") + chalk45.dim(" opens a hidden prompt (no inline paste)."));
|
|
30894
31234
|
console.log(chalk45.dim(" Tip: ") + paint("accent", "/connect") + chalk45.dim(" auto-detects the provider from any pasted key."));
|
|
30895
31235
|
}
|
|
30896
|
-
function fail(message, ctx) {
|
|
31236
|
+
function fail(message, ctx, code = "usage_error") {
|
|
31237
|
+
if (isStructuredOutput(ctx.execution)) {
|
|
31238
|
+
emitError("config", new NtrpError(code, message, 2 /* Usage */));
|
|
31239
|
+
}
|
|
30897
31240
|
console.error(chalk45.red(` ${message}`));
|
|
30898
31241
|
if (ctx.oneShot) process.exit(1);
|
|
30899
31242
|
}
|
|
@@ -30910,9 +31253,18 @@ async function promptSecretValue(key, ctx) {
|
|
|
30910
31253
|
prompts.close();
|
|
30911
31254
|
}
|
|
30912
31255
|
}
|
|
31256
|
+
function redactConfig(config) {
|
|
31257
|
+
const secrets = secretKeys();
|
|
31258
|
+
const out = {};
|
|
31259
|
+
for (const [k, v] of Object.entries(config)) {
|
|
31260
|
+
out[k] = secrets.has(k) ? display(k, v) : v;
|
|
31261
|
+
}
|
|
31262
|
+
return out;
|
|
31263
|
+
}
|
|
30913
31264
|
async function handler22(args, ctx) {
|
|
30914
31265
|
const { positional } = parseArgs2(args);
|
|
30915
31266
|
const sub = positional[0] ?? "list";
|
|
31267
|
+
const structured = isStructuredOutput(ctx.execution);
|
|
30916
31268
|
switch (sub) {
|
|
30917
31269
|
case "set": {
|
|
30918
31270
|
const key = positional[1];
|
|
@@ -30924,6 +31276,10 @@ async function handler22(args, ctx) {
|
|
|
30924
31276
|
}
|
|
30925
31277
|
let value = inlineValue;
|
|
30926
31278
|
if (!value && secretKeys().has(key)) {
|
|
31279
|
+
if (structured) {
|
|
31280
|
+
fail(`/config set ${key} requires an inline value in --json mode`, ctx, "value_required");
|
|
31281
|
+
return;
|
|
31282
|
+
}
|
|
30927
31283
|
try {
|
|
30928
31284
|
value = await promptSecretValue(key, ctx);
|
|
30929
31285
|
} catch (err) {
|
|
@@ -30938,7 +31294,11 @@ async function handler22(args, ctx) {
|
|
|
30938
31294
|
setConfigValue(key, value);
|
|
30939
31295
|
const saved = getConfigValue(key);
|
|
30940
31296
|
if (saved !== value) {
|
|
30941
|
-
fail(`Failed to save ${key} \u2014 check permissions on ~/.ntrp/config.json`, ctx);
|
|
31297
|
+
fail(`Failed to save ${key} \u2014 check permissions on ~/.ntrp/config.json`, ctx, "save_failed");
|
|
31298
|
+
return;
|
|
31299
|
+
}
|
|
31300
|
+
if (structured) {
|
|
31301
|
+
emitResult("config", { action: "set", key, value: display(key, value) });
|
|
30942
31302
|
return;
|
|
30943
31303
|
}
|
|
30944
31304
|
console.log();
|
|
@@ -30972,6 +31332,15 @@ async function handler22(args, ctx) {
|
|
|
30972
31332
|
return;
|
|
30973
31333
|
}
|
|
30974
31334
|
const value = getConfigValue(key);
|
|
31335
|
+
if (structured) {
|
|
31336
|
+
emitResult("config", {
|
|
31337
|
+
action: "get",
|
|
31338
|
+
key,
|
|
31339
|
+
value: value === void 0 ? null : display(key, value),
|
|
31340
|
+
is_set: value !== void 0
|
|
31341
|
+
});
|
|
31342
|
+
return;
|
|
31343
|
+
}
|
|
30975
31344
|
if (value === void 0) console.log(chalk45.dim(` ${key} is not set`));
|
|
30976
31345
|
else console.log(` ${key} = ${display(key, value)}`);
|
|
30977
31346
|
return;
|
|
@@ -30979,6 +31348,14 @@ async function handler22(args, ctx) {
|
|
|
30979
31348
|
case "list": {
|
|
30980
31349
|
const config = loadConfig();
|
|
30981
31350
|
const entries2 = Object.entries(config);
|
|
31351
|
+
if (structured) {
|
|
31352
|
+
emitResult("config", {
|
|
31353
|
+
action: "list",
|
|
31354
|
+
config: redactConfig(config),
|
|
31355
|
+
count: entries2.length
|
|
31356
|
+
});
|
|
31357
|
+
return;
|
|
31358
|
+
}
|
|
30982
31359
|
if (entries2.length === 0) {
|
|
30983
31360
|
console.log(chalk45.dim(" No configuration set."));
|
|
30984
31361
|
return;
|
|
@@ -30993,11 +31370,15 @@ async function handler22(args, ctx) {
|
|
|
30993
31370
|
return;
|
|
30994
31371
|
}
|
|
30995
31372
|
deleteConfigValue(key);
|
|
31373
|
+
if (structured) {
|
|
31374
|
+
emitResult("config", { action: "delete", key });
|
|
31375
|
+
return;
|
|
31376
|
+
}
|
|
30996
31377
|
console.log(chalk45.green(` Deleted ${key}`));
|
|
30997
31378
|
return;
|
|
30998
31379
|
}
|
|
30999
31380
|
default: {
|
|
31000
|
-
fail(`Unknown subcommand: ${sub}`, ctx);
|
|
31381
|
+
fail(`Unknown subcommand: ${sub}`, ctx, "unknown_subcommand");
|
|
31001
31382
|
usage();
|
|
31002
31383
|
return;
|
|
31003
31384
|
}
|
|
@@ -31012,6 +31393,9 @@ var init_config = __esm({
|
|
|
31012
31393
|
init_argparse();
|
|
31013
31394
|
init_prompts();
|
|
31014
31395
|
init_theme();
|
|
31396
|
+
init_emit();
|
|
31397
|
+
init_errors2();
|
|
31398
|
+
init_types2();
|
|
31015
31399
|
}
|
|
31016
31400
|
});
|
|
31017
31401
|
|
|
@@ -31029,8 +31413,13 @@ async function afterLicenseSuccess(ctx) {
|
|
|
31029
31413
|
async function handler23(args, ctx) {
|
|
31030
31414
|
const { positional } = parseArgs2(args);
|
|
31031
31415
|
const key = positional[0];
|
|
31416
|
+
const structured = isStructuredOutput(ctx.execution);
|
|
31032
31417
|
if (!key) {
|
|
31033
|
-
if (ctx.oneShot || !process.stdin.isTTY) {
|
|
31418
|
+
if (ctx.oneShot || !process.stdin.isTTY || structured) {
|
|
31419
|
+
const message = "Usage: /activate <key>. Or type /activate in ntrp to paste a key.";
|
|
31420
|
+
if (structured) {
|
|
31421
|
+
emitError("activate", new NtrpError("key_required", message, 2 /* Usage */));
|
|
31422
|
+
}
|
|
31034
31423
|
console.error(chalk46.red("\n Usage: /activate <key>"));
|
|
31035
31424
|
console.error(chalk46.dim(" Or type /activate in ntrp to paste a key.\n"));
|
|
31036
31425
|
if (ctx.oneShot) process.exit(1);
|
|
@@ -31056,18 +31445,29 @@ async function handler23(args, ctx) {
|
|
|
31056
31445
|
try {
|
|
31057
31446
|
const result = await activateLicenseKey(key);
|
|
31058
31447
|
if (!result.valid) {
|
|
31448
|
+
if (structured) {
|
|
31449
|
+
emitResult("activate", { valid: false, message: result.message });
|
|
31450
|
+
process.exit(1);
|
|
31451
|
+
}
|
|
31059
31452
|
console.error(chalk46.red(`
|
|
31060
31453
|
${result.message}
|
|
31061
31454
|
`));
|
|
31062
31455
|
if (ctx.oneShot) process.exit(1);
|
|
31063
31456
|
return;
|
|
31064
31457
|
}
|
|
31458
|
+
if (structured) {
|
|
31459
|
+
emitResult("activate", { valid: true, message: result.message });
|
|
31460
|
+
return;
|
|
31461
|
+
}
|
|
31065
31462
|
console.log(chalk46.green(`
|
|
31066
31463
|
License activated: ${result.message}
|
|
31067
31464
|
`));
|
|
31068
31465
|
await afterLicenseSuccess(ctx);
|
|
31069
31466
|
} catch (err) {
|
|
31070
31467
|
const message = err instanceof Error ? err.message : "License activation failed";
|
|
31468
|
+
if (structured) {
|
|
31469
|
+
emitError("activate", new NtrpError("activation_failed", message, 3 /* Auth */));
|
|
31470
|
+
}
|
|
31071
31471
|
console.error(chalk46.red(`
|
|
31072
31472
|
${message}
|
|
31073
31473
|
`));
|
|
@@ -31083,6 +31483,9 @@ var init_activate = __esm({
|
|
|
31083
31483
|
init_activation();
|
|
31084
31484
|
init_upgrade();
|
|
31085
31485
|
init_theme();
|
|
31486
|
+
init_emit();
|
|
31487
|
+
init_errors2();
|
|
31488
|
+
init_types2();
|
|
31086
31489
|
}
|
|
31087
31490
|
});
|
|
31088
31491
|
|
|
@@ -32180,10 +32583,10 @@ async function runNaturalLanguage(input, ctx) {
|
|
|
32180
32583
|
setAgentContext(ctx);
|
|
32181
32584
|
try {
|
|
32182
32585
|
const analysisBlock = buildAnalysisBlock(ctx);
|
|
32183
|
-
const
|
|
32586
|
+
const responseMode = resolveExploreResponseMode(input, ctx, ctx.conversation.length);
|
|
32587
|
+
const conversationBlock = getConversationPhaseBlock(ctx, { responseMode });
|
|
32184
32588
|
const bundle = await loadSessionAnalysisBundle();
|
|
32185
32589
|
const sessionArtifact = hasAnyAnalysis(bundle) ? buildExploreContextBlock(bundle, ctx) : "";
|
|
32186
|
-
const responseMode = resolveExploreResponseMode(input, ctx, ctx.conversation.length);
|
|
32187
32590
|
for await (const event of agenticFindings(snapshot, ctx.snapshot.divergences, {
|
|
32188
32591
|
mode: "fresh",
|
|
32189
32592
|
userQuestion: input,
|
|
@@ -32281,6 +32684,7 @@ var init_nl = __esm({
|
|
|
32281
32684
|
"use strict";
|
|
32282
32685
|
init_spinner();
|
|
32283
32686
|
init_context2();
|
|
32687
|
+
init_situation();
|
|
32284
32688
|
init_phase();
|
|
32285
32689
|
init_agent_context();
|
|
32286
32690
|
init_orchestrator();
|
|
@@ -32346,7 +32750,7 @@ async function* streamAsk(question, ctx) {
|
|
|
32346
32750
|
sessionContext: ctx.resumedSessionSummary,
|
|
32347
32751
|
includeMetrics: true,
|
|
32348
32752
|
analysisBlock: buildAnalysisBlock(ctx),
|
|
32349
|
-
conversationBlock: getConversationPhaseBlock(ctx),
|
|
32753
|
+
conversationBlock: getConversationPhaseBlock(ctx, { responseMode }),
|
|
32350
32754
|
sessionArtifact,
|
|
32351
32755
|
responseMode,
|
|
32352
32756
|
priorMessages: ctx.conversation,
|
|
@@ -32391,7 +32795,7 @@ var init_ask = __esm({
|
|
|
32391
32795
|
init_divergence();
|
|
32392
32796
|
init_repl_api();
|
|
32393
32797
|
init_context2();
|
|
32394
|
-
|
|
32798
|
+
init_situation();
|
|
32395
32799
|
init_session_analysis();
|
|
32396
32800
|
init_smoke_protocol();
|
|
32397
32801
|
}
|
|
@@ -33584,7 +33988,7 @@ async function runThinkTurn(input, ctx) {
|
|
|
33584
33988
|
setAgentContext(ctx);
|
|
33585
33989
|
try {
|
|
33586
33990
|
const analysisBlock = buildAnalysisBlock(ctx);
|
|
33587
|
-
const conversationBlock = getConversationPhaseBlock(ctx);
|
|
33991
|
+
const conversationBlock = getConversationPhaseBlock(ctx, { channel: "think", responseMode: "deep" });
|
|
33588
33992
|
const bundle = await loadSessionAnalysisBundle();
|
|
33589
33993
|
const sessionArtifact = hasAnyAnalysis(bundle) ? buildExploreContextBlock(bundle, ctx) : "";
|
|
33590
33994
|
for await (const event of agenticFindings(snapshot, ctx.snapshot.divergences, {
|
|
@@ -33671,7 +34075,7 @@ var init_think = __esm({
|
|
|
33671
34075
|
"use strict";
|
|
33672
34076
|
init_spinner();
|
|
33673
34077
|
init_context2();
|
|
33674
|
-
|
|
34078
|
+
init_situation();
|
|
33675
34079
|
init_agent_context();
|
|
33676
34080
|
init_agentic_loop();
|
|
33677
34081
|
init_thread();
|
|
@@ -33735,7 +34139,7 @@ function printChannelIntro(seed) {
|
|
|
33735
34139
|
console.log(
|
|
33736
34140
|
" " + chalk67.dim(
|
|
33737
34141
|
"Socratic channel \u2014 explore, challenge assumptions, pull evidence. Type "
|
|
33738
|
-
) + chalk67.cyan("done") + chalk67.dim(" or ") + chalk67.cyan("cancel") + chalk67.dim(" to return to
|
|
34142
|
+
) + chalk67.cyan("done") + chalk67.dim(" or ") + chalk67.cyan("cancel") + chalk67.dim(" to return to \u203A.")
|
|
33739
34143
|
);
|
|
33740
34144
|
if (seed) {
|
|
33741
34145
|
console.log(" " + chalk67.dim("Seed: ") + seed);
|
|
@@ -33821,7 +34225,7 @@ function clearThinkFlow(ctx, reason) {
|
|
|
33821
34225
|
console.log();
|
|
33822
34226
|
console.log(
|
|
33823
34227
|
" " + chalk67.dim(
|
|
33824
|
-
reason === "done" ? "Think channel closed. Back to
|
|
34228
|
+
reason === "done" ? "Think channel closed. Back to \u203A." : "Think session cancelled. Continue exploration."
|
|
33825
34229
|
)
|
|
33826
34230
|
);
|
|
33827
34231
|
console.log();
|
|
@@ -35705,7 +36109,7 @@ async function importHandler(runtimePath) {
|
|
|
35705
36109
|
case "../commands/publish.js":
|
|
35706
36110
|
return Promise.resolve().then(() => (init_publish2(), publish_exports));
|
|
35707
36111
|
case "../commands/profile.js":
|
|
35708
|
-
return Promise.resolve().then(() => (init_profile2(),
|
|
36112
|
+
return Promise.resolve().then(() => (init_profile2(), profile_exports2));
|
|
35709
36113
|
case "../commands/config.js":
|
|
35710
36114
|
return Promise.resolve().then(() => (init_config(), config_exports));
|
|
35711
36115
|
case "../commands/activate.js":
|
|
@@ -36241,7 +36645,7 @@ handler: ../commands/thinkwithme.ts
|
|
|
36241
36645
|
|
|
36242
36646
|
Open a dedicated \`think \u203A\` channel to explore ideas, challenge assumptions, and pull evidence from your data.
|
|
36243
36647
|
Bare \`/thinkwithme\` enters the channel. Type \`/thinkwithme <topic>\` to seed the first turn.
|
|
36244
|
-
Requires an analysis and an AI key (\`/connect\`). Type \`done\` or \`cancel\` to return to
|
|
36648
|
+
Requires an analysis and an AI key (\`/connect\`). Type \`done\` or \`cancel\` to return to \u203A.
|
|
36245
36649
|
When you are ready to commit to a plan, say so or the partner can hand off to \`/strategy\`.`
|
|
36246
36650
|
},
|
|
36247
36651
|
{
|
|
@@ -36429,6 +36833,7 @@ Manage CLI configuration stored at \`~/.ntrp/config.json\`. Useful keys:
|
|
|
36429
36833
|
\`api-key\` (Anthropic), \`openai-api-key\` (and \`groq-api-key\`, \`google-api-key\`, ...),
|
|
36430
36834
|
\`llm-primary\` (default provider), \`llm-tier\`, \`llm-auto-failover\`,
|
|
36431
36835
|
\`voice-personality\`, \`voice-roast\`,
|
|
36836
|
+
\`sales-motion\`, \`rep_hourly_cost\`, \`hours_per_activity\`,
|
|
36432
36837
|
\`default-format\`, \`export-dir\`, \`ai-inbox-dir\` (or type \`/inbox set\`).
|
|
36433
36838
|
|
|
36434
36839
|
Setting a provider key opens a hidden prompt and auto-discovers that provider's models.
|
|
@@ -39251,7 +39656,7 @@ function printHelp() {
|
|
|
39251
39656
|
console.log(" " + chalk88.dim("After analysis, type questions in English."));
|
|
39252
39657
|
console.log(" " + chalk88.dim("Type ") + paint("accent", '"how should we fix this?"') + chalk88.dim(" to make a strategy."));
|
|
39253
39658
|
console.log(" " + chalk88.dim("Type ") + paint("accent", '"ship a board deck"') + chalk88.dim(" to write a handoff."));
|
|
39254
|
-
console.log(" " + chalk88.dim("The ") + paint("accent", "
|
|
39659
|
+
console.log(" " + chalk88.dim("The ") + paint("accent", "\u203A") + chalk88.dim(" prompt shows brief or deep after analysis. Brief is the default."));
|
|
39255
39660
|
console.log();
|
|
39256
39661
|
console.log(" " + sectionHeading("Shortcuts"));
|
|
39257
39662
|
const shortcuts = [
|
|
@@ -39310,6 +39715,7 @@ var init_repl = __esm({
|
|
|
39310
39715
|
init_prompt_parts();
|
|
39311
39716
|
init_store();
|
|
39312
39717
|
init_phase();
|
|
39718
|
+
init_ghost_hints();
|
|
39313
39719
|
init_recommended_action();
|
|
39314
39720
|
init_inline_suggestion();
|
|
39315
39721
|
init_loop_guard2();
|
|
@@ -39370,23 +39776,7 @@ var init_repl = __esm({
|
|
|
39370
39776
|
"May your pipeline stay hydrated.",
|
|
39371
39777
|
"Don't let the zombie deals bite."
|
|
39372
39778
|
];
|
|
39373
|
-
GHOST_HINTS =
|
|
39374
|
-
orient: [
|
|
39375
|
-
'try "pipeline health"',
|
|
39376
|
-
"try /deepdive",
|
|
39377
|
-
"try /deepdive guide",
|
|
39378
|
-
'try "is our retention real for the board?"',
|
|
39379
|
-
'try "what is the most expensive problem to solve?"',
|
|
39380
|
-
'try "board deck on Q3"'
|
|
39381
|
-
],
|
|
39382
|
-
explore: [
|
|
39383
|
-
'try "what is ARR?"',
|
|
39384
|
-
"try /deepdive freshness",
|
|
39385
|
-
'try "how should we fix this?"',
|
|
39386
|
-
'try "which segment is weakest?"',
|
|
39387
|
-
'try "ship a board deck"'
|
|
39388
|
-
]
|
|
39389
|
-
};
|
|
39779
|
+
GHOST_HINTS = PHASE_GHOST_HINTS;
|
|
39390
39780
|
ghostHintTurn = 0;
|
|
39391
39781
|
activeGhostHint = null;
|
|
39392
39782
|
suggestionPainted = false;
|