@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/mcp/server.js
CHANGED
|
@@ -6026,6 +6026,10 @@ function stripTools(req) {
|
|
|
6026
6026
|
return rest;
|
|
6027
6027
|
}
|
|
6028
6028
|
async function outboundRequest(req) {
|
|
6029
|
+
if (req.skipPseudonymize) {
|
|
6030
|
+
const { skipPseudonymize: _drop, ...rest } = req;
|
|
6031
|
+
return rest;
|
|
6032
|
+
}
|
|
6029
6033
|
await ensureLexiconSeeded();
|
|
6030
6034
|
return protectRequest(req);
|
|
6031
6035
|
}
|
|
@@ -6287,13 +6291,14 @@ var init_failover = __esm({
|
|
|
6287
6291
|
});
|
|
6288
6292
|
|
|
6289
6293
|
// src/ai/llm/complete.ts
|
|
6290
|
-
async function llmCompleteText(surface, system, userMessage, max_tokens, ctx) {
|
|
6294
|
+
async function llmCompleteText(surface, system, userMessage, max_tokens, ctx, opts = {}) {
|
|
6291
6295
|
const { response, meta } = await completeWithFailover(
|
|
6292
6296
|
{
|
|
6293
6297
|
surface,
|
|
6294
6298
|
system,
|
|
6295
6299
|
messages: [{ role: "user", content: userMessage }],
|
|
6296
|
-
max_tokens
|
|
6300
|
+
max_tokens,
|
|
6301
|
+
...opts.skipPseudonymize ? { skipPseudonymize: true } : {}
|
|
6297
6302
|
},
|
|
6298
6303
|
{ ctx }
|
|
6299
6304
|
);
|
|
@@ -9975,10 +9980,9 @@ section: Settings
|
|
|
9975
9980
|
handler: ../commands/onboard.ts
|
|
9976
9981
|
---
|
|
9977
9982
|
|
|
9978
|
-
Start the
|
|
9979
|
-
|
|
9980
|
-
|
|
9981
|
-
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.
|
|
9983
|
+
Start the progressive setup ladder. Each \`/onboard\` runs the next incomplete tier:
|
|
9984
|
+
profile (keyless) \u2192 domain research + API key \u2192 sample demo data \u2192 production CSV/folder path.
|
|
9985
|
+
Drag-drop a CSV or folder into the REPL anytime for real data.
|
|
9982
9986
|
The profile is stored at \`~/.ntrp/profile.json\`. It flows into findings, NL answers, and demo data.`
|
|
9983
9987
|
},
|
|
9984
9988
|
{
|
|
@@ -10317,6 +10321,7 @@ CLI slide deck: SaaS refresher, five vital signs (definition, formula, visual, d
|
|
|
10317
10321
|
Bare \`/deepdive\` runs the full onboarding tour. Type \`/deepdive guide\` to jump to how to use NTRP (includes teaching the inbox once).
|
|
10318
10322
|
Type \`/deepdive handoff\` for the ship-to-AI slide. Type \`/deepdive <metric>\` to jump to one metric card.
|
|
10319
10323
|
Type \`/deepdive list\` to print the catalog. Works without an AI key. Re-run anytime from the homescreen.
|
|
10324
|
+
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.
|
|
10320
10325
|
Live values overlay when an analysis exists.`
|
|
10321
10326
|
},
|
|
10322
10327
|
{
|
|
@@ -10331,7 +10336,7 @@ handler: ../commands/thinkwithme.ts
|
|
|
10331
10336
|
|
|
10332
10337
|
Open a dedicated \`think \u203A\` channel to explore ideas, challenge assumptions, and pull evidence from your data.
|
|
10333
10338
|
Bare \`/thinkwithme\` enters the channel. Type \`/thinkwithme <topic>\` to seed the first turn.
|
|
10334
|
-
Requires an analysis and an AI key (\`/connect\`). Type \`done\` or \`
|
|
10339
|
+
Requires an analysis and an AI key (\`/connect\`). Type \`done\`, \`cancel\`, or \`b\` / \`back\` to return to \u203A.
|
|
10335
10340
|
When you are ready to commit to a plan, say so or the partner can hand off to \`/strategy\`.`
|
|
10336
10341
|
},
|
|
10337
10342
|
{
|
|
@@ -13242,10 +13247,12 @@ var init_agent_context = __esm({
|
|
|
13242
13247
|
var scope_exports = {};
|
|
13243
13248
|
__export(scope_exports, {
|
|
13244
13249
|
confirmScope: () => confirmScope,
|
|
13250
|
+
isBackInput: () => isBackInput,
|
|
13245
13251
|
isScopeAdjustInput: () => isScopeAdjustInput,
|
|
13246
13252
|
isScopeConfirmInput: () => isScopeConfirmInput,
|
|
13247
13253
|
isSkipClarifier: () => isSkipClarifier,
|
|
13248
|
-
proposeScopeFromIntent: () => proposeScopeFromIntent
|
|
13254
|
+
proposeScopeFromIntent: () => proposeScopeFromIntent,
|
|
13255
|
+
unconfirmScope: () => unconfirmScope
|
|
13249
13256
|
});
|
|
13250
13257
|
function proposeScopeFromIntent(input) {
|
|
13251
13258
|
const text = input.trim();
|
|
@@ -13290,12 +13297,20 @@ function confirmScope(ctx) {
|
|
|
13290
13297
|
if (ctx.scope.primary_lens === "revenue_metrics" || ctx.scope.primary_lens === "gtm_health") {
|
|
13291
13298
|
}
|
|
13292
13299
|
}
|
|
13300
|
+
function unconfirmScope(ctx) {
|
|
13301
|
+
if (!ctx.scope?.confirmed_at) return;
|
|
13302
|
+
const { confirmed_at: _dropped, ...rest } = ctx.scope;
|
|
13303
|
+
ctx.scope = rest;
|
|
13304
|
+
}
|
|
13293
13305
|
function isScopeConfirmInput(input) {
|
|
13294
13306
|
return /^(yes|y|yeah|yep|correct|looks good|that'?s right|confirm|ok|okay|go)$/i.test(input.trim());
|
|
13295
13307
|
}
|
|
13296
13308
|
function isScopeAdjustInput(input) {
|
|
13297
13309
|
return /^(adjust|change|refine|edit|no|nope)$/i.test(input.trim());
|
|
13298
13310
|
}
|
|
13311
|
+
function isBackInput(input) {
|
|
13312
|
+
return /^(b|back|prev)\s*[.!]?\s*$/i.test(input.trim());
|
|
13313
|
+
}
|
|
13299
13314
|
function isSkipClarifier(input) {
|
|
13300
13315
|
return /^skip$/i.test(input.trim());
|
|
13301
13316
|
}
|
|
@@ -14246,7 +14261,8 @@ function buildConversationPrompt(ctx) {
|
|
|
14246
14261
|
const enterHint2 = action ? chalk8.dim(` \xB7 \u23CE ${action.hint}`) : "";
|
|
14247
14262
|
return accentPromptHead(phase, sessionName) + chalk8.dim(stack) + enterHint2 + " ";
|
|
14248
14263
|
}
|
|
14249
|
-
const
|
|
14264
|
+
const modalBack = phase === "scope" || phase === "awaiting_data" || phase === "strategize" ? " \xB7 b back" : "";
|
|
14265
|
+
const enterHint = action ? chalk8.dim(`\u23CE ${action.hint}${modalBack} `) : "";
|
|
14250
14266
|
return accentPromptHead(phase, sessionName) + enterHint;
|
|
14251
14267
|
}
|
|
14252
14268
|
var PROMPT_LABELS;
|
|
@@ -18837,7 +18853,7 @@ async function handleStrategizeFlow(input, ctx) {
|
|
|
18837
18853
|
" " + chalk17.dim("That looks like a question. A strategy objective is waiting.")
|
|
18838
18854
|
);
|
|
18839
18855
|
console.log(
|
|
18840
|
-
" " + chalk17.dim("Type ") + chalk17.cyan("yes") + chalk17.dim(" to make the plan. Type ") + chalk17.cyan("adjust") + chalk17.dim(" to change the objective. Type ") + chalk17.cyan("cancel") + chalk17.dim(" to answer questions first.")
|
|
18856
|
+
" " + chalk17.dim("Type ") + chalk17.cyan("yes") + chalk17.dim(" to make the plan. Type ") + chalk17.cyan("b") + chalk17.dim(" / ") + chalk17.cyan("adjust") + chalk17.dim(" to change the objective. Type ") + chalk17.cyan("cancel") + chalk17.dim(" to answer questions first.")
|
|
18841
18857
|
);
|
|
18842
18858
|
console.log();
|
|
18843
18859
|
return "Awaiting confirm";
|
|
@@ -18850,7 +18866,7 @@ async function handleStrategizeFlow(input, ctx) {
|
|
|
18850
18866
|
}
|
|
18851
18867
|
console.log();
|
|
18852
18868
|
console.log(
|
|
18853
|
-
" " + chalk17.dim("Type ") + chalk17.cyan("yes") + chalk17.dim(" to make the plan. Type ") + chalk17.cyan("adjust") + chalk17.dim(" to change the objective. Type ") + chalk17.cyan("cancel") + chalk17.dim(" to stop.")
|
|
18869
|
+
" " + chalk17.dim("Type ") + chalk17.cyan("yes") + chalk17.dim(" to make the plan. Type ") + chalk17.cyan("b") + chalk17.dim(" / ") + chalk17.cyan("adjust") + chalk17.dim(" to change the objective. Type ") + chalk17.cyan("cancel") + chalk17.dim(" to stop.")
|
|
18854
18870
|
);
|
|
18855
18871
|
console.log();
|
|
18856
18872
|
return "Awaiting confirm";
|
|
@@ -19027,7 +19043,7 @@ function printObjectiveCard(ctx, objective, proposed) {
|
|
|
19027
19043
|
);
|
|
19028
19044
|
console.log();
|
|
19029
19045
|
console.log(
|
|
19030
|
-
" " + chalk17.dim("Confirm? ") + chalk17.cyan("\u23CE yes") + chalk17.dim(" \xB7 ") + chalk17.cyan("adjust") + chalk17.dim(" \xB7 ") + chalk17.cyan("cancel")
|
|
19046
|
+
" " + chalk17.dim("Confirm? ") + chalk17.cyan("\u23CE yes") + chalk17.dim(" \xB7 ") + chalk17.cyan("b back") + chalk17.dim(" \xB7 ") + chalk17.cyan("adjust") + chalk17.dim(" \xB7 ") + chalk17.cyan("cancel")
|
|
19031
19047
|
);
|
|
19032
19048
|
console.log();
|
|
19033
19049
|
}
|
|
@@ -19170,7 +19186,7 @@ function buildSituationalAwarenessBlock(ctx, opts = {}) {
|
|
|
19170
19186
|
`Phase: ${phase} (${formatPhaseLabel(phase)}) \u2014 ${PHASE_MEANING[phase]}`
|
|
19171
19187
|
];
|
|
19172
19188
|
if (channel === "think") {
|
|
19173
|
-
lines.push("Channel: think \u2014 socratic partner; type done/cancel returns to \u203A");
|
|
19189
|
+
lines.push("Channel: think \u2014 socratic partner; type done/cancel/b back returns to \u203A");
|
|
19174
19190
|
lines.push("Response mode: deep (tools on)");
|
|
19175
19191
|
} else if (channel === "strategist") {
|
|
19176
19192
|
lines.push("Channel: strategist \u2014 measurable plan engine owns sequencing");
|
|
@@ -19214,11 +19230,13 @@ function buildSituationalAwarenessBlock(ctx, opts = {}) {
|
|
|
19214
19230
|
} else if (phase === "awaiting_data") {
|
|
19215
19231
|
lines.push("Already done: scope confirmed; analysis not ready");
|
|
19216
19232
|
lines.push(
|
|
19217
|
-
'Available now: tell the user to paste a CSV path, type "use demo data",
|
|
19233
|
+
'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'
|
|
19218
19234
|
);
|
|
19219
19235
|
} else if (phase === "scope") {
|
|
19220
19236
|
lines.push("Already done: intent proposed; waiting on confirm");
|
|
19221
|
-
lines.push(
|
|
19237
|
+
lines.push(
|
|
19238
|
+
"Available now: wait for CLI confirm (\u23CE yes) or b/back/adjust \u2014 do not invent a different question"
|
|
19239
|
+
);
|
|
19222
19240
|
} else if (phase === "orient") {
|
|
19223
19241
|
lines.push("Already done: nothing locked yet");
|
|
19224
19242
|
lines.push("Available now: help the user name a focus; CLI will propose scope from their words");
|
|
@@ -19291,13 +19309,13 @@ var init_situation = __esm({
|
|
|
19291
19309
|
FUNNEL_SPINE = "Funnel: orient \u2192 scope \u2192 data \u2192 compute \u2192 explore | side doors: think, strategy, ship";
|
|
19292
19310
|
PHASE_MEANING = {
|
|
19293
19311
|
orient: "setup \u2014 open chat; user has not locked a scope yet",
|
|
19294
|
-
scope: "scope confirm \u2014 CLI owns yes/adjust; do not re-propose unless asked",
|
|
19295
|
-
awaiting_data: "data gate \u2014 load CSV/demo or compute; CLI owns the gate",
|
|
19312
|
+
scope: "scope confirm \u2014 CLI owns yes / b back / adjust; do not re-propose unless asked",
|
|
19313
|
+
awaiting_data: "data gate \u2014 load CSV/demo or compute; b/back rewinds to scope confirm (does not unload data); CLI owns the gate",
|
|
19296
19314
|
compute: "compute in progress \u2014 wait",
|
|
19297
19315
|
explore: "analysis complete \u2014 free-form Q&A on this session's artifact",
|
|
19298
|
-
think: "socratic think channel \u2014 return to \u203A via done/cancel",
|
|
19299
|
-
strategize: "strategist flow \u2014 objective confirm or plan drafting",
|
|
19300
|
-
deliver: "ship/handoff wizard \u2014 CLI owns write confirms"
|
|
19316
|
+
think: "socratic think channel \u2014 return to \u203A via done/cancel/b back",
|
|
19317
|
+
strategize: "strategist flow \u2014 objective confirm (yes / b back / adjust) or plan drafting",
|
|
19318
|
+
deliver: "ship/handoff wizard \u2014 CLI owns write confirms; b/back or cancel leaves the draft"
|
|
19301
19319
|
};
|
|
19302
19320
|
}
|
|
19303
19321
|
});
|
|
@@ -20824,724 +20842,985 @@ var init_demo = __esm({
|
|
|
20824
20842
|
}
|
|
20825
20843
|
});
|
|
20826
20844
|
|
|
20827
|
-
// src/
|
|
20828
|
-
var
|
|
20829
|
-
__export(
|
|
20830
|
-
|
|
20831
|
-
|
|
20845
|
+
// src/demo/scenarios.ts
|
|
20846
|
+
var scenarios_exports = {};
|
|
20847
|
+
__export(scenarios_exports, {
|
|
20848
|
+
NAMED_DEMO_SCENARIOS: () => NAMED_DEMO_SCENARIOS,
|
|
20849
|
+
SCENARIOS: () => SCENARIOS,
|
|
20850
|
+
SCENARIO_LIST: () => SCENARIO_LIST,
|
|
20851
|
+
blendScenarios: () => blendScenarios,
|
|
20852
|
+
getScenario: () => getScenario,
|
|
20853
|
+
isNamedDemoScenario: () => isNamedDemoScenario,
|
|
20854
|
+
pickRandomScenario: () => pickRandomScenario,
|
|
20855
|
+
resolveScenarioInput: () => resolveScenarioInput
|
|
20832
20856
|
});
|
|
20833
|
-
|
|
20834
|
-
|
|
20835
|
-
|
|
20836
|
-
|
|
20837
|
-
|
|
20838
|
-
|
|
20839
|
-
});
|
|
20840
|
-
return {
|
|
20841
|
-
headers: result.meta.fields ?? [],
|
|
20842
|
-
rows: result.data,
|
|
20843
|
-
rowCount: result.data.length
|
|
20844
|
-
};
|
|
20857
|
+
function getScenario(key) {
|
|
20858
|
+
const scenario = SCENARIOS[key];
|
|
20859
|
+
if (!scenario) {
|
|
20860
|
+
throw new Error(`Unknown scenario: ${key}. Valid: ${Object.keys(SCENARIOS).join(", ")}`);
|
|
20861
|
+
}
|
|
20862
|
+
return scenario;
|
|
20845
20863
|
}
|
|
20846
|
-
function
|
|
20847
|
-
return
|
|
20864
|
+
function isNamedDemoScenario(raw) {
|
|
20865
|
+
return NAMED_DEMO_SCENARIOS.includes(raw);
|
|
20848
20866
|
}
|
|
20849
|
-
|
|
20850
|
-
|
|
20851
|
-
|
|
20867
|
+
function resolveScenarioInput(raw) {
|
|
20868
|
+
const input = raw?.trim();
|
|
20869
|
+
if (!input) return void 0;
|
|
20870
|
+
if (input === "research_blend") return "research_blend";
|
|
20871
|
+
if (NAMED_DEMO_SCENARIOS.includes(input)) {
|
|
20872
|
+
return input;
|
|
20852
20873
|
}
|
|
20853
|
-
|
|
20854
|
-
|
|
20855
|
-
|
|
20856
|
-
|
|
20857
|
-
return
|
|
20874
|
+
const n = Number(input);
|
|
20875
|
+
if (Number.isInteger(n) && n >= 1 && n <= NAMED_DEMO_SCENARIOS.length) {
|
|
20876
|
+
return NAMED_DEMO_SCENARIOS[n - 1];
|
|
20877
|
+
}
|
|
20878
|
+
return null;
|
|
20858
20879
|
}
|
|
20859
|
-
|
|
20860
|
-
|
|
20861
|
-
|
|
20880
|
+
function pickRandomScenario() {
|
|
20881
|
+
return RANDOM_POOL[Math.floor(Math.random() * RANDOM_POOL.length)];
|
|
20882
|
+
}
|
|
20883
|
+
function blendScenarios(_research) {
|
|
20884
|
+
const blended = {
|
|
20885
|
+
...BASELINE,
|
|
20886
|
+
key: "research_blend",
|
|
20887
|
+
label: "Research-Derived Blend",
|
|
20888
|
+
description: "Realistic data with mild-to-moderate problems across all vital signs.",
|
|
20889
|
+
story: "Generated with default research blend. Problems are seeded across all vital signs at realistic levels.",
|
|
20890
|
+
hook: "Mild-to-moderate problems seeded across all five vitals.",
|
|
20891
|
+
// Bump all problems slightly above baseline for discoverability
|
|
20892
|
+
staleContactRatio: 0.2,
|
|
20893
|
+
pastCloseDateRatio: 0.15,
|
|
20894
|
+
mqlDropRatio: 0.15,
|
|
20895
|
+
qualifiedNoOutreachRatio: 0.15,
|
|
20896
|
+
stuckDealRatio: 0.15,
|
|
20897
|
+
noiseActivityRatio: 0.2,
|
|
20898
|
+
singleThreadRatio: 0.25
|
|
20899
|
+
};
|
|
20900
|
+
return blended;
|
|
20901
|
+
}
|
|
20902
|
+
var BASELINE, SCENARIOS, SCENARIO_LIST, NAMED_DEMO_SCENARIOS, RANDOM_POOL;
|
|
20903
|
+
var init_scenarios = __esm({
|
|
20904
|
+
"src/demo/scenarios.ts"() {
|
|
20862
20905
|
"use strict";
|
|
20863
|
-
|
|
20864
|
-
|
|
20865
|
-
|
|
20866
|
-
|
|
20867
|
-
|
|
20868
|
-
|
|
20906
|
+
BASELINE = {
|
|
20907
|
+
enterpriseRatio: 0.2,
|
|
20908
|
+
midMarketRatio: 0.3,
|
|
20909
|
+
smbRatio: 0.5,
|
|
20910
|
+
staleContactRatio: 0.15,
|
|
20911
|
+
staleContactRatioEnterprise: 0.2,
|
|
20912
|
+
staleContactRatioSmb: 0.1,
|
|
20913
|
+
pastCloseDateRatio: 0.1,
|
|
20914
|
+
staleDays: 120,
|
|
20915
|
+
mqlDropRatio: 0.1,
|
|
20916
|
+
qualifiedNoOutreachRatio: 0.1,
|
|
20917
|
+
stuckDealRatio: 0.1,
|
|
20918
|
+
stuckInNegotiationDays: 45,
|
|
20919
|
+
avgDaysPerStageEnterprise: 25,
|
|
20920
|
+
avgDaysPerStageSmb: 8,
|
|
20921
|
+
activityVolumeMultiplier: 1,
|
|
20922
|
+
noiseActivityRatio: 0.15,
|
|
20923
|
+
singleThreadRatio: 0.2,
|
|
20924
|
+
loneWolfRepIndex: null,
|
|
20925
|
+
loneWolfSingleThreadRatio: 0,
|
|
20926
|
+
freshnessGapDays: 90
|
|
20927
|
+
};
|
|
20928
|
+
SCENARIOS = {
|
|
20929
|
+
hidden_crisis: {
|
|
20930
|
+
...BASELINE,
|
|
20931
|
+
key: "hidden_crisis",
|
|
20932
|
+
label: "The Hidden Crisis",
|
|
20933
|
+
description: "Overall health looks yellow but Enterprise is deep red, masked by strong SMB numbers.",
|
|
20934
|
+
story: "Your aggregate numbers look okay \u2014 but when you break it by segment, Enterprise is dying. 60% of enterprise contacts have gone dark, deals are single-threaded, and SMB is carrying the average.",
|
|
20935
|
+
hook: "SMB is carrying the average while Enterprise dies quietly.",
|
|
20936
|
+
staleContactRatio: 0.3,
|
|
20937
|
+
staleContactRatioEnterprise: 0.6,
|
|
20938
|
+
staleContactRatioSmb: 0.1,
|
|
20939
|
+
singleThreadRatio: 0.5,
|
|
20940
|
+
enterpriseRatio: 0.3,
|
|
20941
|
+
midMarketRatio: 0.3,
|
|
20942
|
+
smbRatio: 0.4
|
|
20869
20943
|
},
|
|
20870
|
-
|
|
20871
|
-
|
|
20872
|
-
|
|
20873
|
-
|
|
20944
|
+
leaky_bucket: {
|
|
20945
|
+
...BASELINE,
|
|
20946
|
+
key: "leaky_bucket",
|
|
20947
|
+
label: "The Leaky Bucket",
|
|
20948
|
+
description: "Marketing generates plenty of leads but 40% vanish at handoff to sales.",
|
|
20949
|
+
story: "Marketing is doing its job \u2014 MQLs are flowing. But 40% of qualified leads never show up in sales workflows. They're falling through the cracks at handoff, and nobody's noticing because marketing reports MQL count and sales reports pipeline value.",
|
|
20950
|
+
hook: "MQLs flow in, then 40% vanish at the sales handoff.",
|
|
20951
|
+
mqlDropRatio: 0.4,
|
|
20952
|
+
qualifiedNoOutreachRatio: 0.35,
|
|
20953
|
+
staleContactRatio: 0.2
|
|
20874
20954
|
},
|
|
20875
|
-
|
|
20876
|
-
|
|
20877
|
-
|
|
20955
|
+
stale_pipeline: {
|
|
20956
|
+
...BASELINE,
|
|
20957
|
+
key: "stale_pipeline",
|
|
20958
|
+
label: "The Stale Pipeline",
|
|
20959
|
+
description: "Big pipeline number but half the deals are zombies stuck in late stages.",
|
|
20960
|
+
story: "The pipeline report says $5M. But look closer: half those deals have close dates in the past, 40% are stuck in Negotiation for 120+ days, and nobody's touching them. You're forecasting on fiction.",
|
|
20961
|
+
hook: "Half the pipeline is zombies \u2014 you're forecasting on fiction.",
|
|
20962
|
+
pastCloseDateRatio: 0.5,
|
|
20963
|
+
stuckDealRatio: 0.4,
|
|
20964
|
+
stuckInNegotiationDays: 120,
|
|
20965
|
+
staleContactRatio: 0.25,
|
|
20966
|
+
staleDays: 90
|
|
20967
|
+
},
|
|
20968
|
+
lone_wolf: {
|
|
20969
|
+
...BASELINE,
|
|
20970
|
+
key: "lone_wolf",
|
|
20971
|
+
label: "The Lone Wolf",
|
|
20972
|
+
description: "One rep has great numbers but every single deal is single-threaded.",
|
|
20973
|
+
story: "Your top rep is crushing it on paper \u2014 biggest pipeline, highest close rate. But every deal has exactly one contact. One champion goes on vacation, gets promoted, or leaves, and the entire pipeline collapses.",
|
|
20974
|
+
hook: "Top rep, huge pipeline, one contact per deal \u2014 one exit from collapse.",
|
|
20975
|
+
loneWolfRepIndex: 0,
|
|
20976
|
+
loneWolfSingleThreadRatio: 1,
|
|
20977
|
+
singleThreadRatio: 0.15
|
|
20978
|
+
},
|
|
20979
|
+
busy_bees: {
|
|
20980
|
+
...BASELINE,
|
|
20981
|
+
key: "busy_bees",
|
|
20982
|
+
label: "The Busy Bees",
|
|
20983
|
+
description: "High activity volume across the team, but most of it hits dead ends.",
|
|
20984
|
+
story: "Your team is busy. Activity metrics look great \u2014 calls are up, emails are up, meetings are up. But 60% of that activity is aimed at contacts with no associated pipeline. Reps are spraying, not aiming.",
|
|
20985
|
+
hook: "Reps are spraying, not aiming.",
|
|
20986
|
+
activityVolumeMultiplier: 3,
|
|
20987
|
+
noiseActivityRatio: 0.6,
|
|
20988
|
+
staleContactRatio: 0.2
|
|
20989
|
+
},
|
|
20990
|
+
even_keel: {
|
|
20991
|
+
...BASELINE,
|
|
20992
|
+
key: "even_keel",
|
|
20993
|
+
label: "The Even Keel",
|
|
20994
|
+
description: "A reasonably healthy book \u2014 enough yellow to listen, not a five-alarm fire.",
|
|
20995
|
+
story: "Most numbers sit in a normal band. A few contacts have gone quiet, a handful of deals are slow, activity is mostly on-pipeline. This is what 'fine' looks like on the stethoscope \u2014 useful when you want to evaluate NTRP without a manufactured crisis.",
|
|
20996
|
+
hook: "Reasonably healthy \u2014 enough signal to listen, not a crisis."
|
|
20997
|
+
},
|
|
20998
|
+
compound_pain: {
|
|
20999
|
+
...BASELINE,
|
|
21000
|
+
key: "compound_pain",
|
|
21001
|
+
label: "The Compound Fracture",
|
|
21002
|
+
description: "Several vitals are red at once \u2014 stale pipeline, leaky handoff, noisy activity, thin threads.",
|
|
21003
|
+
story: "This isn't one problem. Enterprise contacts have gone dark, MQLs vanish at handoff, late-stage deals are zombies, and a lot of activity never touches pipeline. The gating logic has to pick a first red \u2014 that's the point of this book.",
|
|
21004
|
+
hook: "Several vitals red at once \u2014 the stethoscope has to pick a first listen.",
|
|
21005
|
+
enterpriseRatio: 0.3,
|
|
21006
|
+
midMarketRatio: 0.3,
|
|
21007
|
+
smbRatio: 0.4,
|
|
21008
|
+
staleContactRatio: 0.35,
|
|
21009
|
+
staleContactRatioEnterprise: 0.55,
|
|
21010
|
+
staleContactRatioSmb: 0.15,
|
|
21011
|
+
pastCloseDateRatio: 0.35,
|
|
21012
|
+
staleDays: 100,
|
|
21013
|
+
mqlDropRatio: 0.3,
|
|
21014
|
+
qualifiedNoOutreachRatio: 0.25,
|
|
21015
|
+
stuckDealRatio: 0.3,
|
|
21016
|
+
stuckInNegotiationDays: 90,
|
|
21017
|
+
activityVolumeMultiplier: 2,
|
|
21018
|
+
noiseActivityRatio: 0.4,
|
|
21019
|
+
singleThreadRatio: 0.4
|
|
20878
21020
|
}
|
|
20879
21021
|
};
|
|
20880
|
-
|
|
20881
|
-
|
|
20882
|
-
|
|
20883
|
-
|
|
20884
|
-
|
|
20885
|
-
|
|
20886
|
-
|
|
20887
|
-
|
|
20888
|
-
|
|
20889
|
-
|
|
20890
|
-
|
|
20891
|
-
call: "call",
|
|
20892
|
-
email: "email",
|
|
20893
|
-
sequence_step: "email"
|
|
20894
|
-
};
|
|
20895
|
-
CAMPAIGN_TYPE_MAP = {
|
|
20896
|
-
outbound: "email_sequence",
|
|
20897
|
-
inbound: "email_sequence",
|
|
20898
|
-
trigger: "email_sequence"
|
|
20899
|
-
};
|
|
21022
|
+
SCENARIO_LIST = Object.values(SCENARIOS);
|
|
21023
|
+
NAMED_DEMO_SCENARIOS = [
|
|
21024
|
+
"hidden_crisis",
|
|
21025
|
+
"leaky_bucket",
|
|
21026
|
+
"stale_pipeline",
|
|
21027
|
+
"lone_wolf",
|
|
21028
|
+
"busy_bees",
|
|
21029
|
+
"even_keel",
|
|
21030
|
+
"compound_pain"
|
|
21031
|
+
];
|
|
21032
|
+
RANDOM_POOL = [...NAMED_DEMO_SCENARIOS];
|
|
20900
21033
|
}
|
|
20901
21034
|
});
|
|
20902
21035
|
|
|
20903
|
-
// src/
|
|
20904
|
-
|
|
20905
|
-
|
|
20906
|
-
|
|
20907
|
-
|
|
20908
|
-
|
|
20909
|
-
|
|
20910
|
-
|
|
20911
|
-
|
|
20912
|
-
|
|
20913
|
-
|
|
20914
|
-
|
|
20915
|
-
|
|
20916
|
-
|
|
20917
|
-
|
|
20918
|
-
|
|
20919
|
-
|
|
20920
|
-
|
|
20921
|
-
|
|
20922
|
-
|
|
20923
|
-
|
|
20924
|
-
if (
|
|
20925
|
-
|
|
20926
|
-
|
|
20927
|
-
|
|
20928
|
-
|
|
20929
|
-
|
|
20930
|
-
|
|
20931
|
-
|
|
21036
|
+
// src/demo/scenario-fit.ts
|
|
21037
|
+
function dealBandFromCycleDays(days) {
|
|
21038
|
+
if (days == null || !Number.isFinite(days) || days <= 0) return void 0;
|
|
21039
|
+
if (days <= 21) return "velocity";
|
|
21040
|
+
if (days <= 45) return "core";
|
|
21041
|
+
if (days <= 90) return "mid";
|
|
21042
|
+
return "enterprise";
|
|
21043
|
+
}
|
|
21044
|
+
function dealBandFromAverageDealSize(raw) {
|
|
21045
|
+
if (!raw) return void 0;
|
|
21046
|
+
const t = raw.trim().toLowerCase();
|
|
21047
|
+
if (!t) return void 0;
|
|
21048
|
+
const match = t.match(/(\d+(?:\.\d+)?)\s*(k|m|million|thousand)?/i);
|
|
21049
|
+
if (!match) return void 0;
|
|
21050
|
+
let n = Number(match[1]);
|
|
21051
|
+
if (!Number.isFinite(n)) return void 0;
|
|
21052
|
+
const unit = (match[2] ?? "").toLowerCase();
|
|
21053
|
+
if (unit === "k" || unit === "thousand") n *= 1e3;
|
|
21054
|
+
else if (unit === "m" || unit === "million") n *= 1e6;
|
|
21055
|
+
else if (n > 0 && n < 500) n *= 1e3;
|
|
21056
|
+
if (n < 15e3) return "velocity";
|
|
21057
|
+
if (n < 5e4) return "core";
|
|
21058
|
+
if (n < 15e4) return "mid";
|
|
21059
|
+
return "enterprise";
|
|
21060
|
+
}
|
|
21061
|
+
function signalsFromProfile(profile) {
|
|
21062
|
+
if (!profile) return {};
|
|
21063
|
+
const text = [
|
|
21064
|
+
profile.industry,
|
|
21065
|
+
profile.product_description,
|
|
21066
|
+
profile.target_customer,
|
|
21067
|
+
profile.user_scope,
|
|
21068
|
+
profile.custom_context
|
|
21069
|
+
].filter(Boolean).join("\n");
|
|
20932
21070
|
return {
|
|
20933
|
-
|
|
20934
|
-
|
|
20935
|
-
|
|
21071
|
+
salesMotion: profile.sales_motion,
|
|
21072
|
+
dealBand: dealBandFromAverageDealSize(profile.average_deal_size) ?? dealBandFromCycleDays(profile.sales_cycle_days),
|
|
21073
|
+
cycleDays: profile.sales_cycle_days,
|
|
21074
|
+
text
|
|
20936
21075
|
};
|
|
20937
21076
|
}
|
|
20938
|
-
|
|
20939
|
-
|
|
20940
|
-
|
|
20941
|
-
|
|
20942
|
-
|
|
20943
|
-
|
|
20944
|
-
|
|
20945
|
-
|
|
20946
|
-
|
|
20947
|
-
{ entityType: "opportunities", required: ["StageName", "Amount", "CloseDate"], optional: ["AccountId", "OwnerId", "Probability"], weight: 1 },
|
|
20948
|
-
{ entityType: "activities", required: ["Subject", "ActivityDate", "TaskSubtype"], optional: ["WhoId", "WhatId", "Status"], weight: 1 }
|
|
20949
|
-
],
|
|
20950
|
-
hubspot: [
|
|
20951
|
-
{ entityType: "organizations", required: ["Company name", "Company Domain Name"], optional: ["Company ID", "Industry", "Annual Revenue"], weight: 1 },
|
|
20952
|
-
{ entityType: "people", required: ["First Name", "Last Name", "Email"], optional: ["Contact ID", "Associated Company ID", "Lifecycle Stage"], weight: 1 },
|
|
20953
|
-
{ entityType: "activities", required: ["Engagement ID", "Type"], optional: ["Timestamp", "Created At", "Contact ID"], weight: 1 }
|
|
20954
|
-
],
|
|
20955
|
-
outreach: [
|
|
20956
|
-
{ entityType: "campaigns", required: ["sequence_type", "step_count"], optional: ["name", "id", "enabled"], weight: 1 },
|
|
20957
|
-
{ entityType: "activities", required: ["prospect_id", "prospect_email"], optional: ["type", "created_at", "subject"], weight: 1 }
|
|
20958
|
-
]
|
|
20959
|
-
};
|
|
20960
|
-
}
|
|
20961
|
-
});
|
|
20962
|
-
|
|
20963
|
-
// src/pipeline/importer.ts
|
|
20964
|
-
async function importRows(params) {
|
|
20965
|
-
let result;
|
|
20966
|
-
switch (params.entityType) {
|
|
20967
|
-
case "organizations":
|
|
20968
|
-
result = await importOrganizations(params);
|
|
20969
|
-
break;
|
|
20970
|
-
case "people":
|
|
20971
|
-
result = await importPeople(params);
|
|
20972
|
-
break;
|
|
20973
|
-
case "opportunities":
|
|
20974
|
-
result = await importOpportunities(params);
|
|
20975
|
-
break;
|
|
20976
|
-
case "activities":
|
|
20977
|
-
result = await importActivities(params);
|
|
20978
|
-
break;
|
|
20979
|
-
case "campaigns":
|
|
20980
|
-
result = await importCampaigns(params);
|
|
20981
|
-
break;
|
|
20982
|
-
default:
|
|
20983
|
-
result = { imported: 0, errors: [`Unknown entity type: ${params.entityType}`] };
|
|
20984
|
-
}
|
|
20985
|
-
const { invalidateLexiconSeed: invalidateLexiconSeed2 } = await Promise.resolve().then(() => (init_lexicon_seed(), lexicon_seed_exports));
|
|
20986
|
-
invalidateLexiconSeed2();
|
|
20987
|
-
return result;
|
|
20988
|
-
}
|
|
20989
|
-
function reverseMap(mappings) {
|
|
20990
|
-
const reversed = {};
|
|
20991
|
-
for (const [csvCol, ontologyField] of Object.entries(mappings)) {
|
|
20992
|
-
reversed[ontologyField] = csvCol;
|
|
21077
|
+
function keywordHits(text) {
|
|
21078
|
+
const hits = {};
|
|
21079
|
+
const hay = text.toLowerCase();
|
|
21080
|
+
for (const { scenario, patterns } of KEYWORD_HINTS) {
|
|
21081
|
+
let n = 0;
|
|
21082
|
+
for (const re of patterns) {
|
|
21083
|
+
if (re.test(hay)) n++;
|
|
21084
|
+
}
|
|
21085
|
+
if (n > 0) hits[scenario] = n;
|
|
20993
21086
|
}
|
|
20994
|
-
return
|
|
21087
|
+
return hits;
|
|
20995
21088
|
}
|
|
20996
|
-
function
|
|
20997
|
-
|
|
20998
|
-
if (
|
|
20999
|
-
|
|
21000
|
-
return
|
|
21089
|
+
function matrixPick(motion, band) {
|
|
21090
|
+
if (motion && band) return MATRIX[motion][band];
|
|
21091
|
+
if (motion) return MOTION_ONLY[motion];
|
|
21092
|
+
if (band) return BAND_ONLY[band];
|
|
21093
|
+
return "even_keel";
|
|
21001
21094
|
}
|
|
21002
|
-
function
|
|
21003
|
-
const
|
|
21004
|
-
|
|
21005
|
-
|
|
21006
|
-
if (!mappedCols.has(key) && value !== void 0 && value !== "") {
|
|
21007
|
-
metadata[key] = value;
|
|
21008
|
-
}
|
|
21095
|
+
function reasonFor(scenario, signals, via) {
|
|
21096
|
+
const s = getScenario(scenario);
|
|
21097
|
+
if (via === "keywords") {
|
|
21098
|
+
return `Your notes sound like ${s.label} \u2014 ${s.hook}`;
|
|
21009
21099
|
}
|
|
21010
|
-
|
|
21100
|
+
const motion = signals.salesMotion;
|
|
21101
|
+
const band = signals.dealBand;
|
|
21102
|
+
if (motion && band) {
|
|
21103
|
+
return `${labelMotion(motion)} with ${labelBand(band)} deals maps to ${s.label}.`;
|
|
21104
|
+
}
|
|
21105
|
+
if (motion) return `${labelMotion(motion)} books usually show up as ${s.label}.`;
|
|
21106
|
+
if (band) return `${labelBand(band)} deals usually show up as ${s.label}.`;
|
|
21107
|
+
return `${s.label} is the even-keeled starting book when we don't know the motion yet.`;
|
|
21011
21108
|
}
|
|
21012
|
-
function
|
|
21013
|
-
return
|
|
21109
|
+
function labelMotion(m) {
|
|
21110
|
+
return MOTION_FIT_CHOICES.find((c) => c.value === m)?.label ?? m;
|
|
21014
21111
|
}
|
|
21015
|
-
function
|
|
21016
|
-
|
|
21017
|
-
const num2 = Number(value);
|
|
21018
|
-
if (!isNaN(num2) && num2 > 1e12) return new Date(num2).toISOString();
|
|
21019
|
-
if (!isNaN(num2) && num2 > 1e9) return new Date(num2 * 1e3).toISOString();
|
|
21020
|
-
const d = new Date(value);
|
|
21021
|
-
if (!isNaN(d.getTime())) return d.toISOString();
|
|
21022
|
-
return null;
|
|
21112
|
+
function labelBand(b) {
|
|
21113
|
+
return DEAL_BAND_CHOICES.find((c) => c.value === b)?.label ?? b;
|
|
21023
21114
|
}
|
|
21024
|
-
|
|
21025
|
-
const
|
|
21026
|
-
if (
|
|
21027
|
-
|
|
21028
|
-
|
|
21029
|
-
|
|
21030
|
-
|
|
21031
|
-
|
|
21032
|
-
|
|
21033
|
-
|
|
21034
|
-
|
|
21115
|
+
function inferDemoScenario(signals) {
|
|
21116
|
+
const text = signals.text?.trim() ?? "";
|
|
21117
|
+
if (text) {
|
|
21118
|
+
const hits = keywordHits(text);
|
|
21119
|
+
let best;
|
|
21120
|
+
let bestN = 0;
|
|
21121
|
+
for (const id of NAMED_DEMO_SCENARIOS) {
|
|
21122
|
+
const n = hits[id] ?? 0;
|
|
21123
|
+
if (n > bestN) {
|
|
21124
|
+
best = id;
|
|
21125
|
+
bestN = n;
|
|
21126
|
+
}
|
|
21127
|
+
}
|
|
21128
|
+
if (best && bestN > 0) {
|
|
21129
|
+
return { scenario: best, reason: reasonFor(best, signals, "keywords"), source: "heuristic" };
|
|
21130
|
+
}
|
|
21035
21131
|
}
|
|
21036
|
-
|
|
21132
|
+
const scenario = matrixPick(signals.salesMotion, signals.dealBand);
|
|
21133
|
+
return { scenario, reason: reasonFor(scenario, signals, "matrix"), source: "heuristic" };
|
|
21037
21134
|
}
|
|
21038
|
-
|
|
21039
|
-
const
|
|
21040
|
-
|
|
21041
|
-
|
|
21042
|
-
|
|
21043
|
-
|
|
21044
|
-
|
|
21045
|
-
|
|
21046
|
-
|
|
21047
|
-
|
|
21135
|
+
function getPreferredDemoScenario() {
|
|
21136
|
+
const raw = getConfigValue(PREF_SCENARIO_KEY);
|
|
21137
|
+
return raw && isNamedDemoScenario(raw) ? raw : void 0;
|
|
21138
|
+
}
|
|
21139
|
+
function saveDemoFit(opts) {
|
|
21140
|
+
setConfigValue(PREF_SCENARIO_KEY, opts.scenario);
|
|
21141
|
+
if (opts.motion) {
|
|
21142
|
+
setConfigValue(PREF_MOTION_KEY, opts.motion);
|
|
21143
|
+
if (opts.syncSalesMotion && !isProfileConfigured(loadProfile())) {
|
|
21144
|
+
setConfigValue("sales-motion", opts.motion);
|
|
21048
21145
|
}
|
|
21049
|
-
const website = getMapped(row, fieldMap, "canonical_domain") ?? "";
|
|
21050
|
-
const canonicalDomain = stripUrl(website);
|
|
21051
|
-
await insertOrganization({
|
|
21052
|
-
canonical_name: canonicalName,
|
|
21053
|
-
canonical_domain: canonicalDomain || null,
|
|
21054
|
-
source_system: sourceSystem,
|
|
21055
|
-
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21056
|
-
raw_data: row,
|
|
21057
|
-
metadata: buildMetadata(row, mappings)
|
|
21058
|
-
});
|
|
21059
|
-
imported++;
|
|
21060
21146
|
}
|
|
21061
|
-
|
|
21147
|
+
if (opts.dealBand) setConfigValue(PREF_BAND_KEY, opts.dealBand);
|
|
21062
21148
|
}
|
|
21063
|
-
|
|
21064
|
-
|
|
21065
|
-
|
|
21066
|
-
|
|
21067
|
-
|
|
21068
|
-
|
|
21069
|
-
|
|
21070
|
-
|
|
21071
|
-
|
|
21072
|
-
}
|
|
21073
|
-
const orgIdMap = await resolveSourceIds("organizations", sourceSystem, orgSourceIds);
|
|
21074
|
-
for (const row of rows) {
|
|
21075
|
-
const firstName = getMapped(row, fieldMap, "first_name") ?? "";
|
|
21076
|
-
const lastName = getMapped(row, fieldMap, "last_name") ?? "";
|
|
21077
|
-
const canonicalName = [firstName, lastName].filter(Boolean).join(" ") || getMapped(row, fieldMap, "canonical_name") || "";
|
|
21078
|
-
if (!canonicalName) {
|
|
21079
|
-
errors.push("Row missing name fields");
|
|
21080
|
-
continue;
|
|
21081
|
-
}
|
|
21082
|
-
const orgSourceId = getMapped(row, fieldMap, "organization_source_id");
|
|
21083
|
-
await insertPerson({
|
|
21084
|
-
canonical_name: canonicalName,
|
|
21085
|
-
canonical_email: getMapped(row, fieldMap, "canonical_email") || null,
|
|
21086
|
-
organization_id: orgSourceId ? orgIdMap.get(orgSourceId) ?? null : null,
|
|
21087
|
-
source_system: sourceSystem,
|
|
21088
|
-
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21089
|
-
raw_data: row,
|
|
21090
|
-
metadata: buildMetadata(row, mappings)
|
|
21091
|
-
});
|
|
21092
|
-
imported++;
|
|
21093
|
-
}
|
|
21094
|
-
return { imported, errors };
|
|
21149
|
+
function scenarioMenuChoices() {
|
|
21150
|
+
return NAMED_DEMO_SCENARIOS.map((id) => {
|
|
21151
|
+
const s = getScenario(id);
|
|
21152
|
+
return {
|
|
21153
|
+
value: id,
|
|
21154
|
+
label: s.label,
|
|
21155
|
+
description: s.hook
|
|
21156
|
+
};
|
|
21157
|
+
});
|
|
21095
21158
|
}
|
|
21096
|
-
|
|
21097
|
-
|
|
21098
|
-
|
|
21099
|
-
|
|
21100
|
-
|
|
21101
|
-
|
|
21102
|
-
|
|
21103
|
-
|
|
21104
|
-
|
|
21105
|
-
|
|
21106
|
-
|
|
21107
|
-
|
|
21108
|
-
|
|
21109
|
-
|
|
21110
|
-
|
|
21111
|
-
|
|
21112
|
-
|
|
21113
|
-
|
|
21114
|
-
|
|
21115
|
-
|
|
21116
|
-
|
|
21117
|
-
|
|
21118
|
-
|
|
21119
|
-
|
|
21120
|
-
|
|
21121
|
-
|
|
21122
|
-
|
|
21123
|
-
|
|
21124
|
-
|
|
21125
|
-
|
|
21126
|
-
|
|
21127
|
-
|
|
21128
|
-
|
|
21129
|
-
|
|
21130
|
-
|
|
21131
|
-
|
|
21132
|
-
|
|
21159
|
+
var PREF_SCENARIO_KEY, PREF_MOTION_KEY, PREF_BAND_KEY, MOTION_FIT_CHOICES, DEAL_BAND_CHOICES, MATRIX, MOTION_ONLY, BAND_ONLY, KEYWORD_HINTS;
|
|
21160
|
+
var init_scenario_fit = __esm({
|
|
21161
|
+
"src/demo/scenario-fit.ts"() {
|
|
21162
|
+
"use strict";
|
|
21163
|
+
init_store();
|
|
21164
|
+
init_profile();
|
|
21165
|
+
init_scenarios();
|
|
21166
|
+
PREF_SCENARIO_KEY = "demo-scenario-preference";
|
|
21167
|
+
PREF_MOTION_KEY = "demo-fit-motion";
|
|
21168
|
+
PREF_BAND_KEY = "demo-fit-deal-band";
|
|
21169
|
+
MOTION_FIT_CHOICES = [
|
|
21170
|
+
{
|
|
21171
|
+
value: "plg",
|
|
21172
|
+
label: "Product-led / self-serve",
|
|
21173
|
+
description: "Users start themselves; sales assists or expands"
|
|
21174
|
+
},
|
|
21175
|
+
{
|
|
21176
|
+
value: "smb_velocity",
|
|
21177
|
+
label: "High-volume SMB",
|
|
21178
|
+
description: "Fast cycles, lots of small deals, outbound or inbound machine"
|
|
21179
|
+
},
|
|
21180
|
+
{
|
|
21181
|
+
value: "mid_market",
|
|
21182
|
+
label: "Mid-market, structured process",
|
|
21183
|
+
description: "A real sales cycle, a few stakeholders, moderate ACV"
|
|
21184
|
+
},
|
|
21185
|
+
{
|
|
21186
|
+
value: "enterprise",
|
|
21187
|
+
label: "Enterprise, long cycles",
|
|
21188
|
+
description: "Large deals, many buyers, quarters not weeks"
|
|
21189
|
+
}
|
|
21190
|
+
];
|
|
21191
|
+
DEAL_BAND_CHOICES = [
|
|
21192
|
+
{
|
|
21193
|
+
value: "velocity",
|
|
21194
|
+
label: "Under ~$15K, days to a couple of weeks",
|
|
21195
|
+
description: "Velocity / transactional"
|
|
21196
|
+
},
|
|
21197
|
+
{
|
|
21198
|
+
value: "core",
|
|
21199
|
+
label: "~$15K\u2013$50K, a few weeks",
|
|
21200
|
+
description: "Core SMB"
|
|
21201
|
+
},
|
|
21202
|
+
{
|
|
21203
|
+
value: "mid",
|
|
21204
|
+
label: "~$50K\u2013$150K, 1\u20133 months",
|
|
21205
|
+
description: "Classic mid-market"
|
|
21206
|
+
},
|
|
21207
|
+
{
|
|
21208
|
+
value: "enterprise",
|
|
21209
|
+
label: "$150K+, a quarter or more",
|
|
21210
|
+
description: "Enterprise / strategic"
|
|
21211
|
+
}
|
|
21212
|
+
];
|
|
21213
|
+
MATRIX = {
|
|
21214
|
+
plg: {
|
|
21215
|
+
velocity: "leaky_bucket",
|
|
21216
|
+
core: "leaky_bucket",
|
|
21217
|
+
mid: "hidden_crisis",
|
|
21218
|
+
enterprise: "hidden_crisis"
|
|
21219
|
+
},
|
|
21220
|
+
smb_velocity: {
|
|
21221
|
+
velocity: "busy_bees",
|
|
21222
|
+
core: "busy_bees",
|
|
21223
|
+
mid: "leaky_bucket",
|
|
21224
|
+
enterprise: "lone_wolf"
|
|
21225
|
+
},
|
|
21226
|
+
mid_market: {
|
|
21227
|
+
velocity: "busy_bees",
|
|
21228
|
+
core: "stale_pipeline",
|
|
21229
|
+
mid: "stale_pipeline",
|
|
21230
|
+
enterprise: "hidden_crisis"
|
|
21231
|
+
},
|
|
21232
|
+
enterprise: {
|
|
21233
|
+
velocity: "lone_wolf",
|
|
21234
|
+
core: "lone_wolf",
|
|
21235
|
+
mid: "hidden_crisis",
|
|
21236
|
+
enterprise: "hidden_crisis"
|
|
21237
|
+
}
|
|
21238
|
+
};
|
|
21239
|
+
MOTION_ONLY = {
|
|
21240
|
+
plg: "leaky_bucket",
|
|
21241
|
+
smb_velocity: "busy_bees",
|
|
21242
|
+
mid_market: "stale_pipeline",
|
|
21243
|
+
enterprise: "hidden_crisis"
|
|
21244
|
+
};
|
|
21245
|
+
BAND_ONLY = {
|
|
21246
|
+
velocity: "busy_bees",
|
|
21247
|
+
core: "leaky_bucket",
|
|
21248
|
+
mid: "stale_pipeline",
|
|
21249
|
+
enterprise: "hidden_crisis"
|
|
21250
|
+
};
|
|
21251
|
+
KEYWORD_HINTS = [
|
|
21252
|
+
{ scenario: "even_keel", patterns: [/\beven keel\b/, /\breasonably healthy\b/, /\bno crisis\b/, /\bjust evaluating\b/, /\bgreen[- ]field\b/] },
|
|
21253
|
+
{ scenario: "compound_pain", patterns: [/\beverything('?s| is) (on fire|red|broken)\b/, /\bcompound\b/, /\ball (five )?vitals\b/, /\bmultiple problems\b/] },
|
|
21254
|
+
{ scenario: "leaky_bucket", patterns: [/\bhandoff\b/, /\bmqls?\b/, /\bleak/, /\bdrop[- ]rate/, /\bvanish/, /\brouting\b/, /\bmarketing.?sales\b/] },
|
|
21255
|
+
{ scenario: "stale_pipeline", patterns: [/\bzombie/, /\bstale\b/, /\bpast[- ]due\b/, /\bforecast(ing)? on fiction\b/, /\bstuck in negotiation\b/, /\bquiet deals?\b/] },
|
|
21256
|
+
{ scenario: "lone_wolf", patterns: [/\bsingle[- ]thread/, /\blone wolf\b/, /\bone contact\b/, /\bchampion leaves\b/] },
|
|
21257
|
+
{ scenario: "busy_bees", patterns: [/\bspray/, /\bnois(e|y)\b/, /\bmisdirected\b/, /\bactivity (volume|metrics)\b/, /\bbusy bees\b/, /\bnot (on|hitting) pipeline\b/] },
|
|
21258
|
+
{ scenario: "hidden_crisis", patterns: [/\bhidden crisis\b/, /\benterprise (is )?(dying|red|stale)\b/, /\bsegment.{0,20}mask/, /\baverages? (look|looks) (fine|okay|yellow)\b/] }
|
|
21259
|
+
];
|
|
21133
21260
|
}
|
|
21134
|
-
|
|
21261
|
+
});
|
|
21262
|
+
|
|
21263
|
+
// src/conversation/onboard-tiers.ts
|
|
21264
|
+
import { existsSync as existsSync22, statSync as statSync4 } from "fs";
|
|
21265
|
+
function flagSet(tier) {
|
|
21266
|
+
return Boolean(getConfigValue(TIER_CONFIG_KEYS[tier]));
|
|
21135
21267
|
}
|
|
21136
|
-
|
|
21137
|
-
const
|
|
21138
|
-
const
|
|
21139
|
-
|
|
21140
|
-
const fieldMap = reverseMap(mappings);
|
|
21141
|
-
const personSourceIds = /* @__PURE__ */ new Set();
|
|
21142
|
-
for (const row of rows) {
|
|
21143
|
-
const p = getMapped(row, fieldMap, "person_source_id");
|
|
21144
|
-
if (p) personSourceIds.add(p);
|
|
21145
|
-
}
|
|
21146
|
-
const personIdMap = await resolveSourceIds("people", sourceSystem, personSourceIds);
|
|
21147
|
-
for (const row of rows) {
|
|
21148
|
-
const rawType = getMapped(row, fieldMap, "activity_type") ?? "custom";
|
|
21149
|
-
const activityType = ACTIVITY_TYPE_MAP[rawType] ?? "custom";
|
|
21150
|
-
const rawOccurredAt = getMapped(row, fieldMap, "occurred_at") ?? "";
|
|
21151
|
-
const occurredAt = parseTimestamp(rawOccurredAt);
|
|
21152
|
-
if (!occurredAt) {
|
|
21153
|
-
errors.push("Row missing or invalid occurred_at");
|
|
21154
|
-
continue;
|
|
21155
|
-
}
|
|
21156
|
-
const personSourceId = getMapped(row, fieldMap, "person_source_id");
|
|
21157
|
-
await insertActivity({
|
|
21158
|
-
activity_type: activityType,
|
|
21159
|
-
occurred_at: occurredAt,
|
|
21160
|
-
person_id: personSourceId ? personIdMap.get(personSourceId) ?? null : null,
|
|
21161
|
-
organization_id: null,
|
|
21162
|
-
opportunity_id: null,
|
|
21163
|
-
source_system: sourceSystem,
|
|
21164
|
-
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21165
|
-
raw_data: row,
|
|
21166
|
-
metadata: buildMetadata(row, mappings)
|
|
21167
|
-
});
|
|
21168
|
-
imported++;
|
|
21268
|
+
function markOnboardTierComplete(...tiers) {
|
|
21269
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
21270
|
+
for (const tier of tiers) {
|
|
21271
|
+
if (!flagSet(tier)) setConfigValue(TIER_CONFIG_KEYS[tier], at);
|
|
21169
21272
|
}
|
|
21170
|
-
return { imported, errors };
|
|
21171
21273
|
}
|
|
21172
|
-
|
|
21173
|
-
|
|
21174
|
-
const errors = [];
|
|
21175
|
-
let imported = 0;
|
|
21176
|
-
const fieldMap = reverseMap(mappings);
|
|
21177
|
-
for (const row of rows) {
|
|
21178
|
-
const canonicalName = getMapped(row, fieldMap, "canonical_name");
|
|
21179
|
-
if (!canonicalName) {
|
|
21180
|
-
errors.push("Row missing canonical_name");
|
|
21181
|
-
continue;
|
|
21182
|
-
}
|
|
21183
|
-
const rawType = row["sequence_type"] ?? "";
|
|
21184
|
-
const campaignType = CAMPAIGN_TYPE_MAP[rawType] ?? "email_sequence";
|
|
21185
|
-
await insertCampaign({
|
|
21186
|
-
canonical_name: canonicalName,
|
|
21187
|
-
campaign_type: campaignType,
|
|
21188
|
-
source_system: sourceSystem,
|
|
21189
|
-
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21190
|
-
raw_data: row,
|
|
21191
|
-
metadata: buildMetadata(row, mappings)
|
|
21192
|
-
});
|
|
21193
|
-
imported++;
|
|
21194
|
-
}
|
|
21195
|
-
return { imported, errors };
|
|
21274
|
+
function markProductionDataSeen() {
|
|
21275
|
+
markOnboardTierComplete("production");
|
|
21196
21276
|
}
|
|
21197
|
-
|
|
21198
|
-
"
|
|
21277
|
+
function markDemoDataSeen() {
|
|
21278
|
+
markOnboardTierComplete("demo");
|
|
21279
|
+
}
|
|
21280
|
+
var TIER_CONFIG_KEYS;
|
|
21281
|
+
var init_onboard_tiers = __esm({
|
|
21282
|
+
"src/conversation/onboard-tiers.ts"() {
|
|
21199
21283
|
"use strict";
|
|
21200
|
-
|
|
21201
|
-
|
|
21202
|
-
|
|
21284
|
+
init_store();
|
|
21285
|
+
init_profile();
|
|
21286
|
+
init_repl_api();
|
|
21287
|
+
init_scenario_fit();
|
|
21288
|
+
TIER_CONFIG_KEYS = {
|
|
21289
|
+
profile: "onboard-tier-profile",
|
|
21290
|
+
domain: "onboard-tier-domain",
|
|
21291
|
+
demo: "onboard-tier-demo",
|
|
21292
|
+
production: "onboard-tier-production"
|
|
21293
|
+
};
|
|
21203
21294
|
}
|
|
21204
21295
|
});
|
|
21205
21296
|
|
|
21206
|
-
// src/pipeline/
|
|
21207
|
-
|
|
21208
|
-
|
|
21209
|
-
|
|
21210
|
-
|
|
21297
|
+
// src/pipeline/csv-parse.ts
|
|
21298
|
+
var csv_parse_exports = {};
|
|
21299
|
+
__export(csv_parse_exports, {
|
|
21300
|
+
getPreviewRows: () => getPreviewRows,
|
|
21301
|
+
parseCSV: () => parseCSV
|
|
21302
|
+
});
|
|
21303
|
+
import Papa from "papaparse";
|
|
21304
|
+
function parseCSV(content) {
|
|
21305
|
+
const result = Papa.parse(content, {
|
|
21306
|
+
header: true,
|
|
21307
|
+
skipEmptyLines: true,
|
|
21308
|
+
transformHeader: (h) => h.trim()
|
|
21309
|
+
});
|
|
21310
|
+
return {
|
|
21311
|
+
headers: result.meta.fields ?? [],
|
|
21312
|
+
rows: result.data,
|
|
21313
|
+
rowCount: result.data.length
|
|
21314
|
+
};
|
|
21211
21315
|
}
|
|
21212
|
-
|
|
21213
|
-
|
|
21214
|
-
const orgs = await all(
|
|
21215
|
-
`SELECT id, canonical_domain, created_at FROM organizations WHERE canonical_id IS NULL AND canonical_domain IS NOT NULL ORDER BY created_at ASC`
|
|
21216
|
-
);
|
|
21217
|
-
const groups = /* @__PURE__ */ new Map();
|
|
21218
|
-
for (const org of orgs) {
|
|
21219
|
-
const key = org.canonical_domain.toLowerCase();
|
|
21220
|
-
const group = groups.get(key);
|
|
21221
|
-
if (group) group.push(org);
|
|
21222
|
-
else groups.set(key, [org]);
|
|
21223
|
-
}
|
|
21224
|
-
for (const [, group] of groups) {
|
|
21225
|
-
if (group.length <= 1) continue;
|
|
21226
|
-
const canonical = group[0];
|
|
21227
|
-
const duplicateIds = group.slice(1).map((d) => d.id);
|
|
21228
|
-
const placeholders = duplicateIds.map(() => "?").join(", ");
|
|
21229
|
-
await run(`UPDATE organizations SET canonical_id = ? WHERE id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21230
|
-
await run(`UPDATE people SET organization_id = ? WHERE organization_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21231
|
-
await run(`UPDATE opportunities SET organization_id = ? WHERE organization_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21232
|
-
await run(`UPDATE activities SET organization_id = ? WHERE organization_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21233
|
-
resolved += duplicateIds.length;
|
|
21234
|
-
}
|
|
21235
|
-
return resolved;
|
|
21236
|
-
}
|
|
21237
|
-
async function resolvePeople() {
|
|
21238
|
-
let resolved = 0;
|
|
21239
|
-
const people = await all(
|
|
21240
|
-
`SELECT id, canonical_email, organization_id, created_at FROM people WHERE canonical_id IS NULL AND canonical_email IS NOT NULL ORDER BY created_at ASC`
|
|
21241
|
-
);
|
|
21242
|
-
const groups = /* @__PURE__ */ new Map();
|
|
21243
|
-
for (const person of people) {
|
|
21244
|
-
const key = person.canonical_email.toLowerCase();
|
|
21245
|
-
const group = groups.get(key);
|
|
21246
|
-
if (group) group.push(person);
|
|
21247
|
-
else groups.set(key, [person]);
|
|
21248
|
-
}
|
|
21249
|
-
for (const [, group] of groups) {
|
|
21250
|
-
if (group.length <= 1) continue;
|
|
21251
|
-
const canonical = group[0];
|
|
21252
|
-
const duplicates = group.slice(1);
|
|
21253
|
-
const duplicateIds = duplicates.map((d) => d.id);
|
|
21254
|
-
const placeholders = duplicateIds.map(() => "?").join(", ");
|
|
21255
|
-
if (!canonical.organization_id) {
|
|
21256
|
-
const withOrg = duplicates.find((d) => d.organization_id);
|
|
21257
|
-
if (withOrg) {
|
|
21258
|
-
await run(`UPDATE people SET organization_id = ? WHERE id = ?`, [withOrg.organization_id, canonical.id]);
|
|
21259
|
-
}
|
|
21260
|
-
}
|
|
21261
|
-
await run(`UPDATE people SET canonical_id = ? WHERE id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21262
|
-
await run(`UPDATE activities SET person_id = ? WHERE person_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21263
|
-
await run(`UPDATE opportunities SET owner_id = ? WHERE owner_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21264
|
-
resolved += duplicateIds.length;
|
|
21265
|
-
}
|
|
21266
|
-
return resolved;
|
|
21316
|
+
function getPreviewRows(rows, count = 5) {
|
|
21317
|
+
return rows.slice(0, count);
|
|
21267
21318
|
}
|
|
21268
|
-
var
|
|
21269
|
-
"src/pipeline/
|
|
21319
|
+
var init_csv_parse = __esm({
|
|
21320
|
+
"src/pipeline/csv-parse.ts"() {
|
|
21270
21321
|
"use strict";
|
|
21271
|
-
init_connection();
|
|
21272
21322
|
}
|
|
21273
21323
|
});
|
|
21274
21324
|
|
|
21275
|
-
// src/
|
|
21276
|
-
function
|
|
21277
|
-
|
|
21278
|
-
return () => {
|
|
21279
|
-
a = a + 1831565813 | 0;
|
|
21280
|
-
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
21281
|
-
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
21282
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
21283
|
-
};
|
|
21284
|
-
}
|
|
21285
|
-
function createSeededRandom(seed) {
|
|
21286
|
-
const raw = mulberry32(seed);
|
|
21287
|
-
const rng = {
|
|
21288
|
-
next: raw,
|
|
21289
|
-
nextInt(min, max) {
|
|
21290
|
-
return Math.floor(raw() * (max - min + 1)) + min;
|
|
21291
|
-
},
|
|
21292
|
-
nextFloat(min, max) {
|
|
21293
|
-
return raw() * (max - min) + min;
|
|
21294
|
-
},
|
|
21295
|
-
pick(arr) {
|
|
21296
|
-
if (arr.length === 0) {
|
|
21297
|
-
throw new Error("Cannot pick from an empty array");
|
|
21298
|
-
}
|
|
21299
|
-
return arr[Math.floor(raw() * arr.length)];
|
|
21300
|
-
},
|
|
21301
|
-
pickN(arr, n) {
|
|
21302
|
-
const copy = [...arr];
|
|
21303
|
-
rng.shuffle(copy);
|
|
21304
|
-
return copy.slice(0, Math.min(n, copy.length));
|
|
21305
|
-
},
|
|
21306
|
-
shuffle(arr) {
|
|
21307
|
-
for (let i = arr.length - 1; i > 0; i--) {
|
|
21308
|
-
const j = Math.floor(raw() * (i + 1));
|
|
21309
|
-
const current = arr[i];
|
|
21310
|
-
arr[i] = arr[j];
|
|
21311
|
-
arr[j] = current;
|
|
21312
|
-
}
|
|
21313
|
-
return arr;
|
|
21314
|
-
},
|
|
21315
|
-
chance(probability) {
|
|
21316
|
-
return raw() < probability;
|
|
21317
|
-
},
|
|
21318
|
-
uuid() {
|
|
21319
|
-
const bytes = Array.from({ length: 16 }, () => Math.floor(raw() * 256));
|
|
21320
|
-
bytes[6] = bytes[6] & 15 | 64;
|
|
21321
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
21322
|
-
const hex = bytes.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
21323
|
-
return [
|
|
21324
|
-
hex.slice(0, 8),
|
|
21325
|
-
hex.slice(8, 12),
|
|
21326
|
-
hex.slice(12, 16),
|
|
21327
|
-
hex.slice(16, 20),
|
|
21328
|
-
hex.slice(20, 32)
|
|
21329
|
-
].join("-");
|
|
21330
|
-
},
|
|
21331
|
-
date(start, end) {
|
|
21332
|
-
const s = start.getTime();
|
|
21333
|
-
const e = end.getTime();
|
|
21334
|
-
return new Date(s + raw() * (e - s));
|
|
21335
|
-
},
|
|
21336
|
-
weightedPick(items, weights) {
|
|
21337
|
-
if (items.length === 0) {
|
|
21338
|
-
throw new Error("Cannot pick from an empty weighted item list");
|
|
21339
|
-
}
|
|
21340
|
-
const total = weights.reduce((sum, w) => sum + w, 0);
|
|
21341
|
-
let r = raw() * total;
|
|
21342
|
-
for (let i = 0; i < items.length; i++) {
|
|
21343
|
-
r -= weights[i] ?? 0;
|
|
21344
|
-
if (r <= 0) return items[i];
|
|
21345
|
-
}
|
|
21346
|
-
return items[items.length - 1];
|
|
21347
|
-
}
|
|
21348
|
-
};
|
|
21349
|
-
return rng;
|
|
21325
|
+
// src/pipeline/csv-mappings.ts
|
|
21326
|
+
function getDefaultMappings(sourceSystem, entityType) {
|
|
21327
|
+
return DEFAULT_MAPPINGS[sourceSystem]?.[entityType] ?? {};
|
|
21350
21328
|
}
|
|
21351
|
-
var
|
|
21352
|
-
|
|
21329
|
+
var DEFAULT_MAPPINGS, ACTIVITY_TYPE_MAP, CAMPAIGN_TYPE_MAP;
|
|
21330
|
+
var init_csv_mappings = __esm({
|
|
21331
|
+
"src/pipeline/csv-mappings.ts"() {
|
|
21353
21332
|
"use strict";
|
|
21333
|
+
DEFAULT_MAPPINGS = {
|
|
21334
|
+
salesforce: {
|
|
21335
|
+
organizations: { Name: "canonical_name", Website: "canonical_domain", Id: "source_id" },
|
|
21336
|
+
people: { Email: "canonical_email", FirstName: "first_name", LastName: "last_name", Id: "source_id", AccountId: "organization_source_id" },
|
|
21337
|
+
opportunities: { Name: "canonical_name", Amount: "amount", StageName: "current_stage", CloseDate: "close_date", Id: "source_id", AccountId: "organization_source_id", OwnerId: "owner_source_id" },
|
|
21338
|
+
activities: { TaskSubtype: "activity_type", ActivityDate: "occurred_at", Id: "source_id", WhoId: "person_source_id" }
|
|
21339
|
+
},
|
|
21340
|
+
hubspot: {
|
|
21341
|
+
organizations: { "Company name": "canonical_name", "Company Domain Name": "canonical_domain", "Company ID": "source_id" },
|
|
21342
|
+
people: { Email: "canonical_email", "First Name": "first_name", "Last Name": "last_name", "Contact ID": "source_id", "Associated Company ID": "organization_source_id" },
|
|
21343
|
+
activities: { Type: "activity_type", "Created At": "occurred_at", "Engagement ID": "source_id", "Contact ID": "person_source_id" }
|
|
21344
|
+
},
|
|
21345
|
+
outreach: {
|
|
21346
|
+
campaigns: { name: "canonical_name", id: "source_id" },
|
|
21347
|
+
activities: { type: "activity_type", created_at: "occurred_at", id: "source_id", prospect_id: "person_source_id" }
|
|
21348
|
+
}
|
|
21349
|
+
};
|
|
21350
|
+
ACTIVITY_TYPE_MAP = {
|
|
21351
|
+
Call: "call",
|
|
21352
|
+
Email: "email",
|
|
21353
|
+
Task: "custom",
|
|
21354
|
+
CALL: "call",
|
|
21355
|
+
EMAIL: "email",
|
|
21356
|
+
MEETING: "meeting",
|
|
21357
|
+
NOTE: "custom",
|
|
21358
|
+
TASK: "custom",
|
|
21359
|
+
INCOMING_EMAIL: "email",
|
|
21360
|
+
FORWARDED_EMAIL: "email",
|
|
21361
|
+
call: "call",
|
|
21362
|
+
email: "email",
|
|
21363
|
+
sequence_step: "email"
|
|
21364
|
+
};
|
|
21365
|
+
CAMPAIGN_TYPE_MAP = {
|
|
21366
|
+
outbound: "email_sequence",
|
|
21367
|
+
inbound: "email_sequence",
|
|
21368
|
+
trigger: "email_sequence"
|
|
21369
|
+
};
|
|
21354
21370
|
}
|
|
21355
21371
|
});
|
|
21356
21372
|
|
|
21357
|
-
// src/
|
|
21358
|
-
var
|
|
21359
|
-
__export(
|
|
21360
|
-
|
|
21361
|
-
SCENARIOS: () => SCENARIOS,
|
|
21362
|
-
SCENARIO_LIST: () => SCENARIO_LIST,
|
|
21363
|
-
blendScenarios: () => blendScenarios,
|
|
21364
|
-
getScenario: () => getScenario,
|
|
21365
|
-
isNamedDemoScenario: () => isNamedDemoScenario,
|
|
21366
|
-
pickRandomScenario: () => pickRandomScenario,
|
|
21367
|
-
resolveScenarioInput: () => resolveScenarioInput
|
|
21373
|
+
// src/pipeline/csv-detect.ts
|
|
21374
|
+
var csv_detect_exports = {};
|
|
21375
|
+
__export(csv_detect_exports, {
|
|
21376
|
+
detectEntityType: () => detectEntityType
|
|
21368
21377
|
});
|
|
21369
|
-
function
|
|
21370
|
-
const
|
|
21371
|
-
if (!
|
|
21372
|
-
|
|
21373
|
-
|
|
21374
|
-
|
|
21375
|
-
|
|
21376
|
-
|
|
21377
|
-
|
|
21378
|
-
|
|
21379
|
-
|
|
21380
|
-
|
|
21381
|
-
|
|
21382
|
-
|
|
21383
|
-
|
|
21384
|
-
return input;
|
|
21378
|
+
function detectEntityType(headers, sourceSystem) {
|
|
21379
|
+
const rules = SIGNATURES[sourceSystem];
|
|
21380
|
+
if (!rules) return null;
|
|
21381
|
+
const headerSet = new Set(headers);
|
|
21382
|
+
let bestMatch = null;
|
|
21383
|
+
for (const rule of rules) {
|
|
21384
|
+
const requiredMatches = rule.required.filter((col) => headerSet.has(col)).length;
|
|
21385
|
+
const requiredTotal = rule.required.length;
|
|
21386
|
+
if (requiredMatches < Math.ceil(requiredTotal / 2)) continue;
|
|
21387
|
+
const optionalMatches = rule.optional.filter((col) => headerSet.has(col)).length;
|
|
21388
|
+
const optionalTotal = rule.optional.length;
|
|
21389
|
+
const score = requiredMatches / requiredTotal * 0.7 + (optionalTotal > 0 ? optionalMatches / optionalTotal * 0.3 : 0.3);
|
|
21390
|
+
if (!bestMatch || score > bestMatch.score) {
|
|
21391
|
+
bestMatch = { rule, score };
|
|
21392
|
+
}
|
|
21385
21393
|
}
|
|
21386
|
-
|
|
21387
|
-
|
|
21388
|
-
|
|
21394
|
+
if (!bestMatch) return null;
|
|
21395
|
+
const defaultMappings = getDefaultMappings(sourceSystem, bestMatch.rule.entityType);
|
|
21396
|
+
const mappings = {};
|
|
21397
|
+
for (const [csvCol, ontologyField] of Object.entries(defaultMappings)) {
|
|
21398
|
+
if (headerSet.has(csvCol)) {
|
|
21399
|
+
mappings[csvCol] = ontologyField;
|
|
21400
|
+
}
|
|
21389
21401
|
}
|
|
21390
|
-
return
|
|
21391
|
-
|
|
21392
|
-
|
|
21393
|
-
|
|
21402
|
+
return {
|
|
21403
|
+
entityType: bestMatch.rule.entityType,
|
|
21404
|
+
confidence: Math.round(bestMatch.score * 100) / 100,
|
|
21405
|
+
mappings
|
|
21406
|
+
};
|
|
21394
21407
|
}
|
|
21395
|
-
|
|
21396
|
-
|
|
21397
|
-
|
|
21398
|
-
key: "research_blend",
|
|
21399
|
-
label: "Research-Derived Blend",
|
|
21400
|
-
description: "Realistic data with mild-to-moderate problems across all vital signs.",
|
|
21401
|
-
story: "Generated with default research blend. Problems are seeded across all vital signs at realistic levels.",
|
|
21402
|
-
hook: "Mild-to-moderate problems seeded across all five vitals.",
|
|
21403
|
-
// Bump all problems slightly above baseline for discoverability
|
|
21404
|
-
staleContactRatio: 0.2,
|
|
21405
|
-
pastCloseDateRatio: 0.15,
|
|
21406
|
-
mqlDropRatio: 0.15,
|
|
21407
|
-
qualifiedNoOutreachRatio: 0.15,
|
|
21408
|
-
stuckDealRatio: 0.15,
|
|
21409
|
-
noiseActivityRatio: 0.2,
|
|
21410
|
-
singleThreadRatio: 0.25
|
|
21411
|
-
};
|
|
21412
|
-
return blended;
|
|
21413
|
-
}
|
|
21414
|
-
var BASELINE, SCENARIOS, SCENARIO_LIST, NAMED_DEMO_SCENARIOS, RANDOM_POOL;
|
|
21415
|
-
var init_scenarios = __esm({
|
|
21416
|
-
"src/demo/scenarios.ts"() {
|
|
21408
|
+
var SIGNATURES;
|
|
21409
|
+
var init_csv_detect = __esm({
|
|
21410
|
+
"src/pipeline/csv-detect.ts"() {
|
|
21417
21411
|
"use strict";
|
|
21418
|
-
|
|
21419
|
-
|
|
21420
|
-
|
|
21421
|
-
|
|
21422
|
-
|
|
21423
|
-
|
|
21424
|
-
|
|
21425
|
-
|
|
21426
|
-
|
|
21427
|
-
|
|
21428
|
-
|
|
21429
|
-
|
|
21430
|
-
|
|
21431
|
-
|
|
21432
|
-
|
|
21433
|
-
|
|
21434
|
-
|
|
21435
|
-
singleThreadRatio: 0.2,
|
|
21436
|
-
loneWolfRepIndex: null,
|
|
21437
|
-
loneWolfSingleThreadRatio: 0,
|
|
21438
|
-
freshnessGapDays: 90
|
|
21412
|
+
init_csv_mappings();
|
|
21413
|
+
SIGNATURES = {
|
|
21414
|
+
salesforce: [
|
|
21415
|
+
{ entityType: "organizations", required: ["Name", "Industry"], optional: ["AnnualRevenue", "BillingCountry", "Website", "NumberOfEmployees"], weight: 1 },
|
|
21416
|
+
{ entityType: "people", required: ["FirstName", "LastName", "Email"], optional: ["AccountId", "Title", "Phone"], weight: 1 },
|
|
21417
|
+
{ entityType: "opportunities", required: ["StageName", "Amount", "CloseDate"], optional: ["AccountId", "OwnerId", "Probability"], weight: 1 },
|
|
21418
|
+
{ entityType: "activities", required: ["Subject", "ActivityDate", "TaskSubtype"], optional: ["WhoId", "WhatId", "Status"], weight: 1 }
|
|
21419
|
+
],
|
|
21420
|
+
hubspot: [
|
|
21421
|
+
{ entityType: "organizations", required: ["Company name", "Company Domain Name"], optional: ["Company ID", "Industry", "Annual Revenue"], weight: 1 },
|
|
21422
|
+
{ entityType: "people", required: ["First Name", "Last Name", "Email"], optional: ["Contact ID", "Associated Company ID", "Lifecycle Stage"], weight: 1 },
|
|
21423
|
+
{ entityType: "activities", required: ["Engagement ID", "Type"], optional: ["Timestamp", "Created At", "Contact ID"], weight: 1 }
|
|
21424
|
+
],
|
|
21425
|
+
outreach: [
|
|
21426
|
+
{ entityType: "campaigns", required: ["sequence_type", "step_count"], optional: ["name", "id", "enabled"], weight: 1 },
|
|
21427
|
+
{ entityType: "activities", required: ["prospect_id", "prospect_email"], optional: ["type", "created_at", "subject"], weight: 1 }
|
|
21428
|
+
]
|
|
21439
21429
|
};
|
|
21440
|
-
|
|
21441
|
-
|
|
21442
|
-
|
|
21443
|
-
|
|
21444
|
-
|
|
21445
|
-
|
|
21446
|
-
|
|
21447
|
-
|
|
21448
|
-
|
|
21449
|
-
|
|
21450
|
-
|
|
21451
|
-
|
|
21452
|
-
|
|
21453
|
-
|
|
21454
|
-
|
|
21455
|
-
|
|
21456
|
-
|
|
21457
|
-
|
|
21458
|
-
|
|
21459
|
-
|
|
21460
|
-
|
|
21461
|
-
|
|
21462
|
-
|
|
21463
|
-
|
|
21464
|
-
|
|
21465
|
-
|
|
21466
|
-
|
|
21467
|
-
|
|
21468
|
-
|
|
21469
|
-
|
|
21470
|
-
|
|
21471
|
-
|
|
21472
|
-
|
|
21473
|
-
|
|
21474
|
-
|
|
21475
|
-
|
|
21476
|
-
|
|
21477
|
-
|
|
21478
|
-
|
|
21479
|
-
|
|
21480
|
-
|
|
21481
|
-
|
|
21482
|
-
|
|
21483
|
-
|
|
21484
|
-
|
|
21485
|
-
|
|
21486
|
-
|
|
21487
|
-
|
|
21488
|
-
|
|
21489
|
-
|
|
21490
|
-
|
|
21491
|
-
|
|
21492
|
-
|
|
21493
|
-
|
|
21494
|
-
|
|
21495
|
-
|
|
21496
|
-
|
|
21497
|
-
|
|
21498
|
-
|
|
21499
|
-
|
|
21500
|
-
|
|
21501
|
-
|
|
21502
|
-
|
|
21503
|
-
|
|
21504
|
-
|
|
21505
|
-
|
|
21506
|
-
|
|
21507
|
-
|
|
21508
|
-
|
|
21509
|
-
|
|
21510
|
-
|
|
21511
|
-
|
|
21512
|
-
|
|
21513
|
-
|
|
21514
|
-
|
|
21515
|
-
|
|
21516
|
-
|
|
21517
|
-
|
|
21518
|
-
|
|
21519
|
-
|
|
21520
|
-
|
|
21521
|
-
|
|
21522
|
-
|
|
21523
|
-
|
|
21524
|
-
|
|
21525
|
-
|
|
21526
|
-
|
|
21527
|
-
|
|
21528
|
-
|
|
21529
|
-
|
|
21530
|
-
|
|
21531
|
-
|
|
21430
|
+
}
|
|
21431
|
+
});
|
|
21432
|
+
|
|
21433
|
+
// src/pipeline/importer.ts
|
|
21434
|
+
async function importRows(params) {
|
|
21435
|
+
let result;
|
|
21436
|
+
switch (params.entityType) {
|
|
21437
|
+
case "organizations":
|
|
21438
|
+
result = await importOrganizations(params);
|
|
21439
|
+
break;
|
|
21440
|
+
case "people":
|
|
21441
|
+
result = await importPeople(params);
|
|
21442
|
+
break;
|
|
21443
|
+
case "opportunities":
|
|
21444
|
+
result = await importOpportunities(params);
|
|
21445
|
+
break;
|
|
21446
|
+
case "activities":
|
|
21447
|
+
result = await importActivities(params);
|
|
21448
|
+
break;
|
|
21449
|
+
case "campaigns":
|
|
21450
|
+
result = await importCampaigns(params);
|
|
21451
|
+
break;
|
|
21452
|
+
default:
|
|
21453
|
+
result = { imported: 0, errors: [`Unknown entity type: ${params.entityType}`] };
|
|
21454
|
+
}
|
|
21455
|
+
const { invalidateLexiconSeed: invalidateLexiconSeed2 } = await Promise.resolve().then(() => (init_lexicon_seed(), lexicon_seed_exports));
|
|
21456
|
+
invalidateLexiconSeed2();
|
|
21457
|
+
return result;
|
|
21458
|
+
}
|
|
21459
|
+
function reverseMap(mappings) {
|
|
21460
|
+
const reversed = {};
|
|
21461
|
+
for (const [csvCol, ontologyField] of Object.entries(mappings)) {
|
|
21462
|
+
reversed[ontologyField] = csvCol;
|
|
21463
|
+
}
|
|
21464
|
+
return reversed;
|
|
21465
|
+
}
|
|
21466
|
+
function getMapped(row, fieldMap, field) {
|
|
21467
|
+
const csvCol = fieldMap[field];
|
|
21468
|
+
if (!csvCol) return void 0;
|
|
21469
|
+
const val = row[csvCol];
|
|
21470
|
+
return val !== void 0 && val !== "" ? val : void 0;
|
|
21471
|
+
}
|
|
21472
|
+
function buildMetadata(row, mappings) {
|
|
21473
|
+
const mappedCols = new Set(Object.keys(mappings));
|
|
21474
|
+
const metadata = {};
|
|
21475
|
+
for (const [key, value] of Object.entries(row)) {
|
|
21476
|
+
if (!mappedCols.has(key) && value !== void 0 && value !== "") {
|
|
21477
|
+
metadata[key] = value;
|
|
21478
|
+
}
|
|
21479
|
+
}
|
|
21480
|
+
return metadata;
|
|
21481
|
+
}
|
|
21482
|
+
function stripUrl(url) {
|
|
21483
|
+
return url.replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/$/, "");
|
|
21484
|
+
}
|
|
21485
|
+
function parseTimestamp(value) {
|
|
21486
|
+
if (!value) return null;
|
|
21487
|
+
const num2 = Number(value);
|
|
21488
|
+
if (!isNaN(num2) && num2 > 1e12) return new Date(num2).toISOString();
|
|
21489
|
+
if (!isNaN(num2) && num2 > 1e9) return new Date(num2 * 1e3).toISOString();
|
|
21490
|
+
const d = new Date(value);
|
|
21491
|
+
if (!isNaN(d.getTime())) return d.toISOString();
|
|
21492
|
+
return null;
|
|
21493
|
+
}
|
|
21494
|
+
async function resolveSourceIds(table, sourceSystem, sourceIds) {
|
|
21495
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
21496
|
+
if (sourceIds.size === 0) return idMap;
|
|
21497
|
+
const ids = Array.from(sourceIds);
|
|
21498
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
21499
|
+
const rows = await all(
|
|
21500
|
+
`SELECT id, source_id FROM ${table} WHERE source_system = ? AND source_id IN (${placeholders})`,
|
|
21501
|
+
[sourceSystem, ...ids]
|
|
21502
|
+
);
|
|
21503
|
+
for (const row of rows) {
|
|
21504
|
+
idMap.set(row.source_id, row.id);
|
|
21505
|
+
}
|
|
21506
|
+
return idMap;
|
|
21507
|
+
}
|
|
21508
|
+
async function importOrganizations(params) {
|
|
21509
|
+
const { rows, mappings, sourceSystem } = params;
|
|
21510
|
+
const errors = [];
|
|
21511
|
+
let imported = 0;
|
|
21512
|
+
const fieldMap = reverseMap(mappings);
|
|
21513
|
+
for (const row of rows) {
|
|
21514
|
+
const canonicalName = getMapped(row, fieldMap, "canonical_name");
|
|
21515
|
+
if (!canonicalName) {
|
|
21516
|
+
errors.push("Row missing canonical_name");
|
|
21517
|
+
continue;
|
|
21518
|
+
}
|
|
21519
|
+
const website = getMapped(row, fieldMap, "canonical_domain") ?? "";
|
|
21520
|
+
const canonicalDomain = stripUrl(website);
|
|
21521
|
+
await insertOrganization({
|
|
21522
|
+
canonical_name: canonicalName,
|
|
21523
|
+
canonical_domain: canonicalDomain || null,
|
|
21524
|
+
source_system: sourceSystem,
|
|
21525
|
+
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21526
|
+
raw_data: row,
|
|
21527
|
+
metadata: buildMetadata(row, mappings)
|
|
21528
|
+
});
|
|
21529
|
+
imported++;
|
|
21530
|
+
}
|
|
21531
|
+
return { imported, errors };
|
|
21532
|
+
}
|
|
21533
|
+
async function importPeople(params) {
|
|
21534
|
+
const { rows, mappings, sourceSystem } = params;
|
|
21535
|
+
const errors = [];
|
|
21536
|
+
let imported = 0;
|
|
21537
|
+
const fieldMap = reverseMap(mappings);
|
|
21538
|
+
const orgSourceIds = /* @__PURE__ */ new Set();
|
|
21539
|
+
for (const row of rows) {
|
|
21540
|
+
const orgSrcId = getMapped(row, fieldMap, "organization_source_id");
|
|
21541
|
+
if (orgSrcId) orgSourceIds.add(orgSrcId);
|
|
21542
|
+
}
|
|
21543
|
+
const orgIdMap = await resolveSourceIds("organizations", sourceSystem, orgSourceIds);
|
|
21544
|
+
for (const row of rows) {
|
|
21545
|
+
const firstName = getMapped(row, fieldMap, "first_name") ?? "";
|
|
21546
|
+
const lastName = getMapped(row, fieldMap, "last_name") ?? "";
|
|
21547
|
+
const canonicalName = [firstName, lastName].filter(Boolean).join(" ") || getMapped(row, fieldMap, "canonical_name") || "";
|
|
21548
|
+
if (!canonicalName) {
|
|
21549
|
+
errors.push("Row missing name fields");
|
|
21550
|
+
continue;
|
|
21551
|
+
}
|
|
21552
|
+
const orgSourceId = getMapped(row, fieldMap, "organization_source_id");
|
|
21553
|
+
await insertPerson({
|
|
21554
|
+
canonical_name: canonicalName,
|
|
21555
|
+
canonical_email: getMapped(row, fieldMap, "canonical_email") || null,
|
|
21556
|
+
organization_id: orgSourceId ? orgIdMap.get(orgSourceId) ?? null : null,
|
|
21557
|
+
source_system: sourceSystem,
|
|
21558
|
+
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21559
|
+
raw_data: row,
|
|
21560
|
+
metadata: buildMetadata(row, mappings)
|
|
21561
|
+
});
|
|
21562
|
+
imported++;
|
|
21563
|
+
}
|
|
21564
|
+
return { imported, errors };
|
|
21565
|
+
}
|
|
21566
|
+
async function importOpportunities(params) {
|
|
21567
|
+
const { rows, mappings, sourceSystem } = params;
|
|
21568
|
+
const errors = [];
|
|
21569
|
+
let imported = 0;
|
|
21570
|
+
const fieldMap = reverseMap(mappings);
|
|
21571
|
+
const orgSourceIds = /* @__PURE__ */ new Set();
|
|
21572
|
+
const ownerSourceIds = /* @__PURE__ */ new Set();
|
|
21573
|
+
for (const row of rows) {
|
|
21574
|
+
const o = getMapped(row, fieldMap, "organization_source_id");
|
|
21575
|
+
if (o) orgSourceIds.add(o);
|
|
21576
|
+
const w = getMapped(row, fieldMap, "owner_source_id");
|
|
21577
|
+
if (w) ownerSourceIds.add(w);
|
|
21578
|
+
}
|
|
21579
|
+
const orgIdMap = await resolveSourceIds("organizations", sourceSystem, orgSourceIds);
|
|
21580
|
+
const ownerIdMap = await resolveSourceIds("people", sourceSystem, ownerSourceIds);
|
|
21581
|
+
for (const row of rows) {
|
|
21582
|
+
const canonicalName = getMapped(row, fieldMap, "canonical_name");
|
|
21583
|
+
if (!canonicalName) {
|
|
21584
|
+
errors.push("Row missing canonical_name");
|
|
21585
|
+
continue;
|
|
21586
|
+
}
|
|
21587
|
+
const orgSourceId = getMapped(row, fieldMap, "organization_source_id");
|
|
21588
|
+
const ownerSourceId = getMapped(row, fieldMap, "owner_source_id");
|
|
21589
|
+
const amountStr = getMapped(row, fieldMap, "amount");
|
|
21590
|
+
await insertOpportunity({
|
|
21591
|
+
canonical_name: canonicalName,
|
|
21592
|
+
organization_id: orgSourceId ? orgIdMap.get(orgSourceId) ?? null : null,
|
|
21593
|
+
owner_id: ownerSourceId ? ownerIdMap.get(ownerSourceId) ?? null : null,
|
|
21594
|
+
current_stage: getMapped(row, fieldMap, "current_stage") || null,
|
|
21595
|
+
amount: amountStr ? parseFloat(amountStr) || null : null,
|
|
21596
|
+
close_date: getMapped(row, fieldMap, "close_date") || null,
|
|
21597
|
+
source_system: sourceSystem,
|
|
21598
|
+
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21599
|
+
raw_data: row,
|
|
21600
|
+
metadata: buildMetadata(row, mappings)
|
|
21601
|
+
});
|
|
21602
|
+
imported++;
|
|
21603
|
+
}
|
|
21604
|
+
return { imported, errors };
|
|
21605
|
+
}
|
|
21606
|
+
async function importActivities(params) {
|
|
21607
|
+
const { rows, mappings, sourceSystem } = params;
|
|
21608
|
+
const errors = [];
|
|
21609
|
+
let imported = 0;
|
|
21610
|
+
const fieldMap = reverseMap(mappings);
|
|
21611
|
+
const personSourceIds = /* @__PURE__ */ new Set();
|
|
21612
|
+
for (const row of rows) {
|
|
21613
|
+
const p = getMapped(row, fieldMap, "person_source_id");
|
|
21614
|
+
if (p) personSourceIds.add(p);
|
|
21615
|
+
}
|
|
21616
|
+
const personIdMap = await resolveSourceIds("people", sourceSystem, personSourceIds);
|
|
21617
|
+
for (const row of rows) {
|
|
21618
|
+
const rawType = getMapped(row, fieldMap, "activity_type") ?? "custom";
|
|
21619
|
+
const activityType = ACTIVITY_TYPE_MAP[rawType] ?? "custom";
|
|
21620
|
+
const rawOccurredAt = getMapped(row, fieldMap, "occurred_at") ?? "";
|
|
21621
|
+
const occurredAt = parseTimestamp(rawOccurredAt);
|
|
21622
|
+
if (!occurredAt) {
|
|
21623
|
+
errors.push("Row missing or invalid occurred_at");
|
|
21624
|
+
continue;
|
|
21625
|
+
}
|
|
21626
|
+
const personSourceId = getMapped(row, fieldMap, "person_source_id");
|
|
21627
|
+
await insertActivity({
|
|
21628
|
+
activity_type: activityType,
|
|
21629
|
+
occurred_at: occurredAt,
|
|
21630
|
+
person_id: personSourceId ? personIdMap.get(personSourceId) ?? null : null,
|
|
21631
|
+
organization_id: null,
|
|
21632
|
+
opportunity_id: null,
|
|
21633
|
+
source_system: sourceSystem,
|
|
21634
|
+
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21635
|
+
raw_data: row,
|
|
21636
|
+
metadata: buildMetadata(row, mappings)
|
|
21637
|
+
});
|
|
21638
|
+
imported++;
|
|
21639
|
+
}
|
|
21640
|
+
return { imported, errors };
|
|
21641
|
+
}
|
|
21642
|
+
async function importCampaigns(params) {
|
|
21643
|
+
const { rows, mappings, sourceSystem } = params;
|
|
21644
|
+
const errors = [];
|
|
21645
|
+
let imported = 0;
|
|
21646
|
+
const fieldMap = reverseMap(mappings);
|
|
21647
|
+
for (const row of rows) {
|
|
21648
|
+
const canonicalName = getMapped(row, fieldMap, "canonical_name");
|
|
21649
|
+
if (!canonicalName) {
|
|
21650
|
+
errors.push("Row missing canonical_name");
|
|
21651
|
+
continue;
|
|
21652
|
+
}
|
|
21653
|
+
const rawType = row["sequence_type"] ?? "";
|
|
21654
|
+
const campaignType = CAMPAIGN_TYPE_MAP[rawType] ?? "email_sequence";
|
|
21655
|
+
await insertCampaign({
|
|
21656
|
+
canonical_name: canonicalName,
|
|
21657
|
+
campaign_type: campaignType,
|
|
21658
|
+
source_system: sourceSystem,
|
|
21659
|
+
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21660
|
+
raw_data: row,
|
|
21661
|
+
metadata: buildMetadata(row, mappings)
|
|
21662
|
+
});
|
|
21663
|
+
imported++;
|
|
21664
|
+
}
|
|
21665
|
+
return { imported, errors };
|
|
21666
|
+
}
|
|
21667
|
+
var init_importer = __esm({
|
|
21668
|
+
"src/pipeline/importer.ts"() {
|
|
21669
|
+
"use strict";
|
|
21670
|
+
init_csv_mappings();
|
|
21671
|
+
init_queries();
|
|
21672
|
+
init_connection();
|
|
21673
|
+
}
|
|
21674
|
+
});
|
|
21675
|
+
|
|
21676
|
+
// src/pipeline/resolver.ts
|
|
21677
|
+
async function resolveIdentities() {
|
|
21678
|
+
const orgsResolved = await resolveOrganizations();
|
|
21679
|
+
const peopleResolved = await resolvePeople();
|
|
21680
|
+
return { resolved: orgsResolved + peopleResolved, peopleResolved, orgsResolved };
|
|
21681
|
+
}
|
|
21682
|
+
async function resolveOrganizations() {
|
|
21683
|
+
let resolved = 0;
|
|
21684
|
+
const orgs = await all(
|
|
21685
|
+
`SELECT id, canonical_domain, created_at FROM organizations WHERE canonical_id IS NULL AND canonical_domain IS NOT NULL ORDER BY created_at ASC`
|
|
21686
|
+
);
|
|
21687
|
+
const groups = /* @__PURE__ */ new Map();
|
|
21688
|
+
for (const org of orgs) {
|
|
21689
|
+
const key = org.canonical_domain.toLowerCase();
|
|
21690
|
+
const group = groups.get(key);
|
|
21691
|
+
if (group) group.push(org);
|
|
21692
|
+
else groups.set(key, [org]);
|
|
21693
|
+
}
|
|
21694
|
+
for (const [, group] of groups) {
|
|
21695
|
+
if (group.length <= 1) continue;
|
|
21696
|
+
const canonical = group[0];
|
|
21697
|
+
const duplicateIds = group.slice(1).map((d) => d.id);
|
|
21698
|
+
const placeholders = duplicateIds.map(() => "?").join(", ");
|
|
21699
|
+
await run(`UPDATE organizations SET canonical_id = ? WHERE id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21700
|
+
await run(`UPDATE people SET organization_id = ? WHERE organization_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21701
|
+
await run(`UPDATE opportunities SET organization_id = ? WHERE organization_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21702
|
+
await run(`UPDATE activities SET organization_id = ? WHERE organization_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21703
|
+
resolved += duplicateIds.length;
|
|
21704
|
+
}
|
|
21705
|
+
return resolved;
|
|
21706
|
+
}
|
|
21707
|
+
async function resolvePeople() {
|
|
21708
|
+
let resolved = 0;
|
|
21709
|
+
const people = await all(
|
|
21710
|
+
`SELECT id, canonical_email, organization_id, created_at FROM people WHERE canonical_id IS NULL AND canonical_email IS NOT NULL ORDER BY created_at ASC`
|
|
21711
|
+
);
|
|
21712
|
+
const groups = /* @__PURE__ */ new Map();
|
|
21713
|
+
for (const person of people) {
|
|
21714
|
+
const key = person.canonical_email.toLowerCase();
|
|
21715
|
+
const group = groups.get(key);
|
|
21716
|
+
if (group) group.push(person);
|
|
21717
|
+
else groups.set(key, [person]);
|
|
21718
|
+
}
|
|
21719
|
+
for (const [, group] of groups) {
|
|
21720
|
+
if (group.length <= 1) continue;
|
|
21721
|
+
const canonical = group[0];
|
|
21722
|
+
const duplicates = group.slice(1);
|
|
21723
|
+
const duplicateIds = duplicates.map((d) => d.id);
|
|
21724
|
+
const placeholders = duplicateIds.map(() => "?").join(", ");
|
|
21725
|
+
if (!canonical.organization_id) {
|
|
21726
|
+
const withOrg = duplicates.find((d) => d.organization_id);
|
|
21727
|
+
if (withOrg) {
|
|
21728
|
+
await run(`UPDATE people SET organization_id = ? WHERE id = ?`, [withOrg.organization_id, canonical.id]);
|
|
21532
21729
|
}
|
|
21533
|
-
}
|
|
21534
|
-
|
|
21535
|
-
|
|
21536
|
-
|
|
21537
|
-
|
|
21538
|
-
|
|
21539
|
-
|
|
21540
|
-
|
|
21541
|
-
|
|
21542
|
-
|
|
21543
|
-
|
|
21544
|
-
|
|
21730
|
+
}
|
|
21731
|
+
await run(`UPDATE people SET canonical_id = ? WHERE id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21732
|
+
await run(`UPDATE activities SET person_id = ? WHERE person_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21733
|
+
await run(`UPDATE opportunities SET owner_id = ? WHERE owner_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21734
|
+
resolved += duplicateIds.length;
|
|
21735
|
+
}
|
|
21736
|
+
return resolved;
|
|
21737
|
+
}
|
|
21738
|
+
var init_resolver2 = __esm({
|
|
21739
|
+
"src/pipeline/resolver.ts"() {
|
|
21740
|
+
"use strict";
|
|
21741
|
+
init_connection();
|
|
21742
|
+
}
|
|
21743
|
+
});
|
|
21744
|
+
|
|
21745
|
+
// src/demo/seed.ts
|
|
21746
|
+
function mulberry32(seed) {
|
|
21747
|
+
let a = seed | 0;
|
|
21748
|
+
return () => {
|
|
21749
|
+
a = a + 1831565813 | 0;
|
|
21750
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
21751
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
21752
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
21753
|
+
};
|
|
21754
|
+
}
|
|
21755
|
+
function createSeededRandom(seed) {
|
|
21756
|
+
const raw = mulberry32(seed);
|
|
21757
|
+
const rng = {
|
|
21758
|
+
next: raw,
|
|
21759
|
+
nextInt(min, max) {
|
|
21760
|
+
return Math.floor(raw() * (max - min + 1)) + min;
|
|
21761
|
+
},
|
|
21762
|
+
nextFloat(min, max) {
|
|
21763
|
+
return raw() * (max - min) + min;
|
|
21764
|
+
},
|
|
21765
|
+
pick(arr) {
|
|
21766
|
+
if (arr.length === 0) {
|
|
21767
|
+
throw new Error("Cannot pick from an empty array");
|
|
21768
|
+
}
|
|
21769
|
+
return arr[Math.floor(raw() * arr.length)];
|
|
21770
|
+
},
|
|
21771
|
+
pickN(arr, n) {
|
|
21772
|
+
const copy = [...arr];
|
|
21773
|
+
rng.shuffle(copy);
|
|
21774
|
+
return copy.slice(0, Math.min(n, copy.length));
|
|
21775
|
+
},
|
|
21776
|
+
shuffle(arr) {
|
|
21777
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
21778
|
+
const j = Math.floor(raw() * (i + 1));
|
|
21779
|
+
const current = arr[i];
|
|
21780
|
+
arr[i] = arr[j];
|
|
21781
|
+
arr[j] = current;
|
|
21782
|
+
}
|
|
21783
|
+
return arr;
|
|
21784
|
+
},
|
|
21785
|
+
chance(probability) {
|
|
21786
|
+
return raw() < probability;
|
|
21787
|
+
},
|
|
21788
|
+
uuid() {
|
|
21789
|
+
const bytes = Array.from({ length: 16 }, () => Math.floor(raw() * 256));
|
|
21790
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
21791
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
21792
|
+
const hex = bytes.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
21793
|
+
return [
|
|
21794
|
+
hex.slice(0, 8),
|
|
21795
|
+
hex.slice(8, 12),
|
|
21796
|
+
hex.slice(12, 16),
|
|
21797
|
+
hex.slice(16, 20),
|
|
21798
|
+
hex.slice(20, 32)
|
|
21799
|
+
].join("-");
|
|
21800
|
+
},
|
|
21801
|
+
date(start, end) {
|
|
21802
|
+
const s = start.getTime();
|
|
21803
|
+
const e = end.getTime();
|
|
21804
|
+
return new Date(s + raw() * (e - s));
|
|
21805
|
+
},
|
|
21806
|
+
weightedPick(items, weights) {
|
|
21807
|
+
if (items.length === 0) {
|
|
21808
|
+
throw new Error("Cannot pick from an empty weighted item list");
|
|
21809
|
+
}
|
|
21810
|
+
const total = weights.reduce((sum, w) => sum + w, 0);
|
|
21811
|
+
let r = raw() * total;
|
|
21812
|
+
for (let i = 0; i < items.length; i++) {
|
|
21813
|
+
r -= weights[i] ?? 0;
|
|
21814
|
+
if (r <= 0) return items[i];
|
|
21815
|
+
}
|
|
21816
|
+
return items[items.length - 1];
|
|
21817
|
+
}
|
|
21818
|
+
};
|
|
21819
|
+
return rng;
|
|
21820
|
+
}
|
|
21821
|
+
var init_seed = __esm({
|
|
21822
|
+
"src/demo/seed.ts"() {
|
|
21823
|
+
"use strict";
|
|
21545
21824
|
}
|
|
21546
21825
|
});
|
|
21547
21826
|
|
|
@@ -23528,320 +23807,93 @@ async function insertDirect(dataset) {
|
|
|
23528
23807
|
metadata: opp.metadata,
|
|
23529
23808
|
created_at: createdAt
|
|
23530
23809
|
});
|
|
23531
|
-
oppIdMap.set(opp.localId, dbId);
|
|
23532
|
-
}
|
|
23533
|
-
for (const act of dataset.activities) {
|
|
23534
|
-
await insertActivity({
|
|
23535
|
-
activity_type: act.activity_type,
|
|
23536
|
-
occurred_at: act.occurred_at,
|
|
23537
|
-
person_id: act.personLocalId ? personIdMap.get(act.personLocalId) ?? null : null,
|
|
23538
|
-
organization_id: act.organizationLocalId ? orgIdMap.get(act.organizationLocalId) ?? null : null,
|
|
23539
|
-
opportunity_id: act.opportunityLocalId ? oppIdMap.get(act.opportunityLocalId) ?? null : null,
|
|
23540
|
-
source_system: act.source_system,
|
|
23541
|
-
source_id: act.source_id,
|
|
23542
|
-
raw_data: act.raw_data,
|
|
23543
|
-
metadata: act.metadata
|
|
23544
|
-
});
|
|
23545
|
-
}
|
|
23546
|
-
for (const campaign of dataset.campaigns) {
|
|
23547
|
-
await insertCampaign({
|
|
23548
|
-
canonical_name: campaign.canonical_name,
|
|
23549
|
-
campaign_type: campaign.campaign_type,
|
|
23550
|
-
source_system: campaign.source_system,
|
|
23551
|
-
source_id: campaign.source_id,
|
|
23552
|
-
raw_data: campaign.raw_data,
|
|
23553
|
-
metadata: campaign.metadata
|
|
23554
|
-
});
|
|
23555
|
-
}
|
|
23556
|
-
for (const event of dataset.revenueEvents ?? []) {
|
|
23557
|
-
await insertRevenueEvent({
|
|
23558
|
-
organization_id: orgIdMap.get(event.organizationLocalId) ?? null,
|
|
23559
|
-
period: event.period,
|
|
23560
|
-
amount: event.amount,
|
|
23561
|
-
event_type: event.event_type,
|
|
23562
|
-
source_system: event.source_system,
|
|
23563
|
-
source_id: event.source_id,
|
|
23564
|
-
raw_data: event.raw_data
|
|
23565
|
-
});
|
|
23566
|
-
}
|
|
23567
|
-
const { invalidateLexiconSeed: invalidateLexiconSeed2 } = await Promise.resolve().then(() => (init_lexicon_seed(), lexicon_seed_exports));
|
|
23568
|
-
invalidateLexiconSeed2();
|
|
23569
|
-
return {
|
|
23570
|
-
mode: "direct",
|
|
23571
|
-
counts: {
|
|
23572
|
-
organizations: dataset.organizations.length,
|
|
23573
|
-
people: dataset.people.length,
|
|
23574
|
-
opportunities: dataset.opportunities.length,
|
|
23575
|
-
activities: dataset.activities.length,
|
|
23576
|
-
campaigns: dataset.campaigns.length,
|
|
23577
|
-
revenue_events: dataset.revenueEvents?.length ?? 0
|
|
23578
|
-
},
|
|
23579
|
-
health: null
|
|
23580
|
-
};
|
|
23581
|
-
}
|
|
23582
|
-
function hashString(str2) {
|
|
23583
|
-
let hash = 0;
|
|
23584
|
-
for (let i = 0; i < str2.length; i++) {
|
|
23585
|
-
const char = str2.charCodeAt(i);
|
|
23586
|
-
hash = (hash << 5) - hash + char | 0;
|
|
23587
|
-
}
|
|
23588
|
-
return Math.abs(hash);
|
|
23589
|
-
}
|
|
23590
|
-
var init_generator = __esm({
|
|
23591
|
-
"src/demo/generator.ts"() {
|
|
23592
|
-
"use strict";
|
|
23593
|
-
init_queries();
|
|
23594
|
-
init_seed();
|
|
23595
|
-
init_scenarios();
|
|
23596
|
-
init_organizations();
|
|
23597
|
-
init_people();
|
|
23598
|
-
init_opportunities();
|
|
23599
|
-
init_activities();
|
|
23600
|
-
init_saas_enrichment();
|
|
23601
|
-
init_salesforce();
|
|
23602
|
-
init_hubspot();
|
|
23603
|
-
init_outreach();
|
|
23604
|
-
}
|
|
23605
|
-
});
|
|
23606
|
-
|
|
23607
|
-
// src/demo/scenario-fit.ts
|
|
23608
|
-
function dealBandFromCycleDays(days) {
|
|
23609
|
-
if (days == null || !Number.isFinite(days) || days <= 0) return void 0;
|
|
23610
|
-
if (days <= 21) return "velocity";
|
|
23611
|
-
if (days <= 45) return "core";
|
|
23612
|
-
if (days <= 90) return "mid";
|
|
23613
|
-
return "enterprise";
|
|
23614
|
-
}
|
|
23615
|
-
function dealBandFromAverageDealSize(raw) {
|
|
23616
|
-
if (!raw) return void 0;
|
|
23617
|
-
const t = raw.trim().toLowerCase();
|
|
23618
|
-
if (!t) return void 0;
|
|
23619
|
-
const match = t.match(/(\d+(?:\.\d+)?)\s*(k|m|million|thousand)?/i);
|
|
23620
|
-
if (!match) return void 0;
|
|
23621
|
-
let n = Number(match[1]);
|
|
23622
|
-
if (!Number.isFinite(n)) return void 0;
|
|
23623
|
-
const unit = (match[2] ?? "").toLowerCase();
|
|
23624
|
-
if (unit === "k" || unit === "thousand") n *= 1e3;
|
|
23625
|
-
else if (unit === "m" || unit === "million") n *= 1e6;
|
|
23626
|
-
else if (n > 0 && n < 500) n *= 1e3;
|
|
23627
|
-
if (n < 15e3) return "velocity";
|
|
23628
|
-
if (n < 5e4) return "core";
|
|
23629
|
-
if (n < 15e4) return "mid";
|
|
23630
|
-
return "enterprise";
|
|
23631
|
-
}
|
|
23632
|
-
function signalsFromProfile(profile) {
|
|
23633
|
-
if (!profile) return {};
|
|
23634
|
-
const text = [
|
|
23635
|
-
profile.industry,
|
|
23636
|
-
profile.product_description,
|
|
23637
|
-
profile.target_customer,
|
|
23638
|
-
profile.user_scope,
|
|
23639
|
-
profile.custom_context
|
|
23640
|
-
].filter(Boolean).join("\n");
|
|
23641
|
-
return {
|
|
23642
|
-
salesMotion: profile.sales_motion,
|
|
23643
|
-
dealBand: dealBandFromAverageDealSize(profile.average_deal_size) ?? dealBandFromCycleDays(profile.sales_cycle_days),
|
|
23644
|
-
cycleDays: profile.sales_cycle_days,
|
|
23645
|
-
text
|
|
23646
|
-
};
|
|
23647
|
-
}
|
|
23648
|
-
function keywordHits(text) {
|
|
23649
|
-
const hits = {};
|
|
23650
|
-
const hay = text.toLowerCase();
|
|
23651
|
-
for (const { scenario, patterns } of KEYWORD_HINTS) {
|
|
23652
|
-
let n = 0;
|
|
23653
|
-
for (const re of patterns) {
|
|
23654
|
-
if (re.test(hay)) n++;
|
|
23655
|
-
}
|
|
23656
|
-
if (n > 0) hits[scenario] = n;
|
|
23657
|
-
}
|
|
23658
|
-
return hits;
|
|
23659
|
-
}
|
|
23660
|
-
function matrixPick(motion, band) {
|
|
23661
|
-
if (motion && band) return MATRIX[motion][band];
|
|
23662
|
-
if (motion) return MOTION_ONLY[motion];
|
|
23663
|
-
if (band) return BAND_ONLY[band];
|
|
23664
|
-
return "even_keel";
|
|
23665
|
-
}
|
|
23666
|
-
function reasonFor(scenario, signals, via) {
|
|
23667
|
-
const s = getScenario(scenario);
|
|
23668
|
-
if (via === "keywords") {
|
|
23669
|
-
return `Your notes sound like ${s.label} \u2014 ${s.hook}`;
|
|
23810
|
+
oppIdMap.set(opp.localId, dbId);
|
|
23670
23811
|
}
|
|
23671
|
-
const
|
|
23672
|
-
|
|
23673
|
-
|
|
23674
|
-
|
|
23812
|
+
for (const act of dataset.activities) {
|
|
23813
|
+
await insertActivity({
|
|
23814
|
+
activity_type: act.activity_type,
|
|
23815
|
+
occurred_at: act.occurred_at,
|
|
23816
|
+
person_id: act.personLocalId ? personIdMap.get(act.personLocalId) ?? null : null,
|
|
23817
|
+
organization_id: act.organizationLocalId ? orgIdMap.get(act.organizationLocalId) ?? null : null,
|
|
23818
|
+
opportunity_id: act.opportunityLocalId ? oppIdMap.get(act.opportunityLocalId) ?? null : null,
|
|
23819
|
+
source_system: act.source_system,
|
|
23820
|
+
source_id: act.source_id,
|
|
23821
|
+
raw_data: act.raw_data,
|
|
23822
|
+
metadata: act.metadata
|
|
23823
|
+
});
|
|
23675
23824
|
}
|
|
23676
|
-
|
|
23677
|
-
|
|
23678
|
-
|
|
23679
|
-
|
|
23680
|
-
|
|
23681
|
-
|
|
23682
|
-
|
|
23683
|
-
|
|
23684
|
-
|
|
23685
|
-
}
|
|
23686
|
-
function inferDemoScenario(signals) {
|
|
23687
|
-
const text = signals.text?.trim() ?? "";
|
|
23688
|
-
if (text) {
|
|
23689
|
-
const hits = keywordHits(text);
|
|
23690
|
-
let best;
|
|
23691
|
-
let bestN = 0;
|
|
23692
|
-
for (const id of NAMED_DEMO_SCENARIOS) {
|
|
23693
|
-
const n = hits[id] ?? 0;
|
|
23694
|
-
if (n > bestN) {
|
|
23695
|
-
best = id;
|
|
23696
|
-
bestN = n;
|
|
23697
|
-
}
|
|
23698
|
-
}
|
|
23699
|
-
if (best && bestN > 0) {
|
|
23700
|
-
return { scenario: best, reason: reasonFor(best, signals, "keywords"), source: "heuristic" };
|
|
23701
|
-
}
|
|
23825
|
+
for (const campaign of dataset.campaigns) {
|
|
23826
|
+
await insertCampaign({
|
|
23827
|
+
canonical_name: campaign.canonical_name,
|
|
23828
|
+
campaign_type: campaign.campaign_type,
|
|
23829
|
+
source_system: campaign.source_system,
|
|
23830
|
+
source_id: campaign.source_id,
|
|
23831
|
+
raw_data: campaign.raw_data,
|
|
23832
|
+
metadata: campaign.metadata
|
|
23833
|
+
});
|
|
23702
23834
|
}
|
|
23703
|
-
const
|
|
23704
|
-
|
|
23705
|
-
|
|
23706
|
-
|
|
23707
|
-
|
|
23708
|
-
|
|
23709
|
-
|
|
23710
|
-
|
|
23711
|
-
|
|
23712
|
-
|
|
23713
|
-
setConfigValue(PREF_MOTION_KEY, opts.motion);
|
|
23714
|
-
if (opts.syncSalesMotion && !isProfileConfigured(loadProfile())) {
|
|
23715
|
-
setConfigValue("sales-motion", opts.motion);
|
|
23716
|
-
}
|
|
23835
|
+
for (const event of dataset.revenueEvents ?? []) {
|
|
23836
|
+
await insertRevenueEvent({
|
|
23837
|
+
organization_id: orgIdMap.get(event.organizationLocalId) ?? null,
|
|
23838
|
+
period: event.period,
|
|
23839
|
+
amount: event.amount,
|
|
23840
|
+
event_type: event.event_type,
|
|
23841
|
+
source_system: event.source_system,
|
|
23842
|
+
source_id: event.source_id,
|
|
23843
|
+
raw_data: event.raw_data
|
|
23844
|
+
});
|
|
23717
23845
|
}
|
|
23718
|
-
|
|
23846
|
+
const { invalidateLexiconSeed: invalidateLexiconSeed2 } = await Promise.resolve().then(() => (init_lexicon_seed(), lexicon_seed_exports));
|
|
23847
|
+
invalidateLexiconSeed2();
|
|
23848
|
+
return {
|
|
23849
|
+
mode: "direct",
|
|
23850
|
+
counts: {
|
|
23851
|
+
organizations: dataset.organizations.length,
|
|
23852
|
+
people: dataset.people.length,
|
|
23853
|
+
opportunities: dataset.opportunities.length,
|
|
23854
|
+
activities: dataset.activities.length,
|
|
23855
|
+
campaigns: dataset.campaigns.length,
|
|
23856
|
+
revenue_events: dataset.revenueEvents?.length ?? 0
|
|
23857
|
+
},
|
|
23858
|
+
health: null
|
|
23859
|
+
};
|
|
23719
23860
|
}
|
|
23720
|
-
function
|
|
23721
|
-
|
|
23722
|
-
|
|
23723
|
-
|
|
23724
|
-
|
|
23725
|
-
|
|
23726
|
-
|
|
23727
|
-
};
|
|
23728
|
-
});
|
|
23861
|
+
function hashString(str2) {
|
|
23862
|
+
let hash = 0;
|
|
23863
|
+
for (let i = 0; i < str2.length; i++) {
|
|
23864
|
+
const char = str2.charCodeAt(i);
|
|
23865
|
+
hash = (hash << 5) - hash + char | 0;
|
|
23866
|
+
}
|
|
23867
|
+
return Math.abs(hash);
|
|
23729
23868
|
}
|
|
23730
|
-
var
|
|
23731
|
-
|
|
23732
|
-
"src/demo/scenario-fit.ts"() {
|
|
23869
|
+
var init_generator = __esm({
|
|
23870
|
+
"src/demo/generator.ts"() {
|
|
23733
23871
|
"use strict";
|
|
23734
|
-
|
|
23735
|
-
|
|
23872
|
+
init_queries();
|
|
23873
|
+
init_seed();
|
|
23736
23874
|
init_scenarios();
|
|
23737
|
-
|
|
23738
|
-
|
|
23739
|
-
|
|
23740
|
-
|
|
23741
|
-
|
|
23742
|
-
|
|
23743
|
-
|
|
23744
|
-
|
|
23745
|
-
},
|
|
23746
|
-
{
|
|
23747
|
-
value: "smb_velocity",
|
|
23748
|
-
label: "High-volume SMB",
|
|
23749
|
-
description: "Fast cycles, lots of small deals, outbound or inbound machine"
|
|
23750
|
-
},
|
|
23751
|
-
{
|
|
23752
|
-
value: "mid_market",
|
|
23753
|
-
label: "Mid-market, structured process",
|
|
23754
|
-
description: "A real sales cycle, a few stakeholders, moderate ACV"
|
|
23755
|
-
},
|
|
23756
|
-
{
|
|
23757
|
-
value: "enterprise",
|
|
23758
|
-
label: "Enterprise, long cycles",
|
|
23759
|
-
description: "Large deals, many buyers, quarters not weeks"
|
|
23760
|
-
}
|
|
23761
|
-
];
|
|
23762
|
-
DEAL_BAND_CHOICES = [
|
|
23763
|
-
{
|
|
23764
|
-
value: "velocity",
|
|
23765
|
-
label: "Under ~$15K, days to a couple of weeks",
|
|
23766
|
-
description: "Velocity / transactional"
|
|
23767
|
-
},
|
|
23768
|
-
{
|
|
23769
|
-
value: "core",
|
|
23770
|
-
label: "~$15K\u2013$50K, a few weeks",
|
|
23771
|
-
description: "Core SMB"
|
|
23772
|
-
},
|
|
23773
|
-
{
|
|
23774
|
-
value: "mid",
|
|
23775
|
-
label: "~$50K\u2013$150K, 1\u20133 months",
|
|
23776
|
-
description: "Classic mid-market"
|
|
23777
|
-
},
|
|
23778
|
-
{
|
|
23779
|
-
value: "enterprise",
|
|
23780
|
-
label: "$150K+, a quarter or more",
|
|
23781
|
-
description: "Enterprise / strategic"
|
|
23782
|
-
}
|
|
23783
|
-
];
|
|
23784
|
-
MATRIX = {
|
|
23785
|
-
plg: {
|
|
23786
|
-
velocity: "leaky_bucket",
|
|
23787
|
-
core: "leaky_bucket",
|
|
23788
|
-
mid: "hidden_crisis",
|
|
23789
|
-
enterprise: "hidden_crisis"
|
|
23790
|
-
},
|
|
23791
|
-
smb_velocity: {
|
|
23792
|
-
velocity: "busy_bees",
|
|
23793
|
-
core: "busy_bees",
|
|
23794
|
-
mid: "leaky_bucket",
|
|
23795
|
-
enterprise: "lone_wolf"
|
|
23796
|
-
},
|
|
23797
|
-
mid_market: {
|
|
23798
|
-
velocity: "busy_bees",
|
|
23799
|
-
core: "stale_pipeline",
|
|
23800
|
-
mid: "stale_pipeline",
|
|
23801
|
-
enterprise: "hidden_crisis"
|
|
23802
|
-
},
|
|
23803
|
-
enterprise: {
|
|
23804
|
-
velocity: "lone_wolf",
|
|
23805
|
-
core: "lone_wolf",
|
|
23806
|
-
mid: "hidden_crisis",
|
|
23807
|
-
enterprise: "hidden_crisis"
|
|
23808
|
-
}
|
|
23809
|
-
};
|
|
23810
|
-
MOTION_ONLY = {
|
|
23811
|
-
plg: "leaky_bucket",
|
|
23812
|
-
smb_velocity: "busy_bees",
|
|
23813
|
-
mid_market: "stale_pipeline",
|
|
23814
|
-
enterprise: "hidden_crisis"
|
|
23815
|
-
};
|
|
23816
|
-
BAND_ONLY = {
|
|
23817
|
-
velocity: "busy_bees",
|
|
23818
|
-
core: "leaky_bucket",
|
|
23819
|
-
mid: "stale_pipeline",
|
|
23820
|
-
enterprise: "hidden_crisis"
|
|
23821
|
-
};
|
|
23822
|
-
KEYWORD_HINTS = [
|
|
23823
|
-
{ scenario: "even_keel", patterns: [/\beven keel\b/, /\breasonably healthy\b/, /\bno crisis\b/, /\bjust evaluating\b/, /\bgreen[- ]field\b/] },
|
|
23824
|
-
{ scenario: "compound_pain", patterns: [/\beverything('?s| is) (on fire|red|broken)\b/, /\bcompound\b/, /\ball (five )?vitals\b/, /\bmultiple problems\b/] },
|
|
23825
|
-
{ scenario: "leaky_bucket", patterns: [/\bhandoff\b/, /\bmqls?\b/, /\bleak/, /\bdrop[- ]rate/, /\bvanish/, /\brouting\b/, /\bmarketing.?sales\b/] },
|
|
23826
|
-
{ scenario: "stale_pipeline", patterns: [/\bzombie/, /\bstale\b/, /\bpast[- ]due\b/, /\bforecast(ing)? on fiction\b/, /\bstuck in negotiation\b/, /\bquiet deals?\b/] },
|
|
23827
|
-
{ scenario: "lone_wolf", patterns: [/\bsingle[- ]thread/, /\blone wolf\b/, /\bone contact\b/, /\bchampion leaves\b/] },
|
|
23828
|
-
{ scenario: "busy_bees", patterns: [/\bspray/, /\bnois(e|y)\b/, /\bmisdirected\b/, /\bactivity (volume|metrics)\b/, /\bbusy bees\b/, /\bnot (on|hitting) pipeline\b/] },
|
|
23829
|
-
{ scenario: "hidden_crisis", patterns: [/\bhidden crisis\b/, /\benterprise (is )?(dying|red|stale)\b/, /\bsegment.{0,20}mask/, /\baverages? (look|looks) (fine|okay|yellow)\b/] }
|
|
23830
|
-
];
|
|
23875
|
+
init_organizations();
|
|
23876
|
+
init_people();
|
|
23877
|
+
init_opportunities();
|
|
23878
|
+
init_activities();
|
|
23879
|
+
init_saas_enrichment();
|
|
23880
|
+
init_salesforce();
|
|
23881
|
+
init_hubspot();
|
|
23882
|
+
init_outreach();
|
|
23831
23883
|
}
|
|
23832
23884
|
});
|
|
23833
23885
|
|
|
23834
23886
|
// src/demo/taxonomy-cache.ts
|
|
23835
|
-
import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, existsSync as
|
|
23887
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, existsSync as existsSync23, mkdirSync as mkdirSync13, unlinkSync as unlinkSync3 } from "fs";
|
|
23836
23888
|
import { homedir as homedir7 } from "os";
|
|
23837
23889
|
import { join as join24 } from "path";
|
|
23838
23890
|
function ensureDir6() {
|
|
23839
|
-
if (!
|
|
23891
|
+
if (!existsSync23(NTRP_DIR4)) {
|
|
23840
23892
|
mkdirSync13(NTRP_DIR4, { recursive: true });
|
|
23841
23893
|
}
|
|
23842
23894
|
}
|
|
23843
23895
|
function loadCachedTaxonomy(profile) {
|
|
23844
|
-
if (!
|
|
23896
|
+
if (!existsSync23(TAXONOMY_PATH)) return null;
|
|
23845
23897
|
try {
|
|
23846
23898
|
const parsed = JSON.parse(readFileSync19(TAXONOMY_PATH, "utf-8"));
|
|
23847
23899
|
if (!parsed || typeof parsed !== "object") return null;
|
|
@@ -24318,7 +24370,7 @@ __export(inbox_setup_exports, {
|
|
|
24318
24370
|
shouldOfferInboxSkillSetup: () => shouldOfferInboxSkillSetup
|
|
24319
24371
|
});
|
|
24320
24372
|
import chalk27 from "chalk";
|
|
24321
|
-
import { existsSync as
|
|
24373
|
+
import { existsSync as existsSync24 } from "fs";
|
|
24322
24374
|
function markDemoOffered() {
|
|
24323
24375
|
setConfigValue("ai-inbox-nudge-seen", "true");
|
|
24324
24376
|
}
|
|
@@ -24350,7 +24402,7 @@ function printSkipHint(beat) {
|
|
|
24350
24402
|
async function reuseInboxFolderIfPresent(session, beat, folderPath) {
|
|
24351
24403
|
if (getAiInboxDir()) return false;
|
|
24352
24404
|
const candidates = folderPath ? [folderPath] : [.../* @__PURE__ */ new Set([defaultAiInboxDir(), legacyAiInboxDir()])];
|
|
24353
|
-
const existing = candidates.find((p) =>
|
|
24405
|
+
const existing = candidates.find((p) => existsSync24(p));
|
|
24354
24406
|
if (!existing) return false;
|
|
24355
24407
|
console.log(" " + chalk27.dim("Pickup folder still on disk: ") + existing);
|
|
24356
24408
|
const reuse = await session.confirm("Reuse this pickup folder?", true);
|
|
@@ -24456,7 +24508,7 @@ __export(ingest_exports, {
|
|
|
24456
24508
|
handler: () => handler3
|
|
24457
24509
|
});
|
|
24458
24510
|
import chalk28 from "chalk";
|
|
24459
|
-
import { readFileSync as readFileSync20, existsSync as
|
|
24511
|
+
import { readFileSync as readFileSync20, existsSync as existsSync25 } from "fs";
|
|
24460
24512
|
import { basename as basename6 } from "path";
|
|
24461
24513
|
async function handler3(args, ctx) {
|
|
24462
24514
|
const { positional, flags } = parseArgs(args, [
|
|
@@ -24480,7 +24532,7 @@ async function handler3(args, ctx) {
|
|
|
24480
24532
|
console.error(chalk28.dim(" /ingest --demo [--scenario <name>]"));
|
|
24481
24533
|
process.exit(1);
|
|
24482
24534
|
}
|
|
24483
|
-
if (!
|
|
24535
|
+
if (!existsSync25(file)) {
|
|
24484
24536
|
console.error(chalk28.red(` File not found: ${file}`));
|
|
24485
24537
|
process.exit(1);
|
|
24486
24538
|
}
|
|
@@ -24862,39 +24914,54 @@ var ingest_chat_exports = {};
|
|
|
24862
24914
|
__export(ingest_chat_exports, {
|
|
24863
24915
|
extractFilePath: () => extractFilePath,
|
|
24864
24916
|
ingestFromChat: () => ingestFromChat,
|
|
24917
|
+
ingestPathFromChat: () => ingestPathFromChat,
|
|
24865
24918
|
isDemoIntent: () => isDemoIntent,
|
|
24919
|
+
listCsvsInFolder: () => listCsvsInFolder,
|
|
24866
24920
|
loadDemoFromChat: () => loadDemoFromChat,
|
|
24867
24921
|
looksLikeFilePath: () => looksLikeFilePath
|
|
24868
24922
|
});
|
|
24869
|
-
import { existsSync as
|
|
24870
|
-
import { basename as basename7, resolve as resolve9 } from "path";
|
|
24923
|
+
import { existsSync as existsSync26, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
24924
|
+
import { basename as basename7, join as join25, resolve as resolve9 } from "path";
|
|
24871
24925
|
import { homedir as homedir8 } from "os";
|
|
24872
24926
|
import chalk30 from "chalk";
|
|
24873
24927
|
function extractFilePath(input) {
|
|
24874
|
-
const trimmed = input.trim();
|
|
24928
|
+
const trimmed = input.trim().replace(/^["']|["']$/g, "");
|
|
24929
|
+
if (!trimmed) return null;
|
|
24875
24930
|
const patterns = [
|
|
24876
|
-
/^["'](
|
|
24877
|
-
/^@(
|
|
24878
|
-
/(?:here'?s|file|path|upload)[:\s]+["']?([^\s"']
|
|
24879
|
-
/^~\/\S
|
|
24880
|
-
/^\.\.?\/\S
|
|
24881
|
-
/^\/\S
|
|
24882
|
-
/^[A-Za-z]:\\[^\s]
|
|
24883
|
-
/^[^\s]
|
|
24931
|
+
/^["'](.+)["']$/i,
|
|
24932
|
+
/^@(.+)$/i,
|
|
24933
|
+
/(?:here'?s|file|path|upload|folder|dir)[:\s]+["']?([^\s"']+)["']?/i,
|
|
24934
|
+
/^~\/\S+$/i,
|
|
24935
|
+
/^\.\.?\/\S+$/i,
|
|
24936
|
+
/^\/\S+$/i,
|
|
24937
|
+
/^[A-Za-z]:\\[^\s]+$/i,
|
|
24938
|
+
/^[^\s]+$/i
|
|
24884
24939
|
];
|
|
24885
24940
|
for (const re of patterns) {
|
|
24886
24941
|
const m = trimmed.match(re);
|
|
24887
|
-
|
|
24888
|
-
|
|
24889
|
-
|
|
24890
|
-
|
|
24891
|
-
if (
|
|
24892
|
-
|
|
24893
|
-
|
|
24942
|
+
const candidate = (m?.[1] ?? (re.test(trimmed) ? trimmed : null))?.replace(/^["']|["']$/g, "");
|
|
24943
|
+
if (!candidate) continue;
|
|
24944
|
+
if (!looksLikePathToken(candidate)) continue;
|
|
24945
|
+
const p = expandPath(candidate);
|
|
24946
|
+
if (existsSync26(p)) {
|
|
24947
|
+
try {
|
|
24948
|
+
const st = statSync5(p);
|
|
24949
|
+
if (st.isFile() || st.isDirectory()) return p;
|
|
24950
|
+
} catch {
|
|
24951
|
+
}
|
|
24894
24952
|
}
|
|
24895
24953
|
}
|
|
24896
24954
|
return null;
|
|
24897
24955
|
}
|
|
24956
|
+
function looksLikePathToken(token) {
|
|
24957
|
+
if (token.length < 2) return false;
|
|
24958
|
+
if (/^(yes|no|y|n|back|b|prev|cancel|skip|help|demo)$/i.test(token)) return false;
|
|
24959
|
+
if (token.includes("/") || token.includes("\\")) return true;
|
|
24960
|
+
if (token.startsWith("~")) return true;
|
|
24961
|
+
if (/^[A-Za-z]:/.test(token)) return true;
|
|
24962
|
+
if (/\.[A-Za-z0-9]{1,8}$/.test(token)) return true;
|
|
24963
|
+
return false;
|
|
24964
|
+
}
|
|
24898
24965
|
function expandPath(p) {
|
|
24899
24966
|
if (p.startsWith("~/")) return resolve9(homedir8(), p.slice(2));
|
|
24900
24967
|
return resolve9(p);
|
|
@@ -24902,6 +24969,73 @@ function expandPath(p) {
|
|
|
24902
24969
|
function looksLikeFilePath(input) {
|
|
24903
24970
|
return extractFilePath(input) !== null;
|
|
24904
24971
|
}
|
|
24972
|
+
function listCsvsInFolder(dir) {
|
|
24973
|
+
try {
|
|
24974
|
+
if (!statSync5(dir).isDirectory()) return [];
|
|
24975
|
+
return readdirSync6(dir).filter((name) => name.toLowerCase().endsWith(".csv")).map((name) => join25(dir, name)).sort();
|
|
24976
|
+
} catch {
|
|
24977
|
+
return [];
|
|
24978
|
+
}
|
|
24979
|
+
}
|
|
24980
|
+
async function ingestPathFromChat(ctx, rawPath) {
|
|
24981
|
+
let st;
|
|
24982
|
+
try {
|
|
24983
|
+
st = statSync5(rawPath);
|
|
24984
|
+
} catch {
|
|
24985
|
+
console.log(" " + chalk30.red(`Path not found: ${rawPath}`));
|
|
24986
|
+
return false;
|
|
24987
|
+
}
|
|
24988
|
+
if (st.isDirectory()) {
|
|
24989
|
+
const csvs = listCsvsInFolder(rawPath);
|
|
24990
|
+
if (csvs.length === 0) {
|
|
24991
|
+
console.log();
|
|
24992
|
+
console.log(" " + paint("accent", "Folder noted") + chalk30.dim(` \u2014 ${rawPath}`));
|
|
24993
|
+
console.log(" " + chalk30.dim("No CSV files one level deep. Drop a .csv path, or put exports in that folder."));
|
|
24994
|
+
ctx.attachments = [
|
|
24995
|
+
...ctx.attachments ?? [],
|
|
24996
|
+
{ path: rawPath, ingested_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
24997
|
+
];
|
|
24998
|
+
saveSessionState(ctx);
|
|
24999
|
+
markProductionDataSeen();
|
|
25000
|
+
return false;
|
|
25001
|
+
}
|
|
25002
|
+
if (csvs.length === 1) {
|
|
25003
|
+
console.log(" " + chalk30.dim(`Found ${basename7(csvs[0])} in folder \u2014 ingesting.`));
|
|
25004
|
+
return ingestFromChat(ctx, csvs[0]);
|
|
25005
|
+
}
|
|
25006
|
+
if (!ctx.rl) {
|
|
25007
|
+
console.log(" " + chalk30.dim(`Found ${csvs.length} CSVs \u2014 re-run interactively to pick one.`));
|
|
25008
|
+
return false;
|
|
25009
|
+
}
|
|
25010
|
+
const prompts = createPromptSession(ctx.rl, ctx);
|
|
25011
|
+
try {
|
|
25012
|
+
const picked = await prompts.choose(
|
|
25013
|
+
"Which CSV in that folder?",
|
|
25014
|
+
csvs.map((p) => ({ value: p, label: basename7(p), description: p })),
|
|
25015
|
+
{ default: csvs[0] }
|
|
25016
|
+
);
|
|
25017
|
+
return ingestFromChat(ctx, picked);
|
|
25018
|
+
} finally {
|
|
25019
|
+
prompts.close();
|
|
25020
|
+
}
|
|
25021
|
+
}
|
|
25022
|
+
if (st.isFile()) {
|
|
25023
|
+
if (!rawPath.toLowerCase().endsWith(".csv")) {
|
|
25024
|
+
console.log();
|
|
25025
|
+
console.log(" " + paint("accent", "Path noted") + chalk30.dim(` \u2014 ${rawPath}`));
|
|
25026
|
+
console.log(" " + chalk30.dim("NTRP ingests CSV exports today. Drop a .csv from that location when ready."));
|
|
25027
|
+
ctx.attachments = [
|
|
25028
|
+
...ctx.attachments ?? [],
|
|
25029
|
+
{ path: rawPath, ingested_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
25030
|
+
];
|
|
25031
|
+
saveSessionState(ctx);
|
|
25032
|
+
markProductionDataSeen();
|
|
25033
|
+
return false;
|
|
25034
|
+
}
|
|
25035
|
+
return ingestFromChat(ctx, rawPath);
|
|
25036
|
+
}
|
|
25037
|
+
return false;
|
|
25038
|
+
}
|
|
24905
25039
|
async function ingestFromChat(ctx, filePath) {
|
|
24906
25040
|
if (!ctx.rl) {
|
|
24907
25041
|
console.log(" " + chalk30.red("Ingest confirm requires interactive mode."));
|
|
@@ -24966,6 +25100,7 @@ async function ingestFromChat(ctx, filePath) {
|
|
|
24966
25100
|
};
|
|
24967
25101
|
invalidateGapAudit(ctx);
|
|
24968
25102
|
saveSessionState(ctx);
|
|
25103
|
+
markProductionDataSeen();
|
|
24969
25104
|
console.log();
|
|
24970
25105
|
console.log(" " + paint("accent", "\u2713 Data loaded") + chalk30.dim(` \u2014 ${name}`));
|
|
24971
25106
|
recordMessage(ctx, "user", `[ingested ${name}]`);
|
|
@@ -25041,6 +25176,7 @@ async function loadDemoFromChat(ctx, scenario, opts = {}) {
|
|
|
25041
25176
|
counts,
|
|
25042
25177
|
ingested_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
25043
25178
|
};
|
|
25179
|
+
markDemoDataSeen();
|
|
25044
25180
|
if (!ctx.scope) {
|
|
25045
25181
|
const { proposeScopeFromIntent: proposeScopeFromIntent2 } = await Promise.resolve().then(() => (init_scope(), scope_exports));
|
|
25046
25182
|
const proposal = proposeScopeFromIntent2("demo pipeline and metrics");
|
|
@@ -25077,6 +25213,7 @@ var init_ingest_chat = __esm({
|
|
|
25077
25213
|
init_compute2();
|
|
25078
25214
|
init_theme();
|
|
25079
25215
|
init_demo();
|
|
25216
|
+
init_onboard_tiers();
|
|
25080
25217
|
}
|
|
25081
25218
|
});
|
|
25082
25219
|
|
|
@@ -25835,9 +25972,9 @@ async function handleGetSessionBrief(input) {
|
|
|
25835
25972
|
if (!target) {
|
|
25836
25973
|
return { error: `No session matching "${raw}".` };
|
|
25837
25974
|
}
|
|
25838
|
-
const { existsSync:
|
|
25975
|
+
const { existsSync: existsSync29, readFileSync: readFileSync23 } = await import("fs");
|
|
25839
25976
|
const briefPath = contextDocPathForSession2(target.id);
|
|
25840
|
-
if (!
|
|
25977
|
+
if (!existsSync29(briefPath)) {
|
|
25841
25978
|
return {
|
|
25842
25979
|
session_id: target.id,
|
|
25843
25980
|
error: "No context brief on disk for this session (created before brief storage existed).",
|
|
@@ -26852,14 +26989,14 @@ var init_ask = __esm({
|
|
|
26852
26989
|
});
|
|
26853
26990
|
|
|
26854
26991
|
// src/services/setup.ts
|
|
26855
|
-
import { existsSync as
|
|
26856
|
-
import { join as
|
|
26992
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync21, writeFileSync as writeFileSync16 } from "fs";
|
|
26993
|
+
import { join as join26 } from "path";
|
|
26857
26994
|
function setupCheck() {
|
|
26858
26995
|
const home = ntrpHome();
|
|
26859
26996
|
let writable = false;
|
|
26860
26997
|
try {
|
|
26861
26998
|
mkdirSync14(home, { recursive: true });
|
|
26862
|
-
const probe =
|
|
26999
|
+
const probe = join26(home, ".write-check");
|
|
26863
27000
|
writeFileSync16(probe, "ok\n");
|
|
26864
27001
|
writable = true;
|
|
26865
27002
|
} catch {
|
|
@@ -26911,11 +27048,11 @@ var init_setup = __esm({
|
|
|
26911
27048
|
});
|
|
26912
27049
|
|
|
26913
27050
|
// src/version.ts
|
|
26914
|
-
import { existsSync as
|
|
26915
|
-
import { dirname as dirname5, join as
|
|
27051
|
+
import { existsSync as existsSync28, readFileSync as readFileSync22 } from "fs";
|
|
27052
|
+
import { dirname as dirname5, join as join27 } from "path";
|
|
26916
27053
|
import { fileURLToPath } from "url";
|
|
26917
27054
|
function readVersionFromPackageJson(packageJsonPath) {
|
|
26918
|
-
if (!
|
|
27055
|
+
if (!existsSync28(packageJsonPath)) return null;
|
|
26919
27056
|
try {
|
|
26920
27057
|
const pkg = JSON.parse(readFileSync22(packageJsonPath, "utf-8"));
|
|
26921
27058
|
if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
|
|
@@ -26925,7 +27062,7 @@ function readVersionFromPackageJson(packageJsonPath) {
|
|
|
26925
27062
|
}
|
|
26926
27063
|
function readVersionNearEntry(entryPath) {
|
|
26927
27064
|
const start = dirname5(entryPath);
|
|
26928
|
-
for (const rel of [
|
|
27065
|
+
for (const rel of [join27(start, "..", "package.json"), join27(start, "../..", "package.json")]) {
|
|
26929
27066
|
const version = readVersionFromPackageJson(rel);
|
|
26930
27067
|
if (version) return version;
|
|
26931
27068
|
}
|