@sonnechasser/ntrp 1.4.9 → 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 +729 -166
- package/dist/mcp/server.js +1116 -993
- 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
|
);
|
|
@@ -22822,9 +22827,9 @@ async function handleGetSessionBrief(input) {
|
|
|
22822
22827
|
if (!target) {
|
|
22823
22828
|
return { error: `No session matching "${raw}".` };
|
|
22824
22829
|
}
|
|
22825
|
-
const { existsSync:
|
|
22830
|
+
const { existsSync: existsSync38, readFileSync: readFileSync24 } = await import("fs");
|
|
22826
22831
|
const briefPath = contextDocPathForSession2(target.id);
|
|
22827
|
-
if (!
|
|
22832
|
+
if (!existsSync38(briefPath)) {
|
|
22828
22833
|
return {
|
|
22829
22834
|
session_id: target.id,
|
|
22830
22835
|
error: "No context brief on disk for this session (created before brief storage existed).",
|
|
@@ -24277,20 +24282,128 @@ var init_diagnose = __esm({
|
|
|
24277
24282
|
});
|
|
24278
24283
|
|
|
24279
24284
|
// src/ai/profile-draft.ts
|
|
24280
|
-
async function draftCompanyProfile(seed,
|
|
24285
|
+
async function draftCompanyProfile(seed, ctx, opts = {}) {
|
|
24281
24286
|
if (!seed.company_url && !seed.company_name) {
|
|
24282
24287
|
throw new Error("draftCompanyProfile requires a URL or a company name");
|
|
24283
24288
|
}
|
|
24284
|
-
|
|
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) {
|
|
24285
24329
|
const out = {};
|
|
24286
|
-
|
|
24287
|
-
|
|
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;
|
|
24288
24356
|
return out;
|
|
24289
24357
|
}
|
|
24358
|
+
var SYSTEM_PROMPT4, ALLOWED_MOTIONS, ALLOWED_CRMS, ALLOWED_ENGAGEMENT;
|
|
24290
24359
|
var init_profile_draft = __esm({
|
|
24291
24360
|
"src/ai/profile-draft.ts"() {
|
|
24292
24361
|
"use strict";
|
|
24362
|
+
init_repl_api();
|
|
24363
|
+
init_complete();
|
|
24293
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"]);
|
|
24294
24407
|
}
|
|
24295
24408
|
});
|
|
24296
24409
|
|
|
@@ -24532,7 +24645,7 @@ OUTPUT \u2014 STRICT JSON only, no fences, no commentary:
|
|
|
24532
24645
|
});
|
|
24533
24646
|
|
|
24534
24647
|
// src/ai/profile-clarify.ts
|
|
24535
|
-
function
|
|
24648
|
+
function stripFences4(text) {
|
|
24536
24649
|
const trimmed = text.trim();
|
|
24537
24650
|
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
|
|
24538
24651
|
if (fenced) return fenced[1].trim();
|
|
@@ -24552,7 +24665,7 @@ function formatDraft(draft) {
|
|
|
24552
24665
|
return parts.join("\n");
|
|
24553
24666
|
}
|
|
24554
24667
|
function parseClarifyingQuestions(text) {
|
|
24555
|
-
const cleaned =
|
|
24668
|
+
const cleaned = stripFences4(text);
|
|
24556
24669
|
let parsed;
|
|
24557
24670
|
try {
|
|
24558
24671
|
parsed = JSON.parse(cleaned);
|
|
@@ -24649,7 +24762,7 @@ ${draftBlock}${userBlock}${answersBlock}
|
|
|
24649
24762
|
|
|
24650
24763
|
Emit the refined profile now as STRICT JSON.`;
|
|
24651
24764
|
const { text } = await llmCompleteText("onboard", REFINE_SYSTEM_PROMPT, userMessage, 2048, ctx);
|
|
24652
|
-
const cleaned =
|
|
24765
|
+
const cleaned = stripFences4(text);
|
|
24653
24766
|
let parsed;
|
|
24654
24767
|
try {
|
|
24655
24768
|
parsed = JSON.parse(cleaned);
|
|
@@ -24671,7 +24784,7 @@ function validateRefined(raw) {
|
|
|
24671
24784
|
const targetCustomer = str2("target_customer");
|
|
24672
24785
|
if (targetCustomer) out.target_customer = targetCustomer;
|
|
24673
24786
|
const motion = str2("sales_motion")?.toLowerCase();
|
|
24674
|
-
if (motion &&
|
|
24787
|
+
if (motion && ALLOWED_MOTIONS2.has(motion)) {
|
|
24675
24788
|
out.sales_motion = motion;
|
|
24676
24789
|
}
|
|
24677
24790
|
const dealSize = str2("average_deal_size");
|
|
@@ -24681,14 +24794,14 @@ function validateRefined(raw) {
|
|
|
24681
24794
|
out.sales_cycle_days = Math.round(cycleDays);
|
|
24682
24795
|
}
|
|
24683
24796
|
const crm = str2("primary_crm")?.toLowerCase();
|
|
24684
|
-
if (crm &&
|
|
24797
|
+
if (crm && ALLOWED_CRMS2.has(crm)) out.primary_crm = crm;
|
|
24685
24798
|
const engagement = str2("engagement_tool")?.toLowerCase();
|
|
24686
|
-
if (engagement &&
|
|
24799
|
+
if (engagement && ALLOWED_ENGAGEMENT2.has(engagement)) out.engagement_tool = engagement;
|
|
24687
24800
|
const userScope = str2("user_scope");
|
|
24688
24801
|
if (userScope) out.user_scope = userScope;
|
|
24689
24802
|
return out;
|
|
24690
24803
|
}
|
|
24691
|
-
var CLARIFY_SYSTEM_PROMPT, REFINE_SYSTEM_PROMPT,
|
|
24804
|
+
var CLARIFY_SYSTEM_PROMPT, REFINE_SYSTEM_PROMPT, ALLOWED_MOTIONS2, ALLOWED_CRMS2, ALLOWED_ENGAGEMENT2;
|
|
24692
24805
|
var init_profile_clarify = __esm({
|
|
24693
24806
|
"src/ai/profile-clarify.ts"() {
|
|
24694
24807
|
"use strict";
|
|
@@ -24753,9 +24866,9 @@ OUTPUT FORMAT \u2014 respond with STRICT JSON only, no preamble, no markdown fen
|
|
|
24753
24866
|
"engagement_tool": "outreach",
|
|
24754
24867
|
"user_scope": "string"
|
|
24755
24868
|
}`;
|
|
24756
|
-
|
|
24757
|
-
|
|
24758
|
-
|
|
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"]);
|
|
24759
24872
|
}
|
|
24760
24873
|
});
|
|
24761
24874
|
|
|
@@ -25014,6 +25127,155 @@ var init_profile2 = __esm({
|
|
|
25014
25127
|
}
|
|
25015
25128
|
});
|
|
25016
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
|
+
|
|
25017
25279
|
// src/conversation/voice-setup.ts
|
|
25018
25280
|
var voice_setup_exports = {};
|
|
25019
25281
|
__export(voice_setup_exports, {
|
|
@@ -25373,97 +25635,314 @@ __export(onboard_exports, {
|
|
|
25373
25635
|
profileExists: () => profileExists
|
|
25374
25636
|
});
|
|
25375
25637
|
import chalk25 from "chalk";
|
|
25638
|
+
import { existsSync as existsSync26 } from "fs";
|
|
25639
|
+
import { basename as basename7 } from "path";
|
|
25376
25640
|
async function handler5(args, ctx) {
|
|
25377
25641
|
const { flags } = parseArgs2(args, ["force", "skip-brand"]);
|
|
25378
25642
|
const force = getBool(flags, "force");
|
|
25379
25643
|
const skipBrand = getBool(flags, "skip-brand");
|
|
25644
|
+
const tierOverride = normalizeTierFlag(getString(flags, "tier"));
|
|
25380
25645
|
const priorProfile = loadProfile();
|
|
25381
25646
|
const configured = isProfileConfigured(priorProfile);
|
|
25382
25647
|
let existing = configured ? priorProfile : null;
|
|
25383
25648
|
if (!skipBrand) printCenteredLogo();
|
|
25384
25649
|
const session = createPromptSession(ctx.rl, ctx);
|
|
25385
25650
|
try {
|
|
25386
|
-
if (configured && !force) {
|
|
25387
|
-
|
|
25388
|
-
|
|
25389
|
-
|
|
25390
|
-
|
|
25391
|
-
console.log(" " + chalk25.dim("
|
|
25392
|
-
console.log(
|
|
25393
|
-
|
|
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}`;
|
|
25394
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();
|
|
25673
|
+
}
|
|
25674
|
+
} else if (configured && force && !tierOverride) {
|
|
25675
|
+
console.log();
|
|
25676
|
+
console.log(" " + bold(`Rebuilding profile for ${priorProfile.company_name}.`));
|
|
25395
25677
|
existing = null;
|
|
25678
|
+
resetOnboardTierProgress();
|
|
25396
25679
|
} else if (priorProfile && !configured) {
|
|
25397
25680
|
console.log();
|
|
25398
|
-
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."));
|
|
25399
25682
|
existing = null;
|
|
25683
|
+
resetOnboardTierProgress();
|
|
25400
25684
|
}
|
|
25401
|
-
|
|
25402
|
-
|
|
25403
|
-
|
|
25404
|
-
"
|
|
25405
|
-
|
|
25406
|
-
|
|
25407
|
-
|
|
25408
|
-
|
|
25409
|
-
|
|
25410
|
-
|
|
25411
|
-
|
|
25412
|
-
|
|
25413
|
-
|
|
25414
|
-
|
|
25415
|
-
|
|
25416
|
-
|
|
25417
|
-
|
|
25418
|
-
|
|
25419
|
-
schema_version: 1,
|
|
25420
|
-
company_name: canonicalName,
|
|
25421
|
-
...companyUrl ? { company_url: companyUrl } : existing?.company_url ? { company_url: existing.company_url } : {},
|
|
25422
|
-
industry: draft.industry ?? existing?.industry ?? "",
|
|
25423
|
-
product_description: draft.product_description ?? existing?.product_description ?? "",
|
|
25424
|
-
target_customer: draft.target_customer ?? existing?.target_customer ?? "",
|
|
25425
|
-
sales_motion: salesMotion,
|
|
25426
|
-
...draft.average_deal_size !== void 0 ? { average_deal_size: draft.average_deal_size } : {},
|
|
25427
|
-
...draft.sales_cycle_days !== void 0 ? { sales_cycle_days: draft.sales_cycle_days } : {},
|
|
25428
|
-
...draft.primary_crm !== void 0 ? { primary_crm: draft.primary_crm } : {},
|
|
25429
|
-
...draft.engagement_tool !== void 0 ? { engagement_tool: draft.engagement_tool } : {},
|
|
25430
|
-
...existing?.user_scope ? { user_scope: existing.user_scope } : {},
|
|
25431
|
-
created_at: existing?.created_at ?? now2,
|
|
25432
|
-
updated_at: now2
|
|
25433
|
-
};
|
|
25434
|
-
renderProfile(candidate);
|
|
25435
|
-
candidate = await adaptiveRefine(session, candidate, ctx);
|
|
25436
|
-
for (; ; ) {
|
|
25437
|
-
renderProfile(candidate);
|
|
25438
|
-
const action = (await session.ask("Is this profile correct? Type Y, edit, or redraft", { default: "Y" })).toLowerCase();
|
|
25439
|
-
if (action === "y" || action === "yes" || action === "") break;
|
|
25440
|
-
if (action === "redraft") {
|
|
25441
|
-
const fresh = await fillMissingFields(session, { company_url: candidate.company_url }, null);
|
|
25442
|
-
candidate = applyDraft(candidate, fresh);
|
|
25443
|
-
continue;
|
|
25444
|
-
}
|
|
25445
|
-
if (action === "edit" || action === "e") {
|
|
25446
|
-
candidate = await editLoop(session, candidate);
|
|
25447
|
-
continue;
|
|
25448
|
-
}
|
|
25449
|
-
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);
|
|
25450
25703
|
}
|
|
25451
|
-
|
|
25452
|
-
|
|
25453
|
-
|
|
25454
|
-
|
|
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()) {
|
|
25455
25772
|
console.log();
|
|
25456
25773
|
console.log(" " + chalk25.green("\u2713") + " " + bold(`Profile saved for ${candidate.company_name}`));
|
|
25457
|
-
console.log(" " + chalk25.dim(
|
|
25774
|
+
console.log(" " + chalk25.dim("A key is already connected \u2014 running domain research now."));
|
|
25458
25775
|
console.log();
|
|
25776
|
+
await runDomainResearchOnProfile(session, ctx, candidate);
|
|
25777
|
+
markOnboardTierComplete("domain");
|
|
25459
25778
|
await offerInboxSkillSetup(session, { beat: "production" });
|
|
25460
|
-
await
|
|
25461
|
-
|
|
25462
|
-
|
|
25463
|
-
return `Profile saved for ${candidate.company_name}`;
|
|
25464
|
-
} finally {
|
|
25465
|
-
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}`;
|
|
25466
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;
|
|
25467
25946
|
}
|
|
25468
25947
|
async function fillMissingFields(session, draft, existing) {
|
|
25469
25948
|
const out = { ...draft };
|
|
@@ -25685,16 +26164,10 @@ async function editField(session, p, field) {
|
|
|
25685
26164
|
}
|
|
25686
26165
|
}
|
|
25687
26166
|
}
|
|
25688
|
-
function printIntro() {
|
|
25689
|
-
console.log(" " + bold("Let's set up your company profile."));
|
|
25690
|
-
console.log(" " + chalk25.dim("NTRP will research your business so every answer,"));
|
|
25691
|
-
console.log(" " + chalk25.dim("finding, and demo is tailored to your reality."));
|
|
25692
|
-
console.log();
|
|
25693
|
-
}
|
|
25694
26167
|
function printKeylessNote() {
|
|
25695
|
-
console.log(" " + chalk25.dim("Skipping AI \u2014
|
|
26168
|
+
console.log(" " + chalk25.dim("Skipping AI \u2014 domain research needs a key."));
|
|
25696
26169
|
console.log(
|
|
25697
|
-
" " + 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(".")
|
|
25698
26171
|
);
|
|
25699
26172
|
console.log();
|
|
25700
26173
|
}
|
|
@@ -25704,9 +26177,9 @@ async function ensureLlmKeys(session) {
|
|
|
25704
26177
|
const { providerLabel: providerLabel2 } = await Promise.resolve().then(() => (init_providers(), providers_exports));
|
|
25705
26178
|
const { countAvailableEngines: countAvailableEngines2 } = await Promise.resolve().then(() => (init_session_state(), session_state_exports));
|
|
25706
26179
|
console.log();
|
|
25707
|
-
console.log(" " + bold("Connect a key") + chalk25.dim(" (
|
|
25708
|
-
console.log(" " + chalk25.dim("Scores and metrics are local math. No AI is required."));
|
|
25709
|
-
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."));
|
|
25710
26183
|
console.log(
|
|
25711
26184
|
" " + chalk25.dim("Paste any provider API key. Anthropic, OpenAI, Groq, Gemini, Mistral, and others.")
|
|
25712
26185
|
);
|
|
@@ -25798,6 +26271,8 @@ var init_onboard = __esm({
|
|
|
25798
26271
|
init_profile_clarify();
|
|
25799
26272
|
init_profile2();
|
|
25800
26273
|
init_inbox_setup();
|
|
26274
|
+
init_onboard_tiers();
|
|
26275
|
+
init_path_safety();
|
|
25801
26276
|
SALES_MOTION_CHOICES = [
|
|
25802
26277
|
{ value: "plg", label: "PLG \u2014 Product-Led Growth", description: PRESET_DESCRIPTIONS.plg },
|
|
25803
26278
|
{ value: "smb_velocity", label: "SMB Velocity \u2014 Fast cycles, high volume", description: PRESET_DESCRIPTIONS.smb_velocity },
|
|
@@ -25838,8 +26313,8 @@ __export(new_exports, {
|
|
|
25838
26313
|
handler: () => handler6
|
|
25839
26314
|
});
|
|
25840
26315
|
import chalk26 from "chalk";
|
|
25841
|
-
import { existsSync as
|
|
25842
|
-
import { basename as
|
|
26316
|
+
import { existsSync as existsSync27 } from "fs";
|
|
26317
|
+
import { basename as basename8 } from "path";
|
|
25843
26318
|
async function handler6(args, ctx) {
|
|
25844
26319
|
const { positional, flags } = parseArgs2(args, ["demo", "empty", "list-scenarios", "regen-taxonomy"]);
|
|
25845
26320
|
let source = null;
|
|
@@ -25860,7 +26335,7 @@ async function handler6(args, ctx) {
|
|
|
25860
26335
|
console.error(chalk26.red(" Usage: /new <file.csv> | --demo [--scenario <name>] | --empty [--lens health|metrics]"));
|
|
25861
26336
|
return;
|
|
25862
26337
|
}
|
|
25863
|
-
if (source.kind === "file" && !
|
|
26338
|
+
if (source.kind === "file" && !existsSync27(source.path)) {
|
|
25864
26339
|
console.error(chalk26.red(` File not found: ${source.path}`));
|
|
25865
26340
|
return;
|
|
25866
26341
|
}
|
|
@@ -25884,7 +26359,7 @@ async function handler6(args, ctx) {
|
|
|
25884
26359
|
const sourceFlag = getString(flags, "source", "s");
|
|
25885
26360
|
if (sourceFlag) passthrough.push("--source", sourceFlag);
|
|
25886
26361
|
await ingest(passthrough, ctx);
|
|
25887
|
-
datasetLabel2 =
|
|
26362
|
+
datasetLabel2 = basename8(source.path);
|
|
25888
26363
|
datasetSource = source.path;
|
|
25889
26364
|
} else if (source.kind === "demo") {
|
|
25890
26365
|
const { handler: demo } = await Promise.resolve().then(() => (init_generate(), generate_exports));
|
|
@@ -26087,7 +26562,7 @@ __export(end_exports, {
|
|
|
26087
26562
|
handler: () => handler7
|
|
26088
26563
|
});
|
|
26089
26564
|
import chalk27 from "chalk";
|
|
26090
|
-
import { existsSync as
|
|
26565
|
+
import { existsSync as existsSync28 } from "fs";
|
|
26091
26566
|
async function handler7(args, ctx) {
|
|
26092
26567
|
if (args.length > 0) {
|
|
26093
26568
|
console.error(chalk27.red(" Usage: /end"));
|
|
@@ -26124,10 +26599,10 @@ async function handler7(args, ctx) {
|
|
|
26124
26599
|
if (summary) {
|
|
26125
26600
|
console.log(" " + chalk27.dim(summary));
|
|
26126
26601
|
}
|
|
26127
|
-
if (
|
|
26602
|
+
if (existsSync28(transcriptPathForSession(endedId))) {
|
|
26128
26603
|
console.log(" " + chalk27.dim("Transcript: ") + chalk27.dim(transcriptPathForSession(endedId)));
|
|
26129
26604
|
}
|
|
26130
|
-
if (
|
|
26605
|
+
if (existsSync28(contextDocPathForSession(endedId))) {
|
|
26131
26606
|
console.log(" " + chalk27.dim("Context brief: ") + chalk27.dim(contextDocPathForSession(endedId)));
|
|
26132
26607
|
}
|
|
26133
26608
|
console.log();
|
|
@@ -26149,7 +26624,7 @@ __export(session_exports, {
|
|
|
26149
26624
|
});
|
|
26150
26625
|
import chalk28 from "chalk";
|
|
26151
26626
|
import { join as join23 } from "path";
|
|
26152
|
-
import { existsSync as
|
|
26627
|
+
import { existsSync as existsSync29 } from "fs";
|
|
26153
26628
|
async function handler8(args, ctx) {
|
|
26154
26629
|
const sub = args[0];
|
|
26155
26630
|
if (!sub) return listSessionsView(ctx);
|
|
@@ -26295,7 +26770,7 @@ async function pickUp(idArg, ctx) {
|
|
|
26295
26770
|
);
|
|
26296
26771
|
}
|
|
26297
26772
|
const contextPath = contextDocPathForSession(target.id);
|
|
26298
|
-
if (
|
|
26773
|
+
if (existsSync29(contextPath)) {
|
|
26299
26774
|
console.log(" " + chalk28.dim("Context brief: ") + chalk28.dim(contextPath));
|
|
26300
26775
|
}
|
|
26301
26776
|
console.log();
|
|
@@ -27247,7 +27722,7 @@ var init_bundle = __esm({
|
|
|
27247
27722
|
|
|
27248
27723
|
// src/repositories/markdown.ts
|
|
27249
27724
|
import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync17 } from "fs";
|
|
27250
|
-
import { basename as
|
|
27725
|
+
import { basename as basename9, dirname as dirname5, join as join26, resolve as resolve8 } from "path";
|
|
27251
27726
|
import { stringify as stringifyYaml2 } from "yaml";
|
|
27252
27727
|
function renderMarkdownFiles(pkg) {
|
|
27253
27728
|
const bundleJson = JSON.stringify(pkg, null, 2) + "\n";
|
|
@@ -27437,7 +27912,7 @@ function getRootPath(target) {
|
|
|
27437
27912
|
return resolve8(target.directory ?? `ntrp-repository-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`);
|
|
27438
27913
|
}
|
|
27439
27914
|
function safeFilename(value) {
|
|
27440
|
-
return (
|
|
27915
|
+
return (basename9(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "strategy") + ".md";
|
|
27441
27916
|
}
|
|
27442
27917
|
function escapeSummary(value) {
|
|
27443
27918
|
return value.replace(/[<>]/g, "");
|
|
@@ -31967,7 +32442,7 @@ var init_checkout = __esm({
|
|
|
31967
32442
|
});
|
|
31968
32443
|
|
|
31969
32444
|
// src/services/setup.ts
|
|
31970
|
-
import { existsSync as
|
|
32445
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync17, readFileSync as readFileSync20, writeFileSync as writeFileSync19 } from "fs";
|
|
31971
32446
|
import { join as join29 } from "path";
|
|
31972
32447
|
function setupCheck() {
|
|
31973
32448
|
const home = ntrpHome();
|
|
@@ -33403,7 +33878,7 @@ var init_metrics = __esm({
|
|
|
33403
33878
|
});
|
|
33404
33879
|
|
|
33405
33880
|
// src/ai/feedback-apply.ts
|
|
33406
|
-
function
|
|
33881
|
+
function stripFences5(text) {
|
|
33407
33882
|
const trimmed = text.trim();
|
|
33408
33883
|
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
|
|
33409
33884
|
if (fenced) return fenced[1].trim();
|
|
@@ -33433,8 +33908,8 @@ OPERATOR FEEDBACK:
|
|
|
33433
33908
|
"${feedbackText}"
|
|
33434
33909
|
|
|
33435
33910
|
Apply the feedback now as STRICT JSON.`;
|
|
33436
|
-
const { text } = await llmCompleteText("feedback",
|
|
33437
|
-
const cleaned =
|
|
33911
|
+
const { text } = await llmCompleteText("feedback", SYSTEM_PROMPT5, userMessage, 1024, ctx);
|
|
33912
|
+
const cleaned = stripFences5(text);
|
|
33438
33913
|
let parsed;
|
|
33439
33914
|
try {
|
|
33440
33915
|
parsed = JSON.parse(cleaned);
|
|
@@ -33461,7 +33936,7 @@ function validatePatch(raw) {
|
|
|
33461
33936
|
const targetCustomer = str2("target_customer");
|
|
33462
33937
|
if (targetCustomer) out.target_customer = targetCustomer;
|
|
33463
33938
|
const motion = str2("sales_motion")?.toLowerCase();
|
|
33464
|
-
if (motion &&
|
|
33939
|
+
if (motion && ALLOWED_MOTIONS3.has(motion)) {
|
|
33465
33940
|
out.sales_motion = motion;
|
|
33466
33941
|
}
|
|
33467
33942
|
const dealSize = str2("average_deal_size");
|
|
@@ -33471,25 +33946,25 @@ function validatePatch(raw) {
|
|
|
33471
33946
|
out.sales_cycle_days = Math.round(cycleDays);
|
|
33472
33947
|
}
|
|
33473
33948
|
const crm = str2("primary_crm")?.toLowerCase();
|
|
33474
|
-
if (crm &&
|
|
33949
|
+
if (crm && ALLOWED_CRMS3.has(crm)) out.primary_crm = crm;
|
|
33475
33950
|
const engagement = str2("engagement_tool")?.toLowerCase();
|
|
33476
|
-
if (engagement &&
|
|
33951
|
+
if (engagement && ALLOWED_ENGAGEMENT3.has(engagement)) out.engagement_tool = engagement;
|
|
33477
33952
|
const userScope = str2("user_scope");
|
|
33478
33953
|
if (userScope) out.user_scope = userScope;
|
|
33479
33954
|
const customContext = str2("custom_context");
|
|
33480
33955
|
if (customContext) out.custom_context = customContext;
|
|
33481
33956
|
return out;
|
|
33482
33957
|
}
|
|
33483
|
-
var
|
|
33958
|
+
var ALLOWED_MOTIONS3, ALLOWED_CRMS3, ALLOWED_ENGAGEMENT3, SYSTEM_PROMPT5;
|
|
33484
33959
|
var init_feedback_apply = __esm({
|
|
33485
33960
|
"src/ai/feedback-apply.ts"() {
|
|
33486
33961
|
"use strict";
|
|
33487
33962
|
init_repl_api();
|
|
33488
33963
|
init_complete();
|
|
33489
|
-
|
|
33490
|
-
|
|
33491
|
-
|
|
33492
|
-
|
|
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.
|
|
33493
33968
|
|
|
33494
33969
|
YOUR JOB:
|
|
33495
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).
|
|
@@ -33974,7 +34449,7 @@ __export(sessions_exports, {
|
|
|
33974
34449
|
handler: () => handler35
|
|
33975
34450
|
});
|
|
33976
34451
|
import chalk62 from "chalk";
|
|
33977
|
-
import { existsSync as
|
|
34452
|
+
import { existsSync as existsSync31 } from "fs";
|
|
33978
34453
|
async function handler35(args, _ctx) {
|
|
33979
34454
|
const sub = args[0] ?? "list";
|
|
33980
34455
|
if (sub === "list" || !args[0]) {
|
|
@@ -34081,12 +34556,12 @@ function showSession(idArg) {
|
|
|
34081
34556
|
console.log();
|
|
34082
34557
|
const transcriptPath = transcriptPathForSession(session.id);
|
|
34083
34558
|
const contextPath = contextDocPathForSession(session.id);
|
|
34084
|
-
if (
|
|
34559
|
+
if (existsSync31(transcriptPath) || existsSync31(contextPath)) {
|
|
34085
34560
|
console.log(" " + chalk62.dim("\u2500".repeat(40)));
|
|
34086
|
-
if (
|
|
34561
|
+
if (existsSync31(contextPath)) {
|
|
34087
34562
|
console.log(" " + chalk62.dim("Context brief: ") + chalk62.dim(contextPath));
|
|
34088
34563
|
}
|
|
34089
|
-
if (
|
|
34564
|
+
if (existsSync31(transcriptPath)) {
|
|
34090
34565
|
console.log(" " + chalk62.dim("Full transcript: ") + chalk62.dim(transcriptPath));
|
|
34091
34566
|
}
|
|
34092
34567
|
console.log();
|
|
@@ -34355,6 +34830,7 @@ var init_privacy_notice = __esm({
|
|
|
34355
34830
|
"Direct identifiers (names, emails, domains, deal names) are replaced with local tokens before any LLM HTTP call.",
|
|
34356
34831
|
"The mapping stays in ~/.ntrp/privacy/ on this machine. The CLI shows real names; the provider never does.",
|
|
34357
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.",
|
|
34358
34834
|
"Computed scores and dollar aggregates still go to the provider you connected.",
|
|
34359
34835
|
"If you turn on web retrieval, named-account queries are refused. Generic GTM terms may go to Tavily or Brave.",
|
|
34360
34836
|
"A custom --base-url receives the same tokenized payload.",
|
|
@@ -35422,20 +35898,20 @@ var init_model = __esm({
|
|
|
35422
35898
|
});
|
|
35423
35899
|
|
|
35424
35900
|
// src/config/update-check.ts
|
|
35425
|
-
import { existsSync as
|
|
35901
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync18, readFileSync as readFileSync21, unlinkSync as unlinkSync5, writeFileSync as writeFileSync20 } from "fs";
|
|
35426
35902
|
import { join as join33 } from "path";
|
|
35427
35903
|
function cachePath2() {
|
|
35428
35904
|
return join33(ntrpHome(), "update-check.json");
|
|
35429
35905
|
}
|
|
35430
35906
|
function ensureDir7() {
|
|
35431
35907
|
const dir = ntrpHome();
|
|
35432
|
-
if (!
|
|
35908
|
+
if (!existsSync32(dir)) {
|
|
35433
35909
|
mkdirSync18(dir, { recursive: true });
|
|
35434
35910
|
}
|
|
35435
35911
|
}
|
|
35436
35912
|
function loadUpdateCheckCache() {
|
|
35437
35913
|
const path = cachePath2();
|
|
35438
|
-
if (!
|
|
35914
|
+
if (!existsSync32(path)) return null;
|
|
35439
35915
|
try {
|
|
35440
35916
|
const parsed = JSON.parse(readFileSync21(path, "utf-8"));
|
|
35441
35917
|
if (!parsed || typeof parsed !== "object" || typeof parsed.lastCheck !== "number" || typeof parsed.latestVersion !== "string") {
|
|
@@ -35456,7 +35932,7 @@ function isCacheFresh(cache2, ttlMs = CACHE_TTL_MS2) {
|
|
|
35456
35932
|
}
|
|
35457
35933
|
function invalidateUpdateCheckCache() {
|
|
35458
35934
|
const path = cachePath2();
|
|
35459
|
-
if (
|
|
35935
|
+
if (existsSync32(path)) {
|
|
35460
35936
|
unlinkSync5(path);
|
|
35461
35937
|
}
|
|
35462
35938
|
}
|
|
@@ -35470,11 +35946,11 @@ var init_update_check = __esm({
|
|
|
35470
35946
|
});
|
|
35471
35947
|
|
|
35472
35948
|
// src/version.ts
|
|
35473
|
-
import { existsSync as
|
|
35949
|
+
import { existsSync as existsSync33, readFileSync as readFileSync22 } from "fs";
|
|
35474
35950
|
import { dirname as dirname6, join as join34 } from "path";
|
|
35475
35951
|
import { fileURLToPath } from "url";
|
|
35476
35952
|
function readVersionFromPackageJson(packageJsonPath) {
|
|
35477
|
-
if (!
|
|
35953
|
+
if (!existsSync33(packageJsonPath)) return null;
|
|
35478
35954
|
try {
|
|
35479
35955
|
const pkg = JSON.parse(readFileSync22(packageJsonPath, "utf-8"));
|
|
35480
35956
|
if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
|
|
@@ -35614,7 +36090,7 @@ __export(relaunch_exports, {
|
|
|
35614
36090
|
resolveRelaunchEntry: () => resolveRelaunchEntry,
|
|
35615
36091
|
updateRestartSummary: () => updateRestartSummary
|
|
35616
36092
|
});
|
|
35617
|
-
import { existsSync as
|
|
36093
|
+
import { existsSync as existsSync34 } from "fs";
|
|
35618
36094
|
import { join as join35 } from "path";
|
|
35619
36095
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
35620
36096
|
import { spawnSync } from "child_process";
|
|
@@ -35640,7 +36116,7 @@ function npmGlobalEntry() {
|
|
|
35640
36116
|
const listed = spawnSync("npm", ["root", "-g"], { encoding: "utf-8" });
|
|
35641
36117
|
if (listed.status !== 0) return null;
|
|
35642
36118
|
const entry = join35(listed.stdout.trim(), NPM_PACKAGE, "dist/index.js");
|
|
35643
|
-
return
|
|
36119
|
+
return existsSync34(entry) ? entry : null;
|
|
35644
36120
|
}
|
|
35645
36121
|
function thisBundleEntry() {
|
|
35646
36122
|
return fileURLToPath2(import.meta.url);
|
|
@@ -35650,10 +36126,10 @@ function resolveRelaunchEntry(toVersion) {
|
|
|
35650
36126
|
(p) => Boolean(p)
|
|
35651
36127
|
);
|
|
35652
36128
|
for (const entry of candidates) {
|
|
35653
|
-
if (!
|
|
36129
|
+
if (!existsSync34(entry)) continue;
|
|
35654
36130
|
if (readVersionNearEntry(entry) === toVersion) return entry;
|
|
35655
36131
|
}
|
|
35656
|
-
return candidates.find((p) =>
|
|
36132
|
+
return candidates.find((p) => existsSync34(p)) ?? thisBundleEntry();
|
|
35657
36133
|
}
|
|
35658
36134
|
function relaunchArgv(toVersion) {
|
|
35659
36135
|
return [resolveRelaunchEntry(toVersion)];
|
|
@@ -36176,7 +36652,7 @@ __export(exports_exports, {
|
|
|
36176
36652
|
handler: () => handler48
|
|
36177
36653
|
});
|
|
36178
36654
|
import chalk78 from "chalk";
|
|
36179
|
-
import { existsSync as
|
|
36655
|
+
import { existsSync as existsSync35 } from "fs";
|
|
36180
36656
|
import { join as join36 } from "path";
|
|
36181
36657
|
function usage4() {
|
|
36182
36658
|
console.log(chalk78.dim(" Usage:"));
|
|
@@ -36335,7 +36811,7 @@ function runMove(args, ctx) {
|
|
|
36335
36811
|
}
|
|
36336
36812
|
try {
|
|
36337
36813
|
const destDir = resolveUserPath(dest);
|
|
36338
|
-
if (!
|
|
36814
|
+
if (!existsSync35(destDir)) {
|
|
36339
36815
|
}
|
|
36340
36816
|
const event = moveExport(idOrName, destDir);
|
|
36341
36817
|
console.log();
|
|
@@ -36707,10 +37183,9 @@ section: Settings
|
|
|
36707
37183
|
handler: ../commands/onboard.ts
|
|
36708
37184
|
---
|
|
36709
37185
|
|
|
36710
|
-
Start the
|
|
36711
|
-
|
|
36712
|
-
|
|
36713
|
-
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.
|
|
36714
37189
|
The profile is stored at \`~/.ntrp/profile.json\`. It flows into findings, NL answers, and demo data.`
|
|
36715
37190
|
},
|
|
36716
37191
|
{
|
|
@@ -37370,7 +37845,7 @@ Remaining nuances merge into a custom_context paragraph that flows into all AI s
|
|
|
37370
37845
|
});
|
|
37371
37846
|
|
|
37372
37847
|
// src/ai/prompt-parts.ts
|
|
37373
|
-
import { existsSync as
|
|
37848
|
+
import { existsSync as existsSync36, readFileSync as readFileSync23 } from "fs";
|
|
37374
37849
|
import { join as join37 } from "path";
|
|
37375
37850
|
function buildCompanyProfileBlock() {
|
|
37376
37851
|
const p = loadProfile();
|
|
@@ -37391,7 +37866,7 @@ function buildCompanyProfileBlock() {
|
|
|
37391
37866
|
function loadAnalystFile() {
|
|
37392
37867
|
const path = join37(ntrpHome(), ANALYST_FILE_NAME);
|
|
37393
37868
|
try {
|
|
37394
|
-
if (!
|
|
37869
|
+
if (!existsSync36(path)) return null;
|
|
37395
37870
|
const raw = sanitizeExternalText(readFileSync23(path, "utf-8").trim());
|
|
37396
37871
|
if (!raw) return null;
|
|
37397
37872
|
if (raw.length <= ANALYST_FILE_MAX_CHARS) return raw;
|
|
@@ -37920,39 +38395,54 @@ var ingest_chat_exports = {};
|
|
|
37920
38395
|
__export(ingest_chat_exports, {
|
|
37921
38396
|
extractFilePath: () => extractFilePath,
|
|
37922
38397
|
ingestFromChat: () => ingestFromChat,
|
|
38398
|
+
ingestPathFromChat: () => ingestPathFromChat,
|
|
37923
38399
|
isDemoIntent: () => isDemoIntent,
|
|
38400
|
+
listCsvsInFolder: () => listCsvsInFolder,
|
|
37924
38401
|
loadDemoFromChat: () => loadDemoFromChat,
|
|
37925
38402
|
looksLikeFilePath: () => looksLikeFilePath
|
|
37926
38403
|
});
|
|
37927
|
-
import { existsSync as
|
|
37928
|
-
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";
|
|
37929
38406
|
import { homedir as homedir8 } from "os";
|
|
37930
38407
|
import chalk80 from "chalk";
|
|
37931
38408
|
function extractFilePath(input) {
|
|
37932
|
-
const trimmed = input.trim();
|
|
38409
|
+
const trimmed = input.trim().replace(/^["']|["']$/g, "");
|
|
38410
|
+
if (!trimmed) return null;
|
|
37933
38411
|
const patterns = [
|
|
37934
|
-
/^["'](
|
|
37935
|
-
/^@(
|
|
37936
|
-
/(?:here'?s|file|path|upload)[:\s]+["']?([^\s"']
|
|
37937
|
-
/^~\/\S
|
|
37938
|
-
/^\.\.?\/\S
|
|
37939
|
-
/^\/\S
|
|
37940
|
-
/^[A-Za-z]:\\[^\s]
|
|
37941
|
-
/^[^\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
|
|
37942
38420
|
];
|
|
37943
38421
|
for (const re of patterns) {
|
|
37944
38422
|
const m = trimmed.match(re);
|
|
37945
|
-
|
|
37946
|
-
|
|
37947
|
-
|
|
37948
|
-
|
|
37949
|
-
if (
|
|
37950
|
-
|
|
37951
|
-
|
|
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
|
+
}
|
|
37952
38433
|
}
|
|
37953
38434
|
}
|
|
37954
38435
|
return null;
|
|
37955
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
|
+
}
|
|
37956
38446
|
function expandPath(p) {
|
|
37957
38447
|
if (p.startsWith("~/")) return resolve9(homedir8(), p.slice(2));
|
|
37958
38448
|
return resolve9(p);
|
|
@@ -37960,12 +38450,79 @@ function expandPath(p) {
|
|
|
37960
38450
|
function looksLikeFilePath(input) {
|
|
37961
38451
|
return extractFilePath(input) !== null;
|
|
37962
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
|
+
}
|
|
37963
38520
|
async function ingestFromChat(ctx, filePath) {
|
|
37964
38521
|
if (!ctx.rl) {
|
|
37965
38522
|
console.log(" " + chalk80.red("Ingest confirm requires interactive mode."));
|
|
37966
38523
|
return false;
|
|
37967
38524
|
}
|
|
37968
|
-
const name =
|
|
38525
|
+
const name = basename10(filePath);
|
|
37969
38526
|
const prompts = createPromptSession(ctx.rl, ctx);
|
|
37970
38527
|
try {
|
|
37971
38528
|
const ok = await prompts.confirm(`Ingest ${name} as CRM export?`, true);
|
|
@@ -38024,6 +38581,7 @@ async function ingestFromChat(ctx, filePath) {
|
|
|
38024
38581
|
};
|
|
38025
38582
|
invalidateGapAudit(ctx);
|
|
38026
38583
|
saveSessionState(ctx);
|
|
38584
|
+
markProductionDataSeen();
|
|
38027
38585
|
console.log();
|
|
38028
38586
|
console.log(" " + paint("accent", "\u2713 Data loaded") + chalk80.dim(` \u2014 ${name}`));
|
|
38029
38587
|
recordMessage(ctx, "user", `[ingested ${name}]`);
|
|
@@ -38099,6 +38657,7 @@ async function loadDemoFromChat(ctx, scenario, opts = {}) {
|
|
|
38099
38657
|
counts,
|
|
38100
38658
|
ingested_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
38101
38659
|
};
|
|
38660
|
+
markDemoDataSeen();
|
|
38102
38661
|
if (!ctx.scope) {
|
|
38103
38662
|
const { proposeScopeFromIntent: proposeScopeFromIntent2 } = await Promise.resolve().then(() => (init_scope(), scope_exports));
|
|
38104
38663
|
const proposal = proposeScopeFromIntent2("demo pipeline and metrics");
|
|
@@ -38135,6 +38694,7 @@ var init_ingest_chat = __esm({
|
|
|
38135
38694
|
init_compute2();
|
|
38136
38695
|
init_theme();
|
|
38137
38696
|
init_demo();
|
|
38697
|
+
init_onboard_tiers();
|
|
38138
38698
|
}
|
|
38139
38699
|
});
|
|
38140
38700
|
|
|
@@ -38482,7 +39042,7 @@ async function runFirstRunFork(ctx, options = {}) {
|
|
|
38482
39042
|
}
|
|
38483
39043
|
function printFirstRunChip() {
|
|
38484
39044
|
console.log(
|
|
38485
|
-
" " + 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).")
|
|
38486
39046
|
);
|
|
38487
39047
|
console.log();
|
|
38488
39048
|
}
|
|
@@ -38564,6 +39124,8 @@ async function loadFirstRunDemo(ctx, scenario) {
|
|
|
38564
39124
|
counts,
|
|
38565
39125
|
ingested_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
38566
39126
|
};
|
|
39127
|
+
const { markDemoDataSeen: markDemoDataSeen2 } = await Promise.resolve().then(() => (init_onboard_tiers(), onboard_tiers_exports));
|
|
39128
|
+
markDemoDataSeen2();
|
|
38567
39129
|
const { saveSessionState: saveSessionState2 } = await Promise.resolve().then(() => (init_context2(), context_exports));
|
|
38568
39130
|
saveSessionState2(ctx);
|
|
38569
39131
|
return true;
|
|
@@ -38927,7 +39489,8 @@ async function conversationRouter(input, ctx) {
|
|
|
38927
39489
|
const phase = resolveConversationPhase(ctx);
|
|
38928
39490
|
if (looksLikeFilePath(line)) {
|
|
38929
39491
|
const path = extractFilePath(line);
|
|
38930
|
-
await
|
|
39492
|
+
const { ingestPathFromChat: ingestPathFromChat2 } = await Promise.resolve().then(() => (init_ingest_chat(), ingest_chat_exports));
|
|
39493
|
+
await ingestPathFromChat2(ctx, path);
|
|
38931
39494
|
return { handled: true, summary: "Data ingested" };
|
|
38932
39495
|
}
|
|
38933
39496
|
if (isDemoIntent(line)) {
|
|
@@ -39853,7 +40416,7 @@ __export(repl_exports, {
|
|
|
39853
40416
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
39854
40417
|
import { clearLine as clearLine2, cursorTo as cursorTo2 } from "readline";
|
|
39855
40418
|
import chalk88 from "chalk";
|
|
39856
|
-
import { join as
|
|
40419
|
+
import { join as join39 } from "path";
|
|
39857
40420
|
function buildPrompt(ctx) {
|
|
39858
40421
|
return buildConversationPrompt(ctx);
|
|
39859
40422
|
}
|
|
@@ -40232,7 +40795,7 @@ function printHelp() {
|
|
|
40232
40795
|
["/remember <fact>", "Store a fact, a decision, or a preference"],
|
|
40233
40796
|
["/recall [topic]", "Show what NTRP stores about your business"],
|
|
40234
40797
|
["/rate good|bad <note>", "Correct the last answer. A bad note becomes a calibration"],
|
|
40235
|
-
[`${
|
|
40798
|
+
[`${join39(ntrpHome(), ANALYST_FILE_NAME)}`, "Standing operator instructions (tone, priorities, house rules)"]
|
|
40236
40799
|
];
|
|
40237
40800
|
const teachMaxW = Math.max(...teach.map(([c]) => c.length)) + 2;
|
|
40238
40801
|
for (const [cmd, desc] of teach) {
|