@sonnechasser/ntrp 1.4.8 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +867 -191
- package/dist/mcp/server.js +1145 -1008
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -6587,6 +6587,10 @@ function stripTools(req) {
|
|
|
6587
6587
|
return rest;
|
|
6588
6588
|
}
|
|
6589
6589
|
async function outboundRequest(req) {
|
|
6590
|
+
if (req.skipPseudonymize) {
|
|
6591
|
+
const { skipPseudonymize: _drop, ...rest } = req;
|
|
6592
|
+
return rest;
|
|
6593
|
+
}
|
|
6590
6594
|
await ensureLexiconSeeded();
|
|
6591
6595
|
return protectRequest(req);
|
|
6592
6596
|
}
|
|
@@ -6848,13 +6852,14 @@ var init_failover = __esm({
|
|
|
6848
6852
|
});
|
|
6849
6853
|
|
|
6850
6854
|
// src/ai/llm/complete.ts
|
|
6851
|
-
async function llmCompleteText(surface, system, userMessage, max_tokens, ctx) {
|
|
6855
|
+
async function llmCompleteText(surface, system, userMessage, max_tokens, ctx, opts = {}) {
|
|
6852
6856
|
const { response, meta } = await completeWithFailover(
|
|
6853
6857
|
{
|
|
6854
6858
|
surface,
|
|
6855
6859
|
system,
|
|
6856
6860
|
messages: [{ role: "user", content: userMessage }],
|
|
6857
|
-
max_tokens
|
|
6861
|
+
max_tokens,
|
|
6862
|
+
...opts.skipPseudonymize ? { skipPseudonymize: true } : {}
|
|
6858
6863
|
},
|
|
6859
6864
|
{ ctx }
|
|
6860
6865
|
);
|
|
@@ -12330,7 +12335,7 @@ var init_guide_slides = __esm({
|
|
|
12330
12335
|
tagline: "Start with the question you care about \u2014 we'll mirror it back before we compute.",
|
|
12331
12336
|
visual: {
|
|
12332
12337
|
kind: "funnel",
|
|
12333
|
-
caption: "When a dim \u23CE hint shows, bare Enter submits it. No hint means Enter does nothing.",
|
|
12338
|
+
caption: "When a dim \u23CE hint shows, bare Enter submits it. Type b (or back) to step back one gate. No hint means Enter does nothing.",
|
|
12334
12339
|
funnel: [
|
|
12335
12340
|
{ label: "Type a question", widthPct: 100 },
|
|
12336
12341
|
{ label: "\u23CE yes (scope)", widthPct: 78 },
|
|
@@ -12341,6 +12346,8 @@ var init_guide_slides = __esm({
|
|
|
12341
12346
|
lines: [
|
|
12342
12347
|
`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.`,
|
|
12343
12348
|
"",
|
|
12349
|
+
"Hit Enter by accident on \u23CE yes? Type b or back \u2014 that rewinds to the focus card. The same word leaves a strategy or think overlay. It does not unload demo data or undo compute; /scratch or a new session is the blunt reset.",
|
|
12350
|
+
"",
|
|
12344
12351
|
"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.",
|
|
12345
12352
|
"",
|
|
12346
12353
|
"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."
|
|
@@ -12349,7 +12356,7 @@ var init_guide_slides = __esm({
|
|
|
12349
12356
|
"Power-user slash commands still work. They stay hidden from /help: /new, /diagnose, /metrics, /ingest, /session.",
|
|
12350
12357
|
'"use demo data" loads a fitted sample book. Company profile is optional. In scripts, /demo --no-profile.',
|
|
12351
12358
|
'After compute, the prompt returns to \u203A. Brief is the default depth; type "go deep" for the long read.',
|
|
12352
|
-
"/home shows phase status. /help lists conversation shortcuts \
|
|
12359
|
+
"/home shows phase status. /help lists conversation shortcuts and the Keys legend (\u23CE, b/back, cancel)."
|
|
12353
12360
|
]
|
|
12354
12361
|
};
|
|
12355
12362
|
ASK = {
|
|
@@ -12369,7 +12376,7 @@ var init_guide_slides = __esm({
|
|
|
12369
12376
|
"",
|
|
12370
12377
|
'"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.',
|
|
12371
12378
|
"",
|
|
12372
|
-
"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."
|
|
12379
|
+
"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 \u2014 and b / back steps back one confirm gate (or the previous tour slide while you're in /deepdive)."
|
|
12373
12380
|
],
|
|
12374
12381
|
deepdive: [
|
|
12375
12382
|
"/connect ollama for a keyless local model. /connect --base-url <url> --id <name> for any OpenAI-compatible endpoint.",
|
|
@@ -12597,7 +12604,9 @@ function paintIntro(opts) {
|
|
|
12597
12604
|
extraLines: [
|
|
12598
12605
|
...TOUR_INTRO_LINES,
|
|
12599
12606
|
"",
|
|
12600
|
-
chalk10.dim(
|
|
12607
|
+
chalk10.dim(
|
|
12608
|
+
"No AI key needed for this tour. Enter advances; b goes to the previous slide; /deepdive (or d) opens more on any slide. After the tour, b / back steps back one confirm gate in the REPL."
|
|
12609
|
+
)
|
|
12601
12610
|
],
|
|
12602
12611
|
footer: `${progressDots(opts.index, opts.total)} \u23CE next \xB7 q skip`
|
|
12603
12612
|
});
|
|
@@ -12796,7 +12805,7 @@ async function offerFirstRunTour(ctx) {
|
|
|
12796
12805
|
{
|
|
12797
12806
|
value: "tour",
|
|
12798
12807
|
label: "Take the tour",
|
|
12799
|
-
description: "\u23CE
|
|
12808
|
+
description: "\u23CE next \xB7 b back on slides \u2014 /deepdive on any slide for more"
|
|
12800
12809
|
},
|
|
12801
12810
|
{
|
|
12802
12811
|
value: "skip",
|
|
@@ -12862,7 +12871,7 @@ var init_metric_tour = __esm({
|
|
|
12862
12871
|
"",
|
|
12863
12872
|
"We're a stethoscope, not a hospital: we listen hard, translate scores into dollars, and point at plays \u2014 you decide the surgery."
|
|
12864
12873
|
];
|
|
12865
|
-
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.";
|
|
12874
|
+
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. On slides, b goes back; after the tour, the same word steps back at confirm gates.";
|
|
12866
12875
|
}
|
|
12867
12876
|
});
|
|
12868
12877
|
|
|
@@ -15593,7 +15602,8 @@ function buildConversationPrompt(ctx) {
|
|
|
15593
15602
|
const enterHint2 = action ? chalk11.dim(` \xB7 \u23CE ${action.hint}`) : "";
|
|
15594
15603
|
return accentPromptHead(phase, sessionName) + chalk11.dim(stack) + enterHint2 + " ";
|
|
15595
15604
|
}
|
|
15596
|
-
const
|
|
15605
|
+
const modalBack = phase === "scope" || phase === "awaiting_data" || phase === "strategize" ? " \xB7 b back" : "";
|
|
15606
|
+
const enterHint = action ? chalk11.dim(`\u23CE ${action.hint}${modalBack} `) : "";
|
|
15597
15607
|
return accentPromptHead(phase, sessionName) + enterHint;
|
|
15598
15608
|
}
|
|
15599
15609
|
var PROMPT_LABELS;
|
|
@@ -21813,10 +21823,12 @@ var init_agent_context = __esm({
|
|
|
21813
21823
|
var scope_exports = {};
|
|
21814
21824
|
__export(scope_exports, {
|
|
21815
21825
|
confirmScope: () => confirmScope,
|
|
21826
|
+
isBackInput: () => isBackInput,
|
|
21816
21827
|
isScopeAdjustInput: () => isScopeAdjustInput,
|
|
21817
21828
|
isScopeConfirmInput: () => isScopeConfirmInput,
|
|
21818
21829
|
isSkipClarifier: () => isSkipClarifier,
|
|
21819
|
-
proposeScopeFromIntent: () => proposeScopeFromIntent
|
|
21830
|
+
proposeScopeFromIntent: () => proposeScopeFromIntent,
|
|
21831
|
+
unconfirmScope: () => unconfirmScope
|
|
21820
21832
|
});
|
|
21821
21833
|
function proposeScopeFromIntent(input) {
|
|
21822
21834
|
const text = input.trim();
|
|
@@ -21861,12 +21873,20 @@ function confirmScope(ctx) {
|
|
|
21861
21873
|
if (ctx.scope.primary_lens === "revenue_metrics" || ctx.scope.primary_lens === "gtm_health") {
|
|
21862
21874
|
}
|
|
21863
21875
|
}
|
|
21876
|
+
function unconfirmScope(ctx) {
|
|
21877
|
+
if (!ctx.scope?.confirmed_at) return;
|
|
21878
|
+
const { confirmed_at: _dropped, ...rest } = ctx.scope;
|
|
21879
|
+
ctx.scope = rest;
|
|
21880
|
+
}
|
|
21864
21881
|
function isScopeConfirmInput(input) {
|
|
21865
21882
|
return /^(yes|y|yeah|yep|correct|looks good|that'?s right|confirm|ok|okay|go)$/i.test(input.trim());
|
|
21866
21883
|
}
|
|
21867
21884
|
function isScopeAdjustInput(input) {
|
|
21868
21885
|
return /^(adjust|change|refine|edit|no|nope)$/i.test(input.trim());
|
|
21869
21886
|
}
|
|
21887
|
+
function isBackInput(input) {
|
|
21888
|
+
return /^(b|back|prev)\s*[.!]?\s*$/i.test(input.trim());
|
|
21889
|
+
}
|
|
21870
21890
|
function isSkipClarifier(input) {
|
|
21871
21891
|
return /^skip$/i.test(input.trim());
|
|
21872
21892
|
}
|
|
@@ -22807,9 +22827,9 @@ async function handleGetSessionBrief(input) {
|
|
|
22807
22827
|
if (!target) {
|
|
22808
22828
|
return { error: `No session matching "${raw}".` };
|
|
22809
22829
|
}
|
|
22810
|
-
const { existsSync:
|
|
22830
|
+
const { existsSync: existsSync38, readFileSync: readFileSync24 } = await import("fs");
|
|
22811
22831
|
const briefPath = contextDocPathForSession2(target.id);
|
|
22812
|
-
if (!
|
|
22832
|
+
if (!existsSync38(briefPath)) {
|
|
22813
22833
|
return {
|
|
22814
22834
|
session_id: target.id,
|
|
22815
22835
|
error: "No context brief on disk for this session (created before brief storage existed).",
|
|
@@ -23300,7 +23320,7 @@ function buildSituationalAwarenessBlock(ctx, opts = {}) {
|
|
|
23300
23320
|
`Phase: ${phase} (${formatPhaseLabel(phase)}) \u2014 ${PHASE_MEANING[phase]}`
|
|
23301
23321
|
];
|
|
23302
23322
|
if (channel === "think") {
|
|
23303
|
-
lines.push("Channel: think \u2014 socratic partner; type done/cancel returns to \u203A");
|
|
23323
|
+
lines.push("Channel: think \u2014 socratic partner; type done/cancel/b back returns to \u203A");
|
|
23304
23324
|
lines.push("Response mode: deep (tools on)");
|
|
23305
23325
|
} else if (channel === "strategist") {
|
|
23306
23326
|
lines.push("Channel: strategist \u2014 measurable plan engine owns sequencing");
|
|
@@ -23344,11 +23364,13 @@ function buildSituationalAwarenessBlock(ctx, opts = {}) {
|
|
|
23344
23364
|
} else if (phase === "awaiting_data") {
|
|
23345
23365
|
lines.push("Already done: scope confirmed; analysis not ready");
|
|
23346
23366
|
lines.push(
|
|
23347
|
-
'Available now: tell the user to paste a CSV path, type "use demo data",
|
|
23367
|
+
'Available now: tell the user to paste a CSV path, type "use demo data", \u23CE go ahead when can_compute, or b/back to rewind the focus card \u2014 you cannot open files'
|
|
23348
23368
|
);
|
|
23349
23369
|
} else if (phase === "scope") {
|
|
23350
23370
|
lines.push("Already done: intent proposed; waiting on confirm");
|
|
23351
|
-
lines.push(
|
|
23371
|
+
lines.push(
|
|
23372
|
+
"Available now: wait for CLI confirm (\u23CE yes) or b/back/adjust \u2014 do not invent a different question"
|
|
23373
|
+
);
|
|
23352
23374
|
} else if (phase === "orient") {
|
|
23353
23375
|
lines.push("Already done: nothing locked yet");
|
|
23354
23376
|
lines.push("Available now: help the user name a focus; CLI will propose scope from their words");
|
|
@@ -23421,13 +23443,13 @@ var init_situation = __esm({
|
|
|
23421
23443
|
FUNNEL_SPINE = "Funnel: orient \u2192 scope \u2192 data \u2192 compute \u2192 explore | side doors: think, strategy, ship";
|
|
23422
23444
|
PHASE_MEANING = {
|
|
23423
23445
|
orient: "setup \u2014 open chat; user has not locked a scope yet",
|
|
23424
|
-
scope: "scope confirm \u2014 CLI owns yes/adjust; do not re-propose unless asked",
|
|
23425
|
-
awaiting_data: "data gate \u2014 load CSV/demo or compute; CLI owns the gate",
|
|
23446
|
+
scope: "scope confirm \u2014 CLI owns yes / b back / adjust; do not re-propose unless asked",
|
|
23447
|
+
awaiting_data: "data gate \u2014 load CSV/demo or compute; b/back rewinds to scope confirm (does not unload data); CLI owns the gate",
|
|
23426
23448
|
compute: "compute in progress \u2014 wait",
|
|
23427
23449
|
explore: "analysis complete \u2014 free-form Q&A on this session's artifact",
|
|
23428
|
-
think: "socratic think channel \u2014 return to \u203A via done/cancel",
|
|
23429
|
-
strategize: "strategist flow \u2014 objective confirm or plan drafting",
|
|
23430
|
-
deliver: "ship/handoff wizard \u2014 CLI owns write confirms"
|
|
23450
|
+
think: "socratic think channel \u2014 return to \u203A via done/cancel/b back",
|
|
23451
|
+
strategize: "strategist flow \u2014 objective confirm (yes / b back / adjust) or plan drafting",
|
|
23452
|
+
deliver: "ship/handoff wizard \u2014 CLI owns write confirms; b/back or cancel leaves the draft"
|
|
23431
23453
|
};
|
|
23432
23454
|
}
|
|
23433
23455
|
});
|
|
@@ -24260,20 +24282,128 @@ var init_diagnose = __esm({
|
|
|
24260
24282
|
});
|
|
24261
24283
|
|
|
24262
24284
|
// src/ai/profile-draft.ts
|
|
24263
|
-
async function draftCompanyProfile(seed,
|
|
24285
|
+
async function draftCompanyProfile(seed, ctx, opts = {}) {
|
|
24264
24286
|
if (!seed.company_url && !seed.company_name) {
|
|
24265
24287
|
throw new Error("draftCompanyProfile requires a URL or a company name");
|
|
24266
24288
|
}
|
|
24267
|
-
|
|
24289
|
+
const wantResearch = opts.research === true || opts.research !== false && hasAnyLlmProvider();
|
|
24290
|
+
if (!wantResearch) {
|
|
24291
|
+
seedOperatorIdentity(seed.company_name, seed.company_url);
|
|
24292
|
+
const out = {};
|
|
24293
|
+
if (seed.company_name?.trim()) out.company_name = seed.company_name.trim();
|
|
24294
|
+
if (seed.company_url) out.company_url = seed.company_url;
|
|
24295
|
+
return out;
|
|
24296
|
+
}
|
|
24297
|
+
if (!ctx) {
|
|
24298
|
+
throw new Error("draftCompanyProfile research requires a Context");
|
|
24299
|
+
}
|
|
24300
|
+
assertReplAi(ctx);
|
|
24301
|
+
const inputBlock = seed.company_url ? `Company website: ${seed.company_url}${seed.company_name ? `
|
|
24302
|
+
Company name (hint): ${seed.company_name}` : ""}` : `Company name: ${seed.company_name}`;
|
|
24303
|
+
const userMessage = `${inputBlock}
|
|
24304
|
+
|
|
24305
|
+
Research this company deeply using your training knowledge. Identify the canonical company name, what they sell, who buys it, and how they sell it. Emit the profile now as STRICT JSON.`;
|
|
24306
|
+
const { text } = await llmCompleteText("onboard", SYSTEM_PROMPT4, userMessage, 2048, ctx, {
|
|
24307
|
+
skipPseudonymize: true
|
|
24308
|
+
});
|
|
24309
|
+
const cleaned = stripFences3(text);
|
|
24310
|
+
let parsed;
|
|
24311
|
+
try {
|
|
24312
|
+
parsed = JSON.parse(cleaned);
|
|
24313
|
+
} catch {
|
|
24314
|
+
throw new Error("AI response is not valid JSON");
|
|
24315
|
+
}
|
|
24316
|
+
const draft = validateDraft(parsed);
|
|
24317
|
+
const nameForLexicon = draft.company_name ?? seed.company_name;
|
|
24318
|
+
seedOperatorIdentity(nameForLexicon, seed.company_url);
|
|
24319
|
+
if (seed.company_url && !draft.company_url) draft.company_url = seed.company_url;
|
|
24320
|
+
return draft;
|
|
24321
|
+
}
|
|
24322
|
+
function stripFences3(text) {
|
|
24323
|
+
const trimmed = text.trim();
|
|
24324
|
+
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
|
|
24325
|
+
if (fenced) return fenced[1].trim();
|
|
24326
|
+
return trimmed;
|
|
24327
|
+
}
|
|
24328
|
+
function validateDraft(raw) {
|
|
24268
24329
|
const out = {};
|
|
24269
|
-
|
|
24270
|
-
|
|
24330
|
+
const str2 = (k) => {
|
|
24331
|
+
const v = raw[k];
|
|
24332
|
+
return typeof v === "string" && v.trim().length > 0 ? v.trim() : void 0;
|
|
24333
|
+
};
|
|
24334
|
+
const companyName = str2("company_name");
|
|
24335
|
+
if (companyName) out.company_name = companyName;
|
|
24336
|
+
const industry = str2("industry");
|
|
24337
|
+
if (industry) out.industry = industry;
|
|
24338
|
+
const productDescription = str2("product_description");
|
|
24339
|
+
if (productDescription) out.product_description = productDescription;
|
|
24340
|
+
const targetCustomer = str2("target_customer");
|
|
24341
|
+
if (targetCustomer) out.target_customer = targetCustomer;
|
|
24342
|
+
const motion = str2("sales_motion")?.toLowerCase();
|
|
24343
|
+
if (motion && ALLOWED_MOTIONS.has(motion)) {
|
|
24344
|
+
out.sales_motion = motion;
|
|
24345
|
+
}
|
|
24346
|
+
const dealSize = str2("average_deal_size");
|
|
24347
|
+
if (dealSize) out.average_deal_size = dealSize;
|
|
24348
|
+
const cycleDays = raw["sales_cycle_days"];
|
|
24349
|
+
if (typeof cycleDays === "number" && Number.isFinite(cycleDays) && cycleDays > 0) {
|
|
24350
|
+
out.sales_cycle_days = Math.round(cycleDays);
|
|
24351
|
+
}
|
|
24352
|
+
const crm = str2("primary_crm")?.toLowerCase();
|
|
24353
|
+
if (crm && ALLOWED_CRMS.has(crm)) out.primary_crm = crm;
|
|
24354
|
+
const engagement = str2("engagement_tool")?.toLowerCase();
|
|
24355
|
+
if (engagement && ALLOWED_ENGAGEMENT.has(engagement)) out.engagement_tool = engagement;
|
|
24271
24356
|
return out;
|
|
24272
24357
|
}
|
|
24358
|
+
var SYSTEM_PROMPT4, ALLOWED_MOTIONS, ALLOWED_CRMS, ALLOWED_ENGAGEMENT;
|
|
24273
24359
|
var init_profile_draft = __esm({
|
|
24274
24360
|
"src/ai/profile-draft.ts"() {
|
|
24275
24361
|
"use strict";
|
|
24362
|
+
init_repl_api();
|
|
24363
|
+
init_complete();
|
|
24276
24364
|
init_lexicon_seed();
|
|
24365
|
+
SYSTEM_PROMPT4 = `You are a senior GTM researcher with deep recall of the B2B / SaaS / services landscape. Given a company website (or, as a fallback, a plain company name), you produce a rich, specific profile of the business so a pipeline-health tool can tailor every answer to their reality.
|
|
24366
|
+
|
|
24367
|
+
RESEARCH DEPTH \u2014 this is the most important part:
|
|
24368
|
+
- Recognize the company from your training knowledge whenever possible. Pull on everything you know: product line, founding story, funding stage, acquisitions, known customers, pricing tiers, sales process, typical buyer titles, competitive landscape.
|
|
24369
|
+
- When you don't recognize the exact company, infer aggressively but conservatively from the URL, TLD, subdomain, naming conventions, and any industry signals. A domain like "acmedental.com" strongly suggests dental-industry B2B; "quantumledger.io" suggests a fintech/SaaS.
|
|
24370
|
+
- Think about the shape of their GTM: do they sell top-down to CIOs (enterprise), bottom-up to developers (PLG), to SMB owners (velocity), or mid-market (structured)?
|
|
24371
|
+
- Identify the most probable sales motion. Your guess becomes the wizard's default.
|
|
24372
|
+
- For known companies, be specific: reference real product names, real ICP, real buyer personas. For unknown ones, be honest \u2014 use generic-but-plausible descriptors and keep dollar figures as ranges.
|
|
24373
|
+
- NEVER invent specific revenue figures, customer counts, or headcount numbers. Stick to public ranges ("$5K-$50K ACV", "~$250K+ ACV", "mid-market $20M-$200M revenue", etc.).
|
|
24374
|
+
|
|
24375
|
+
FIELDS \u2014 emit only the ones you're reasonably confident about; the wizard will ask the user interactively for anything you omit.
|
|
24376
|
+
|
|
24377
|
+
REQUIRED (emit these whenever possible):
|
|
24378
|
+
- company_name: string \u2014 the canonical marketed name of the company (e.g. "HashiCorp", not "hashicorp.com")
|
|
24379
|
+
- industry: string \u2014 very specific; include both category and sub-category (e.g. "B2B SaaS \u2014 infrastructure automation / developer tools", "B2B services \u2014 dental lab equipment & supplies", "Fintech \u2014 B2B embedded payments")
|
|
24380
|
+
- product_description: string \u2014 2-4 sentences describing what they sell, who pays for it, and how it's delivered (SaaS subscription, hardware + service, self-serve, etc.)
|
|
24381
|
+
- target_customer: string \u2014 plain-English ICP: company size, geography, persona titles, deal triggers. Be specific.
|
|
24382
|
+
- sales_motion: "plg" | "smb_velocity" | "mid_market" | "enterprise" \u2014 the motion most consistent with the product + ICP
|
|
24383
|
+
|
|
24384
|
+
OPTIONAL (include only with reasonable confidence):
|
|
24385
|
+
- average_deal_size: string \u2014 a range like "$5K-$50K ACV", "$45K-$120K (hardware + year 1 software)", "$250K+"
|
|
24386
|
+
- sales_cycle_days: integer \u2014 median cycle length in days
|
|
24387
|
+
- primary_crm: "salesforce" | "hubspot" | "pipedrive" | "other" \u2014 only with a signal (brand tier, pricing, known choice)
|
|
24388
|
+
- engagement_tool: "outreach" | "salesloft" | "apollo" | "none" \u2014 same bar
|
|
24389
|
+
|
|
24390
|
+
OUTPUT FORMAT \u2014 respond with STRICT JSON only, no preamble, no markdown fences, no commentary:
|
|
24391
|
+
{
|
|
24392
|
+
"company_name": "string",
|
|
24393
|
+
"industry": "string",
|
|
24394
|
+
"product_description": "string",
|
|
24395
|
+
"target_customer": "string",
|
|
24396
|
+
"sales_motion": "plg" | "smb_velocity" | "mid_market" | "enterprise",
|
|
24397
|
+
"average_deal_size": "string",
|
|
24398
|
+
"sales_cycle_days": number,
|
|
24399
|
+
"primary_crm": "salesforce" | "hubspot" | "pipedrive" | "other",
|
|
24400
|
+
"engagement_tool": "outreach" | "salesloft" | "apollo" | "none"
|
|
24401
|
+
}
|
|
24402
|
+
|
|
24403
|
+
Any field you are not confident about MUST be omitted entirely (do not include it as null, empty string, or "unknown"). The wizard will fall through to scripted Q&A to fill gaps.`;
|
|
24404
|
+
ALLOWED_MOTIONS = /* @__PURE__ */ new Set(["plg", "smb_velocity", "mid_market", "enterprise"]);
|
|
24405
|
+
ALLOWED_CRMS = /* @__PURE__ */ new Set(["salesforce", "hubspot", "pipedrive", "other"]);
|
|
24406
|
+
ALLOWED_ENGAGEMENT = /* @__PURE__ */ new Set(["outreach", "salesloft", "apollo", "none"]);
|
|
24277
24407
|
}
|
|
24278
24408
|
});
|
|
24279
24409
|
|
|
@@ -24515,7 +24645,7 @@ OUTPUT \u2014 STRICT JSON only, no fences, no commentary:
|
|
|
24515
24645
|
});
|
|
24516
24646
|
|
|
24517
24647
|
// src/ai/profile-clarify.ts
|
|
24518
|
-
function
|
|
24648
|
+
function stripFences4(text) {
|
|
24519
24649
|
const trimmed = text.trim();
|
|
24520
24650
|
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
|
|
24521
24651
|
if (fenced) return fenced[1].trim();
|
|
@@ -24535,7 +24665,7 @@ function formatDraft(draft) {
|
|
|
24535
24665
|
return parts.join("\n");
|
|
24536
24666
|
}
|
|
24537
24667
|
function parseClarifyingQuestions(text) {
|
|
24538
|
-
const cleaned =
|
|
24668
|
+
const cleaned = stripFences4(text);
|
|
24539
24669
|
let parsed;
|
|
24540
24670
|
try {
|
|
24541
24671
|
parsed = JSON.parse(cleaned);
|
|
@@ -24632,7 +24762,7 @@ ${draftBlock}${userBlock}${answersBlock}
|
|
|
24632
24762
|
|
|
24633
24763
|
Emit the refined profile now as STRICT JSON.`;
|
|
24634
24764
|
const { text } = await llmCompleteText("onboard", REFINE_SYSTEM_PROMPT, userMessage, 2048, ctx);
|
|
24635
|
-
const cleaned =
|
|
24765
|
+
const cleaned = stripFences4(text);
|
|
24636
24766
|
let parsed;
|
|
24637
24767
|
try {
|
|
24638
24768
|
parsed = JSON.parse(cleaned);
|
|
@@ -24654,7 +24784,7 @@ function validateRefined(raw) {
|
|
|
24654
24784
|
const targetCustomer = str2("target_customer");
|
|
24655
24785
|
if (targetCustomer) out.target_customer = targetCustomer;
|
|
24656
24786
|
const motion = str2("sales_motion")?.toLowerCase();
|
|
24657
|
-
if (motion &&
|
|
24787
|
+
if (motion && ALLOWED_MOTIONS2.has(motion)) {
|
|
24658
24788
|
out.sales_motion = motion;
|
|
24659
24789
|
}
|
|
24660
24790
|
const dealSize = str2("average_deal_size");
|
|
@@ -24664,14 +24794,14 @@ function validateRefined(raw) {
|
|
|
24664
24794
|
out.sales_cycle_days = Math.round(cycleDays);
|
|
24665
24795
|
}
|
|
24666
24796
|
const crm = str2("primary_crm")?.toLowerCase();
|
|
24667
|
-
if (crm &&
|
|
24797
|
+
if (crm && ALLOWED_CRMS2.has(crm)) out.primary_crm = crm;
|
|
24668
24798
|
const engagement = str2("engagement_tool")?.toLowerCase();
|
|
24669
|
-
if (engagement &&
|
|
24799
|
+
if (engagement && ALLOWED_ENGAGEMENT2.has(engagement)) out.engagement_tool = engagement;
|
|
24670
24800
|
const userScope = str2("user_scope");
|
|
24671
24801
|
if (userScope) out.user_scope = userScope;
|
|
24672
24802
|
return out;
|
|
24673
24803
|
}
|
|
24674
|
-
var CLARIFY_SYSTEM_PROMPT, REFINE_SYSTEM_PROMPT,
|
|
24804
|
+
var CLARIFY_SYSTEM_PROMPT, REFINE_SYSTEM_PROMPT, ALLOWED_MOTIONS2, ALLOWED_CRMS2, ALLOWED_ENGAGEMENT2;
|
|
24675
24805
|
var init_profile_clarify = __esm({
|
|
24676
24806
|
"src/ai/profile-clarify.ts"() {
|
|
24677
24807
|
"use strict";
|
|
@@ -24736,9 +24866,9 @@ OUTPUT FORMAT \u2014 respond with STRICT JSON only, no preamble, no markdown fen
|
|
|
24736
24866
|
"engagement_tool": "outreach",
|
|
24737
24867
|
"user_scope": "string"
|
|
24738
24868
|
}`;
|
|
24739
|
-
|
|
24740
|
-
|
|
24741
|
-
|
|
24869
|
+
ALLOWED_MOTIONS2 = /* @__PURE__ */ new Set(["plg", "smb_velocity", "mid_market", "enterprise"]);
|
|
24870
|
+
ALLOWED_CRMS2 = /* @__PURE__ */ new Set(["salesforce", "hubspot", "pipedrive", "other"]);
|
|
24871
|
+
ALLOWED_ENGAGEMENT2 = /* @__PURE__ */ new Set(["outreach", "salesloft", "apollo", "none"]);
|
|
24742
24872
|
}
|
|
24743
24873
|
});
|
|
24744
24874
|
|
|
@@ -24997,6 +25127,155 @@ var init_profile2 = __esm({
|
|
|
24997
25127
|
}
|
|
24998
25128
|
});
|
|
24999
25129
|
|
|
25130
|
+
// src/conversation/onboard-tiers.ts
|
|
25131
|
+
var onboard_tiers_exports = {};
|
|
25132
|
+
__export(onboard_tiers_exports, {
|
|
25133
|
+
ONBOARD_TIER_ORDER: () => ONBOARD_TIER_ORDER,
|
|
25134
|
+
canRunDomainTier: () => canRunDomainTier,
|
|
25135
|
+
clearOnboardTierFlags: () => clearOnboardTierFlags,
|
|
25136
|
+
describeOnboardTier: () => describeOnboardTier,
|
|
25137
|
+
getOnboardTierFlag: () => getOnboardTierFlag,
|
|
25138
|
+
getOnboardTierStatus: () => getOnboardTierStatus,
|
|
25139
|
+
listCompletedOnboardTier: () => listCompletedOnboardTier,
|
|
25140
|
+
markDemoDataSeen: () => markDemoDataSeen,
|
|
25141
|
+
markOnboardTierComplete: () => markOnboardTierComplete,
|
|
25142
|
+
markProductionDataSeen: () => markProductionDataSeen,
|
|
25143
|
+
pathLooksPresent: () => pathLooksPresent,
|
|
25144
|
+
resetOnboardTierProgress: () => resetOnboardTierProgress,
|
|
25145
|
+
resolveNextOnboardTier: () => resolveNextOnboardTier
|
|
25146
|
+
});
|
|
25147
|
+
import { existsSync as existsSync25, statSync as statSync4 } from "fs";
|
|
25148
|
+
function flagSet(tier) {
|
|
25149
|
+
return Boolean(getConfigValue(TIER_CONFIG_KEYS[tier]));
|
|
25150
|
+
}
|
|
25151
|
+
function getOnboardTierFlag(tier) {
|
|
25152
|
+
return getConfigValue(TIER_CONFIG_KEYS[tier]);
|
|
25153
|
+
}
|
|
25154
|
+
function markOnboardTierComplete(...tiers) {
|
|
25155
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
25156
|
+
for (const tier of tiers) {
|
|
25157
|
+
if (!flagSet(tier)) setConfigValue(TIER_CONFIG_KEYS[tier], at);
|
|
25158
|
+
}
|
|
25159
|
+
}
|
|
25160
|
+
function clearOnboardTierFlags(...tiers) {
|
|
25161
|
+
for (const tier of tiers) {
|
|
25162
|
+
deleteConfigValue(TIER_CONFIG_KEYS[tier]);
|
|
25163
|
+
}
|
|
25164
|
+
}
|
|
25165
|
+
function resetOnboardTierProgress() {
|
|
25166
|
+
clearOnboardTierFlags(...ONBOARD_TIER_ORDER);
|
|
25167
|
+
}
|
|
25168
|
+
function hasProductionDataset(ctx) {
|
|
25169
|
+
const source = ctx?.dataset?.source;
|
|
25170
|
+
if (source && !source.startsWith("demo:")) {
|
|
25171
|
+
if (source.startsWith("/") || /^[A-Za-z]:[\\/]/.test(source) || source.startsWith("~")) {
|
|
25172
|
+
return true;
|
|
25173
|
+
}
|
|
25174
|
+
if (!source.includes(":") && existsSync25(source)) return true;
|
|
25175
|
+
if (source.startsWith("csv:") || source.startsWith("file:") || source.startsWith("folder:")) {
|
|
25176
|
+
return true;
|
|
25177
|
+
}
|
|
25178
|
+
}
|
|
25179
|
+
if (ctx?.attachments && ctx.attachments.length > 0) return true;
|
|
25180
|
+
return flagSet("production");
|
|
25181
|
+
}
|
|
25182
|
+
function hasDemoExperience(ctx) {
|
|
25183
|
+
if (flagSet("demo")) return true;
|
|
25184
|
+
if (getPreferredDemoScenario()) return true;
|
|
25185
|
+
const source = ctx?.dataset?.source;
|
|
25186
|
+
if (source?.startsWith("demo:")) return true;
|
|
25187
|
+
return false;
|
|
25188
|
+
}
|
|
25189
|
+
function listCompletedOnboardTier(ctx) {
|
|
25190
|
+
const done = [];
|
|
25191
|
+
const profileOk = flagSet("profile") || isProfileConfigured(loadProfile());
|
|
25192
|
+
if (profileOk) done.push("profile");
|
|
25193
|
+
else return done;
|
|
25194
|
+
if (flagSet("domain")) done.push("domain");
|
|
25195
|
+
else return done;
|
|
25196
|
+
if (hasDemoExperience(ctx)) done.push("demo");
|
|
25197
|
+
else return done;
|
|
25198
|
+
if (hasProductionDataset(ctx) || flagSet("production")) done.push("production");
|
|
25199
|
+
return done;
|
|
25200
|
+
}
|
|
25201
|
+
function resolveNextOnboardTier(ctx) {
|
|
25202
|
+
const done = new Set(listCompletedOnboardTier(ctx));
|
|
25203
|
+
for (const tier of ONBOARD_TIER_ORDER) {
|
|
25204
|
+
if (!done.has(tier)) return tier;
|
|
25205
|
+
}
|
|
25206
|
+
return null;
|
|
25207
|
+
}
|
|
25208
|
+
function getOnboardTierStatus(ctx) {
|
|
25209
|
+
const completed = listCompletedOnboardTier(ctx);
|
|
25210
|
+
const next = resolveNextOnboardTier(ctx);
|
|
25211
|
+
const meta = next ? TIER_META[next] : null;
|
|
25212
|
+
return {
|
|
25213
|
+
completed,
|
|
25214
|
+
next,
|
|
25215
|
+
nextLabel: meta?.label ?? null,
|
|
25216
|
+
nextHint: meta?.hint ?? null
|
|
25217
|
+
};
|
|
25218
|
+
}
|
|
25219
|
+
function describeOnboardTier(tier) {
|
|
25220
|
+
return TIER_META[tier];
|
|
25221
|
+
}
|
|
25222
|
+
function canRunDomainTier() {
|
|
25223
|
+
return isProfileConfigured(loadProfile()) && hasAnyLlmProvider();
|
|
25224
|
+
}
|
|
25225
|
+
function markProductionDataSeen() {
|
|
25226
|
+
markOnboardTierComplete("production");
|
|
25227
|
+
}
|
|
25228
|
+
function markDemoDataSeen() {
|
|
25229
|
+
markOnboardTierComplete("demo");
|
|
25230
|
+
}
|
|
25231
|
+
function pathLooksPresent(raw) {
|
|
25232
|
+
try {
|
|
25233
|
+
return existsSync25(raw) && (statSync4(raw).isFile() || statSync4(raw).isDirectory());
|
|
25234
|
+
} catch {
|
|
25235
|
+
return false;
|
|
25236
|
+
}
|
|
25237
|
+
}
|
|
25238
|
+
var ONBOARD_TIER_ORDER, TIER_CONFIG_KEYS, TIER_META;
|
|
25239
|
+
var init_onboard_tiers = __esm({
|
|
25240
|
+
"src/conversation/onboard-tiers.ts"() {
|
|
25241
|
+
"use strict";
|
|
25242
|
+
init_store();
|
|
25243
|
+
init_profile();
|
|
25244
|
+
init_repl_api();
|
|
25245
|
+
init_scenario_fit();
|
|
25246
|
+
ONBOARD_TIER_ORDER = [
|
|
25247
|
+
"profile",
|
|
25248
|
+
"domain",
|
|
25249
|
+
"demo",
|
|
25250
|
+
"production"
|
|
25251
|
+
];
|
|
25252
|
+
TIER_CONFIG_KEYS = {
|
|
25253
|
+
profile: "onboard-tier-profile",
|
|
25254
|
+
domain: "onboard-tier-domain",
|
|
25255
|
+
demo: "onboard-tier-demo",
|
|
25256
|
+
production: "onboard-tier-production"
|
|
25257
|
+
};
|
|
25258
|
+
TIER_META = {
|
|
25259
|
+
profile: {
|
|
25260
|
+
label: "Company profile",
|
|
25261
|
+
hint: "Name, industry, ICP \u2014 works without an API key"
|
|
25262
|
+
},
|
|
25263
|
+
domain: {
|
|
25264
|
+
label: "Domain research",
|
|
25265
|
+
hint: "Connect a key and let NTRP research your company"
|
|
25266
|
+
},
|
|
25267
|
+
demo: {
|
|
25268
|
+
label: "Sample data",
|
|
25269
|
+
hint: "Load a fitted demo book of business"
|
|
25270
|
+
},
|
|
25271
|
+
production: {
|
|
25272
|
+
label: "Your data",
|
|
25273
|
+
hint: "Drag-drop a CSV or folder path into the REPL"
|
|
25274
|
+
}
|
|
25275
|
+
};
|
|
25276
|
+
}
|
|
25277
|
+
});
|
|
25278
|
+
|
|
25000
25279
|
// src/conversation/voice-setup.ts
|
|
25001
25280
|
var voice_setup_exports = {};
|
|
25002
25281
|
__export(voice_setup_exports, {
|
|
@@ -25356,97 +25635,314 @@ __export(onboard_exports, {
|
|
|
25356
25635
|
profileExists: () => profileExists
|
|
25357
25636
|
});
|
|
25358
25637
|
import chalk25 from "chalk";
|
|
25638
|
+
import { existsSync as existsSync26 } from "fs";
|
|
25639
|
+
import { basename as basename7 } from "path";
|
|
25359
25640
|
async function handler5(args, ctx) {
|
|
25360
25641
|
const { flags } = parseArgs2(args, ["force", "skip-brand"]);
|
|
25361
25642
|
const force = getBool(flags, "force");
|
|
25362
25643
|
const skipBrand = getBool(flags, "skip-brand");
|
|
25644
|
+
const tierOverride = normalizeTierFlag(getString(flags, "tier"));
|
|
25363
25645
|
const priorProfile = loadProfile();
|
|
25364
25646
|
const configured = isProfileConfigured(priorProfile);
|
|
25365
25647
|
let existing = configured ? priorProfile : null;
|
|
25366
25648
|
if (!skipBrand) printCenteredLogo();
|
|
25367
25649
|
const session = createPromptSession(ctx.rl, ctx);
|
|
25368
25650
|
try {
|
|
25369
|
-
if (configured && !force) {
|
|
25370
|
-
|
|
25371
|
-
|
|
25372
|
-
|
|
25373
|
-
|
|
25374
|
-
console.log(" " + chalk25.dim("
|
|
25375
|
-
console.log(
|
|
25376
|
-
|
|
25651
|
+
if (configured && !force && !tierOverride) {
|
|
25652
|
+
const status = getOnboardTierStatus(ctx);
|
|
25653
|
+
if (status.next === null) {
|
|
25654
|
+
console.log();
|
|
25655
|
+
console.log(" " + bold(`You're fully set up for ${priorProfile.company_name}.`));
|
|
25656
|
+
console.log(" " + chalk25.dim("Profile \xB7 domain research \xB7 sample data \xB7 production path \u2014 done."));
|
|
25657
|
+
console.log(
|
|
25658
|
+
" " + chalk25.dim("Type ") + paint("accent", "/onboard --force") + chalk25.dim(" to rebuild the profile, or drop a new CSV anytime.")
|
|
25659
|
+
);
|
|
25660
|
+
console.log();
|
|
25661
|
+
return `Onboarding complete for ${priorProfile.company_name}`;
|
|
25662
|
+
}
|
|
25663
|
+
if (status.next === "profile") {
|
|
25664
|
+
console.log();
|
|
25665
|
+
console.log(" " + bold(`Profile already exists for ${priorProfile.company_name}.`));
|
|
25666
|
+
const overwrite = await session.confirm("Overwrite this profile?", false);
|
|
25667
|
+
if (!overwrite) {
|
|
25668
|
+
console.log(" " + chalk25.dim("Keeping existing profile."));
|
|
25669
|
+
return;
|
|
25670
|
+
}
|
|
25671
|
+
existing = null;
|
|
25672
|
+
resetOnboardTierProgress();
|
|
25377
25673
|
}
|
|
25674
|
+
} else if (configured && force && !tierOverride) {
|
|
25675
|
+
console.log();
|
|
25676
|
+
console.log(" " + bold(`Rebuilding profile for ${priorProfile.company_name}.`));
|
|
25378
25677
|
existing = null;
|
|
25678
|
+
resetOnboardTierProgress();
|
|
25379
25679
|
} else if (priorProfile && !configured) {
|
|
25380
25680
|
console.log();
|
|
25381
|
-
console.log(" " + chalk25.dim("NTRP found an incomplete profile. Setup continues."));
|
|
25681
|
+
console.log(" " + chalk25.dim("NTRP found an incomplete profile. Setup continues from the start."));
|
|
25382
25682
|
existing = null;
|
|
25683
|
+
resetOnboardTierProgress();
|
|
25383
25684
|
}
|
|
25384
|
-
|
|
25385
|
-
|
|
25386
|
-
|
|
25387
|
-
"
|
|
25388
|
-
|
|
25389
|
-
|
|
25390
|
-
|
|
25391
|
-
|
|
25392
|
-
|
|
25393
|
-
|
|
25394
|
-
|
|
25395
|
-
|
|
25396
|
-
|
|
25397
|
-
|
|
25398
|
-
|
|
25399
|
-
|
|
25400
|
-
|
|
25401
|
-
|
|
25402
|
-
schema_version: 1,
|
|
25403
|
-
company_name: canonicalName,
|
|
25404
|
-
...companyUrl ? { company_url: companyUrl } : existing?.company_url ? { company_url: existing.company_url } : {},
|
|
25405
|
-
industry: draft.industry ?? existing?.industry ?? "",
|
|
25406
|
-
product_description: draft.product_description ?? existing?.product_description ?? "",
|
|
25407
|
-
target_customer: draft.target_customer ?? existing?.target_customer ?? "",
|
|
25408
|
-
sales_motion: salesMotion,
|
|
25409
|
-
...draft.average_deal_size !== void 0 ? { average_deal_size: draft.average_deal_size } : {},
|
|
25410
|
-
...draft.sales_cycle_days !== void 0 ? { sales_cycle_days: draft.sales_cycle_days } : {},
|
|
25411
|
-
...draft.primary_crm !== void 0 ? { primary_crm: draft.primary_crm } : {},
|
|
25412
|
-
...draft.engagement_tool !== void 0 ? { engagement_tool: draft.engagement_tool } : {},
|
|
25413
|
-
...existing?.user_scope ? { user_scope: existing.user_scope } : {},
|
|
25414
|
-
created_at: existing?.created_at ?? now2,
|
|
25415
|
-
updated_at: now2
|
|
25416
|
-
};
|
|
25417
|
-
renderProfile(candidate);
|
|
25418
|
-
candidate = await adaptiveRefine(session, candidate, ctx);
|
|
25419
|
-
for (; ; ) {
|
|
25420
|
-
renderProfile(candidate);
|
|
25421
|
-
const action = (await session.ask("Is this profile correct? Type Y, edit, or redraft", { default: "Y" })).toLowerCase();
|
|
25422
|
-
if (action === "y" || action === "yes" || action === "") break;
|
|
25423
|
-
if (action === "redraft") {
|
|
25424
|
-
const fresh = await fillMissingFields(session, { company_url: candidate.company_url }, null);
|
|
25425
|
-
candidate = applyDraft(candidate, fresh);
|
|
25426
|
-
continue;
|
|
25427
|
-
}
|
|
25428
|
-
if (action === "edit" || action === "e") {
|
|
25429
|
-
candidate = await editLoop(session, candidate);
|
|
25430
|
-
continue;
|
|
25431
|
-
}
|
|
25432
|
-
console.log(" " + chalk25.red(`Unknown choice: ${action}. Type Y, edit, or redraft.`));
|
|
25685
|
+
const next = tierOverride ?? (force ? "profile" : resolveNextOnboardTier(ctx));
|
|
25686
|
+
if (!next) {
|
|
25687
|
+
console.log();
|
|
25688
|
+
console.log(" " + bold("Onboarding is complete."));
|
|
25689
|
+
console.log(" " + chalk25.dim("Drop a CSV or folder path into the REPL anytime to refresh data."));
|
|
25690
|
+
console.log();
|
|
25691
|
+
return "Onboarding complete";
|
|
25692
|
+
}
|
|
25693
|
+
printTierIntro(next);
|
|
25694
|
+
switch (next) {
|
|
25695
|
+
case "profile":
|
|
25696
|
+
return await runProfileTier(session, ctx, existing);
|
|
25697
|
+
case "domain":
|
|
25698
|
+
return await runDomainTier(session, ctx);
|
|
25699
|
+
case "demo":
|
|
25700
|
+
return await runDemoTier(session, ctx);
|
|
25701
|
+
case "production":
|
|
25702
|
+
return await runProductionTier(session, ctx);
|
|
25433
25703
|
}
|
|
25434
|
-
|
|
25435
|
-
|
|
25436
|
-
|
|
25437
|
-
|
|
25704
|
+
} finally {
|
|
25705
|
+
session.close();
|
|
25706
|
+
}
|
|
25707
|
+
}
|
|
25708
|
+
function normalizeTierFlag(raw) {
|
|
25709
|
+
if (!raw) return null;
|
|
25710
|
+
const t = raw.trim().toLowerCase();
|
|
25711
|
+
if (t === "profile" || t === "domain" || t === "demo" || t === "production") return t;
|
|
25712
|
+
return null;
|
|
25713
|
+
}
|
|
25714
|
+
function printTierIntro(tier) {
|
|
25715
|
+
const meta = describeOnboardTier(tier);
|
|
25716
|
+
console.log();
|
|
25717
|
+
console.log(" " + bold(`Onboard \xB7 ${meta.label}`));
|
|
25718
|
+
console.log(" " + chalk25.dim(meta.hint));
|
|
25719
|
+
console.log();
|
|
25720
|
+
}
|
|
25721
|
+
function printNextTierHint(ctx) {
|
|
25722
|
+
const status = getOnboardTierStatus(ctx);
|
|
25723
|
+
if (!status.next) {
|
|
25724
|
+
console.log(" " + chalk25.dim("You're through the progressive setup ladder."));
|
|
25725
|
+
return;
|
|
25726
|
+
}
|
|
25727
|
+
console.log(
|
|
25728
|
+
" " + chalk25.dim("Next: type ") + paint("accent", "/onboard") + chalk25.dim(` for ${status.nextLabel?.toLowerCase()} \u2014 ${status.nextHint}`)
|
|
25729
|
+
);
|
|
25730
|
+
}
|
|
25731
|
+
async function runProfileTier(session, ctx, existing) {
|
|
25732
|
+
console.log(" " + chalk25.dim("No API key needed for this step. Connect one later for domain research."));
|
|
25733
|
+
console.log();
|
|
25734
|
+
const siteOrName = await session.askRequired(
|
|
25735
|
+
"What is the company website? Or type the company name."
|
|
25736
|
+
);
|
|
25737
|
+
const isUrlish = looksLikeUrl(siteOrName);
|
|
25738
|
+
const companyUrl = isUrlish ? normalizeUrl(siteOrName) : void 0;
|
|
25739
|
+
const seedName = isUrlish ? void 0 : siteOrName.trim();
|
|
25740
|
+
const seed = {
|
|
25741
|
+
...companyUrl ? { company_url: companyUrl } : {},
|
|
25742
|
+
...seedName ? { company_name: seedName } : {}
|
|
25743
|
+
};
|
|
25744
|
+
let draft = await draftCompanyProfile(seed, ctx, { research: false });
|
|
25745
|
+
draft = await fillMissingFields(session, draft, existing);
|
|
25746
|
+
const canonicalName = draft.company_name ?? seedName ?? existing?.company_name ?? "";
|
|
25747
|
+
const salesMotion = draft.sales_motion ?? existing?.sales_motion ?? "mid_market";
|
|
25748
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
25749
|
+
let candidate = {
|
|
25750
|
+
schema_version: 1,
|
|
25751
|
+
company_name: canonicalName,
|
|
25752
|
+
...companyUrl ? { company_url: companyUrl } : existing?.company_url ? { company_url: existing.company_url } : {},
|
|
25753
|
+
industry: draft.industry ?? existing?.industry ?? "",
|
|
25754
|
+
product_description: draft.product_description ?? existing?.product_description ?? "",
|
|
25755
|
+
target_customer: draft.target_customer ?? existing?.target_customer ?? "",
|
|
25756
|
+
sales_motion: salesMotion,
|
|
25757
|
+
...draft.average_deal_size !== void 0 ? { average_deal_size: draft.average_deal_size } : {},
|
|
25758
|
+
...draft.sales_cycle_days !== void 0 ? { sales_cycle_days: draft.sales_cycle_days } : {},
|
|
25759
|
+
...draft.primary_crm !== void 0 ? { primary_crm: draft.primary_crm } : {},
|
|
25760
|
+
...draft.engagement_tool !== void 0 ? { engagement_tool: draft.engagement_tool } : {},
|
|
25761
|
+
...existing?.user_scope ? { user_scope: existing.user_scope } : {},
|
|
25762
|
+
created_at: existing?.created_at ?? now2,
|
|
25763
|
+
updated_at: now2
|
|
25764
|
+
};
|
|
25765
|
+
candidate = await reviewLoop(session, candidate);
|
|
25766
|
+
saveProfile(candidate);
|
|
25767
|
+
setConfigValue("sales-motion", candidate.sales_motion);
|
|
25768
|
+
invalidateTaxonomy();
|
|
25769
|
+
creditOnboardComplete(ctx);
|
|
25770
|
+
markOnboardTierComplete("profile");
|
|
25771
|
+
if (hasAnyLlmProvider()) {
|
|
25438
25772
|
console.log();
|
|
25439
25773
|
console.log(" " + chalk25.green("\u2713") + " " + bold(`Profile saved for ${candidate.company_name}`));
|
|
25440
|
-
console.log(" " + chalk25.dim(
|
|
25774
|
+
console.log(" " + chalk25.dim("A key is already connected \u2014 running domain research now."));
|
|
25441
25775
|
console.log();
|
|
25776
|
+
await runDomainResearchOnProfile(session, ctx, candidate);
|
|
25777
|
+
markOnboardTierComplete("domain");
|
|
25442
25778
|
await offerInboxSkillSetup(session, { beat: "production" });
|
|
25443
|
-
await
|
|
25444
|
-
|
|
25445
|
-
|
|
25446
|
-
return `Profile saved for ${candidate.company_name}`;
|
|
25447
|
-
} finally {
|
|
25448
|
-
session.close();
|
|
25779
|
+
const { offerVoiceSetup: offerVoiceSetup3 } = await Promise.resolve().then(() => (init_voice_setup(), voice_setup_exports));
|
|
25780
|
+
await offerVoiceSetup3(ctx, session);
|
|
25781
|
+
printNextTierHint(ctx);
|
|
25782
|
+
return `Profile + domain research saved for ${candidate.company_name}`;
|
|
25449
25783
|
}
|
|
25784
|
+
console.log();
|
|
25785
|
+
console.log(" " + chalk25.green("\u2713") + " " + bold(`Profile saved for ${candidate.company_name}`));
|
|
25786
|
+
console.log(" " + chalk25.dim(`~/.ntrp/profile.json`));
|
|
25787
|
+
console.log();
|
|
25788
|
+
await offerInboxSkillSetup(session, { beat: "production" });
|
|
25789
|
+
const { offerVoiceSetup: offerVoiceSetup2 } = await Promise.resolve().then(() => (init_voice_setup(), voice_setup_exports));
|
|
25790
|
+
await offerVoiceSetup2(ctx, session);
|
|
25791
|
+
printNextTierHint(ctx);
|
|
25792
|
+
return `Profile saved for ${candidate.company_name}`;
|
|
25793
|
+
}
|
|
25794
|
+
async function runDomainTier(session, ctx) {
|
|
25795
|
+
const profile = loadProfile();
|
|
25796
|
+
if (!isProfileConfigured(profile) || !profile) {
|
|
25797
|
+
console.log(" " + chalk25.dim("Need a company profile first \u2014 starting there."));
|
|
25798
|
+
return runProfileTier(session, ctx, null);
|
|
25799
|
+
}
|
|
25800
|
+
await ensureLlmKeys(session);
|
|
25801
|
+
if (!hasAnyLlmProvider()) {
|
|
25802
|
+
console.log();
|
|
25803
|
+
console.log(" " + chalk25.dim("No key connected \u2014 domain research stays locked."));
|
|
25804
|
+
console.log(
|
|
25805
|
+
" " + chalk25.dim("Type ") + paint("accent", "/connect") + chalk25.dim(" then ") + paint("accent", "/onboard") + chalk25.dim(" to research ") + bold(profile.company_name) + chalk25.dim(".")
|
|
25806
|
+
);
|
|
25807
|
+
console.log();
|
|
25808
|
+
return "Domain research skipped \u2014 no API key";
|
|
25809
|
+
}
|
|
25810
|
+
const updated = await runDomainResearchOnProfile(session, ctx, profile);
|
|
25811
|
+
markOnboardTierComplete("domain");
|
|
25812
|
+
printNextTierHint(ctx);
|
|
25813
|
+
return `Domain research saved for ${updated.company_name}`;
|
|
25814
|
+
}
|
|
25815
|
+
async function runDomainResearchOnProfile(session, ctx, profile) {
|
|
25816
|
+
const seed = {
|
|
25817
|
+
...profile.company_url ? { company_url: profile.company_url } : {},
|
|
25818
|
+
...profile.company_name ? { company_name: profile.company_name } : {}
|
|
25819
|
+
};
|
|
25820
|
+
let draft = { ...profile };
|
|
25821
|
+
const spinner = makeSpinner("Researching your company\u2026");
|
|
25822
|
+
try {
|
|
25823
|
+
draft = await draftCompanyProfile(seed, ctx, { research: true });
|
|
25824
|
+
spinner.succeed("Research draft ready");
|
|
25825
|
+
} catch (err) {
|
|
25826
|
+
spinner.fail("Could not research the company");
|
|
25827
|
+
console.log(" " + chalk25.dim(String(err.message ?? err)));
|
|
25828
|
+
console.log(" " + chalk25.dim("Falling back to your existing profile \u2014 you can edit fields below."));
|
|
25829
|
+
draft = { ...profile };
|
|
25830
|
+
}
|
|
25831
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
25832
|
+
let candidate = {
|
|
25833
|
+
...profile,
|
|
25834
|
+
company_name: draft.company_name ?? profile.company_name,
|
|
25835
|
+
...draft.company_url || profile.company_url ? { company_url: draft.company_url ?? profile.company_url } : {},
|
|
25836
|
+
industry: draft.industry ?? profile.industry,
|
|
25837
|
+
product_description: draft.product_description ?? profile.product_description,
|
|
25838
|
+
target_customer: draft.target_customer ?? profile.target_customer,
|
|
25839
|
+
sales_motion: draft.sales_motion ?? profile.sales_motion,
|
|
25840
|
+
updated_at: now2
|
|
25841
|
+
};
|
|
25842
|
+
if (draft.average_deal_size !== void 0) candidate.average_deal_size = draft.average_deal_size;
|
|
25843
|
+
else if (profile.average_deal_size) candidate.average_deal_size = profile.average_deal_size;
|
|
25844
|
+
if (draft.sales_cycle_days !== void 0) candidate.sales_cycle_days = draft.sales_cycle_days;
|
|
25845
|
+
else if (profile.sales_cycle_days !== void 0) candidate.sales_cycle_days = profile.sales_cycle_days;
|
|
25846
|
+
if (draft.primary_crm !== void 0) candidate.primary_crm = draft.primary_crm;
|
|
25847
|
+
else if (profile.primary_crm) candidate.primary_crm = profile.primary_crm;
|
|
25848
|
+
if (draft.engagement_tool !== void 0) candidate.engagement_tool = draft.engagement_tool;
|
|
25849
|
+
else if (profile.engagement_tool) candidate.engagement_tool = profile.engagement_tool;
|
|
25850
|
+
const filled = await fillMissingFields(session, candidate, profile);
|
|
25851
|
+
candidate = {
|
|
25852
|
+
...candidate,
|
|
25853
|
+
company_name: filled.company_name ?? candidate.company_name,
|
|
25854
|
+
industry: filled.industry ?? candidate.industry,
|
|
25855
|
+
product_description: filled.product_description ?? candidate.product_description,
|
|
25856
|
+
target_customer: filled.target_customer ?? candidate.target_customer,
|
|
25857
|
+
...filled.sales_motion ? { sales_motion: filled.sales_motion } : {},
|
|
25858
|
+
...filled.average_deal_size !== void 0 ? { average_deal_size: filled.average_deal_size } : {},
|
|
25859
|
+
...filled.sales_cycle_days !== void 0 ? { sales_cycle_days: filled.sales_cycle_days } : {},
|
|
25860
|
+
...filled.primary_crm !== void 0 ? { primary_crm: filled.primary_crm } : {},
|
|
25861
|
+
...filled.engagement_tool !== void 0 ? { engagement_tool: filled.engagement_tool } : {},
|
|
25862
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
25863
|
+
};
|
|
25864
|
+
renderProfile(candidate);
|
|
25865
|
+
candidate = await adaptiveRefine(session, candidate, ctx);
|
|
25866
|
+
candidate = await reviewLoop(session, candidate);
|
|
25867
|
+
saveProfile(candidate);
|
|
25868
|
+
setConfigValue("sales-motion", candidate.sales_motion);
|
|
25869
|
+
invalidateTaxonomy();
|
|
25870
|
+
console.log();
|
|
25871
|
+
console.log(" " + chalk25.green("\u2713") + " " + bold(`Domain research saved for ${candidate.company_name}`));
|
|
25872
|
+
console.log(" " + chalk25.dim(`~/.ntrp/profile.json`));
|
|
25873
|
+
console.log();
|
|
25874
|
+
return candidate;
|
|
25875
|
+
}
|
|
25876
|
+
async function runDemoTier(session, ctx) {
|
|
25877
|
+
const profile = loadProfile();
|
|
25878
|
+
if (!isProfileConfigured(profile) || !profile) {
|
|
25879
|
+
console.log(" " + chalk25.dim("Need a company profile first \u2014 starting there."));
|
|
25880
|
+
return runProfileTier(session, ctx, null);
|
|
25881
|
+
}
|
|
25882
|
+
console.log(" " + chalk25.dim("Fit a sample book of business shaped like yours \u2014 or skip to bring your own later."));
|
|
25883
|
+
console.log();
|
|
25884
|
+
const loaded2 = await offerFittedDemoAfterOnboard(session, ctx, profile);
|
|
25885
|
+
markDemoDataSeen();
|
|
25886
|
+
if (loaded2) {
|
|
25887
|
+
console.log();
|
|
25888
|
+
printNextTierHint(ctx);
|
|
25889
|
+
return "Demo data loaded";
|
|
25890
|
+
}
|
|
25891
|
+
console.log();
|
|
25892
|
+
console.log(" " + chalk25.dim("Skipped sample data. Next up is your production CSV or folder."));
|
|
25893
|
+
printNextTierHint(ctx);
|
|
25894
|
+
return "Demo data skipped";
|
|
25895
|
+
}
|
|
25896
|
+
async function runProductionTier(session, ctx) {
|
|
25897
|
+
console.log(" " + bold("Bring your own pipeline data"));
|
|
25898
|
+
console.log(" " + chalk25.dim("Drag a CSV (or a folder of CSVs) onto this terminal \u2014 same as dropping a path in a shell."));
|
|
25899
|
+
console.log(" " + chalk25.dim("NTRP will read the path and ingest what it can. Or type the path and press Enter."));
|
|
25900
|
+
console.log();
|
|
25901
|
+
const raw = await session.ask("CSV or folder path. Press Enter to skip for now", { default: "" });
|
|
25902
|
+
const trimmed = raw.trim().replace(/^["']|["']$/g, "");
|
|
25903
|
+
if (!trimmed) {
|
|
25904
|
+
markProductionDataSeen();
|
|
25905
|
+
console.log();
|
|
25906
|
+
console.log(
|
|
25907
|
+
" " + chalk25.dim("Skipped. Drop a file path into the REPL anytime \u2014 NTRP will pick it up.")
|
|
25908
|
+
);
|
|
25909
|
+
console.log();
|
|
25910
|
+
return "Production data skipped";
|
|
25911
|
+
}
|
|
25912
|
+
const resolved = resolveUserPath(trimmed);
|
|
25913
|
+
if (!existsSync26(resolved)) {
|
|
25914
|
+
console.log(" " + chalk25.red(`Path not found: ${resolved}`));
|
|
25915
|
+
console.log(" " + chalk25.dim("Try again with /onboard, or drop the path into the REPL."));
|
|
25916
|
+
return "Production path not found";
|
|
25917
|
+
}
|
|
25918
|
+
const { ingestPathFromChat: ingestPathFromChat2 } = await Promise.resolve().then(() => (init_ingest_chat(), ingest_chat_exports));
|
|
25919
|
+
const ok = await ingestPathFromChat2(ctx, resolved);
|
|
25920
|
+
if (ok) {
|
|
25921
|
+
markProductionDataSeen();
|
|
25922
|
+
printNextTierHint(ctx);
|
|
25923
|
+
return `Production data loaded from ${basename7(resolved)}`;
|
|
25924
|
+
}
|
|
25925
|
+
console.log(" " + chalk25.dim("Nothing ingested. Drop a CSV path into the REPL when ready."));
|
|
25926
|
+
return "Production ingest cancelled";
|
|
25927
|
+
}
|
|
25928
|
+
async function reviewLoop(session, initial) {
|
|
25929
|
+
let candidate = initial;
|
|
25930
|
+
for (; ; ) {
|
|
25931
|
+
renderProfile(candidate);
|
|
25932
|
+
const action = (await session.ask("Is this profile correct? Type Y, edit, or redraft", { default: "Y" })).toLowerCase();
|
|
25933
|
+
if (action === "y" || action === "yes" || action === "") break;
|
|
25934
|
+
if (action === "redraft") {
|
|
25935
|
+
const fresh = await fillMissingFields(session, { company_url: candidate.company_url }, null);
|
|
25936
|
+
candidate = applyDraft(candidate, fresh);
|
|
25937
|
+
continue;
|
|
25938
|
+
}
|
|
25939
|
+
if (action === "edit" || action === "e") {
|
|
25940
|
+
candidate = await editLoop(session, candidate);
|
|
25941
|
+
continue;
|
|
25942
|
+
}
|
|
25943
|
+
console.log(" " + chalk25.red(`Unknown choice: ${action}. Type Y, edit, or redraft.`));
|
|
25944
|
+
}
|
|
25945
|
+
return candidate;
|
|
25450
25946
|
}
|
|
25451
25947
|
async function fillMissingFields(session, draft, existing) {
|
|
25452
25948
|
const out = { ...draft };
|
|
@@ -25668,16 +26164,10 @@ async function editField(session, p, field) {
|
|
|
25668
26164
|
}
|
|
25669
26165
|
}
|
|
25670
26166
|
}
|
|
25671
|
-
function printIntro() {
|
|
25672
|
-
console.log(" " + bold("Let's set up your company profile."));
|
|
25673
|
-
console.log(" " + chalk25.dim("NTRP will research your business so every answer,"));
|
|
25674
|
-
console.log(" " + chalk25.dim("finding, and demo is tailored to your reality."));
|
|
25675
|
-
console.log();
|
|
25676
|
-
}
|
|
25677
26167
|
function printKeylessNote() {
|
|
25678
|
-
console.log(" " + chalk25.dim("Skipping AI \u2014
|
|
26168
|
+
console.log(" " + chalk25.dim("Skipping AI \u2014 domain research needs a key."));
|
|
25679
26169
|
console.log(
|
|
25680
|
-
" " + chalk25.dim("Add a key anytime with ") + paint("accent", "/connect") + chalk25.dim("
|
|
26170
|
+
" " + chalk25.dim("Add a key anytime with ") + paint("accent", "/connect") + chalk25.dim(" then type ") + paint("accent", "/onboard") + chalk25.dim(".")
|
|
25681
26171
|
);
|
|
25682
26172
|
console.log();
|
|
25683
26173
|
}
|
|
@@ -25687,9 +26177,9 @@ async function ensureLlmKeys(session) {
|
|
|
25687
26177
|
const { providerLabel: providerLabel2 } = await Promise.resolve().then(() => (init_providers(), providers_exports));
|
|
25688
26178
|
const { countAvailableEngines: countAvailableEngines2 } = await Promise.resolve().then(() => (init_session_state(), session_state_exports));
|
|
25689
26179
|
console.log();
|
|
25690
|
-
console.log(" " + bold("Connect a key") + chalk25.dim(" (
|
|
25691
|
-
console.log(" " + chalk25.dim("Scores and metrics are local math. No AI is required."));
|
|
25692
|
-
console.log(" " + chalk25.dim("A key
|
|
26180
|
+
console.log(" " + bold("Connect a key") + chalk25.dim(" (needed for domain research. Press Enter to skip)"));
|
|
26181
|
+
console.log(" " + chalk25.dim("Scores and metrics are local math. No AI is required for vitals."));
|
|
26182
|
+
console.log(" " + chalk25.dim("A key unlocks company research now, plus findings and chat answers later."));
|
|
25693
26183
|
console.log(
|
|
25694
26184
|
" " + chalk25.dim("Paste any provider API key. Anthropic, OpenAI, Groq, Gemini, Mistral, and others.")
|
|
25695
26185
|
);
|
|
@@ -25781,6 +26271,8 @@ var init_onboard = __esm({
|
|
|
25781
26271
|
init_profile_clarify();
|
|
25782
26272
|
init_profile2();
|
|
25783
26273
|
init_inbox_setup();
|
|
26274
|
+
init_onboard_tiers();
|
|
26275
|
+
init_path_safety();
|
|
25784
26276
|
SALES_MOTION_CHOICES = [
|
|
25785
26277
|
{ value: "plg", label: "PLG \u2014 Product-Led Growth", description: PRESET_DESCRIPTIONS.plg },
|
|
25786
26278
|
{ value: "smb_velocity", label: "SMB Velocity \u2014 Fast cycles, high volume", description: PRESET_DESCRIPTIONS.smb_velocity },
|
|
@@ -25821,8 +26313,8 @@ __export(new_exports, {
|
|
|
25821
26313
|
handler: () => handler6
|
|
25822
26314
|
});
|
|
25823
26315
|
import chalk26 from "chalk";
|
|
25824
|
-
import { existsSync as
|
|
25825
|
-
import { basename as
|
|
26316
|
+
import { existsSync as existsSync27 } from "fs";
|
|
26317
|
+
import { basename as basename8 } from "path";
|
|
25826
26318
|
async function handler6(args, ctx) {
|
|
25827
26319
|
const { positional, flags } = parseArgs2(args, ["demo", "empty", "list-scenarios", "regen-taxonomy"]);
|
|
25828
26320
|
let source = null;
|
|
@@ -25843,7 +26335,7 @@ async function handler6(args, ctx) {
|
|
|
25843
26335
|
console.error(chalk26.red(" Usage: /new <file.csv> | --demo [--scenario <name>] | --empty [--lens health|metrics]"));
|
|
25844
26336
|
return;
|
|
25845
26337
|
}
|
|
25846
|
-
if (source.kind === "file" && !
|
|
26338
|
+
if (source.kind === "file" && !existsSync27(source.path)) {
|
|
25847
26339
|
console.error(chalk26.red(` File not found: ${source.path}`));
|
|
25848
26340
|
return;
|
|
25849
26341
|
}
|
|
@@ -25867,7 +26359,7 @@ async function handler6(args, ctx) {
|
|
|
25867
26359
|
const sourceFlag = getString(flags, "source", "s");
|
|
25868
26360
|
if (sourceFlag) passthrough.push("--source", sourceFlag);
|
|
25869
26361
|
await ingest(passthrough, ctx);
|
|
25870
|
-
datasetLabel2 =
|
|
26362
|
+
datasetLabel2 = basename8(source.path);
|
|
25871
26363
|
datasetSource = source.path;
|
|
25872
26364
|
} else if (source.kind === "demo") {
|
|
25873
26365
|
const { handler: demo } = await Promise.resolve().then(() => (init_generate(), generate_exports));
|
|
@@ -26070,7 +26562,7 @@ __export(end_exports, {
|
|
|
26070
26562
|
handler: () => handler7
|
|
26071
26563
|
});
|
|
26072
26564
|
import chalk27 from "chalk";
|
|
26073
|
-
import { existsSync as
|
|
26565
|
+
import { existsSync as existsSync28 } from "fs";
|
|
26074
26566
|
async function handler7(args, ctx) {
|
|
26075
26567
|
if (args.length > 0) {
|
|
26076
26568
|
console.error(chalk27.red(" Usage: /end"));
|
|
@@ -26107,10 +26599,10 @@ async function handler7(args, ctx) {
|
|
|
26107
26599
|
if (summary) {
|
|
26108
26600
|
console.log(" " + chalk27.dim(summary));
|
|
26109
26601
|
}
|
|
26110
|
-
if (
|
|
26602
|
+
if (existsSync28(transcriptPathForSession(endedId))) {
|
|
26111
26603
|
console.log(" " + chalk27.dim("Transcript: ") + chalk27.dim(transcriptPathForSession(endedId)));
|
|
26112
26604
|
}
|
|
26113
|
-
if (
|
|
26605
|
+
if (existsSync28(contextDocPathForSession(endedId))) {
|
|
26114
26606
|
console.log(" " + chalk27.dim("Context brief: ") + chalk27.dim(contextDocPathForSession(endedId)));
|
|
26115
26607
|
}
|
|
26116
26608
|
console.log();
|
|
@@ -26132,7 +26624,7 @@ __export(session_exports, {
|
|
|
26132
26624
|
});
|
|
26133
26625
|
import chalk28 from "chalk";
|
|
26134
26626
|
import { join as join23 } from "path";
|
|
26135
|
-
import { existsSync as
|
|
26627
|
+
import { existsSync as existsSync29 } from "fs";
|
|
26136
26628
|
async function handler8(args, ctx) {
|
|
26137
26629
|
const sub = args[0];
|
|
26138
26630
|
if (!sub) return listSessionsView(ctx);
|
|
@@ -26278,7 +26770,7 @@ async function pickUp(idArg, ctx) {
|
|
|
26278
26770
|
);
|
|
26279
26771
|
}
|
|
26280
26772
|
const contextPath = contextDocPathForSession(target.id);
|
|
26281
|
-
if (
|
|
26773
|
+
if (existsSync29(contextPath)) {
|
|
26282
26774
|
console.log(" " + chalk28.dim("Context brief: ") + chalk28.dim(contextPath));
|
|
26283
26775
|
}
|
|
26284
26776
|
console.log();
|
|
@@ -27230,7 +27722,7 @@ var init_bundle = __esm({
|
|
|
27230
27722
|
|
|
27231
27723
|
// src/repositories/markdown.ts
|
|
27232
27724
|
import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync17 } from "fs";
|
|
27233
|
-
import { basename as
|
|
27725
|
+
import { basename as basename9, dirname as dirname5, join as join26, resolve as resolve8 } from "path";
|
|
27234
27726
|
import { stringify as stringifyYaml2 } from "yaml";
|
|
27235
27727
|
function renderMarkdownFiles(pkg) {
|
|
27236
27728
|
const bundleJson = JSON.stringify(pkg, null, 2) + "\n";
|
|
@@ -27420,7 +27912,7 @@ function getRootPath(target) {
|
|
|
27420
27912
|
return resolve8(target.directory ?? `ntrp-repository-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`);
|
|
27421
27913
|
}
|
|
27422
27914
|
function safeFilename(value) {
|
|
27423
|
-
return (
|
|
27915
|
+
return (basename9(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "strategy") + ".md";
|
|
27424
27916
|
}
|
|
27425
27917
|
function escapeSummary(value) {
|
|
27426
27918
|
return value.replace(/[<>]/g, "");
|
|
@@ -29957,7 +30449,7 @@ async function handleStrategizeFlow(input, ctx) {
|
|
|
29957
30449
|
" " + chalk37.dim("That looks like a question. A strategy objective is waiting.")
|
|
29958
30450
|
);
|
|
29959
30451
|
console.log(
|
|
29960
|
-
" " + chalk37.dim("Type ") + chalk37.cyan("yes") + chalk37.dim(" to make the plan. Type ") + chalk37.cyan("adjust") + chalk37.dim(" to change the objective. Type ") + chalk37.cyan("cancel") + chalk37.dim(" to answer questions first.")
|
|
30452
|
+
" " + chalk37.dim("Type ") + chalk37.cyan("yes") + chalk37.dim(" to make the plan. Type ") + chalk37.cyan("b") + chalk37.dim(" / ") + chalk37.cyan("adjust") + chalk37.dim(" to change the objective. Type ") + chalk37.cyan("cancel") + chalk37.dim(" to answer questions first.")
|
|
29961
30453
|
);
|
|
29962
30454
|
console.log();
|
|
29963
30455
|
return "Awaiting confirm";
|
|
@@ -29970,7 +30462,7 @@ async function handleStrategizeFlow(input, ctx) {
|
|
|
29970
30462
|
}
|
|
29971
30463
|
console.log();
|
|
29972
30464
|
console.log(
|
|
29973
|
-
" " + chalk37.dim("Type ") + chalk37.cyan("yes") + chalk37.dim(" to make the plan. Type ") + chalk37.cyan("adjust") + chalk37.dim(" to change the objective. Type ") + chalk37.cyan("cancel") + chalk37.dim(" to stop.")
|
|
30465
|
+
" " + chalk37.dim("Type ") + chalk37.cyan("yes") + chalk37.dim(" to make the plan. Type ") + chalk37.cyan("b") + chalk37.dim(" / ") + chalk37.cyan("adjust") + chalk37.dim(" to change the objective. Type ") + chalk37.cyan("cancel") + chalk37.dim(" to stop.")
|
|
29974
30466
|
);
|
|
29975
30467
|
console.log();
|
|
29976
30468
|
return "Awaiting confirm";
|
|
@@ -30147,7 +30639,7 @@ function printObjectiveCard(ctx, objective, proposed) {
|
|
|
30147
30639
|
);
|
|
30148
30640
|
console.log();
|
|
30149
30641
|
console.log(
|
|
30150
|
-
" " + chalk37.dim("Confirm? ") + chalk37.cyan("\u23CE yes") + chalk37.dim(" \xB7 ") + chalk37.cyan("adjust") + chalk37.dim(" \xB7 ") + chalk37.cyan("cancel")
|
|
30642
|
+
" " + chalk37.dim("Confirm? ") + chalk37.cyan("\u23CE yes") + chalk37.dim(" \xB7 ") + chalk37.cyan("b back") + chalk37.dim(" \xB7 ") + chalk37.cyan("adjust") + chalk37.dim(" \xB7 ") + chalk37.cyan("cancel")
|
|
30151
30643
|
);
|
|
30152
30644
|
console.log();
|
|
30153
30645
|
}
|
|
@@ -31950,7 +32442,7 @@ var init_checkout = __esm({
|
|
|
31950
32442
|
});
|
|
31951
32443
|
|
|
31952
32444
|
// src/services/setup.ts
|
|
31953
|
-
import { existsSync as
|
|
32445
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync17, readFileSync as readFileSync20, writeFileSync as writeFileSync19 } from "fs";
|
|
31954
32446
|
import { join as join29 } from "path";
|
|
31955
32447
|
function setupCheck() {
|
|
31956
32448
|
const home = ntrpHome();
|
|
@@ -32515,7 +33007,7 @@ function printScopeProposal(ctx) {
|
|
|
32515
33007
|
}
|
|
32516
33008
|
console.log();
|
|
32517
33009
|
console.log(
|
|
32518
|
-
" " + chalk52.dim("Confirm? ") + chalk52.cyan("\u23CE yes") + chalk52.dim(" \xB7 ") + chalk52.cyan("adjust")
|
|
33010
|
+
" " + chalk52.dim("Confirm? ") + chalk52.cyan("\u23CE yes") + chalk52.dim(" \xB7 ") + chalk52.cyan("b back") + chalk52.dim(" \xB7 ") + chalk52.cyan("adjust")
|
|
32519
33011
|
);
|
|
32520
33012
|
console.log();
|
|
32521
33013
|
}
|
|
@@ -33386,7 +33878,7 @@ var init_metrics = __esm({
|
|
|
33386
33878
|
});
|
|
33387
33879
|
|
|
33388
33880
|
// src/ai/feedback-apply.ts
|
|
33389
|
-
function
|
|
33881
|
+
function stripFences5(text) {
|
|
33390
33882
|
const trimmed = text.trim();
|
|
33391
33883
|
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
|
|
33392
33884
|
if (fenced) return fenced[1].trim();
|
|
@@ -33416,8 +33908,8 @@ OPERATOR FEEDBACK:
|
|
|
33416
33908
|
"${feedbackText}"
|
|
33417
33909
|
|
|
33418
33910
|
Apply the feedback now as STRICT JSON.`;
|
|
33419
|
-
const { text } = await llmCompleteText("feedback",
|
|
33420
|
-
const cleaned =
|
|
33911
|
+
const { text } = await llmCompleteText("feedback", SYSTEM_PROMPT5, userMessage, 1024, ctx);
|
|
33912
|
+
const cleaned = stripFences5(text);
|
|
33421
33913
|
let parsed;
|
|
33422
33914
|
try {
|
|
33423
33915
|
parsed = JSON.parse(cleaned);
|
|
@@ -33444,7 +33936,7 @@ function validatePatch(raw) {
|
|
|
33444
33936
|
const targetCustomer = str2("target_customer");
|
|
33445
33937
|
if (targetCustomer) out.target_customer = targetCustomer;
|
|
33446
33938
|
const motion = str2("sales_motion")?.toLowerCase();
|
|
33447
|
-
if (motion &&
|
|
33939
|
+
if (motion && ALLOWED_MOTIONS3.has(motion)) {
|
|
33448
33940
|
out.sales_motion = motion;
|
|
33449
33941
|
}
|
|
33450
33942
|
const dealSize = str2("average_deal_size");
|
|
@@ -33454,25 +33946,25 @@ function validatePatch(raw) {
|
|
|
33454
33946
|
out.sales_cycle_days = Math.round(cycleDays);
|
|
33455
33947
|
}
|
|
33456
33948
|
const crm = str2("primary_crm")?.toLowerCase();
|
|
33457
|
-
if (crm &&
|
|
33949
|
+
if (crm && ALLOWED_CRMS3.has(crm)) out.primary_crm = crm;
|
|
33458
33950
|
const engagement = str2("engagement_tool")?.toLowerCase();
|
|
33459
|
-
if (engagement &&
|
|
33951
|
+
if (engagement && ALLOWED_ENGAGEMENT3.has(engagement)) out.engagement_tool = engagement;
|
|
33460
33952
|
const userScope = str2("user_scope");
|
|
33461
33953
|
if (userScope) out.user_scope = userScope;
|
|
33462
33954
|
const customContext = str2("custom_context");
|
|
33463
33955
|
if (customContext) out.custom_context = customContext;
|
|
33464
33956
|
return out;
|
|
33465
33957
|
}
|
|
33466
|
-
var
|
|
33958
|
+
var ALLOWED_MOTIONS3, ALLOWED_CRMS3, ALLOWED_ENGAGEMENT3, SYSTEM_PROMPT5;
|
|
33467
33959
|
var init_feedback_apply = __esm({
|
|
33468
33960
|
"src/ai/feedback-apply.ts"() {
|
|
33469
33961
|
"use strict";
|
|
33470
33962
|
init_repl_api();
|
|
33471
33963
|
init_complete();
|
|
33472
|
-
|
|
33473
|
-
|
|
33474
|
-
|
|
33475
|
-
|
|
33964
|
+
ALLOWED_MOTIONS3 = /* @__PURE__ */ new Set(["plg", "smb_velocity", "mid_market", "enterprise"]);
|
|
33965
|
+
ALLOWED_CRMS3 = /* @__PURE__ */ new Set(["salesforce", "hubspot", "pipedrive", "other"]);
|
|
33966
|
+
ALLOWED_ENGAGEMENT3 = /* @__PURE__ */ new Set(["outreach", "salesloft", "apollo", "none"]);
|
|
33967
|
+
SYSTEM_PROMPT5 = `You are a profile-correction engine for NTRP, a GTM pipeline-health tool. The operator is giving you natural-language feedback to correct or augment their company profile.
|
|
33476
33968
|
|
|
33477
33969
|
YOUR JOB:
|
|
33478
33970
|
1. Map feedback to STRUCTURED profile fields when there's a clear match (e.g. "our sales cycle is 6 months" \u2192 sales_cycle_days: 180).
|
|
@@ -33957,7 +34449,7 @@ __export(sessions_exports, {
|
|
|
33957
34449
|
handler: () => handler35
|
|
33958
34450
|
});
|
|
33959
34451
|
import chalk62 from "chalk";
|
|
33960
|
-
import { existsSync as
|
|
34452
|
+
import { existsSync as existsSync31 } from "fs";
|
|
33961
34453
|
async function handler35(args, _ctx) {
|
|
33962
34454
|
const sub = args[0] ?? "list";
|
|
33963
34455
|
if (sub === "list" || !args[0]) {
|
|
@@ -34064,12 +34556,12 @@ function showSession(idArg) {
|
|
|
34064
34556
|
console.log();
|
|
34065
34557
|
const transcriptPath = transcriptPathForSession(session.id);
|
|
34066
34558
|
const contextPath = contextDocPathForSession(session.id);
|
|
34067
|
-
if (
|
|
34559
|
+
if (existsSync31(transcriptPath) || existsSync31(contextPath)) {
|
|
34068
34560
|
console.log(" " + chalk62.dim("\u2500".repeat(40)));
|
|
34069
|
-
if (
|
|
34561
|
+
if (existsSync31(contextPath)) {
|
|
34070
34562
|
console.log(" " + chalk62.dim("Context brief: ") + chalk62.dim(contextPath));
|
|
34071
34563
|
}
|
|
34072
|
-
if (
|
|
34564
|
+
if (existsSync31(transcriptPath)) {
|
|
34073
34565
|
console.log(" " + chalk62.dim("Full transcript: ") + chalk62.dim(transcriptPath));
|
|
34074
34566
|
}
|
|
34075
34567
|
console.log();
|
|
@@ -34338,6 +34830,7 @@ var init_privacy_notice = __esm({
|
|
|
34338
34830
|
"Direct identifiers (names, emails, domains, deal names) are replaced with local tokens before any LLM HTTP call.",
|
|
34339
34831
|
"The mapping stays in ~/.ntrp/privacy/ on this machine. The CLI shows real names; the provider never does.",
|
|
34340
34832
|
"This is pseudonymization, not anonymization \u2014 you can reverse it; the model cannot.",
|
|
34833
|
+
"Exception: /onboard domain research sends the company name and website you typed so the model can draft your profile. That is operator-consented and only that step.",
|
|
34341
34834
|
"Computed scores and dollar aggregates still go to the provider you connected.",
|
|
34342
34835
|
"If you turn on web retrieval, named-account queries are refused. Generic GTM terms may go to Tavily or Brave.",
|
|
34343
34836
|
"A custom --base-url receives the same tokenized payload.",
|
|
@@ -35405,20 +35898,20 @@ var init_model = __esm({
|
|
|
35405
35898
|
});
|
|
35406
35899
|
|
|
35407
35900
|
// src/config/update-check.ts
|
|
35408
|
-
import { existsSync as
|
|
35901
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync18, readFileSync as readFileSync21, unlinkSync as unlinkSync5, writeFileSync as writeFileSync20 } from "fs";
|
|
35409
35902
|
import { join as join33 } from "path";
|
|
35410
35903
|
function cachePath2() {
|
|
35411
35904
|
return join33(ntrpHome(), "update-check.json");
|
|
35412
35905
|
}
|
|
35413
35906
|
function ensureDir7() {
|
|
35414
35907
|
const dir = ntrpHome();
|
|
35415
|
-
if (!
|
|
35908
|
+
if (!existsSync32(dir)) {
|
|
35416
35909
|
mkdirSync18(dir, { recursive: true });
|
|
35417
35910
|
}
|
|
35418
35911
|
}
|
|
35419
35912
|
function loadUpdateCheckCache() {
|
|
35420
35913
|
const path = cachePath2();
|
|
35421
|
-
if (!
|
|
35914
|
+
if (!existsSync32(path)) return null;
|
|
35422
35915
|
try {
|
|
35423
35916
|
const parsed = JSON.parse(readFileSync21(path, "utf-8"));
|
|
35424
35917
|
if (!parsed || typeof parsed !== "object" || typeof parsed.lastCheck !== "number" || typeof parsed.latestVersion !== "string") {
|
|
@@ -35439,7 +35932,7 @@ function isCacheFresh(cache2, ttlMs = CACHE_TTL_MS2) {
|
|
|
35439
35932
|
}
|
|
35440
35933
|
function invalidateUpdateCheckCache() {
|
|
35441
35934
|
const path = cachePath2();
|
|
35442
|
-
if (
|
|
35935
|
+
if (existsSync32(path)) {
|
|
35443
35936
|
unlinkSync5(path);
|
|
35444
35937
|
}
|
|
35445
35938
|
}
|
|
@@ -35453,11 +35946,11 @@ var init_update_check = __esm({
|
|
|
35453
35946
|
});
|
|
35454
35947
|
|
|
35455
35948
|
// src/version.ts
|
|
35456
|
-
import { existsSync as
|
|
35949
|
+
import { existsSync as existsSync33, readFileSync as readFileSync22 } from "fs";
|
|
35457
35950
|
import { dirname as dirname6, join as join34 } from "path";
|
|
35458
35951
|
import { fileURLToPath } from "url";
|
|
35459
35952
|
function readVersionFromPackageJson(packageJsonPath) {
|
|
35460
|
-
if (!
|
|
35953
|
+
if (!existsSync33(packageJsonPath)) return null;
|
|
35461
35954
|
try {
|
|
35462
35955
|
const pkg = JSON.parse(readFileSync22(packageJsonPath, "utf-8"));
|
|
35463
35956
|
if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
|
|
@@ -35597,7 +36090,7 @@ __export(relaunch_exports, {
|
|
|
35597
36090
|
resolveRelaunchEntry: () => resolveRelaunchEntry,
|
|
35598
36091
|
updateRestartSummary: () => updateRestartSummary
|
|
35599
36092
|
});
|
|
35600
|
-
import { existsSync as
|
|
36093
|
+
import { existsSync as existsSync34 } from "fs";
|
|
35601
36094
|
import { join as join35 } from "path";
|
|
35602
36095
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
35603
36096
|
import { spawnSync } from "child_process";
|
|
@@ -35623,7 +36116,7 @@ function npmGlobalEntry() {
|
|
|
35623
36116
|
const listed = spawnSync("npm", ["root", "-g"], { encoding: "utf-8" });
|
|
35624
36117
|
if (listed.status !== 0) return null;
|
|
35625
36118
|
const entry = join35(listed.stdout.trim(), NPM_PACKAGE, "dist/index.js");
|
|
35626
|
-
return
|
|
36119
|
+
return existsSync34(entry) ? entry : null;
|
|
35627
36120
|
}
|
|
35628
36121
|
function thisBundleEntry() {
|
|
35629
36122
|
return fileURLToPath2(import.meta.url);
|
|
@@ -35633,10 +36126,10 @@ function resolveRelaunchEntry(toVersion) {
|
|
|
35633
36126
|
(p) => Boolean(p)
|
|
35634
36127
|
);
|
|
35635
36128
|
for (const entry of candidates) {
|
|
35636
|
-
if (!
|
|
36129
|
+
if (!existsSync34(entry)) continue;
|
|
35637
36130
|
if (readVersionNearEntry(entry) === toVersion) return entry;
|
|
35638
36131
|
}
|
|
35639
|
-
return candidates.find((p) =>
|
|
36132
|
+
return candidates.find((p) => existsSync34(p)) ?? thisBundleEntry();
|
|
35640
36133
|
}
|
|
35641
36134
|
function relaunchArgv(toVersion) {
|
|
35642
36135
|
return [resolveRelaunchEntry(toVersion)];
|
|
@@ -36159,7 +36652,7 @@ __export(exports_exports, {
|
|
|
36159
36652
|
handler: () => handler48
|
|
36160
36653
|
});
|
|
36161
36654
|
import chalk78 from "chalk";
|
|
36162
|
-
import { existsSync as
|
|
36655
|
+
import { existsSync as existsSync35 } from "fs";
|
|
36163
36656
|
import { join as join36 } from "path";
|
|
36164
36657
|
function usage4() {
|
|
36165
36658
|
console.log(chalk78.dim(" Usage:"));
|
|
@@ -36318,7 +36811,7 @@ function runMove(args, ctx) {
|
|
|
36318
36811
|
}
|
|
36319
36812
|
try {
|
|
36320
36813
|
const destDir = resolveUserPath(dest);
|
|
36321
|
-
if (!
|
|
36814
|
+
if (!existsSync35(destDir)) {
|
|
36322
36815
|
}
|
|
36323
36816
|
const event = moveExport(idOrName, destDir);
|
|
36324
36817
|
console.log();
|
|
@@ -36690,10 +37183,9 @@ section: Settings
|
|
|
36690
37183
|
handler: ../commands/onboard.ts
|
|
36691
37184
|
---
|
|
36692
37185
|
|
|
36693
|
-
Start the
|
|
36694
|
-
|
|
36695
|
-
|
|
36696
|
-
Optional last step: pick a folder for desktop-AI handoffs if it is not already set. Skip, and type \`/inbox set\` later \u2014 NTRP will not ask again.
|
|
37186
|
+
Start the progressive setup ladder. Each \`/onboard\` runs the next incomplete tier:
|
|
37187
|
+
profile (keyless) \u2192 domain research + API key \u2192 sample demo data \u2192 production CSV/folder path.
|
|
37188
|
+
Drag-drop a CSV or folder into the REPL anytime for real data.
|
|
36697
37189
|
The profile is stored at \`~/.ntrp/profile.json\`. It flows into findings, NL answers, and demo data.`
|
|
36698
37190
|
},
|
|
36699
37191
|
{
|
|
@@ -37032,6 +37524,7 @@ CLI slide deck: SaaS refresher, five vital signs (definition, formula, visual, d
|
|
|
37032
37524
|
Bare \`/deepdive\` runs the full onboarding tour. Type \`/deepdive guide\` to jump to how to use NTRP (includes teaching the inbox once).
|
|
37033
37525
|
Type \`/deepdive handoff\` for the ship-to-AI slide. Type \`/deepdive <metric>\` to jump to one metric card.
|
|
37034
37526
|
Type \`/deepdive list\` to print the catalog. Works without an AI key. Re-run anytime from the homescreen.
|
|
37527
|
+
During the tour: Enter advances, \`b\` / \`back\` goes to the previous slide, \`q\` quits. After the tour, the same \`b\` / \`back\` word steps back one confirm gate in the REPL (scope, data, strategy) \u2014 it does not unload data or undo compute.
|
|
37035
37528
|
Live values overlay when an analysis exists.`
|
|
37036
37529
|
},
|
|
37037
37530
|
{
|
|
@@ -37046,7 +37539,7 @@ handler: ../commands/thinkwithme.ts
|
|
|
37046
37539
|
|
|
37047
37540
|
Open a dedicated \`think \u203A\` channel to explore ideas, challenge assumptions, and pull evidence from your data.
|
|
37048
37541
|
Bare \`/thinkwithme\` enters the channel. Type \`/thinkwithme <topic>\` to seed the first turn.
|
|
37049
|
-
Requires an analysis and an AI key (\`/connect\`). Type \`done\` or \`
|
|
37542
|
+
Requires an analysis and an AI key (\`/connect\`). Type \`done\`, \`cancel\`, or \`b\` / \`back\` to return to \u203A.
|
|
37050
37543
|
When you are ready to commit to a plan, say so or the partner can hand off to \`/strategy\`.`
|
|
37051
37544
|
},
|
|
37052
37545
|
{
|
|
@@ -37352,7 +37845,7 @@ Remaining nuances merge into a custom_context paragraph that flows into all AI s
|
|
|
37352
37845
|
});
|
|
37353
37846
|
|
|
37354
37847
|
// src/ai/prompt-parts.ts
|
|
37355
|
-
import { existsSync as
|
|
37848
|
+
import { existsSync as existsSync36, readFileSync as readFileSync23 } from "fs";
|
|
37356
37849
|
import { join as join37 } from "path";
|
|
37357
37850
|
function buildCompanyProfileBlock() {
|
|
37358
37851
|
const p = loadProfile();
|
|
@@ -37373,7 +37866,7 @@ function buildCompanyProfileBlock() {
|
|
|
37373
37866
|
function loadAnalystFile() {
|
|
37374
37867
|
const path = join37(ntrpHome(), ANALYST_FILE_NAME);
|
|
37375
37868
|
try {
|
|
37376
|
-
if (!
|
|
37869
|
+
if (!existsSync36(path)) return null;
|
|
37377
37870
|
const raw = sanitizeExternalText(readFileSync23(path, "utf-8").trim());
|
|
37378
37871
|
if (!raw) return null;
|
|
37379
37872
|
if (raw.length <= ANALYST_FILE_MAX_CHARS) return raw;
|
|
@@ -37902,39 +38395,54 @@ var ingest_chat_exports = {};
|
|
|
37902
38395
|
__export(ingest_chat_exports, {
|
|
37903
38396
|
extractFilePath: () => extractFilePath,
|
|
37904
38397
|
ingestFromChat: () => ingestFromChat,
|
|
38398
|
+
ingestPathFromChat: () => ingestPathFromChat,
|
|
37905
38399
|
isDemoIntent: () => isDemoIntent,
|
|
38400
|
+
listCsvsInFolder: () => listCsvsInFolder,
|
|
37906
38401
|
loadDemoFromChat: () => loadDemoFromChat,
|
|
37907
38402
|
looksLikeFilePath: () => looksLikeFilePath
|
|
37908
38403
|
});
|
|
37909
|
-
import { existsSync as
|
|
37910
|
-
import { basename as
|
|
38404
|
+
import { existsSync as existsSync37, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
38405
|
+
import { basename as basename10, join as join38, resolve as resolve9 } from "path";
|
|
37911
38406
|
import { homedir as homedir8 } from "os";
|
|
37912
38407
|
import chalk80 from "chalk";
|
|
37913
38408
|
function extractFilePath(input) {
|
|
37914
|
-
const trimmed = input.trim();
|
|
38409
|
+
const trimmed = input.trim().replace(/^["']|["']$/g, "");
|
|
38410
|
+
if (!trimmed) return null;
|
|
37915
38411
|
const patterns = [
|
|
37916
|
-
/^["'](
|
|
37917
|
-
/^@(
|
|
37918
|
-
/(?:here'?s|file|path|upload)[:\s]+["']?([^\s"']
|
|
37919
|
-
/^~\/\S
|
|
37920
|
-
/^\.\.?\/\S
|
|
37921
|
-
/^\/\S
|
|
37922
|
-
/^[A-Za-z]:\\[^\s]
|
|
37923
|
-
/^[^\s]
|
|
38412
|
+
/^["'](.+)["']$/i,
|
|
38413
|
+
/^@(.+)$/i,
|
|
38414
|
+
/(?:here'?s|file|path|upload|folder|dir)[:\s]+["']?([^\s"']+)["']?/i,
|
|
38415
|
+
/^~\/\S+$/i,
|
|
38416
|
+
/^\.\.?\/\S+$/i,
|
|
38417
|
+
/^\/\S+$/i,
|
|
38418
|
+
/^[A-Za-z]:\\[^\s]+$/i,
|
|
38419
|
+
/^[^\s]+$/i
|
|
37924
38420
|
];
|
|
37925
38421
|
for (const re of patterns) {
|
|
37926
38422
|
const m = trimmed.match(re);
|
|
37927
|
-
|
|
37928
|
-
|
|
37929
|
-
|
|
37930
|
-
|
|
37931
|
-
if (
|
|
37932
|
-
|
|
37933
|
-
|
|
38423
|
+
const candidate = (m?.[1] ?? (re.test(trimmed) ? trimmed : null))?.replace(/^["']|["']$/g, "");
|
|
38424
|
+
if (!candidate) continue;
|
|
38425
|
+
if (!looksLikePathToken(candidate)) continue;
|
|
38426
|
+
const p = expandPath(candidate);
|
|
38427
|
+
if (existsSync37(p)) {
|
|
38428
|
+
try {
|
|
38429
|
+
const st = statSync5(p);
|
|
38430
|
+
if (st.isFile() || st.isDirectory()) return p;
|
|
38431
|
+
} catch {
|
|
38432
|
+
}
|
|
37934
38433
|
}
|
|
37935
38434
|
}
|
|
37936
38435
|
return null;
|
|
37937
38436
|
}
|
|
38437
|
+
function looksLikePathToken(token) {
|
|
38438
|
+
if (token.length < 2) return false;
|
|
38439
|
+
if (/^(yes|no|y|n|back|b|prev|cancel|skip|help|demo)$/i.test(token)) return false;
|
|
38440
|
+
if (token.includes("/") || token.includes("\\")) return true;
|
|
38441
|
+
if (token.startsWith("~")) return true;
|
|
38442
|
+
if (/^[A-Za-z]:/.test(token)) return true;
|
|
38443
|
+
if (/\.[A-Za-z0-9]{1,8}$/.test(token)) return true;
|
|
38444
|
+
return false;
|
|
38445
|
+
}
|
|
37938
38446
|
function expandPath(p) {
|
|
37939
38447
|
if (p.startsWith("~/")) return resolve9(homedir8(), p.slice(2));
|
|
37940
38448
|
return resolve9(p);
|
|
@@ -37942,12 +38450,79 @@ function expandPath(p) {
|
|
|
37942
38450
|
function looksLikeFilePath(input) {
|
|
37943
38451
|
return extractFilePath(input) !== null;
|
|
37944
38452
|
}
|
|
38453
|
+
function listCsvsInFolder(dir) {
|
|
38454
|
+
try {
|
|
38455
|
+
if (!statSync5(dir).isDirectory()) return [];
|
|
38456
|
+
return readdirSync6(dir).filter((name) => name.toLowerCase().endsWith(".csv")).map((name) => join38(dir, name)).sort();
|
|
38457
|
+
} catch {
|
|
38458
|
+
return [];
|
|
38459
|
+
}
|
|
38460
|
+
}
|
|
38461
|
+
async function ingestPathFromChat(ctx, rawPath) {
|
|
38462
|
+
let st;
|
|
38463
|
+
try {
|
|
38464
|
+
st = statSync5(rawPath);
|
|
38465
|
+
} catch {
|
|
38466
|
+
console.log(" " + chalk80.red(`Path not found: ${rawPath}`));
|
|
38467
|
+
return false;
|
|
38468
|
+
}
|
|
38469
|
+
if (st.isDirectory()) {
|
|
38470
|
+
const csvs = listCsvsInFolder(rawPath);
|
|
38471
|
+
if (csvs.length === 0) {
|
|
38472
|
+
console.log();
|
|
38473
|
+
console.log(" " + paint("accent", "Folder noted") + chalk80.dim(` \u2014 ${rawPath}`));
|
|
38474
|
+
console.log(" " + chalk80.dim("No CSV files one level deep. Drop a .csv path, or put exports in that folder."));
|
|
38475
|
+
ctx.attachments = [
|
|
38476
|
+
...ctx.attachments ?? [],
|
|
38477
|
+
{ path: rawPath, ingested_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
38478
|
+
];
|
|
38479
|
+
saveSessionState(ctx);
|
|
38480
|
+
markProductionDataSeen();
|
|
38481
|
+
return false;
|
|
38482
|
+
}
|
|
38483
|
+
if (csvs.length === 1) {
|
|
38484
|
+
console.log(" " + chalk80.dim(`Found ${basename10(csvs[0])} in folder \u2014 ingesting.`));
|
|
38485
|
+
return ingestFromChat(ctx, csvs[0]);
|
|
38486
|
+
}
|
|
38487
|
+
if (!ctx.rl) {
|
|
38488
|
+
console.log(" " + chalk80.dim(`Found ${csvs.length} CSVs \u2014 re-run interactively to pick one.`));
|
|
38489
|
+
return false;
|
|
38490
|
+
}
|
|
38491
|
+
const prompts = createPromptSession(ctx.rl, ctx);
|
|
38492
|
+
try {
|
|
38493
|
+
const picked = await prompts.choose(
|
|
38494
|
+
"Which CSV in that folder?",
|
|
38495
|
+
csvs.map((p) => ({ value: p, label: basename10(p), description: p })),
|
|
38496
|
+
{ default: csvs[0] }
|
|
38497
|
+
);
|
|
38498
|
+
return ingestFromChat(ctx, picked);
|
|
38499
|
+
} finally {
|
|
38500
|
+
prompts.close();
|
|
38501
|
+
}
|
|
38502
|
+
}
|
|
38503
|
+
if (st.isFile()) {
|
|
38504
|
+
if (!rawPath.toLowerCase().endsWith(".csv")) {
|
|
38505
|
+
console.log();
|
|
38506
|
+
console.log(" " + paint("accent", "Path noted") + chalk80.dim(` \u2014 ${rawPath}`));
|
|
38507
|
+
console.log(" " + chalk80.dim("NTRP ingests CSV exports today. Drop a .csv from that location when ready."));
|
|
38508
|
+
ctx.attachments = [
|
|
38509
|
+
...ctx.attachments ?? [],
|
|
38510
|
+
{ path: rawPath, ingested_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
38511
|
+
];
|
|
38512
|
+
saveSessionState(ctx);
|
|
38513
|
+
markProductionDataSeen();
|
|
38514
|
+
return false;
|
|
38515
|
+
}
|
|
38516
|
+
return ingestFromChat(ctx, rawPath);
|
|
38517
|
+
}
|
|
38518
|
+
return false;
|
|
38519
|
+
}
|
|
37945
38520
|
async function ingestFromChat(ctx, filePath) {
|
|
37946
38521
|
if (!ctx.rl) {
|
|
37947
38522
|
console.log(" " + chalk80.red("Ingest confirm requires interactive mode."));
|
|
37948
38523
|
return false;
|
|
37949
38524
|
}
|
|
37950
|
-
const name =
|
|
38525
|
+
const name = basename10(filePath);
|
|
37951
38526
|
const prompts = createPromptSession(ctx.rl, ctx);
|
|
37952
38527
|
try {
|
|
37953
38528
|
const ok = await prompts.confirm(`Ingest ${name} as CRM export?`, true);
|
|
@@ -38006,6 +38581,7 @@ async function ingestFromChat(ctx, filePath) {
|
|
|
38006
38581
|
};
|
|
38007
38582
|
invalidateGapAudit(ctx);
|
|
38008
38583
|
saveSessionState(ctx);
|
|
38584
|
+
markProductionDataSeen();
|
|
38009
38585
|
console.log();
|
|
38010
38586
|
console.log(" " + paint("accent", "\u2713 Data loaded") + chalk80.dim(` \u2014 ${name}`));
|
|
38011
38587
|
recordMessage(ctx, "user", `[ingested ${name}]`);
|
|
@@ -38081,6 +38657,7 @@ async function loadDemoFromChat(ctx, scenario, opts = {}) {
|
|
|
38081
38657
|
counts,
|
|
38082
38658
|
ingested_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
38083
38659
|
};
|
|
38660
|
+
markDemoDataSeen();
|
|
38084
38661
|
if (!ctx.scope) {
|
|
38085
38662
|
const { proposeScopeFromIntent: proposeScopeFromIntent2 } = await Promise.resolve().then(() => (init_scope(), scope_exports));
|
|
38086
38663
|
const proposal = proposeScopeFromIntent2("demo pipeline and metrics");
|
|
@@ -38117,6 +38694,7 @@ var init_ingest_chat = __esm({
|
|
|
38117
38694
|
init_compute2();
|
|
38118
38695
|
init_theme();
|
|
38119
38696
|
init_demo();
|
|
38697
|
+
init_onboard_tiers();
|
|
38120
38698
|
}
|
|
38121
38699
|
});
|
|
38122
38700
|
|
|
@@ -38464,7 +39042,7 @@ async function runFirstRunFork(ctx, options = {}) {
|
|
|
38464
39042
|
}
|
|
38465
39043
|
function printFirstRunChip() {
|
|
38466
39044
|
console.log(
|
|
38467
|
-
" " + chalk82.dim("No profile yet \u2014 ask a question, type ") + chalk82.cyan("use demo data") + chalk82.dim(", or ") + paint("accent", "/onboard") + chalk82.dim("
|
|
39045
|
+
" " + chalk82.dim("No profile yet \u2014 ask a question, type ") + chalk82.cyan("use demo data") + chalk82.dim(", or ") + paint("accent", "/onboard") + chalk82.dim(" (progressive: profile \u2192 research \u2192 demo \u2192 your data).")
|
|
38468
39046
|
);
|
|
38469
39047
|
console.log();
|
|
38470
39048
|
}
|
|
@@ -38546,6 +39124,8 @@ async function loadFirstRunDemo(ctx, scenario) {
|
|
|
38546
39124
|
counts,
|
|
38547
39125
|
ingested_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
38548
39126
|
};
|
|
39127
|
+
const { markDemoDataSeen: markDemoDataSeen2 } = await Promise.resolve().then(() => (init_onboard_tiers(), onboard_tiers_exports));
|
|
39128
|
+
markDemoDataSeen2();
|
|
38549
39129
|
const { saveSessionState: saveSessionState2 } = await Promise.resolve().then(() => (init_context2(), context_exports));
|
|
38550
39130
|
saveSessionState2(ctx);
|
|
38551
39131
|
return true;
|
|
@@ -38792,6 +39372,83 @@ function popModalState(ctx) {
|
|
|
38792
39372
|
}
|
|
38793
39373
|
return popped;
|
|
38794
39374
|
}
|
|
39375
|
+
function handleBackNavigation(ctx, line) {
|
|
39376
|
+
if (!isBackInput(line)) return null;
|
|
39377
|
+
const phase = resolveConversationPhase(ctx);
|
|
39378
|
+
if (phase === "orient" || phase === "explore" || phase === "compute") {
|
|
39379
|
+
return null;
|
|
39380
|
+
}
|
|
39381
|
+
if (phase === "scope") {
|
|
39382
|
+
console.log();
|
|
39383
|
+
console.log(" " + chalk84.dim("What should we focus on instead?"));
|
|
39384
|
+
ctx.scope = void 0;
|
|
39385
|
+
clearPendingAsk(ctx);
|
|
39386
|
+
saveSessionState(ctx);
|
|
39387
|
+
recordMessage(ctx, "user", line);
|
|
39388
|
+
recordMessage(ctx, "agent", "Scope cleared (back)");
|
|
39389
|
+
console.log();
|
|
39390
|
+
return { handled: true, summary: "Scope cleared" };
|
|
39391
|
+
}
|
|
39392
|
+
if (phase === "awaiting_data") {
|
|
39393
|
+
if (!ctx.scope?.confirmed_at) return null;
|
|
39394
|
+
const hadData = sessionHasData(ctx);
|
|
39395
|
+
const hadAnalysis = ctx.stage === "analyzed" || isAnalysisReady(ctx);
|
|
39396
|
+
unconfirmScope(ctx);
|
|
39397
|
+
saveSessionState(ctx);
|
|
39398
|
+
recordMessage(ctx, "user", line);
|
|
39399
|
+
recordMessage(ctx, "agent", "Scope unconfirmed (back)");
|
|
39400
|
+
printScopeProposal(ctx);
|
|
39401
|
+
if (hadData || hadAnalysis) {
|
|
39402
|
+
console.log(
|
|
39403
|
+
" " + chalk84.dim(
|
|
39404
|
+
"Focus gate rewound \u2014 loaded data and any analysis stay. `b` does not unload demo data or undo compute."
|
|
39405
|
+
)
|
|
39406
|
+
);
|
|
39407
|
+
console.log();
|
|
39408
|
+
}
|
|
39409
|
+
return { handled: true, summary: "Scope unconfirmed" };
|
|
39410
|
+
}
|
|
39411
|
+
if (phase === "strategize") {
|
|
39412
|
+
const state2 = ctx.strategistState;
|
|
39413
|
+
if (state2?.step === "objective_confirm") {
|
|
39414
|
+
state2.step = "objective_input";
|
|
39415
|
+
saveSessionState(ctx);
|
|
39416
|
+
recordMessage(ctx, "user", line);
|
|
39417
|
+
recordMessage(ctx, "agent", "Strategy objective adjust (back)");
|
|
39418
|
+
console.log();
|
|
39419
|
+
console.log(" " + chalk84.dim("What is the objective? State a finish line."));
|
|
39420
|
+
console.log();
|
|
39421
|
+
return { handled: true, summary: "Awaiting objective" };
|
|
39422
|
+
}
|
|
39423
|
+
const popped = popModalState(ctx);
|
|
39424
|
+
if (popped.length === 0) return null;
|
|
39425
|
+
saveSessionState(ctx);
|
|
39426
|
+
recordMessage(ctx, "user", line);
|
|
39427
|
+
recordMessage(ctx, "agent", `Back \u2014 dropped ${popped.join(", ")}`);
|
|
39428
|
+
const backTo = formatPhaseLabel(resolveConversationPhase(ctx));
|
|
39429
|
+
console.log();
|
|
39430
|
+
console.log(
|
|
39431
|
+
" " + chalk84.dim(`Back \u2014 dropped ${popped.join(" and ")}. At `) + chalk84.cyan(backTo) + chalk84.dim(".")
|
|
39432
|
+
);
|
|
39433
|
+
console.log();
|
|
39434
|
+
return { handled: true, summary: "Back" };
|
|
39435
|
+
}
|
|
39436
|
+
if (phase === "think" || phase === "deliver") {
|
|
39437
|
+
const popped = popModalState(ctx);
|
|
39438
|
+
if (popped.length === 0) return null;
|
|
39439
|
+
saveSessionState(ctx);
|
|
39440
|
+
recordMessage(ctx, "user", line);
|
|
39441
|
+
recordMessage(ctx, "agent", `Back \u2014 dropped ${popped.join(", ")}`);
|
|
39442
|
+
const backTo = formatPhaseLabel(resolveConversationPhase(ctx));
|
|
39443
|
+
console.log();
|
|
39444
|
+
console.log(
|
|
39445
|
+
" " + chalk84.dim(`Back \u2014 dropped ${popped.join(" and ")}. At `) + chalk84.cyan(backTo) + chalk84.dim(".")
|
|
39446
|
+
);
|
|
39447
|
+
console.log();
|
|
39448
|
+
return { handled: true, summary: "Back" };
|
|
39449
|
+
}
|
|
39450
|
+
return null;
|
|
39451
|
+
}
|
|
38795
39452
|
async function conversationRouter(input, ctx) {
|
|
38796
39453
|
if (ctx.oneShot || (ctx.wizardDepth ?? 0) > 0) {
|
|
38797
39454
|
return { handled: false };
|
|
@@ -38821,6 +39478,8 @@ async function conversationRouter(input, ctx) {
|
|
|
38821
39478
|
return { handled: true, summary: "Cancelled" };
|
|
38822
39479
|
}
|
|
38823
39480
|
}
|
|
39481
|
+
const backResult = handleBackNavigation(ctx, line);
|
|
39482
|
+
if (backResult) return backResult;
|
|
38824
39483
|
if (/^\S+$/.test(line) && !/^\d$/.test(line)) {
|
|
38825
39484
|
const { resolveSessionByToken: resolveSessionByToken2 } = await Promise.resolve().then(() => (init_context2(), context_exports));
|
|
38826
39485
|
if (resolveSessionByToken2(line, { printErrors: false }) !== void 0) {
|
|
@@ -38830,7 +39489,8 @@ async function conversationRouter(input, ctx) {
|
|
|
38830
39489
|
const phase = resolveConversationPhase(ctx);
|
|
38831
39490
|
if (looksLikeFilePath(line)) {
|
|
38832
39491
|
const path = extractFilePath(line);
|
|
38833
|
-
await
|
|
39492
|
+
const { ingestPathFromChat: ingestPathFromChat2 } = await Promise.resolve().then(() => (init_ingest_chat(), ingest_chat_exports));
|
|
39493
|
+
await ingestPathFromChat2(ctx, path);
|
|
38834
39494
|
return { handled: true, summary: "Data ingested" };
|
|
38835
39495
|
}
|
|
38836
39496
|
if (isDemoIntent(line)) {
|
|
@@ -38925,6 +39585,8 @@ var init_router = __esm({
|
|
|
38925
39585
|
init_think_flow();
|
|
38926
39586
|
init_context2();
|
|
38927
39587
|
init_demo();
|
|
39588
|
+
init_scope();
|
|
39589
|
+
init_pending_ask();
|
|
38928
39590
|
FRESH_START_RE = /\b(start (over|fresh)|new analysis|start again|reset session)\b/i;
|
|
38929
39591
|
CANCEL_ESCAPE_RE = /^(cancel|stop|abort|quit|never\s?mind|nevermind|forget it)\s*[.!]?\s*$/i;
|
|
38930
39592
|
}
|
|
@@ -39148,17 +39810,17 @@ var init_loop_guard2 = __esm({
|
|
|
39148
39810
|
PHASE_GUIDES = {
|
|
39149
39811
|
scope: {
|
|
39150
39812
|
mode: "scope confirmation",
|
|
39151
|
-
accepts: "\u23CE or yes (run with this focus) \xB7 adjust (restate it) \xB7 a new focus statement",
|
|
39152
|
-
leave: "cancel drops the proposal \xB7 /home shows the dashboard"
|
|
39813
|
+
accepts: "\u23CE or yes (run with this focus) \xB7 b / back (clear it) \xB7 adjust (restate it) \xB7 a new focus statement",
|
|
39814
|
+
leave: "b / back or cancel drops the proposal \xB7 /home shows the dashboard"
|
|
39153
39815
|
},
|
|
39154
39816
|
awaiting_data: {
|
|
39155
39817
|
mode: "data loading",
|
|
39156
39818
|
accepts: 'a CSV path \xB7 "use demo data" \xB7 "go ahead" (compute once data is loaded) \xB7 \u23CE runs the action shown at the prompt',
|
|
39157
|
-
leave: "
|
|
39819
|
+
leave: "b / back rewinds to the focus confirm card (data stays) \xB7 cancel \xB7 /home shows the dashboard"
|
|
39158
39820
|
},
|
|
39159
39821
|
strategize: {
|
|
39160
39822
|
mode: "strategy objective confirm",
|
|
39161
|
-
accepts: "\u23CE or yes (build the plan) \xB7 adjust (restate the objective) \xB7 cancel",
|
|
39823
|
+
accepts: "\u23CE or yes (build the plan) \xB7 b / back or adjust (restate the objective) \xB7 cancel",
|
|
39162
39824
|
leave: "cancel drops the strategy session and returns to Q&A"
|
|
39163
39825
|
}
|
|
39164
39826
|
};
|
|
@@ -39754,7 +40416,7 @@ __export(repl_exports, {
|
|
|
39754
40416
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
39755
40417
|
import { clearLine as clearLine2, cursorTo as cursorTo2 } from "readline";
|
|
39756
40418
|
import chalk88 from "chalk";
|
|
39757
|
-
import { join as
|
|
40419
|
+
import { join as join39 } from "path";
|
|
39758
40420
|
function buildPrompt(ctx) {
|
|
39759
40421
|
return buildConversationPrompt(ctx);
|
|
39760
40422
|
}
|
|
@@ -40086,6 +40748,20 @@ function printHelp() {
|
|
|
40086
40748
|
console.log(" " + chalk88.dim("Type ") + paint("accent", '"ship a board deck"') + chalk88.dim(" to write a handoff."));
|
|
40087
40749
|
console.log(" " + chalk88.dim("The ") + paint("accent", "\u203A") + chalk88.dim(" prompt shows brief or deep after analysis. Brief is the default."));
|
|
40088
40750
|
console.log();
|
|
40751
|
+
console.log(" " + sectionHeading("Keys"));
|
|
40752
|
+
console.log(
|
|
40753
|
+
" " + paint("accent", "\u23CE") + chalk88.dim(" Runs the armed action shown at the prompt (yes, use demo data, go ahead, /connect).")
|
|
40754
|
+
);
|
|
40755
|
+
console.log(
|
|
40756
|
+
" " + paint("accent", "b") + chalk88.dim(" / ") + paint("accent", "back") + chalk88.dim(" Steps back one confirm gate, or leaves a modal. Does not unload data or undo compute.")
|
|
40757
|
+
);
|
|
40758
|
+
console.log(
|
|
40759
|
+
" " + paint("accent", "cancel") + chalk88.dim(" Drops the current modal overlay (strategy, think, ship, unconfirmed scope).")
|
|
40760
|
+
);
|
|
40761
|
+
console.log(
|
|
40762
|
+
" " + chalk88.dim("On ") + paint("accent", "/deepdive") + chalk88.dim(" slides, ") + paint("accent", "b") + chalk88.dim(" moves to the previous slide.")
|
|
40763
|
+
);
|
|
40764
|
+
console.log();
|
|
40089
40765
|
console.log(" " + sectionHeading("Shortcuts"));
|
|
40090
40766
|
const shortcuts = [
|
|
40091
40767
|
["/home", "Show status"],
|
|
@@ -40119,7 +40795,7 @@ function printHelp() {
|
|
|
40119
40795
|
["/remember <fact>", "Store a fact, a decision, or a preference"],
|
|
40120
40796
|
["/recall [topic]", "Show what NTRP stores about your business"],
|
|
40121
40797
|
["/rate good|bad <note>", "Correct the last answer. A bad note becomes a calibration"],
|
|
40122
|
-
[`${
|
|
40798
|
+
[`${join39(ntrpHome(), ANALYST_FILE_NAME)}`, "Standing operator instructions (tone, priorities, house rules)"]
|
|
40123
40799
|
];
|
|
40124
40800
|
const teachMaxW = Math.max(...teach.map(([c]) => c.length)) + 2;
|
|
40125
40801
|
for (const [cmd, desc] of teach) {
|