@sonnechasser/ntrp 1.4.9 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +793 -176
- package/dist/mcp/server.js +1137 -1001
- 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
|
{
|
|
@@ -10375,7 +10379,7 @@ hidden: true
|
|
|
10375
10379
|
---
|
|
10376
10380
|
|
|
10377
10381
|
Mark every in-progress session as ended. Transcripts and dataset files stay.
|
|
10378
|
-
Interactive ntrp only. Confirm with
|
|
10382
|
+
Interactive ntrp only. Confirm with \u23CE no (type yes to proceed), or pass \`--confirm\` in one-shot.`
|
|
10379
10383
|
},
|
|
10380
10384
|
{
|
|
10381
10385
|
name: "deactivate-demo",
|
|
@@ -13808,6 +13812,8 @@ var init_repl_globals = __esm({
|
|
|
13808
13812
|
var prompts_exports = {};
|
|
13809
13813
|
__export(prompts_exports, {
|
|
13810
13814
|
createPromptSession: () => createPromptSession,
|
|
13815
|
+
formatConfirmDefaultHint: () => formatConfirmDefaultHint,
|
|
13816
|
+
formatEnterDefaultHint: () => formatEnterDefaultHint,
|
|
13811
13817
|
presentRecommendedFirst: () => presentRecommendedFirst,
|
|
13812
13818
|
presentRecommendedMultiFirst: () => presentRecommendedMultiFirst,
|
|
13813
13819
|
resolveAskMultiInput: () => resolveAskMultiInput,
|
|
@@ -13828,17 +13834,29 @@ function secretPromptLine(question) {
|
|
|
13828
13834
|
function stripTerminalArtifacts(input) {
|
|
13829
13835
|
return input.replace(/\x1b\[[0-9;]*[a-zA-Z~]/g, "").replace(/\x1b\][^\x07]*(\x07|\x1b\\)/g, "").replace(/\x1b\[200~/g, "").replace(/\x1b\[201~/g, "");
|
|
13830
13836
|
}
|
|
13837
|
+
function formatEnterDefaultHint(defaultValue) {
|
|
13838
|
+
const lower = defaultValue.trim().toLowerCase();
|
|
13839
|
+
if (lower === "y" || lower === "yes") return "\u23CE yes";
|
|
13840
|
+
if (lower === "n" || lower === "no") return "\u23CE no";
|
|
13841
|
+
return defaultValue;
|
|
13842
|
+
}
|
|
13843
|
+
function formatConfirmDefaultHint(defaultYes = true) {
|
|
13844
|
+
return formatEnterDefaultHint(defaultYes ? "yes" : "no");
|
|
13845
|
+
}
|
|
13831
13846
|
function renderQuestion(question, defaultValue) {
|
|
13832
13847
|
const base = ` ${marker()}${bold(question)}`;
|
|
13833
13848
|
if (defaultValue !== void 0 && defaultValue !== "") {
|
|
13834
|
-
return `${base} ${chalk5.dim(`[${defaultValue}]`)} `;
|
|
13849
|
+
return `${base} ${chalk5.dim(`[${formatEnterDefaultHint(defaultValue)}]`)} `;
|
|
13835
13850
|
}
|
|
13836
13851
|
return `${base} `;
|
|
13837
13852
|
}
|
|
13838
13853
|
function resolveConfirmInput(raw, defaultYes = true) {
|
|
13839
13854
|
const answer = raw.trim().toLowerCase();
|
|
13840
13855
|
if (!answer) return defaultYes;
|
|
13841
|
-
|
|
13856
|
+
if (answer === "y" || answer === "yes" || answer === "yeah" || answer === "yep") {
|
|
13857
|
+
return true;
|
|
13858
|
+
}
|
|
13859
|
+
return false;
|
|
13842
13860
|
}
|
|
13843
13861
|
function presentRecommendedFirst(choices, recommended) {
|
|
13844
13862
|
if (choices.length === 0) return [];
|
|
@@ -13913,8 +13931,7 @@ function createPromptSession(existing, ctx) {
|
|
|
13913
13931
|
}
|
|
13914
13932
|
}
|
|
13915
13933
|
async function confirm(question, defaultYes = true) {
|
|
13916
|
-
const
|
|
13917
|
-
const raw = (await rl2.question(renderQuestion(question, hint))).trim();
|
|
13934
|
+
const raw = (await rl2.question(renderQuestion(question, defaultYes ? "yes" : "no"))).trim();
|
|
13918
13935
|
assertNotGlobalReplCommand(raw);
|
|
13919
13936
|
return resolveConfirmInput(raw, defaultYes);
|
|
13920
13937
|
}
|
|
@@ -18849,7 +18866,7 @@ async function handleStrategizeFlow(input, ctx) {
|
|
|
18849
18866
|
" " + chalk17.dim("That looks like a question. A strategy objective is waiting.")
|
|
18850
18867
|
);
|
|
18851
18868
|
console.log(
|
|
18852
|
-
" " + chalk17.dim("
|
|
18869
|
+
" " + chalk17.dim("Confirm? ") + chalk17.cyan("\u23CE yes") + chalk17.dim(" \xB7 ") + chalk17.cyan("b back") + chalk17.dim(" / ") + chalk17.cyan("adjust") + chalk17.dim(" \xB7 ") + chalk17.cyan("cancel") + chalk17.dim(" to answer questions first.")
|
|
18853
18870
|
);
|
|
18854
18871
|
console.log();
|
|
18855
18872
|
return "Awaiting confirm";
|
|
@@ -18862,7 +18879,7 @@ async function handleStrategizeFlow(input, ctx) {
|
|
|
18862
18879
|
}
|
|
18863
18880
|
console.log();
|
|
18864
18881
|
console.log(
|
|
18865
|
-
" " + chalk17.dim("
|
|
18882
|
+
" " + chalk17.dim("Confirm? ") + chalk17.cyan("\u23CE yes") + chalk17.dim(" \xB7 ") + chalk17.cyan("b back") + chalk17.dim(" / ") + chalk17.cyan("adjust") + chalk17.dim(" \xB7 ") + chalk17.cyan("cancel")
|
|
18866
18883
|
);
|
|
18867
18884
|
console.log();
|
|
18868
18885
|
return "Awaiting confirm";
|
|
@@ -20838,724 +20855,985 @@ var init_demo = __esm({
|
|
|
20838
20855
|
}
|
|
20839
20856
|
});
|
|
20840
20857
|
|
|
20841
|
-
// src/
|
|
20842
|
-
var
|
|
20843
|
-
__export(
|
|
20844
|
-
|
|
20845
|
-
|
|
20858
|
+
// src/demo/scenarios.ts
|
|
20859
|
+
var scenarios_exports = {};
|
|
20860
|
+
__export(scenarios_exports, {
|
|
20861
|
+
NAMED_DEMO_SCENARIOS: () => NAMED_DEMO_SCENARIOS,
|
|
20862
|
+
SCENARIOS: () => SCENARIOS,
|
|
20863
|
+
SCENARIO_LIST: () => SCENARIO_LIST,
|
|
20864
|
+
blendScenarios: () => blendScenarios,
|
|
20865
|
+
getScenario: () => getScenario,
|
|
20866
|
+
isNamedDemoScenario: () => isNamedDemoScenario,
|
|
20867
|
+
pickRandomScenario: () => pickRandomScenario,
|
|
20868
|
+
resolveScenarioInput: () => resolveScenarioInput
|
|
20846
20869
|
});
|
|
20847
|
-
|
|
20848
|
-
|
|
20849
|
-
|
|
20850
|
-
|
|
20851
|
-
|
|
20852
|
-
|
|
20853
|
-
});
|
|
20854
|
-
return {
|
|
20855
|
-
headers: result.meta.fields ?? [],
|
|
20856
|
-
rows: result.data,
|
|
20857
|
-
rowCount: result.data.length
|
|
20858
|
-
};
|
|
20870
|
+
function getScenario(key) {
|
|
20871
|
+
const scenario = SCENARIOS[key];
|
|
20872
|
+
if (!scenario) {
|
|
20873
|
+
throw new Error(`Unknown scenario: ${key}. Valid: ${Object.keys(SCENARIOS).join(", ")}`);
|
|
20874
|
+
}
|
|
20875
|
+
return scenario;
|
|
20859
20876
|
}
|
|
20860
|
-
function
|
|
20861
|
-
return
|
|
20877
|
+
function isNamedDemoScenario(raw) {
|
|
20878
|
+
return NAMED_DEMO_SCENARIOS.includes(raw);
|
|
20862
20879
|
}
|
|
20863
|
-
|
|
20864
|
-
|
|
20865
|
-
|
|
20880
|
+
function resolveScenarioInput(raw) {
|
|
20881
|
+
const input = raw?.trim();
|
|
20882
|
+
if (!input) return void 0;
|
|
20883
|
+
if (input === "research_blend") return "research_blend";
|
|
20884
|
+
if (NAMED_DEMO_SCENARIOS.includes(input)) {
|
|
20885
|
+
return input;
|
|
20866
20886
|
}
|
|
20867
|
-
|
|
20868
|
-
|
|
20869
|
-
|
|
20870
|
-
|
|
20871
|
-
return
|
|
20887
|
+
const n = Number(input);
|
|
20888
|
+
if (Number.isInteger(n) && n >= 1 && n <= NAMED_DEMO_SCENARIOS.length) {
|
|
20889
|
+
return NAMED_DEMO_SCENARIOS[n - 1];
|
|
20890
|
+
}
|
|
20891
|
+
return null;
|
|
20872
20892
|
}
|
|
20873
|
-
|
|
20874
|
-
|
|
20875
|
-
|
|
20893
|
+
function pickRandomScenario() {
|
|
20894
|
+
return RANDOM_POOL[Math.floor(Math.random() * RANDOM_POOL.length)];
|
|
20895
|
+
}
|
|
20896
|
+
function blendScenarios(_research) {
|
|
20897
|
+
const blended = {
|
|
20898
|
+
...BASELINE,
|
|
20899
|
+
key: "research_blend",
|
|
20900
|
+
label: "Research-Derived Blend",
|
|
20901
|
+
description: "Realistic data with mild-to-moderate problems across all vital signs.",
|
|
20902
|
+
story: "Generated with default research blend. Problems are seeded across all vital signs at realistic levels.",
|
|
20903
|
+
hook: "Mild-to-moderate problems seeded across all five vitals.",
|
|
20904
|
+
// Bump all problems slightly above baseline for discoverability
|
|
20905
|
+
staleContactRatio: 0.2,
|
|
20906
|
+
pastCloseDateRatio: 0.15,
|
|
20907
|
+
mqlDropRatio: 0.15,
|
|
20908
|
+
qualifiedNoOutreachRatio: 0.15,
|
|
20909
|
+
stuckDealRatio: 0.15,
|
|
20910
|
+
noiseActivityRatio: 0.2,
|
|
20911
|
+
singleThreadRatio: 0.25
|
|
20912
|
+
};
|
|
20913
|
+
return blended;
|
|
20914
|
+
}
|
|
20915
|
+
var BASELINE, SCENARIOS, SCENARIO_LIST, NAMED_DEMO_SCENARIOS, RANDOM_POOL;
|
|
20916
|
+
var init_scenarios = __esm({
|
|
20917
|
+
"src/demo/scenarios.ts"() {
|
|
20876
20918
|
"use strict";
|
|
20877
|
-
|
|
20878
|
-
|
|
20879
|
-
|
|
20880
|
-
|
|
20881
|
-
|
|
20882
|
-
|
|
20919
|
+
BASELINE = {
|
|
20920
|
+
enterpriseRatio: 0.2,
|
|
20921
|
+
midMarketRatio: 0.3,
|
|
20922
|
+
smbRatio: 0.5,
|
|
20923
|
+
staleContactRatio: 0.15,
|
|
20924
|
+
staleContactRatioEnterprise: 0.2,
|
|
20925
|
+
staleContactRatioSmb: 0.1,
|
|
20926
|
+
pastCloseDateRatio: 0.1,
|
|
20927
|
+
staleDays: 120,
|
|
20928
|
+
mqlDropRatio: 0.1,
|
|
20929
|
+
qualifiedNoOutreachRatio: 0.1,
|
|
20930
|
+
stuckDealRatio: 0.1,
|
|
20931
|
+
stuckInNegotiationDays: 45,
|
|
20932
|
+
avgDaysPerStageEnterprise: 25,
|
|
20933
|
+
avgDaysPerStageSmb: 8,
|
|
20934
|
+
activityVolumeMultiplier: 1,
|
|
20935
|
+
noiseActivityRatio: 0.15,
|
|
20936
|
+
singleThreadRatio: 0.2,
|
|
20937
|
+
loneWolfRepIndex: null,
|
|
20938
|
+
loneWolfSingleThreadRatio: 0,
|
|
20939
|
+
freshnessGapDays: 90
|
|
20940
|
+
};
|
|
20941
|
+
SCENARIOS = {
|
|
20942
|
+
hidden_crisis: {
|
|
20943
|
+
...BASELINE,
|
|
20944
|
+
key: "hidden_crisis",
|
|
20945
|
+
label: "The Hidden Crisis",
|
|
20946
|
+
description: "Overall health looks yellow but Enterprise is deep red, masked by strong SMB numbers.",
|
|
20947
|
+
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.",
|
|
20948
|
+
hook: "SMB is carrying the average while Enterprise dies quietly.",
|
|
20949
|
+
staleContactRatio: 0.3,
|
|
20950
|
+
staleContactRatioEnterprise: 0.6,
|
|
20951
|
+
staleContactRatioSmb: 0.1,
|
|
20952
|
+
singleThreadRatio: 0.5,
|
|
20953
|
+
enterpriseRatio: 0.3,
|
|
20954
|
+
midMarketRatio: 0.3,
|
|
20955
|
+
smbRatio: 0.4
|
|
20883
20956
|
},
|
|
20884
|
-
|
|
20885
|
-
|
|
20886
|
-
|
|
20887
|
-
|
|
20957
|
+
leaky_bucket: {
|
|
20958
|
+
...BASELINE,
|
|
20959
|
+
key: "leaky_bucket",
|
|
20960
|
+
label: "The Leaky Bucket",
|
|
20961
|
+
description: "Marketing generates plenty of leads but 40% vanish at handoff to sales.",
|
|
20962
|
+
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.",
|
|
20963
|
+
hook: "MQLs flow in, then 40% vanish at the sales handoff.",
|
|
20964
|
+
mqlDropRatio: 0.4,
|
|
20965
|
+
qualifiedNoOutreachRatio: 0.35,
|
|
20966
|
+
staleContactRatio: 0.2
|
|
20888
20967
|
},
|
|
20889
|
-
|
|
20890
|
-
|
|
20891
|
-
|
|
20968
|
+
stale_pipeline: {
|
|
20969
|
+
...BASELINE,
|
|
20970
|
+
key: "stale_pipeline",
|
|
20971
|
+
label: "The Stale Pipeline",
|
|
20972
|
+
description: "Big pipeline number but half the deals are zombies stuck in late stages.",
|
|
20973
|
+
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.",
|
|
20974
|
+
hook: "Half the pipeline is zombies \u2014 you're forecasting on fiction.",
|
|
20975
|
+
pastCloseDateRatio: 0.5,
|
|
20976
|
+
stuckDealRatio: 0.4,
|
|
20977
|
+
stuckInNegotiationDays: 120,
|
|
20978
|
+
staleContactRatio: 0.25,
|
|
20979
|
+
staleDays: 90
|
|
20980
|
+
},
|
|
20981
|
+
lone_wolf: {
|
|
20982
|
+
...BASELINE,
|
|
20983
|
+
key: "lone_wolf",
|
|
20984
|
+
label: "The Lone Wolf",
|
|
20985
|
+
description: "One rep has great numbers but every single deal is single-threaded.",
|
|
20986
|
+
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.",
|
|
20987
|
+
hook: "Top rep, huge pipeline, one contact per deal \u2014 one exit from collapse.",
|
|
20988
|
+
loneWolfRepIndex: 0,
|
|
20989
|
+
loneWolfSingleThreadRatio: 1,
|
|
20990
|
+
singleThreadRatio: 0.15
|
|
20991
|
+
},
|
|
20992
|
+
busy_bees: {
|
|
20993
|
+
...BASELINE,
|
|
20994
|
+
key: "busy_bees",
|
|
20995
|
+
label: "The Busy Bees",
|
|
20996
|
+
description: "High activity volume across the team, but most of it hits dead ends.",
|
|
20997
|
+
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.",
|
|
20998
|
+
hook: "Reps are spraying, not aiming.",
|
|
20999
|
+
activityVolumeMultiplier: 3,
|
|
21000
|
+
noiseActivityRatio: 0.6,
|
|
21001
|
+
staleContactRatio: 0.2
|
|
21002
|
+
},
|
|
21003
|
+
even_keel: {
|
|
21004
|
+
...BASELINE,
|
|
21005
|
+
key: "even_keel",
|
|
21006
|
+
label: "The Even Keel",
|
|
21007
|
+
description: "A reasonably healthy book \u2014 enough yellow to listen, not a five-alarm fire.",
|
|
21008
|
+
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.",
|
|
21009
|
+
hook: "Reasonably healthy \u2014 enough signal to listen, not a crisis."
|
|
21010
|
+
},
|
|
21011
|
+
compound_pain: {
|
|
21012
|
+
...BASELINE,
|
|
21013
|
+
key: "compound_pain",
|
|
21014
|
+
label: "The Compound Fracture",
|
|
21015
|
+
description: "Several vitals are red at once \u2014 stale pipeline, leaky handoff, noisy activity, thin threads.",
|
|
21016
|
+
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.",
|
|
21017
|
+
hook: "Several vitals red at once \u2014 the stethoscope has to pick a first listen.",
|
|
21018
|
+
enterpriseRatio: 0.3,
|
|
21019
|
+
midMarketRatio: 0.3,
|
|
21020
|
+
smbRatio: 0.4,
|
|
21021
|
+
staleContactRatio: 0.35,
|
|
21022
|
+
staleContactRatioEnterprise: 0.55,
|
|
21023
|
+
staleContactRatioSmb: 0.15,
|
|
21024
|
+
pastCloseDateRatio: 0.35,
|
|
21025
|
+
staleDays: 100,
|
|
21026
|
+
mqlDropRatio: 0.3,
|
|
21027
|
+
qualifiedNoOutreachRatio: 0.25,
|
|
21028
|
+
stuckDealRatio: 0.3,
|
|
21029
|
+
stuckInNegotiationDays: 90,
|
|
21030
|
+
activityVolumeMultiplier: 2,
|
|
21031
|
+
noiseActivityRatio: 0.4,
|
|
21032
|
+
singleThreadRatio: 0.4
|
|
20892
21033
|
}
|
|
20893
21034
|
};
|
|
20894
|
-
|
|
20895
|
-
|
|
20896
|
-
|
|
20897
|
-
|
|
20898
|
-
|
|
20899
|
-
|
|
20900
|
-
|
|
20901
|
-
|
|
20902
|
-
|
|
20903
|
-
|
|
20904
|
-
|
|
20905
|
-
call: "call",
|
|
20906
|
-
email: "email",
|
|
20907
|
-
sequence_step: "email"
|
|
20908
|
-
};
|
|
20909
|
-
CAMPAIGN_TYPE_MAP = {
|
|
20910
|
-
outbound: "email_sequence",
|
|
20911
|
-
inbound: "email_sequence",
|
|
20912
|
-
trigger: "email_sequence"
|
|
20913
|
-
};
|
|
21035
|
+
SCENARIO_LIST = Object.values(SCENARIOS);
|
|
21036
|
+
NAMED_DEMO_SCENARIOS = [
|
|
21037
|
+
"hidden_crisis",
|
|
21038
|
+
"leaky_bucket",
|
|
21039
|
+
"stale_pipeline",
|
|
21040
|
+
"lone_wolf",
|
|
21041
|
+
"busy_bees",
|
|
21042
|
+
"even_keel",
|
|
21043
|
+
"compound_pain"
|
|
21044
|
+
];
|
|
21045
|
+
RANDOM_POOL = [...NAMED_DEMO_SCENARIOS];
|
|
20914
21046
|
}
|
|
20915
21047
|
});
|
|
20916
21048
|
|
|
20917
|
-
// src/
|
|
20918
|
-
|
|
20919
|
-
|
|
20920
|
-
|
|
20921
|
-
|
|
20922
|
-
|
|
20923
|
-
|
|
20924
|
-
|
|
20925
|
-
|
|
20926
|
-
|
|
20927
|
-
|
|
20928
|
-
|
|
20929
|
-
|
|
20930
|
-
|
|
20931
|
-
|
|
20932
|
-
|
|
20933
|
-
|
|
20934
|
-
|
|
20935
|
-
|
|
20936
|
-
|
|
20937
|
-
|
|
20938
|
-
if (
|
|
20939
|
-
|
|
20940
|
-
|
|
20941
|
-
|
|
20942
|
-
|
|
20943
|
-
|
|
20944
|
-
|
|
20945
|
-
|
|
21049
|
+
// src/demo/scenario-fit.ts
|
|
21050
|
+
function dealBandFromCycleDays(days) {
|
|
21051
|
+
if (days == null || !Number.isFinite(days) || days <= 0) return void 0;
|
|
21052
|
+
if (days <= 21) return "velocity";
|
|
21053
|
+
if (days <= 45) return "core";
|
|
21054
|
+
if (days <= 90) return "mid";
|
|
21055
|
+
return "enterprise";
|
|
21056
|
+
}
|
|
21057
|
+
function dealBandFromAverageDealSize(raw) {
|
|
21058
|
+
if (!raw) return void 0;
|
|
21059
|
+
const t = raw.trim().toLowerCase();
|
|
21060
|
+
if (!t) return void 0;
|
|
21061
|
+
const match = t.match(/(\d+(?:\.\d+)?)\s*(k|m|million|thousand)?/i);
|
|
21062
|
+
if (!match) return void 0;
|
|
21063
|
+
let n = Number(match[1]);
|
|
21064
|
+
if (!Number.isFinite(n)) return void 0;
|
|
21065
|
+
const unit = (match[2] ?? "").toLowerCase();
|
|
21066
|
+
if (unit === "k" || unit === "thousand") n *= 1e3;
|
|
21067
|
+
else if (unit === "m" || unit === "million") n *= 1e6;
|
|
21068
|
+
else if (n > 0 && n < 500) n *= 1e3;
|
|
21069
|
+
if (n < 15e3) return "velocity";
|
|
21070
|
+
if (n < 5e4) return "core";
|
|
21071
|
+
if (n < 15e4) return "mid";
|
|
21072
|
+
return "enterprise";
|
|
21073
|
+
}
|
|
21074
|
+
function signalsFromProfile(profile) {
|
|
21075
|
+
if (!profile) return {};
|
|
21076
|
+
const text = [
|
|
21077
|
+
profile.industry,
|
|
21078
|
+
profile.product_description,
|
|
21079
|
+
profile.target_customer,
|
|
21080
|
+
profile.user_scope,
|
|
21081
|
+
profile.custom_context
|
|
21082
|
+
].filter(Boolean).join("\n");
|
|
20946
21083
|
return {
|
|
20947
|
-
|
|
20948
|
-
|
|
20949
|
-
|
|
21084
|
+
salesMotion: profile.sales_motion,
|
|
21085
|
+
dealBand: dealBandFromAverageDealSize(profile.average_deal_size) ?? dealBandFromCycleDays(profile.sales_cycle_days),
|
|
21086
|
+
cycleDays: profile.sales_cycle_days,
|
|
21087
|
+
text
|
|
20950
21088
|
};
|
|
20951
21089
|
}
|
|
20952
|
-
|
|
20953
|
-
|
|
20954
|
-
|
|
20955
|
-
|
|
20956
|
-
|
|
20957
|
-
|
|
20958
|
-
|
|
20959
|
-
|
|
20960
|
-
|
|
20961
|
-
{ entityType: "opportunities", required: ["StageName", "Amount", "CloseDate"], optional: ["AccountId", "OwnerId", "Probability"], weight: 1 },
|
|
20962
|
-
{ entityType: "activities", required: ["Subject", "ActivityDate", "TaskSubtype"], optional: ["WhoId", "WhatId", "Status"], weight: 1 }
|
|
20963
|
-
],
|
|
20964
|
-
hubspot: [
|
|
20965
|
-
{ entityType: "organizations", required: ["Company name", "Company Domain Name"], optional: ["Company ID", "Industry", "Annual Revenue"], weight: 1 },
|
|
20966
|
-
{ entityType: "people", required: ["First Name", "Last Name", "Email"], optional: ["Contact ID", "Associated Company ID", "Lifecycle Stage"], weight: 1 },
|
|
20967
|
-
{ entityType: "activities", required: ["Engagement ID", "Type"], optional: ["Timestamp", "Created At", "Contact ID"], weight: 1 }
|
|
20968
|
-
],
|
|
20969
|
-
outreach: [
|
|
20970
|
-
{ entityType: "campaigns", required: ["sequence_type", "step_count"], optional: ["name", "id", "enabled"], weight: 1 },
|
|
20971
|
-
{ entityType: "activities", required: ["prospect_id", "prospect_email"], optional: ["type", "created_at", "subject"], weight: 1 }
|
|
20972
|
-
]
|
|
20973
|
-
};
|
|
20974
|
-
}
|
|
20975
|
-
});
|
|
20976
|
-
|
|
20977
|
-
// src/pipeline/importer.ts
|
|
20978
|
-
async function importRows(params) {
|
|
20979
|
-
let result;
|
|
20980
|
-
switch (params.entityType) {
|
|
20981
|
-
case "organizations":
|
|
20982
|
-
result = await importOrganizations(params);
|
|
20983
|
-
break;
|
|
20984
|
-
case "people":
|
|
20985
|
-
result = await importPeople(params);
|
|
20986
|
-
break;
|
|
20987
|
-
case "opportunities":
|
|
20988
|
-
result = await importOpportunities(params);
|
|
20989
|
-
break;
|
|
20990
|
-
case "activities":
|
|
20991
|
-
result = await importActivities(params);
|
|
20992
|
-
break;
|
|
20993
|
-
case "campaigns":
|
|
20994
|
-
result = await importCampaigns(params);
|
|
20995
|
-
break;
|
|
20996
|
-
default:
|
|
20997
|
-
result = { imported: 0, errors: [`Unknown entity type: ${params.entityType}`] };
|
|
20998
|
-
}
|
|
20999
|
-
const { invalidateLexiconSeed: invalidateLexiconSeed2 } = await Promise.resolve().then(() => (init_lexicon_seed(), lexicon_seed_exports));
|
|
21000
|
-
invalidateLexiconSeed2();
|
|
21001
|
-
return result;
|
|
21002
|
-
}
|
|
21003
|
-
function reverseMap(mappings) {
|
|
21004
|
-
const reversed = {};
|
|
21005
|
-
for (const [csvCol, ontologyField] of Object.entries(mappings)) {
|
|
21006
|
-
reversed[ontologyField] = csvCol;
|
|
21090
|
+
function keywordHits(text) {
|
|
21091
|
+
const hits = {};
|
|
21092
|
+
const hay = text.toLowerCase();
|
|
21093
|
+
for (const { scenario, patterns } of KEYWORD_HINTS) {
|
|
21094
|
+
let n = 0;
|
|
21095
|
+
for (const re of patterns) {
|
|
21096
|
+
if (re.test(hay)) n++;
|
|
21097
|
+
}
|
|
21098
|
+
if (n > 0) hits[scenario] = n;
|
|
21007
21099
|
}
|
|
21008
|
-
return
|
|
21100
|
+
return hits;
|
|
21009
21101
|
}
|
|
21010
|
-
function
|
|
21011
|
-
|
|
21012
|
-
if (
|
|
21013
|
-
|
|
21014
|
-
return
|
|
21102
|
+
function matrixPick(motion, band) {
|
|
21103
|
+
if (motion && band) return MATRIX[motion][band];
|
|
21104
|
+
if (motion) return MOTION_ONLY[motion];
|
|
21105
|
+
if (band) return BAND_ONLY[band];
|
|
21106
|
+
return "even_keel";
|
|
21015
21107
|
}
|
|
21016
|
-
function
|
|
21017
|
-
const
|
|
21018
|
-
|
|
21019
|
-
|
|
21020
|
-
if (!mappedCols.has(key) && value !== void 0 && value !== "") {
|
|
21021
|
-
metadata[key] = value;
|
|
21022
|
-
}
|
|
21108
|
+
function reasonFor(scenario, signals, via) {
|
|
21109
|
+
const s = getScenario(scenario);
|
|
21110
|
+
if (via === "keywords") {
|
|
21111
|
+
return `Your notes sound like ${s.label} \u2014 ${s.hook}`;
|
|
21023
21112
|
}
|
|
21024
|
-
|
|
21113
|
+
const motion = signals.salesMotion;
|
|
21114
|
+
const band = signals.dealBand;
|
|
21115
|
+
if (motion && band) {
|
|
21116
|
+
return `${labelMotion(motion)} with ${labelBand(band)} deals maps to ${s.label}.`;
|
|
21117
|
+
}
|
|
21118
|
+
if (motion) return `${labelMotion(motion)} books usually show up as ${s.label}.`;
|
|
21119
|
+
if (band) return `${labelBand(band)} deals usually show up as ${s.label}.`;
|
|
21120
|
+
return `${s.label} is the even-keeled starting book when we don't know the motion yet.`;
|
|
21025
21121
|
}
|
|
21026
|
-
function
|
|
21027
|
-
return
|
|
21122
|
+
function labelMotion(m) {
|
|
21123
|
+
return MOTION_FIT_CHOICES.find((c) => c.value === m)?.label ?? m;
|
|
21028
21124
|
}
|
|
21029
|
-
function
|
|
21030
|
-
|
|
21031
|
-
const num2 = Number(value);
|
|
21032
|
-
if (!isNaN(num2) && num2 > 1e12) return new Date(num2).toISOString();
|
|
21033
|
-
if (!isNaN(num2) && num2 > 1e9) return new Date(num2 * 1e3).toISOString();
|
|
21034
|
-
const d = new Date(value);
|
|
21035
|
-
if (!isNaN(d.getTime())) return d.toISOString();
|
|
21036
|
-
return null;
|
|
21125
|
+
function labelBand(b) {
|
|
21126
|
+
return DEAL_BAND_CHOICES.find((c) => c.value === b)?.label ?? b;
|
|
21037
21127
|
}
|
|
21038
|
-
|
|
21039
|
-
const
|
|
21040
|
-
if (
|
|
21041
|
-
|
|
21042
|
-
|
|
21043
|
-
|
|
21044
|
-
|
|
21045
|
-
|
|
21046
|
-
|
|
21047
|
-
|
|
21048
|
-
|
|
21128
|
+
function inferDemoScenario(signals) {
|
|
21129
|
+
const text = signals.text?.trim() ?? "";
|
|
21130
|
+
if (text) {
|
|
21131
|
+
const hits = keywordHits(text);
|
|
21132
|
+
let best;
|
|
21133
|
+
let bestN = 0;
|
|
21134
|
+
for (const id of NAMED_DEMO_SCENARIOS) {
|
|
21135
|
+
const n = hits[id] ?? 0;
|
|
21136
|
+
if (n > bestN) {
|
|
21137
|
+
best = id;
|
|
21138
|
+
bestN = n;
|
|
21139
|
+
}
|
|
21140
|
+
}
|
|
21141
|
+
if (best && bestN > 0) {
|
|
21142
|
+
return { scenario: best, reason: reasonFor(best, signals, "keywords"), source: "heuristic" };
|
|
21143
|
+
}
|
|
21049
21144
|
}
|
|
21050
|
-
|
|
21145
|
+
const scenario = matrixPick(signals.salesMotion, signals.dealBand);
|
|
21146
|
+
return { scenario, reason: reasonFor(scenario, signals, "matrix"), source: "heuristic" };
|
|
21051
21147
|
}
|
|
21052
|
-
|
|
21053
|
-
const
|
|
21054
|
-
|
|
21055
|
-
|
|
21056
|
-
|
|
21057
|
-
|
|
21058
|
-
|
|
21059
|
-
|
|
21060
|
-
|
|
21061
|
-
|
|
21148
|
+
function getPreferredDemoScenario() {
|
|
21149
|
+
const raw = getConfigValue(PREF_SCENARIO_KEY);
|
|
21150
|
+
return raw && isNamedDemoScenario(raw) ? raw : void 0;
|
|
21151
|
+
}
|
|
21152
|
+
function saveDemoFit(opts) {
|
|
21153
|
+
setConfigValue(PREF_SCENARIO_KEY, opts.scenario);
|
|
21154
|
+
if (opts.motion) {
|
|
21155
|
+
setConfigValue(PREF_MOTION_KEY, opts.motion);
|
|
21156
|
+
if (opts.syncSalesMotion && !isProfileConfigured(loadProfile())) {
|
|
21157
|
+
setConfigValue("sales-motion", opts.motion);
|
|
21062
21158
|
}
|
|
21063
|
-
const website = getMapped(row, fieldMap, "canonical_domain") ?? "";
|
|
21064
|
-
const canonicalDomain = stripUrl(website);
|
|
21065
|
-
await insertOrganization({
|
|
21066
|
-
canonical_name: canonicalName,
|
|
21067
|
-
canonical_domain: canonicalDomain || null,
|
|
21068
|
-
source_system: sourceSystem,
|
|
21069
|
-
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21070
|
-
raw_data: row,
|
|
21071
|
-
metadata: buildMetadata(row, mappings)
|
|
21072
|
-
});
|
|
21073
|
-
imported++;
|
|
21074
21159
|
}
|
|
21075
|
-
|
|
21160
|
+
if (opts.dealBand) setConfigValue(PREF_BAND_KEY, opts.dealBand);
|
|
21076
21161
|
}
|
|
21077
|
-
|
|
21078
|
-
|
|
21079
|
-
|
|
21080
|
-
|
|
21081
|
-
|
|
21082
|
-
|
|
21083
|
-
|
|
21084
|
-
|
|
21085
|
-
|
|
21086
|
-
}
|
|
21087
|
-
const orgIdMap = await resolveSourceIds("organizations", sourceSystem, orgSourceIds);
|
|
21088
|
-
for (const row of rows) {
|
|
21089
|
-
const firstName = getMapped(row, fieldMap, "first_name") ?? "";
|
|
21090
|
-
const lastName = getMapped(row, fieldMap, "last_name") ?? "";
|
|
21091
|
-
const canonicalName = [firstName, lastName].filter(Boolean).join(" ") || getMapped(row, fieldMap, "canonical_name") || "";
|
|
21092
|
-
if (!canonicalName) {
|
|
21093
|
-
errors.push("Row missing name fields");
|
|
21094
|
-
continue;
|
|
21095
|
-
}
|
|
21096
|
-
const orgSourceId = getMapped(row, fieldMap, "organization_source_id");
|
|
21097
|
-
await insertPerson({
|
|
21098
|
-
canonical_name: canonicalName,
|
|
21099
|
-
canonical_email: getMapped(row, fieldMap, "canonical_email") || null,
|
|
21100
|
-
organization_id: orgSourceId ? orgIdMap.get(orgSourceId) ?? null : null,
|
|
21101
|
-
source_system: sourceSystem,
|
|
21102
|
-
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21103
|
-
raw_data: row,
|
|
21104
|
-
metadata: buildMetadata(row, mappings)
|
|
21105
|
-
});
|
|
21106
|
-
imported++;
|
|
21107
|
-
}
|
|
21108
|
-
return { imported, errors };
|
|
21162
|
+
function scenarioMenuChoices() {
|
|
21163
|
+
return NAMED_DEMO_SCENARIOS.map((id) => {
|
|
21164
|
+
const s = getScenario(id);
|
|
21165
|
+
return {
|
|
21166
|
+
value: id,
|
|
21167
|
+
label: s.label,
|
|
21168
|
+
description: s.hook
|
|
21169
|
+
};
|
|
21170
|
+
});
|
|
21109
21171
|
}
|
|
21110
|
-
|
|
21111
|
-
|
|
21112
|
-
|
|
21113
|
-
|
|
21114
|
-
|
|
21115
|
-
|
|
21116
|
-
|
|
21117
|
-
|
|
21118
|
-
|
|
21119
|
-
|
|
21120
|
-
|
|
21121
|
-
|
|
21122
|
-
|
|
21123
|
-
|
|
21124
|
-
|
|
21125
|
-
|
|
21126
|
-
|
|
21127
|
-
|
|
21128
|
-
|
|
21129
|
-
|
|
21130
|
-
|
|
21131
|
-
|
|
21132
|
-
|
|
21133
|
-
|
|
21134
|
-
|
|
21135
|
-
|
|
21136
|
-
|
|
21137
|
-
|
|
21138
|
-
|
|
21139
|
-
|
|
21140
|
-
|
|
21141
|
-
|
|
21142
|
-
|
|
21143
|
-
|
|
21144
|
-
|
|
21145
|
-
|
|
21146
|
-
|
|
21172
|
+
var PREF_SCENARIO_KEY, PREF_MOTION_KEY, PREF_BAND_KEY, MOTION_FIT_CHOICES, DEAL_BAND_CHOICES, MATRIX, MOTION_ONLY, BAND_ONLY, KEYWORD_HINTS;
|
|
21173
|
+
var init_scenario_fit = __esm({
|
|
21174
|
+
"src/demo/scenario-fit.ts"() {
|
|
21175
|
+
"use strict";
|
|
21176
|
+
init_store();
|
|
21177
|
+
init_profile();
|
|
21178
|
+
init_scenarios();
|
|
21179
|
+
PREF_SCENARIO_KEY = "demo-scenario-preference";
|
|
21180
|
+
PREF_MOTION_KEY = "demo-fit-motion";
|
|
21181
|
+
PREF_BAND_KEY = "demo-fit-deal-band";
|
|
21182
|
+
MOTION_FIT_CHOICES = [
|
|
21183
|
+
{
|
|
21184
|
+
value: "plg",
|
|
21185
|
+
label: "Product-led / self-serve",
|
|
21186
|
+
description: "Users start themselves; sales assists or expands"
|
|
21187
|
+
},
|
|
21188
|
+
{
|
|
21189
|
+
value: "smb_velocity",
|
|
21190
|
+
label: "High-volume SMB",
|
|
21191
|
+
description: "Fast cycles, lots of small deals, outbound or inbound machine"
|
|
21192
|
+
},
|
|
21193
|
+
{
|
|
21194
|
+
value: "mid_market",
|
|
21195
|
+
label: "Mid-market, structured process",
|
|
21196
|
+
description: "A real sales cycle, a few stakeholders, moderate ACV"
|
|
21197
|
+
},
|
|
21198
|
+
{
|
|
21199
|
+
value: "enterprise",
|
|
21200
|
+
label: "Enterprise, long cycles",
|
|
21201
|
+
description: "Large deals, many buyers, quarters not weeks"
|
|
21202
|
+
}
|
|
21203
|
+
];
|
|
21204
|
+
DEAL_BAND_CHOICES = [
|
|
21205
|
+
{
|
|
21206
|
+
value: "velocity",
|
|
21207
|
+
label: "Under ~$15K, days to a couple of weeks",
|
|
21208
|
+
description: "Velocity / transactional"
|
|
21209
|
+
},
|
|
21210
|
+
{
|
|
21211
|
+
value: "core",
|
|
21212
|
+
label: "~$15K\u2013$50K, a few weeks",
|
|
21213
|
+
description: "Core SMB"
|
|
21214
|
+
},
|
|
21215
|
+
{
|
|
21216
|
+
value: "mid",
|
|
21217
|
+
label: "~$50K\u2013$150K, 1\u20133 months",
|
|
21218
|
+
description: "Classic mid-market"
|
|
21219
|
+
},
|
|
21220
|
+
{
|
|
21221
|
+
value: "enterprise",
|
|
21222
|
+
label: "$150K+, a quarter or more",
|
|
21223
|
+
description: "Enterprise / strategic"
|
|
21224
|
+
}
|
|
21225
|
+
];
|
|
21226
|
+
MATRIX = {
|
|
21227
|
+
plg: {
|
|
21228
|
+
velocity: "leaky_bucket",
|
|
21229
|
+
core: "leaky_bucket",
|
|
21230
|
+
mid: "hidden_crisis",
|
|
21231
|
+
enterprise: "hidden_crisis"
|
|
21232
|
+
},
|
|
21233
|
+
smb_velocity: {
|
|
21234
|
+
velocity: "busy_bees",
|
|
21235
|
+
core: "busy_bees",
|
|
21236
|
+
mid: "leaky_bucket",
|
|
21237
|
+
enterprise: "lone_wolf"
|
|
21238
|
+
},
|
|
21239
|
+
mid_market: {
|
|
21240
|
+
velocity: "busy_bees",
|
|
21241
|
+
core: "stale_pipeline",
|
|
21242
|
+
mid: "stale_pipeline",
|
|
21243
|
+
enterprise: "hidden_crisis"
|
|
21244
|
+
},
|
|
21245
|
+
enterprise: {
|
|
21246
|
+
velocity: "lone_wolf",
|
|
21247
|
+
core: "lone_wolf",
|
|
21248
|
+
mid: "hidden_crisis",
|
|
21249
|
+
enterprise: "hidden_crisis"
|
|
21250
|
+
}
|
|
21251
|
+
};
|
|
21252
|
+
MOTION_ONLY = {
|
|
21253
|
+
plg: "leaky_bucket",
|
|
21254
|
+
smb_velocity: "busy_bees",
|
|
21255
|
+
mid_market: "stale_pipeline",
|
|
21256
|
+
enterprise: "hidden_crisis"
|
|
21257
|
+
};
|
|
21258
|
+
BAND_ONLY = {
|
|
21259
|
+
velocity: "busy_bees",
|
|
21260
|
+
core: "leaky_bucket",
|
|
21261
|
+
mid: "stale_pipeline",
|
|
21262
|
+
enterprise: "hidden_crisis"
|
|
21263
|
+
};
|
|
21264
|
+
KEYWORD_HINTS = [
|
|
21265
|
+
{ scenario: "even_keel", patterns: [/\beven keel\b/, /\breasonably healthy\b/, /\bno crisis\b/, /\bjust evaluating\b/, /\bgreen[- ]field\b/] },
|
|
21266
|
+
{ scenario: "compound_pain", patterns: [/\beverything('?s| is) (on fire|red|broken)\b/, /\bcompound\b/, /\ball (five )?vitals\b/, /\bmultiple problems\b/] },
|
|
21267
|
+
{ scenario: "leaky_bucket", patterns: [/\bhandoff\b/, /\bmqls?\b/, /\bleak/, /\bdrop[- ]rate/, /\bvanish/, /\brouting\b/, /\bmarketing.?sales\b/] },
|
|
21268
|
+
{ scenario: "stale_pipeline", patterns: [/\bzombie/, /\bstale\b/, /\bpast[- ]due\b/, /\bforecast(ing)? on fiction\b/, /\bstuck in negotiation\b/, /\bquiet deals?\b/] },
|
|
21269
|
+
{ scenario: "lone_wolf", patterns: [/\bsingle[- ]thread/, /\blone wolf\b/, /\bone contact\b/, /\bchampion leaves\b/] },
|
|
21270
|
+
{ scenario: "busy_bees", patterns: [/\bspray/, /\bnois(e|y)\b/, /\bmisdirected\b/, /\bactivity (volume|metrics)\b/, /\bbusy bees\b/, /\bnot (on|hitting) pipeline\b/] },
|
|
21271
|
+
{ scenario: "hidden_crisis", patterns: [/\bhidden crisis\b/, /\benterprise (is )?(dying|red|stale)\b/, /\bsegment.{0,20}mask/, /\baverages? (look|looks) (fine|okay|yellow)\b/] }
|
|
21272
|
+
];
|
|
21147
21273
|
}
|
|
21148
|
-
|
|
21274
|
+
});
|
|
21275
|
+
|
|
21276
|
+
// src/conversation/onboard-tiers.ts
|
|
21277
|
+
import { existsSync as existsSync22, statSync as statSync4 } from "fs";
|
|
21278
|
+
function flagSet(tier) {
|
|
21279
|
+
return Boolean(getConfigValue(TIER_CONFIG_KEYS[tier]));
|
|
21149
21280
|
}
|
|
21150
|
-
|
|
21151
|
-
const
|
|
21152
|
-
const
|
|
21153
|
-
|
|
21154
|
-
const fieldMap = reverseMap(mappings);
|
|
21155
|
-
const personSourceIds = /* @__PURE__ */ new Set();
|
|
21156
|
-
for (const row of rows) {
|
|
21157
|
-
const p = getMapped(row, fieldMap, "person_source_id");
|
|
21158
|
-
if (p) personSourceIds.add(p);
|
|
21159
|
-
}
|
|
21160
|
-
const personIdMap = await resolveSourceIds("people", sourceSystem, personSourceIds);
|
|
21161
|
-
for (const row of rows) {
|
|
21162
|
-
const rawType = getMapped(row, fieldMap, "activity_type") ?? "custom";
|
|
21163
|
-
const activityType = ACTIVITY_TYPE_MAP[rawType] ?? "custom";
|
|
21164
|
-
const rawOccurredAt = getMapped(row, fieldMap, "occurred_at") ?? "";
|
|
21165
|
-
const occurredAt = parseTimestamp(rawOccurredAt);
|
|
21166
|
-
if (!occurredAt) {
|
|
21167
|
-
errors.push("Row missing or invalid occurred_at");
|
|
21168
|
-
continue;
|
|
21169
|
-
}
|
|
21170
|
-
const personSourceId = getMapped(row, fieldMap, "person_source_id");
|
|
21171
|
-
await insertActivity({
|
|
21172
|
-
activity_type: activityType,
|
|
21173
|
-
occurred_at: occurredAt,
|
|
21174
|
-
person_id: personSourceId ? personIdMap.get(personSourceId) ?? null : null,
|
|
21175
|
-
organization_id: null,
|
|
21176
|
-
opportunity_id: null,
|
|
21177
|
-
source_system: sourceSystem,
|
|
21178
|
-
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21179
|
-
raw_data: row,
|
|
21180
|
-
metadata: buildMetadata(row, mappings)
|
|
21181
|
-
});
|
|
21182
|
-
imported++;
|
|
21281
|
+
function markOnboardTierComplete(...tiers) {
|
|
21282
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
21283
|
+
for (const tier of tiers) {
|
|
21284
|
+
if (!flagSet(tier)) setConfigValue(TIER_CONFIG_KEYS[tier], at);
|
|
21183
21285
|
}
|
|
21184
|
-
return { imported, errors };
|
|
21185
21286
|
}
|
|
21186
|
-
|
|
21187
|
-
|
|
21188
|
-
const errors = [];
|
|
21189
|
-
let imported = 0;
|
|
21190
|
-
const fieldMap = reverseMap(mappings);
|
|
21191
|
-
for (const row of rows) {
|
|
21192
|
-
const canonicalName = getMapped(row, fieldMap, "canonical_name");
|
|
21193
|
-
if (!canonicalName) {
|
|
21194
|
-
errors.push("Row missing canonical_name");
|
|
21195
|
-
continue;
|
|
21196
|
-
}
|
|
21197
|
-
const rawType = row["sequence_type"] ?? "";
|
|
21198
|
-
const campaignType = CAMPAIGN_TYPE_MAP[rawType] ?? "email_sequence";
|
|
21199
|
-
await insertCampaign({
|
|
21200
|
-
canonical_name: canonicalName,
|
|
21201
|
-
campaign_type: campaignType,
|
|
21202
|
-
source_system: sourceSystem,
|
|
21203
|
-
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21204
|
-
raw_data: row,
|
|
21205
|
-
metadata: buildMetadata(row, mappings)
|
|
21206
|
-
});
|
|
21207
|
-
imported++;
|
|
21208
|
-
}
|
|
21209
|
-
return { imported, errors };
|
|
21287
|
+
function markProductionDataSeen() {
|
|
21288
|
+
markOnboardTierComplete("production");
|
|
21210
21289
|
}
|
|
21211
|
-
|
|
21212
|
-
"
|
|
21290
|
+
function markDemoDataSeen() {
|
|
21291
|
+
markOnboardTierComplete("demo");
|
|
21292
|
+
}
|
|
21293
|
+
var TIER_CONFIG_KEYS;
|
|
21294
|
+
var init_onboard_tiers = __esm({
|
|
21295
|
+
"src/conversation/onboard-tiers.ts"() {
|
|
21213
21296
|
"use strict";
|
|
21214
|
-
|
|
21215
|
-
|
|
21216
|
-
|
|
21297
|
+
init_store();
|
|
21298
|
+
init_profile();
|
|
21299
|
+
init_repl_api();
|
|
21300
|
+
init_scenario_fit();
|
|
21301
|
+
TIER_CONFIG_KEYS = {
|
|
21302
|
+
profile: "onboard-tier-profile",
|
|
21303
|
+
domain: "onboard-tier-domain",
|
|
21304
|
+
demo: "onboard-tier-demo",
|
|
21305
|
+
production: "onboard-tier-production"
|
|
21306
|
+
};
|
|
21217
21307
|
}
|
|
21218
21308
|
});
|
|
21219
21309
|
|
|
21220
|
-
// src/pipeline/
|
|
21221
|
-
|
|
21222
|
-
|
|
21223
|
-
|
|
21224
|
-
|
|
21310
|
+
// src/pipeline/csv-parse.ts
|
|
21311
|
+
var csv_parse_exports = {};
|
|
21312
|
+
__export(csv_parse_exports, {
|
|
21313
|
+
getPreviewRows: () => getPreviewRows,
|
|
21314
|
+
parseCSV: () => parseCSV
|
|
21315
|
+
});
|
|
21316
|
+
import Papa from "papaparse";
|
|
21317
|
+
function parseCSV(content) {
|
|
21318
|
+
const result = Papa.parse(content, {
|
|
21319
|
+
header: true,
|
|
21320
|
+
skipEmptyLines: true,
|
|
21321
|
+
transformHeader: (h) => h.trim()
|
|
21322
|
+
});
|
|
21323
|
+
return {
|
|
21324
|
+
headers: result.meta.fields ?? [],
|
|
21325
|
+
rows: result.data,
|
|
21326
|
+
rowCount: result.data.length
|
|
21327
|
+
};
|
|
21225
21328
|
}
|
|
21226
|
-
|
|
21227
|
-
|
|
21228
|
-
const orgs = await all(
|
|
21229
|
-
`SELECT id, canonical_domain, created_at FROM organizations WHERE canonical_id IS NULL AND canonical_domain IS NOT NULL ORDER BY created_at ASC`
|
|
21230
|
-
);
|
|
21231
|
-
const groups = /* @__PURE__ */ new Map();
|
|
21232
|
-
for (const org of orgs) {
|
|
21233
|
-
const key = org.canonical_domain.toLowerCase();
|
|
21234
|
-
const group = groups.get(key);
|
|
21235
|
-
if (group) group.push(org);
|
|
21236
|
-
else groups.set(key, [org]);
|
|
21237
|
-
}
|
|
21238
|
-
for (const [, group] of groups) {
|
|
21239
|
-
if (group.length <= 1) continue;
|
|
21240
|
-
const canonical = group[0];
|
|
21241
|
-
const duplicateIds = group.slice(1).map((d) => d.id);
|
|
21242
|
-
const placeholders = duplicateIds.map(() => "?").join(", ");
|
|
21243
|
-
await run(`UPDATE organizations SET canonical_id = ? WHERE id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21244
|
-
await run(`UPDATE people SET organization_id = ? WHERE organization_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21245
|
-
await run(`UPDATE opportunities SET organization_id = ? WHERE organization_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21246
|
-
await run(`UPDATE activities SET organization_id = ? WHERE organization_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21247
|
-
resolved += duplicateIds.length;
|
|
21248
|
-
}
|
|
21249
|
-
return resolved;
|
|
21250
|
-
}
|
|
21251
|
-
async function resolvePeople() {
|
|
21252
|
-
let resolved = 0;
|
|
21253
|
-
const people = await all(
|
|
21254
|
-
`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`
|
|
21255
|
-
);
|
|
21256
|
-
const groups = /* @__PURE__ */ new Map();
|
|
21257
|
-
for (const person of people) {
|
|
21258
|
-
const key = person.canonical_email.toLowerCase();
|
|
21259
|
-
const group = groups.get(key);
|
|
21260
|
-
if (group) group.push(person);
|
|
21261
|
-
else groups.set(key, [person]);
|
|
21262
|
-
}
|
|
21263
|
-
for (const [, group] of groups) {
|
|
21264
|
-
if (group.length <= 1) continue;
|
|
21265
|
-
const canonical = group[0];
|
|
21266
|
-
const duplicates = group.slice(1);
|
|
21267
|
-
const duplicateIds = duplicates.map((d) => d.id);
|
|
21268
|
-
const placeholders = duplicateIds.map(() => "?").join(", ");
|
|
21269
|
-
if (!canonical.organization_id) {
|
|
21270
|
-
const withOrg = duplicates.find((d) => d.organization_id);
|
|
21271
|
-
if (withOrg) {
|
|
21272
|
-
await run(`UPDATE people SET organization_id = ? WHERE id = ?`, [withOrg.organization_id, canonical.id]);
|
|
21273
|
-
}
|
|
21274
|
-
}
|
|
21275
|
-
await run(`UPDATE people SET canonical_id = ? WHERE id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21276
|
-
await run(`UPDATE activities SET person_id = ? WHERE person_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21277
|
-
await run(`UPDATE opportunities SET owner_id = ? WHERE owner_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21278
|
-
resolved += duplicateIds.length;
|
|
21279
|
-
}
|
|
21280
|
-
return resolved;
|
|
21329
|
+
function getPreviewRows(rows, count = 5) {
|
|
21330
|
+
return rows.slice(0, count);
|
|
21281
21331
|
}
|
|
21282
|
-
var
|
|
21283
|
-
"src/pipeline/
|
|
21332
|
+
var init_csv_parse = __esm({
|
|
21333
|
+
"src/pipeline/csv-parse.ts"() {
|
|
21284
21334
|
"use strict";
|
|
21285
|
-
init_connection();
|
|
21286
21335
|
}
|
|
21287
21336
|
});
|
|
21288
21337
|
|
|
21289
|
-
// src/
|
|
21290
|
-
function
|
|
21291
|
-
|
|
21292
|
-
return () => {
|
|
21293
|
-
a = a + 1831565813 | 0;
|
|
21294
|
-
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
21295
|
-
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
21296
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
21297
|
-
};
|
|
21298
|
-
}
|
|
21299
|
-
function createSeededRandom(seed) {
|
|
21300
|
-
const raw = mulberry32(seed);
|
|
21301
|
-
const rng = {
|
|
21302
|
-
next: raw,
|
|
21303
|
-
nextInt(min, max) {
|
|
21304
|
-
return Math.floor(raw() * (max - min + 1)) + min;
|
|
21305
|
-
},
|
|
21306
|
-
nextFloat(min, max) {
|
|
21307
|
-
return raw() * (max - min) + min;
|
|
21308
|
-
},
|
|
21309
|
-
pick(arr) {
|
|
21310
|
-
if (arr.length === 0) {
|
|
21311
|
-
throw new Error("Cannot pick from an empty array");
|
|
21312
|
-
}
|
|
21313
|
-
return arr[Math.floor(raw() * arr.length)];
|
|
21314
|
-
},
|
|
21315
|
-
pickN(arr, n) {
|
|
21316
|
-
const copy = [...arr];
|
|
21317
|
-
rng.shuffle(copy);
|
|
21318
|
-
return copy.slice(0, Math.min(n, copy.length));
|
|
21319
|
-
},
|
|
21320
|
-
shuffle(arr) {
|
|
21321
|
-
for (let i = arr.length - 1; i > 0; i--) {
|
|
21322
|
-
const j = Math.floor(raw() * (i + 1));
|
|
21323
|
-
const current = arr[i];
|
|
21324
|
-
arr[i] = arr[j];
|
|
21325
|
-
arr[j] = current;
|
|
21326
|
-
}
|
|
21327
|
-
return arr;
|
|
21328
|
-
},
|
|
21329
|
-
chance(probability) {
|
|
21330
|
-
return raw() < probability;
|
|
21331
|
-
},
|
|
21332
|
-
uuid() {
|
|
21333
|
-
const bytes = Array.from({ length: 16 }, () => Math.floor(raw() * 256));
|
|
21334
|
-
bytes[6] = bytes[6] & 15 | 64;
|
|
21335
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
21336
|
-
const hex = bytes.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
21337
|
-
return [
|
|
21338
|
-
hex.slice(0, 8),
|
|
21339
|
-
hex.slice(8, 12),
|
|
21340
|
-
hex.slice(12, 16),
|
|
21341
|
-
hex.slice(16, 20),
|
|
21342
|
-
hex.slice(20, 32)
|
|
21343
|
-
].join("-");
|
|
21344
|
-
},
|
|
21345
|
-
date(start, end) {
|
|
21346
|
-
const s = start.getTime();
|
|
21347
|
-
const e = end.getTime();
|
|
21348
|
-
return new Date(s + raw() * (e - s));
|
|
21349
|
-
},
|
|
21350
|
-
weightedPick(items, weights) {
|
|
21351
|
-
if (items.length === 0) {
|
|
21352
|
-
throw new Error("Cannot pick from an empty weighted item list");
|
|
21353
|
-
}
|
|
21354
|
-
const total = weights.reduce((sum, w) => sum + w, 0);
|
|
21355
|
-
let r = raw() * total;
|
|
21356
|
-
for (let i = 0; i < items.length; i++) {
|
|
21357
|
-
r -= weights[i] ?? 0;
|
|
21358
|
-
if (r <= 0) return items[i];
|
|
21359
|
-
}
|
|
21360
|
-
return items[items.length - 1];
|
|
21361
|
-
}
|
|
21362
|
-
};
|
|
21363
|
-
return rng;
|
|
21338
|
+
// src/pipeline/csv-mappings.ts
|
|
21339
|
+
function getDefaultMappings(sourceSystem, entityType) {
|
|
21340
|
+
return DEFAULT_MAPPINGS[sourceSystem]?.[entityType] ?? {};
|
|
21364
21341
|
}
|
|
21365
|
-
var
|
|
21366
|
-
|
|
21342
|
+
var DEFAULT_MAPPINGS, ACTIVITY_TYPE_MAP, CAMPAIGN_TYPE_MAP;
|
|
21343
|
+
var init_csv_mappings = __esm({
|
|
21344
|
+
"src/pipeline/csv-mappings.ts"() {
|
|
21367
21345
|
"use strict";
|
|
21346
|
+
DEFAULT_MAPPINGS = {
|
|
21347
|
+
salesforce: {
|
|
21348
|
+
organizations: { Name: "canonical_name", Website: "canonical_domain", Id: "source_id" },
|
|
21349
|
+
people: { Email: "canonical_email", FirstName: "first_name", LastName: "last_name", Id: "source_id", AccountId: "organization_source_id" },
|
|
21350
|
+
opportunities: { Name: "canonical_name", Amount: "amount", StageName: "current_stage", CloseDate: "close_date", Id: "source_id", AccountId: "organization_source_id", OwnerId: "owner_source_id" },
|
|
21351
|
+
activities: { TaskSubtype: "activity_type", ActivityDate: "occurred_at", Id: "source_id", WhoId: "person_source_id" }
|
|
21352
|
+
},
|
|
21353
|
+
hubspot: {
|
|
21354
|
+
organizations: { "Company name": "canonical_name", "Company Domain Name": "canonical_domain", "Company ID": "source_id" },
|
|
21355
|
+
people: { Email: "canonical_email", "First Name": "first_name", "Last Name": "last_name", "Contact ID": "source_id", "Associated Company ID": "organization_source_id" },
|
|
21356
|
+
activities: { Type: "activity_type", "Created At": "occurred_at", "Engagement ID": "source_id", "Contact ID": "person_source_id" }
|
|
21357
|
+
},
|
|
21358
|
+
outreach: {
|
|
21359
|
+
campaigns: { name: "canonical_name", id: "source_id" },
|
|
21360
|
+
activities: { type: "activity_type", created_at: "occurred_at", id: "source_id", prospect_id: "person_source_id" }
|
|
21361
|
+
}
|
|
21362
|
+
};
|
|
21363
|
+
ACTIVITY_TYPE_MAP = {
|
|
21364
|
+
Call: "call",
|
|
21365
|
+
Email: "email",
|
|
21366
|
+
Task: "custom",
|
|
21367
|
+
CALL: "call",
|
|
21368
|
+
EMAIL: "email",
|
|
21369
|
+
MEETING: "meeting",
|
|
21370
|
+
NOTE: "custom",
|
|
21371
|
+
TASK: "custom",
|
|
21372
|
+
INCOMING_EMAIL: "email",
|
|
21373
|
+
FORWARDED_EMAIL: "email",
|
|
21374
|
+
call: "call",
|
|
21375
|
+
email: "email",
|
|
21376
|
+
sequence_step: "email"
|
|
21377
|
+
};
|
|
21378
|
+
CAMPAIGN_TYPE_MAP = {
|
|
21379
|
+
outbound: "email_sequence",
|
|
21380
|
+
inbound: "email_sequence",
|
|
21381
|
+
trigger: "email_sequence"
|
|
21382
|
+
};
|
|
21368
21383
|
}
|
|
21369
21384
|
});
|
|
21370
21385
|
|
|
21371
|
-
// src/
|
|
21372
|
-
var
|
|
21373
|
-
__export(
|
|
21374
|
-
|
|
21375
|
-
SCENARIOS: () => SCENARIOS,
|
|
21376
|
-
SCENARIO_LIST: () => SCENARIO_LIST,
|
|
21377
|
-
blendScenarios: () => blendScenarios,
|
|
21378
|
-
getScenario: () => getScenario,
|
|
21379
|
-
isNamedDemoScenario: () => isNamedDemoScenario,
|
|
21380
|
-
pickRandomScenario: () => pickRandomScenario,
|
|
21381
|
-
resolveScenarioInput: () => resolveScenarioInput
|
|
21386
|
+
// src/pipeline/csv-detect.ts
|
|
21387
|
+
var csv_detect_exports = {};
|
|
21388
|
+
__export(csv_detect_exports, {
|
|
21389
|
+
detectEntityType: () => detectEntityType
|
|
21382
21390
|
});
|
|
21383
|
-
function
|
|
21384
|
-
const
|
|
21385
|
-
if (!
|
|
21386
|
-
|
|
21387
|
-
|
|
21388
|
-
|
|
21389
|
-
|
|
21390
|
-
|
|
21391
|
-
|
|
21392
|
-
|
|
21393
|
-
|
|
21394
|
-
|
|
21395
|
-
|
|
21396
|
-
|
|
21397
|
-
|
|
21398
|
-
return input;
|
|
21391
|
+
function detectEntityType(headers, sourceSystem) {
|
|
21392
|
+
const rules = SIGNATURES[sourceSystem];
|
|
21393
|
+
if (!rules) return null;
|
|
21394
|
+
const headerSet = new Set(headers);
|
|
21395
|
+
let bestMatch = null;
|
|
21396
|
+
for (const rule of rules) {
|
|
21397
|
+
const requiredMatches = rule.required.filter((col) => headerSet.has(col)).length;
|
|
21398
|
+
const requiredTotal = rule.required.length;
|
|
21399
|
+
if (requiredMatches < Math.ceil(requiredTotal / 2)) continue;
|
|
21400
|
+
const optionalMatches = rule.optional.filter((col) => headerSet.has(col)).length;
|
|
21401
|
+
const optionalTotal = rule.optional.length;
|
|
21402
|
+
const score = requiredMatches / requiredTotal * 0.7 + (optionalTotal > 0 ? optionalMatches / optionalTotal * 0.3 : 0.3);
|
|
21403
|
+
if (!bestMatch || score > bestMatch.score) {
|
|
21404
|
+
bestMatch = { rule, score };
|
|
21405
|
+
}
|
|
21399
21406
|
}
|
|
21400
|
-
|
|
21401
|
-
|
|
21402
|
-
|
|
21407
|
+
if (!bestMatch) return null;
|
|
21408
|
+
const defaultMappings = getDefaultMappings(sourceSystem, bestMatch.rule.entityType);
|
|
21409
|
+
const mappings = {};
|
|
21410
|
+
for (const [csvCol, ontologyField] of Object.entries(defaultMappings)) {
|
|
21411
|
+
if (headerSet.has(csvCol)) {
|
|
21412
|
+
mappings[csvCol] = ontologyField;
|
|
21413
|
+
}
|
|
21403
21414
|
}
|
|
21404
|
-
return
|
|
21405
|
-
|
|
21406
|
-
|
|
21407
|
-
|
|
21415
|
+
return {
|
|
21416
|
+
entityType: bestMatch.rule.entityType,
|
|
21417
|
+
confidence: Math.round(bestMatch.score * 100) / 100,
|
|
21418
|
+
mappings
|
|
21419
|
+
};
|
|
21408
21420
|
}
|
|
21409
|
-
|
|
21410
|
-
|
|
21411
|
-
|
|
21412
|
-
key: "research_blend",
|
|
21413
|
-
label: "Research-Derived Blend",
|
|
21414
|
-
description: "Realistic data with mild-to-moderate problems across all vital signs.",
|
|
21415
|
-
story: "Generated with default research blend. Problems are seeded across all vital signs at realistic levels.",
|
|
21416
|
-
hook: "Mild-to-moderate problems seeded across all five vitals.",
|
|
21417
|
-
// Bump all problems slightly above baseline for discoverability
|
|
21418
|
-
staleContactRatio: 0.2,
|
|
21419
|
-
pastCloseDateRatio: 0.15,
|
|
21420
|
-
mqlDropRatio: 0.15,
|
|
21421
|
-
qualifiedNoOutreachRatio: 0.15,
|
|
21422
|
-
stuckDealRatio: 0.15,
|
|
21423
|
-
noiseActivityRatio: 0.2,
|
|
21424
|
-
singleThreadRatio: 0.25
|
|
21425
|
-
};
|
|
21426
|
-
return blended;
|
|
21427
|
-
}
|
|
21428
|
-
var BASELINE, SCENARIOS, SCENARIO_LIST, NAMED_DEMO_SCENARIOS, RANDOM_POOL;
|
|
21429
|
-
var init_scenarios = __esm({
|
|
21430
|
-
"src/demo/scenarios.ts"() {
|
|
21421
|
+
var SIGNATURES;
|
|
21422
|
+
var init_csv_detect = __esm({
|
|
21423
|
+
"src/pipeline/csv-detect.ts"() {
|
|
21431
21424
|
"use strict";
|
|
21432
|
-
|
|
21433
|
-
|
|
21434
|
-
|
|
21435
|
-
|
|
21436
|
-
|
|
21437
|
-
|
|
21438
|
-
|
|
21439
|
-
|
|
21440
|
-
|
|
21441
|
-
|
|
21442
|
-
|
|
21443
|
-
|
|
21444
|
-
|
|
21445
|
-
|
|
21446
|
-
|
|
21447
|
-
|
|
21448
|
-
|
|
21449
|
-
singleThreadRatio: 0.2,
|
|
21450
|
-
loneWolfRepIndex: null,
|
|
21451
|
-
loneWolfSingleThreadRatio: 0,
|
|
21452
|
-
freshnessGapDays: 90
|
|
21425
|
+
init_csv_mappings();
|
|
21426
|
+
SIGNATURES = {
|
|
21427
|
+
salesforce: [
|
|
21428
|
+
{ entityType: "organizations", required: ["Name", "Industry"], optional: ["AnnualRevenue", "BillingCountry", "Website", "NumberOfEmployees"], weight: 1 },
|
|
21429
|
+
{ entityType: "people", required: ["FirstName", "LastName", "Email"], optional: ["AccountId", "Title", "Phone"], weight: 1 },
|
|
21430
|
+
{ entityType: "opportunities", required: ["StageName", "Amount", "CloseDate"], optional: ["AccountId", "OwnerId", "Probability"], weight: 1 },
|
|
21431
|
+
{ entityType: "activities", required: ["Subject", "ActivityDate", "TaskSubtype"], optional: ["WhoId", "WhatId", "Status"], weight: 1 }
|
|
21432
|
+
],
|
|
21433
|
+
hubspot: [
|
|
21434
|
+
{ entityType: "organizations", required: ["Company name", "Company Domain Name"], optional: ["Company ID", "Industry", "Annual Revenue"], weight: 1 },
|
|
21435
|
+
{ entityType: "people", required: ["First Name", "Last Name", "Email"], optional: ["Contact ID", "Associated Company ID", "Lifecycle Stage"], weight: 1 },
|
|
21436
|
+
{ entityType: "activities", required: ["Engagement ID", "Type"], optional: ["Timestamp", "Created At", "Contact ID"], weight: 1 }
|
|
21437
|
+
],
|
|
21438
|
+
outreach: [
|
|
21439
|
+
{ entityType: "campaigns", required: ["sequence_type", "step_count"], optional: ["name", "id", "enabled"], weight: 1 },
|
|
21440
|
+
{ entityType: "activities", required: ["prospect_id", "prospect_email"], optional: ["type", "created_at", "subject"], weight: 1 }
|
|
21441
|
+
]
|
|
21453
21442
|
};
|
|
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
|
-
|
|
21532
|
-
|
|
21533
|
-
|
|
21534
|
-
|
|
21535
|
-
|
|
21536
|
-
|
|
21537
|
-
|
|
21538
|
-
|
|
21539
|
-
|
|
21540
|
-
|
|
21541
|
-
|
|
21542
|
-
|
|
21543
|
-
|
|
21544
|
-
|
|
21545
|
-
|
|
21443
|
+
}
|
|
21444
|
+
});
|
|
21445
|
+
|
|
21446
|
+
// src/pipeline/importer.ts
|
|
21447
|
+
async function importRows(params) {
|
|
21448
|
+
let result;
|
|
21449
|
+
switch (params.entityType) {
|
|
21450
|
+
case "organizations":
|
|
21451
|
+
result = await importOrganizations(params);
|
|
21452
|
+
break;
|
|
21453
|
+
case "people":
|
|
21454
|
+
result = await importPeople(params);
|
|
21455
|
+
break;
|
|
21456
|
+
case "opportunities":
|
|
21457
|
+
result = await importOpportunities(params);
|
|
21458
|
+
break;
|
|
21459
|
+
case "activities":
|
|
21460
|
+
result = await importActivities(params);
|
|
21461
|
+
break;
|
|
21462
|
+
case "campaigns":
|
|
21463
|
+
result = await importCampaigns(params);
|
|
21464
|
+
break;
|
|
21465
|
+
default:
|
|
21466
|
+
result = { imported: 0, errors: [`Unknown entity type: ${params.entityType}`] };
|
|
21467
|
+
}
|
|
21468
|
+
const { invalidateLexiconSeed: invalidateLexiconSeed2 } = await Promise.resolve().then(() => (init_lexicon_seed(), lexicon_seed_exports));
|
|
21469
|
+
invalidateLexiconSeed2();
|
|
21470
|
+
return result;
|
|
21471
|
+
}
|
|
21472
|
+
function reverseMap(mappings) {
|
|
21473
|
+
const reversed = {};
|
|
21474
|
+
for (const [csvCol, ontologyField] of Object.entries(mappings)) {
|
|
21475
|
+
reversed[ontologyField] = csvCol;
|
|
21476
|
+
}
|
|
21477
|
+
return reversed;
|
|
21478
|
+
}
|
|
21479
|
+
function getMapped(row, fieldMap, field) {
|
|
21480
|
+
const csvCol = fieldMap[field];
|
|
21481
|
+
if (!csvCol) return void 0;
|
|
21482
|
+
const val = row[csvCol];
|
|
21483
|
+
return val !== void 0 && val !== "" ? val : void 0;
|
|
21484
|
+
}
|
|
21485
|
+
function buildMetadata(row, mappings) {
|
|
21486
|
+
const mappedCols = new Set(Object.keys(mappings));
|
|
21487
|
+
const metadata = {};
|
|
21488
|
+
for (const [key, value] of Object.entries(row)) {
|
|
21489
|
+
if (!mappedCols.has(key) && value !== void 0 && value !== "") {
|
|
21490
|
+
metadata[key] = value;
|
|
21491
|
+
}
|
|
21492
|
+
}
|
|
21493
|
+
return metadata;
|
|
21494
|
+
}
|
|
21495
|
+
function stripUrl(url) {
|
|
21496
|
+
return url.replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/$/, "");
|
|
21497
|
+
}
|
|
21498
|
+
function parseTimestamp(value) {
|
|
21499
|
+
if (!value) return null;
|
|
21500
|
+
const num2 = Number(value);
|
|
21501
|
+
if (!isNaN(num2) && num2 > 1e12) return new Date(num2).toISOString();
|
|
21502
|
+
if (!isNaN(num2) && num2 > 1e9) return new Date(num2 * 1e3).toISOString();
|
|
21503
|
+
const d = new Date(value);
|
|
21504
|
+
if (!isNaN(d.getTime())) return d.toISOString();
|
|
21505
|
+
return null;
|
|
21506
|
+
}
|
|
21507
|
+
async function resolveSourceIds(table, sourceSystem, sourceIds) {
|
|
21508
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
21509
|
+
if (sourceIds.size === 0) return idMap;
|
|
21510
|
+
const ids = Array.from(sourceIds);
|
|
21511
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
21512
|
+
const rows = await all(
|
|
21513
|
+
`SELECT id, source_id FROM ${table} WHERE source_system = ? AND source_id IN (${placeholders})`,
|
|
21514
|
+
[sourceSystem, ...ids]
|
|
21515
|
+
);
|
|
21516
|
+
for (const row of rows) {
|
|
21517
|
+
idMap.set(row.source_id, row.id);
|
|
21518
|
+
}
|
|
21519
|
+
return idMap;
|
|
21520
|
+
}
|
|
21521
|
+
async function importOrganizations(params) {
|
|
21522
|
+
const { rows, mappings, sourceSystem } = params;
|
|
21523
|
+
const errors = [];
|
|
21524
|
+
let imported = 0;
|
|
21525
|
+
const fieldMap = reverseMap(mappings);
|
|
21526
|
+
for (const row of rows) {
|
|
21527
|
+
const canonicalName = getMapped(row, fieldMap, "canonical_name");
|
|
21528
|
+
if (!canonicalName) {
|
|
21529
|
+
errors.push("Row missing canonical_name");
|
|
21530
|
+
continue;
|
|
21531
|
+
}
|
|
21532
|
+
const website = getMapped(row, fieldMap, "canonical_domain") ?? "";
|
|
21533
|
+
const canonicalDomain = stripUrl(website);
|
|
21534
|
+
await insertOrganization({
|
|
21535
|
+
canonical_name: canonicalName,
|
|
21536
|
+
canonical_domain: canonicalDomain || null,
|
|
21537
|
+
source_system: sourceSystem,
|
|
21538
|
+
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21539
|
+
raw_data: row,
|
|
21540
|
+
metadata: buildMetadata(row, mappings)
|
|
21541
|
+
});
|
|
21542
|
+
imported++;
|
|
21543
|
+
}
|
|
21544
|
+
return { imported, errors };
|
|
21545
|
+
}
|
|
21546
|
+
async function importPeople(params) {
|
|
21547
|
+
const { rows, mappings, sourceSystem } = params;
|
|
21548
|
+
const errors = [];
|
|
21549
|
+
let imported = 0;
|
|
21550
|
+
const fieldMap = reverseMap(mappings);
|
|
21551
|
+
const orgSourceIds = /* @__PURE__ */ new Set();
|
|
21552
|
+
for (const row of rows) {
|
|
21553
|
+
const orgSrcId = getMapped(row, fieldMap, "organization_source_id");
|
|
21554
|
+
if (orgSrcId) orgSourceIds.add(orgSrcId);
|
|
21555
|
+
}
|
|
21556
|
+
const orgIdMap = await resolveSourceIds("organizations", sourceSystem, orgSourceIds);
|
|
21557
|
+
for (const row of rows) {
|
|
21558
|
+
const firstName = getMapped(row, fieldMap, "first_name") ?? "";
|
|
21559
|
+
const lastName = getMapped(row, fieldMap, "last_name") ?? "";
|
|
21560
|
+
const canonicalName = [firstName, lastName].filter(Boolean).join(" ") || getMapped(row, fieldMap, "canonical_name") || "";
|
|
21561
|
+
if (!canonicalName) {
|
|
21562
|
+
errors.push("Row missing name fields");
|
|
21563
|
+
continue;
|
|
21564
|
+
}
|
|
21565
|
+
const orgSourceId = getMapped(row, fieldMap, "organization_source_id");
|
|
21566
|
+
await insertPerson({
|
|
21567
|
+
canonical_name: canonicalName,
|
|
21568
|
+
canonical_email: getMapped(row, fieldMap, "canonical_email") || null,
|
|
21569
|
+
organization_id: orgSourceId ? orgIdMap.get(orgSourceId) ?? null : null,
|
|
21570
|
+
source_system: sourceSystem,
|
|
21571
|
+
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21572
|
+
raw_data: row,
|
|
21573
|
+
metadata: buildMetadata(row, mappings)
|
|
21574
|
+
});
|
|
21575
|
+
imported++;
|
|
21576
|
+
}
|
|
21577
|
+
return { imported, errors };
|
|
21578
|
+
}
|
|
21579
|
+
async function importOpportunities(params) {
|
|
21580
|
+
const { rows, mappings, sourceSystem } = params;
|
|
21581
|
+
const errors = [];
|
|
21582
|
+
let imported = 0;
|
|
21583
|
+
const fieldMap = reverseMap(mappings);
|
|
21584
|
+
const orgSourceIds = /* @__PURE__ */ new Set();
|
|
21585
|
+
const ownerSourceIds = /* @__PURE__ */ new Set();
|
|
21586
|
+
for (const row of rows) {
|
|
21587
|
+
const o = getMapped(row, fieldMap, "organization_source_id");
|
|
21588
|
+
if (o) orgSourceIds.add(o);
|
|
21589
|
+
const w = getMapped(row, fieldMap, "owner_source_id");
|
|
21590
|
+
if (w) ownerSourceIds.add(w);
|
|
21591
|
+
}
|
|
21592
|
+
const orgIdMap = await resolveSourceIds("organizations", sourceSystem, orgSourceIds);
|
|
21593
|
+
const ownerIdMap = await resolveSourceIds("people", sourceSystem, ownerSourceIds);
|
|
21594
|
+
for (const row of rows) {
|
|
21595
|
+
const canonicalName = getMapped(row, fieldMap, "canonical_name");
|
|
21596
|
+
if (!canonicalName) {
|
|
21597
|
+
errors.push("Row missing canonical_name");
|
|
21598
|
+
continue;
|
|
21599
|
+
}
|
|
21600
|
+
const orgSourceId = getMapped(row, fieldMap, "organization_source_id");
|
|
21601
|
+
const ownerSourceId = getMapped(row, fieldMap, "owner_source_id");
|
|
21602
|
+
const amountStr = getMapped(row, fieldMap, "amount");
|
|
21603
|
+
await insertOpportunity({
|
|
21604
|
+
canonical_name: canonicalName,
|
|
21605
|
+
organization_id: orgSourceId ? orgIdMap.get(orgSourceId) ?? null : null,
|
|
21606
|
+
owner_id: ownerSourceId ? ownerIdMap.get(ownerSourceId) ?? null : null,
|
|
21607
|
+
current_stage: getMapped(row, fieldMap, "current_stage") || null,
|
|
21608
|
+
amount: amountStr ? parseFloat(amountStr) || null : null,
|
|
21609
|
+
close_date: getMapped(row, fieldMap, "close_date") || null,
|
|
21610
|
+
source_system: sourceSystem,
|
|
21611
|
+
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21612
|
+
raw_data: row,
|
|
21613
|
+
metadata: buildMetadata(row, mappings)
|
|
21614
|
+
});
|
|
21615
|
+
imported++;
|
|
21616
|
+
}
|
|
21617
|
+
return { imported, errors };
|
|
21618
|
+
}
|
|
21619
|
+
async function importActivities(params) {
|
|
21620
|
+
const { rows, mappings, sourceSystem } = params;
|
|
21621
|
+
const errors = [];
|
|
21622
|
+
let imported = 0;
|
|
21623
|
+
const fieldMap = reverseMap(mappings);
|
|
21624
|
+
const personSourceIds = /* @__PURE__ */ new Set();
|
|
21625
|
+
for (const row of rows) {
|
|
21626
|
+
const p = getMapped(row, fieldMap, "person_source_id");
|
|
21627
|
+
if (p) personSourceIds.add(p);
|
|
21628
|
+
}
|
|
21629
|
+
const personIdMap = await resolveSourceIds("people", sourceSystem, personSourceIds);
|
|
21630
|
+
for (const row of rows) {
|
|
21631
|
+
const rawType = getMapped(row, fieldMap, "activity_type") ?? "custom";
|
|
21632
|
+
const activityType = ACTIVITY_TYPE_MAP[rawType] ?? "custom";
|
|
21633
|
+
const rawOccurredAt = getMapped(row, fieldMap, "occurred_at") ?? "";
|
|
21634
|
+
const occurredAt = parseTimestamp(rawOccurredAt);
|
|
21635
|
+
if (!occurredAt) {
|
|
21636
|
+
errors.push("Row missing or invalid occurred_at");
|
|
21637
|
+
continue;
|
|
21638
|
+
}
|
|
21639
|
+
const personSourceId = getMapped(row, fieldMap, "person_source_id");
|
|
21640
|
+
await insertActivity({
|
|
21641
|
+
activity_type: activityType,
|
|
21642
|
+
occurred_at: occurredAt,
|
|
21643
|
+
person_id: personSourceId ? personIdMap.get(personSourceId) ?? null : null,
|
|
21644
|
+
organization_id: null,
|
|
21645
|
+
opportunity_id: null,
|
|
21646
|
+
source_system: sourceSystem,
|
|
21647
|
+
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21648
|
+
raw_data: row,
|
|
21649
|
+
metadata: buildMetadata(row, mappings)
|
|
21650
|
+
});
|
|
21651
|
+
imported++;
|
|
21652
|
+
}
|
|
21653
|
+
return { imported, errors };
|
|
21654
|
+
}
|
|
21655
|
+
async function importCampaigns(params) {
|
|
21656
|
+
const { rows, mappings, sourceSystem } = params;
|
|
21657
|
+
const errors = [];
|
|
21658
|
+
let imported = 0;
|
|
21659
|
+
const fieldMap = reverseMap(mappings);
|
|
21660
|
+
for (const row of rows) {
|
|
21661
|
+
const canonicalName = getMapped(row, fieldMap, "canonical_name");
|
|
21662
|
+
if (!canonicalName) {
|
|
21663
|
+
errors.push("Row missing canonical_name");
|
|
21664
|
+
continue;
|
|
21665
|
+
}
|
|
21666
|
+
const rawType = row["sequence_type"] ?? "";
|
|
21667
|
+
const campaignType = CAMPAIGN_TYPE_MAP[rawType] ?? "email_sequence";
|
|
21668
|
+
await insertCampaign({
|
|
21669
|
+
canonical_name: canonicalName,
|
|
21670
|
+
campaign_type: campaignType,
|
|
21671
|
+
source_system: sourceSystem,
|
|
21672
|
+
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21673
|
+
raw_data: row,
|
|
21674
|
+
metadata: buildMetadata(row, mappings)
|
|
21675
|
+
});
|
|
21676
|
+
imported++;
|
|
21677
|
+
}
|
|
21678
|
+
return { imported, errors };
|
|
21679
|
+
}
|
|
21680
|
+
var init_importer = __esm({
|
|
21681
|
+
"src/pipeline/importer.ts"() {
|
|
21682
|
+
"use strict";
|
|
21683
|
+
init_csv_mappings();
|
|
21684
|
+
init_queries();
|
|
21685
|
+
init_connection();
|
|
21686
|
+
}
|
|
21687
|
+
});
|
|
21688
|
+
|
|
21689
|
+
// src/pipeline/resolver.ts
|
|
21690
|
+
async function resolveIdentities() {
|
|
21691
|
+
const orgsResolved = await resolveOrganizations();
|
|
21692
|
+
const peopleResolved = await resolvePeople();
|
|
21693
|
+
return { resolved: orgsResolved + peopleResolved, peopleResolved, orgsResolved };
|
|
21694
|
+
}
|
|
21695
|
+
async function resolveOrganizations() {
|
|
21696
|
+
let resolved = 0;
|
|
21697
|
+
const orgs = await all(
|
|
21698
|
+
`SELECT id, canonical_domain, created_at FROM organizations WHERE canonical_id IS NULL AND canonical_domain IS NOT NULL ORDER BY created_at ASC`
|
|
21699
|
+
);
|
|
21700
|
+
const groups = /* @__PURE__ */ new Map();
|
|
21701
|
+
for (const org of orgs) {
|
|
21702
|
+
const key = org.canonical_domain.toLowerCase();
|
|
21703
|
+
const group = groups.get(key);
|
|
21704
|
+
if (group) group.push(org);
|
|
21705
|
+
else groups.set(key, [org]);
|
|
21706
|
+
}
|
|
21707
|
+
for (const [, group] of groups) {
|
|
21708
|
+
if (group.length <= 1) continue;
|
|
21709
|
+
const canonical = group[0];
|
|
21710
|
+
const duplicateIds = group.slice(1).map((d) => d.id);
|
|
21711
|
+
const placeholders = duplicateIds.map(() => "?").join(", ");
|
|
21712
|
+
await run(`UPDATE organizations SET canonical_id = ? WHERE id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21713
|
+
await run(`UPDATE people SET organization_id = ? WHERE organization_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21714
|
+
await run(`UPDATE opportunities SET organization_id = ? WHERE organization_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21715
|
+
await run(`UPDATE activities SET organization_id = ? WHERE organization_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21716
|
+
resolved += duplicateIds.length;
|
|
21717
|
+
}
|
|
21718
|
+
return resolved;
|
|
21719
|
+
}
|
|
21720
|
+
async function resolvePeople() {
|
|
21721
|
+
let resolved = 0;
|
|
21722
|
+
const people = await all(
|
|
21723
|
+
`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`
|
|
21724
|
+
);
|
|
21725
|
+
const groups = /* @__PURE__ */ new Map();
|
|
21726
|
+
for (const person of people) {
|
|
21727
|
+
const key = person.canonical_email.toLowerCase();
|
|
21728
|
+
const group = groups.get(key);
|
|
21729
|
+
if (group) group.push(person);
|
|
21730
|
+
else groups.set(key, [person]);
|
|
21731
|
+
}
|
|
21732
|
+
for (const [, group] of groups) {
|
|
21733
|
+
if (group.length <= 1) continue;
|
|
21734
|
+
const canonical = group[0];
|
|
21735
|
+
const duplicates = group.slice(1);
|
|
21736
|
+
const duplicateIds = duplicates.map((d) => d.id);
|
|
21737
|
+
const placeholders = duplicateIds.map(() => "?").join(", ");
|
|
21738
|
+
if (!canonical.organization_id) {
|
|
21739
|
+
const withOrg = duplicates.find((d) => d.organization_id);
|
|
21740
|
+
if (withOrg) {
|
|
21741
|
+
await run(`UPDATE people SET organization_id = ? WHERE id = ?`, [withOrg.organization_id, canonical.id]);
|
|
21546
21742
|
}
|
|
21547
|
-
}
|
|
21548
|
-
|
|
21549
|
-
|
|
21550
|
-
|
|
21551
|
-
|
|
21552
|
-
|
|
21553
|
-
|
|
21554
|
-
|
|
21555
|
-
|
|
21556
|
-
|
|
21557
|
-
|
|
21558
|
-
|
|
21743
|
+
}
|
|
21744
|
+
await run(`UPDATE people SET canonical_id = ? WHERE id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21745
|
+
await run(`UPDATE activities SET person_id = ? WHERE person_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21746
|
+
await run(`UPDATE opportunities SET owner_id = ? WHERE owner_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21747
|
+
resolved += duplicateIds.length;
|
|
21748
|
+
}
|
|
21749
|
+
return resolved;
|
|
21750
|
+
}
|
|
21751
|
+
var init_resolver2 = __esm({
|
|
21752
|
+
"src/pipeline/resolver.ts"() {
|
|
21753
|
+
"use strict";
|
|
21754
|
+
init_connection();
|
|
21755
|
+
}
|
|
21756
|
+
});
|
|
21757
|
+
|
|
21758
|
+
// src/demo/seed.ts
|
|
21759
|
+
function mulberry32(seed) {
|
|
21760
|
+
let a = seed | 0;
|
|
21761
|
+
return () => {
|
|
21762
|
+
a = a + 1831565813 | 0;
|
|
21763
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
21764
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
21765
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
21766
|
+
};
|
|
21767
|
+
}
|
|
21768
|
+
function createSeededRandom(seed) {
|
|
21769
|
+
const raw = mulberry32(seed);
|
|
21770
|
+
const rng = {
|
|
21771
|
+
next: raw,
|
|
21772
|
+
nextInt(min, max) {
|
|
21773
|
+
return Math.floor(raw() * (max - min + 1)) + min;
|
|
21774
|
+
},
|
|
21775
|
+
nextFloat(min, max) {
|
|
21776
|
+
return raw() * (max - min) + min;
|
|
21777
|
+
},
|
|
21778
|
+
pick(arr) {
|
|
21779
|
+
if (arr.length === 0) {
|
|
21780
|
+
throw new Error("Cannot pick from an empty array");
|
|
21781
|
+
}
|
|
21782
|
+
return arr[Math.floor(raw() * arr.length)];
|
|
21783
|
+
},
|
|
21784
|
+
pickN(arr, n) {
|
|
21785
|
+
const copy = [...arr];
|
|
21786
|
+
rng.shuffle(copy);
|
|
21787
|
+
return copy.slice(0, Math.min(n, copy.length));
|
|
21788
|
+
},
|
|
21789
|
+
shuffle(arr) {
|
|
21790
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
21791
|
+
const j = Math.floor(raw() * (i + 1));
|
|
21792
|
+
const current = arr[i];
|
|
21793
|
+
arr[i] = arr[j];
|
|
21794
|
+
arr[j] = current;
|
|
21795
|
+
}
|
|
21796
|
+
return arr;
|
|
21797
|
+
},
|
|
21798
|
+
chance(probability) {
|
|
21799
|
+
return raw() < probability;
|
|
21800
|
+
},
|
|
21801
|
+
uuid() {
|
|
21802
|
+
const bytes = Array.from({ length: 16 }, () => Math.floor(raw() * 256));
|
|
21803
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
21804
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
21805
|
+
const hex = bytes.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
21806
|
+
return [
|
|
21807
|
+
hex.slice(0, 8),
|
|
21808
|
+
hex.slice(8, 12),
|
|
21809
|
+
hex.slice(12, 16),
|
|
21810
|
+
hex.slice(16, 20),
|
|
21811
|
+
hex.slice(20, 32)
|
|
21812
|
+
].join("-");
|
|
21813
|
+
},
|
|
21814
|
+
date(start, end) {
|
|
21815
|
+
const s = start.getTime();
|
|
21816
|
+
const e = end.getTime();
|
|
21817
|
+
return new Date(s + raw() * (e - s));
|
|
21818
|
+
},
|
|
21819
|
+
weightedPick(items, weights) {
|
|
21820
|
+
if (items.length === 0) {
|
|
21821
|
+
throw new Error("Cannot pick from an empty weighted item list");
|
|
21822
|
+
}
|
|
21823
|
+
const total = weights.reduce((sum, w) => sum + w, 0);
|
|
21824
|
+
let r = raw() * total;
|
|
21825
|
+
for (let i = 0; i < items.length; i++) {
|
|
21826
|
+
r -= weights[i] ?? 0;
|
|
21827
|
+
if (r <= 0) return items[i];
|
|
21828
|
+
}
|
|
21829
|
+
return items[items.length - 1];
|
|
21830
|
+
}
|
|
21831
|
+
};
|
|
21832
|
+
return rng;
|
|
21833
|
+
}
|
|
21834
|
+
var init_seed = __esm({
|
|
21835
|
+
"src/demo/seed.ts"() {
|
|
21836
|
+
"use strict";
|
|
21559
21837
|
}
|
|
21560
21838
|
});
|
|
21561
21839
|
|
|
@@ -23542,320 +23820,93 @@ async function insertDirect(dataset) {
|
|
|
23542
23820
|
metadata: opp.metadata,
|
|
23543
23821
|
created_at: createdAt
|
|
23544
23822
|
});
|
|
23545
|
-
oppIdMap.set(opp.localId, dbId);
|
|
23546
|
-
}
|
|
23547
|
-
for (const act of dataset.activities) {
|
|
23548
|
-
await insertActivity({
|
|
23549
|
-
activity_type: act.activity_type,
|
|
23550
|
-
occurred_at: act.occurred_at,
|
|
23551
|
-
person_id: act.personLocalId ? personIdMap.get(act.personLocalId) ?? null : null,
|
|
23552
|
-
organization_id: act.organizationLocalId ? orgIdMap.get(act.organizationLocalId) ?? null : null,
|
|
23553
|
-
opportunity_id: act.opportunityLocalId ? oppIdMap.get(act.opportunityLocalId) ?? null : null,
|
|
23554
|
-
source_system: act.source_system,
|
|
23555
|
-
source_id: act.source_id,
|
|
23556
|
-
raw_data: act.raw_data,
|
|
23557
|
-
metadata: act.metadata
|
|
23558
|
-
});
|
|
23559
|
-
}
|
|
23560
|
-
for (const campaign of dataset.campaigns) {
|
|
23561
|
-
await insertCampaign({
|
|
23562
|
-
canonical_name: campaign.canonical_name,
|
|
23563
|
-
campaign_type: campaign.campaign_type,
|
|
23564
|
-
source_system: campaign.source_system,
|
|
23565
|
-
source_id: campaign.source_id,
|
|
23566
|
-
raw_data: campaign.raw_data,
|
|
23567
|
-
metadata: campaign.metadata
|
|
23568
|
-
});
|
|
23569
|
-
}
|
|
23570
|
-
for (const event of dataset.revenueEvents ?? []) {
|
|
23571
|
-
await insertRevenueEvent({
|
|
23572
|
-
organization_id: orgIdMap.get(event.organizationLocalId) ?? null,
|
|
23573
|
-
period: event.period,
|
|
23574
|
-
amount: event.amount,
|
|
23575
|
-
event_type: event.event_type,
|
|
23576
|
-
source_system: event.source_system,
|
|
23577
|
-
source_id: event.source_id,
|
|
23578
|
-
raw_data: event.raw_data
|
|
23579
|
-
});
|
|
23580
|
-
}
|
|
23581
|
-
const { invalidateLexiconSeed: invalidateLexiconSeed2 } = await Promise.resolve().then(() => (init_lexicon_seed(), lexicon_seed_exports));
|
|
23582
|
-
invalidateLexiconSeed2();
|
|
23583
|
-
return {
|
|
23584
|
-
mode: "direct",
|
|
23585
|
-
counts: {
|
|
23586
|
-
organizations: dataset.organizations.length,
|
|
23587
|
-
people: dataset.people.length,
|
|
23588
|
-
opportunities: dataset.opportunities.length,
|
|
23589
|
-
activities: dataset.activities.length,
|
|
23590
|
-
campaigns: dataset.campaigns.length,
|
|
23591
|
-
revenue_events: dataset.revenueEvents?.length ?? 0
|
|
23592
|
-
},
|
|
23593
|
-
health: null
|
|
23594
|
-
};
|
|
23595
|
-
}
|
|
23596
|
-
function hashString(str2) {
|
|
23597
|
-
let hash = 0;
|
|
23598
|
-
for (let i = 0; i < str2.length; i++) {
|
|
23599
|
-
const char = str2.charCodeAt(i);
|
|
23600
|
-
hash = (hash << 5) - hash + char | 0;
|
|
23601
|
-
}
|
|
23602
|
-
return Math.abs(hash);
|
|
23603
|
-
}
|
|
23604
|
-
var init_generator = __esm({
|
|
23605
|
-
"src/demo/generator.ts"() {
|
|
23606
|
-
"use strict";
|
|
23607
|
-
init_queries();
|
|
23608
|
-
init_seed();
|
|
23609
|
-
init_scenarios();
|
|
23610
|
-
init_organizations();
|
|
23611
|
-
init_people();
|
|
23612
|
-
init_opportunities();
|
|
23613
|
-
init_activities();
|
|
23614
|
-
init_saas_enrichment();
|
|
23615
|
-
init_salesforce();
|
|
23616
|
-
init_hubspot();
|
|
23617
|
-
init_outreach();
|
|
23618
|
-
}
|
|
23619
|
-
});
|
|
23620
|
-
|
|
23621
|
-
// src/demo/scenario-fit.ts
|
|
23622
|
-
function dealBandFromCycleDays(days) {
|
|
23623
|
-
if (days == null || !Number.isFinite(days) || days <= 0) return void 0;
|
|
23624
|
-
if (days <= 21) return "velocity";
|
|
23625
|
-
if (days <= 45) return "core";
|
|
23626
|
-
if (days <= 90) return "mid";
|
|
23627
|
-
return "enterprise";
|
|
23628
|
-
}
|
|
23629
|
-
function dealBandFromAverageDealSize(raw) {
|
|
23630
|
-
if (!raw) return void 0;
|
|
23631
|
-
const t = raw.trim().toLowerCase();
|
|
23632
|
-
if (!t) return void 0;
|
|
23633
|
-
const match = t.match(/(\d+(?:\.\d+)?)\s*(k|m|million|thousand)?/i);
|
|
23634
|
-
if (!match) return void 0;
|
|
23635
|
-
let n = Number(match[1]);
|
|
23636
|
-
if (!Number.isFinite(n)) return void 0;
|
|
23637
|
-
const unit = (match[2] ?? "").toLowerCase();
|
|
23638
|
-
if (unit === "k" || unit === "thousand") n *= 1e3;
|
|
23639
|
-
else if (unit === "m" || unit === "million") n *= 1e6;
|
|
23640
|
-
else if (n > 0 && n < 500) n *= 1e3;
|
|
23641
|
-
if (n < 15e3) return "velocity";
|
|
23642
|
-
if (n < 5e4) return "core";
|
|
23643
|
-
if (n < 15e4) return "mid";
|
|
23644
|
-
return "enterprise";
|
|
23645
|
-
}
|
|
23646
|
-
function signalsFromProfile(profile) {
|
|
23647
|
-
if (!profile) return {};
|
|
23648
|
-
const text = [
|
|
23649
|
-
profile.industry,
|
|
23650
|
-
profile.product_description,
|
|
23651
|
-
profile.target_customer,
|
|
23652
|
-
profile.user_scope,
|
|
23653
|
-
profile.custom_context
|
|
23654
|
-
].filter(Boolean).join("\n");
|
|
23655
|
-
return {
|
|
23656
|
-
salesMotion: profile.sales_motion,
|
|
23657
|
-
dealBand: dealBandFromAverageDealSize(profile.average_deal_size) ?? dealBandFromCycleDays(profile.sales_cycle_days),
|
|
23658
|
-
cycleDays: profile.sales_cycle_days,
|
|
23659
|
-
text
|
|
23660
|
-
};
|
|
23661
|
-
}
|
|
23662
|
-
function keywordHits(text) {
|
|
23663
|
-
const hits = {};
|
|
23664
|
-
const hay = text.toLowerCase();
|
|
23665
|
-
for (const { scenario, patterns } of KEYWORD_HINTS) {
|
|
23666
|
-
let n = 0;
|
|
23667
|
-
for (const re of patterns) {
|
|
23668
|
-
if (re.test(hay)) n++;
|
|
23669
|
-
}
|
|
23670
|
-
if (n > 0) hits[scenario] = n;
|
|
23671
|
-
}
|
|
23672
|
-
return hits;
|
|
23673
|
-
}
|
|
23674
|
-
function matrixPick(motion, band) {
|
|
23675
|
-
if (motion && band) return MATRIX[motion][band];
|
|
23676
|
-
if (motion) return MOTION_ONLY[motion];
|
|
23677
|
-
if (band) return BAND_ONLY[band];
|
|
23678
|
-
return "even_keel";
|
|
23679
|
-
}
|
|
23680
|
-
function reasonFor(scenario, signals, via) {
|
|
23681
|
-
const s = getScenario(scenario);
|
|
23682
|
-
if (via === "keywords") {
|
|
23683
|
-
return `Your notes sound like ${s.label} \u2014 ${s.hook}`;
|
|
23823
|
+
oppIdMap.set(opp.localId, dbId);
|
|
23684
23824
|
}
|
|
23685
|
-
const
|
|
23686
|
-
|
|
23687
|
-
|
|
23688
|
-
|
|
23825
|
+
for (const act of dataset.activities) {
|
|
23826
|
+
await insertActivity({
|
|
23827
|
+
activity_type: act.activity_type,
|
|
23828
|
+
occurred_at: act.occurred_at,
|
|
23829
|
+
person_id: act.personLocalId ? personIdMap.get(act.personLocalId) ?? null : null,
|
|
23830
|
+
organization_id: act.organizationLocalId ? orgIdMap.get(act.organizationLocalId) ?? null : null,
|
|
23831
|
+
opportunity_id: act.opportunityLocalId ? oppIdMap.get(act.opportunityLocalId) ?? null : null,
|
|
23832
|
+
source_system: act.source_system,
|
|
23833
|
+
source_id: act.source_id,
|
|
23834
|
+
raw_data: act.raw_data,
|
|
23835
|
+
metadata: act.metadata
|
|
23836
|
+
});
|
|
23689
23837
|
}
|
|
23690
|
-
|
|
23691
|
-
|
|
23692
|
-
|
|
23693
|
-
|
|
23694
|
-
|
|
23695
|
-
|
|
23696
|
-
|
|
23697
|
-
|
|
23698
|
-
|
|
23699
|
-
}
|
|
23700
|
-
function inferDemoScenario(signals) {
|
|
23701
|
-
const text = signals.text?.trim() ?? "";
|
|
23702
|
-
if (text) {
|
|
23703
|
-
const hits = keywordHits(text);
|
|
23704
|
-
let best;
|
|
23705
|
-
let bestN = 0;
|
|
23706
|
-
for (const id of NAMED_DEMO_SCENARIOS) {
|
|
23707
|
-
const n = hits[id] ?? 0;
|
|
23708
|
-
if (n > bestN) {
|
|
23709
|
-
best = id;
|
|
23710
|
-
bestN = n;
|
|
23711
|
-
}
|
|
23712
|
-
}
|
|
23713
|
-
if (best && bestN > 0) {
|
|
23714
|
-
return { scenario: best, reason: reasonFor(best, signals, "keywords"), source: "heuristic" };
|
|
23715
|
-
}
|
|
23838
|
+
for (const campaign of dataset.campaigns) {
|
|
23839
|
+
await insertCampaign({
|
|
23840
|
+
canonical_name: campaign.canonical_name,
|
|
23841
|
+
campaign_type: campaign.campaign_type,
|
|
23842
|
+
source_system: campaign.source_system,
|
|
23843
|
+
source_id: campaign.source_id,
|
|
23844
|
+
raw_data: campaign.raw_data,
|
|
23845
|
+
metadata: campaign.metadata
|
|
23846
|
+
});
|
|
23716
23847
|
}
|
|
23717
|
-
const
|
|
23718
|
-
|
|
23719
|
-
|
|
23720
|
-
|
|
23721
|
-
|
|
23722
|
-
|
|
23723
|
-
|
|
23724
|
-
|
|
23725
|
-
|
|
23726
|
-
|
|
23727
|
-
setConfigValue(PREF_MOTION_KEY, opts.motion);
|
|
23728
|
-
if (opts.syncSalesMotion && !isProfileConfigured(loadProfile())) {
|
|
23729
|
-
setConfigValue("sales-motion", opts.motion);
|
|
23730
|
-
}
|
|
23848
|
+
for (const event of dataset.revenueEvents ?? []) {
|
|
23849
|
+
await insertRevenueEvent({
|
|
23850
|
+
organization_id: orgIdMap.get(event.organizationLocalId) ?? null,
|
|
23851
|
+
period: event.period,
|
|
23852
|
+
amount: event.amount,
|
|
23853
|
+
event_type: event.event_type,
|
|
23854
|
+
source_system: event.source_system,
|
|
23855
|
+
source_id: event.source_id,
|
|
23856
|
+
raw_data: event.raw_data
|
|
23857
|
+
});
|
|
23731
23858
|
}
|
|
23732
|
-
|
|
23859
|
+
const { invalidateLexiconSeed: invalidateLexiconSeed2 } = await Promise.resolve().then(() => (init_lexicon_seed(), lexicon_seed_exports));
|
|
23860
|
+
invalidateLexiconSeed2();
|
|
23861
|
+
return {
|
|
23862
|
+
mode: "direct",
|
|
23863
|
+
counts: {
|
|
23864
|
+
organizations: dataset.organizations.length,
|
|
23865
|
+
people: dataset.people.length,
|
|
23866
|
+
opportunities: dataset.opportunities.length,
|
|
23867
|
+
activities: dataset.activities.length,
|
|
23868
|
+
campaigns: dataset.campaigns.length,
|
|
23869
|
+
revenue_events: dataset.revenueEvents?.length ?? 0
|
|
23870
|
+
},
|
|
23871
|
+
health: null
|
|
23872
|
+
};
|
|
23733
23873
|
}
|
|
23734
|
-
function
|
|
23735
|
-
|
|
23736
|
-
|
|
23737
|
-
|
|
23738
|
-
|
|
23739
|
-
|
|
23740
|
-
|
|
23741
|
-
};
|
|
23742
|
-
});
|
|
23874
|
+
function hashString(str2) {
|
|
23875
|
+
let hash = 0;
|
|
23876
|
+
for (let i = 0; i < str2.length; i++) {
|
|
23877
|
+
const char = str2.charCodeAt(i);
|
|
23878
|
+
hash = (hash << 5) - hash + char | 0;
|
|
23879
|
+
}
|
|
23880
|
+
return Math.abs(hash);
|
|
23743
23881
|
}
|
|
23744
|
-
var
|
|
23745
|
-
|
|
23746
|
-
"src/demo/scenario-fit.ts"() {
|
|
23882
|
+
var init_generator = __esm({
|
|
23883
|
+
"src/demo/generator.ts"() {
|
|
23747
23884
|
"use strict";
|
|
23748
|
-
|
|
23749
|
-
|
|
23885
|
+
init_queries();
|
|
23886
|
+
init_seed();
|
|
23750
23887
|
init_scenarios();
|
|
23751
|
-
|
|
23752
|
-
|
|
23753
|
-
|
|
23754
|
-
|
|
23755
|
-
|
|
23756
|
-
|
|
23757
|
-
|
|
23758
|
-
|
|
23759
|
-
},
|
|
23760
|
-
{
|
|
23761
|
-
value: "smb_velocity",
|
|
23762
|
-
label: "High-volume SMB",
|
|
23763
|
-
description: "Fast cycles, lots of small deals, outbound or inbound machine"
|
|
23764
|
-
},
|
|
23765
|
-
{
|
|
23766
|
-
value: "mid_market",
|
|
23767
|
-
label: "Mid-market, structured process",
|
|
23768
|
-
description: "A real sales cycle, a few stakeholders, moderate ACV"
|
|
23769
|
-
},
|
|
23770
|
-
{
|
|
23771
|
-
value: "enterprise",
|
|
23772
|
-
label: "Enterprise, long cycles",
|
|
23773
|
-
description: "Large deals, many buyers, quarters not weeks"
|
|
23774
|
-
}
|
|
23775
|
-
];
|
|
23776
|
-
DEAL_BAND_CHOICES = [
|
|
23777
|
-
{
|
|
23778
|
-
value: "velocity",
|
|
23779
|
-
label: "Under ~$15K, days to a couple of weeks",
|
|
23780
|
-
description: "Velocity / transactional"
|
|
23781
|
-
},
|
|
23782
|
-
{
|
|
23783
|
-
value: "core",
|
|
23784
|
-
label: "~$15K\u2013$50K, a few weeks",
|
|
23785
|
-
description: "Core SMB"
|
|
23786
|
-
},
|
|
23787
|
-
{
|
|
23788
|
-
value: "mid",
|
|
23789
|
-
label: "~$50K\u2013$150K, 1\u20133 months",
|
|
23790
|
-
description: "Classic mid-market"
|
|
23791
|
-
},
|
|
23792
|
-
{
|
|
23793
|
-
value: "enterprise",
|
|
23794
|
-
label: "$150K+, a quarter or more",
|
|
23795
|
-
description: "Enterprise / strategic"
|
|
23796
|
-
}
|
|
23797
|
-
];
|
|
23798
|
-
MATRIX = {
|
|
23799
|
-
plg: {
|
|
23800
|
-
velocity: "leaky_bucket",
|
|
23801
|
-
core: "leaky_bucket",
|
|
23802
|
-
mid: "hidden_crisis",
|
|
23803
|
-
enterprise: "hidden_crisis"
|
|
23804
|
-
},
|
|
23805
|
-
smb_velocity: {
|
|
23806
|
-
velocity: "busy_bees",
|
|
23807
|
-
core: "busy_bees",
|
|
23808
|
-
mid: "leaky_bucket",
|
|
23809
|
-
enterprise: "lone_wolf"
|
|
23810
|
-
},
|
|
23811
|
-
mid_market: {
|
|
23812
|
-
velocity: "busy_bees",
|
|
23813
|
-
core: "stale_pipeline",
|
|
23814
|
-
mid: "stale_pipeline",
|
|
23815
|
-
enterprise: "hidden_crisis"
|
|
23816
|
-
},
|
|
23817
|
-
enterprise: {
|
|
23818
|
-
velocity: "lone_wolf",
|
|
23819
|
-
core: "lone_wolf",
|
|
23820
|
-
mid: "hidden_crisis",
|
|
23821
|
-
enterprise: "hidden_crisis"
|
|
23822
|
-
}
|
|
23823
|
-
};
|
|
23824
|
-
MOTION_ONLY = {
|
|
23825
|
-
plg: "leaky_bucket",
|
|
23826
|
-
smb_velocity: "busy_bees",
|
|
23827
|
-
mid_market: "stale_pipeline",
|
|
23828
|
-
enterprise: "hidden_crisis"
|
|
23829
|
-
};
|
|
23830
|
-
BAND_ONLY = {
|
|
23831
|
-
velocity: "busy_bees",
|
|
23832
|
-
core: "leaky_bucket",
|
|
23833
|
-
mid: "stale_pipeline",
|
|
23834
|
-
enterprise: "hidden_crisis"
|
|
23835
|
-
};
|
|
23836
|
-
KEYWORD_HINTS = [
|
|
23837
|
-
{ scenario: "even_keel", patterns: [/\beven keel\b/, /\breasonably healthy\b/, /\bno crisis\b/, /\bjust evaluating\b/, /\bgreen[- ]field\b/] },
|
|
23838
|
-
{ scenario: "compound_pain", patterns: [/\beverything('?s| is) (on fire|red|broken)\b/, /\bcompound\b/, /\ball (five )?vitals\b/, /\bmultiple problems\b/] },
|
|
23839
|
-
{ scenario: "leaky_bucket", patterns: [/\bhandoff\b/, /\bmqls?\b/, /\bleak/, /\bdrop[- ]rate/, /\bvanish/, /\brouting\b/, /\bmarketing.?sales\b/] },
|
|
23840
|
-
{ scenario: "stale_pipeline", patterns: [/\bzombie/, /\bstale\b/, /\bpast[- ]due\b/, /\bforecast(ing)? on fiction\b/, /\bstuck in negotiation\b/, /\bquiet deals?\b/] },
|
|
23841
|
-
{ scenario: "lone_wolf", patterns: [/\bsingle[- ]thread/, /\blone wolf\b/, /\bone contact\b/, /\bchampion leaves\b/] },
|
|
23842
|
-
{ scenario: "busy_bees", patterns: [/\bspray/, /\bnois(e|y)\b/, /\bmisdirected\b/, /\bactivity (volume|metrics)\b/, /\bbusy bees\b/, /\bnot (on|hitting) pipeline\b/] },
|
|
23843
|
-
{ scenario: "hidden_crisis", patterns: [/\bhidden crisis\b/, /\benterprise (is )?(dying|red|stale)\b/, /\bsegment.{0,20}mask/, /\baverages? (look|looks) (fine|okay|yellow)\b/] }
|
|
23844
|
-
];
|
|
23888
|
+
init_organizations();
|
|
23889
|
+
init_people();
|
|
23890
|
+
init_opportunities();
|
|
23891
|
+
init_activities();
|
|
23892
|
+
init_saas_enrichment();
|
|
23893
|
+
init_salesforce();
|
|
23894
|
+
init_hubspot();
|
|
23895
|
+
init_outreach();
|
|
23845
23896
|
}
|
|
23846
23897
|
});
|
|
23847
23898
|
|
|
23848
23899
|
// src/demo/taxonomy-cache.ts
|
|
23849
|
-
import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, existsSync as
|
|
23900
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, existsSync as existsSync23, mkdirSync as mkdirSync13, unlinkSync as unlinkSync3 } from "fs";
|
|
23850
23901
|
import { homedir as homedir7 } from "os";
|
|
23851
23902
|
import { join as join24 } from "path";
|
|
23852
23903
|
function ensureDir6() {
|
|
23853
|
-
if (!
|
|
23904
|
+
if (!existsSync23(NTRP_DIR4)) {
|
|
23854
23905
|
mkdirSync13(NTRP_DIR4, { recursive: true });
|
|
23855
23906
|
}
|
|
23856
23907
|
}
|
|
23857
23908
|
function loadCachedTaxonomy(profile) {
|
|
23858
|
-
if (!
|
|
23909
|
+
if (!existsSync23(TAXONOMY_PATH)) return null;
|
|
23859
23910
|
try {
|
|
23860
23911
|
const parsed = JSON.parse(readFileSync19(TAXONOMY_PATH, "utf-8"));
|
|
23861
23912
|
if (!parsed || typeof parsed !== "object") return null;
|
|
@@ -24332,7 +24383,7 @@ __export(inbox_setup_exports, {
|
|
|
24332
24383
|
shouldOfferInboxSkillSetup: () => shouldOfferInboxSkillSetup
|
|
24333
24384
|
});
|
|
24334
24385
|
import chalk27 from "chalk";
|
|
24335
|
-
import { existsSync as
|
|
24386
|
+
import { existsSync as existsSync24 } from "fs";
|
|
24336
24387
|
function markDemoOffered() {
|
|
24337
24388
|
setConfigValue("ai-inbox-nudge-seen", "true");
|
|
24338
24389
|
}
|
|
@@ -24364,7 +24415,7 @@ function printSkipHint(beat) {
|
|
|
24364
24415
|
async function reuseInboxFolderIfPresent(session, beat, folderPath) {
|
|
24365
24416
|
if (getAiInboxDir()) return false;
|
|
24366
24417
|
const candidates = folderPath ? [folderPath] : [.../* @__PURE__ */ new Set([defaultAiInboxDir(), legacyAiInboxDir()])];
|
|
24367
|
-
const existing = candidates.find((p) =>
|
|
24418
|
+
const existing = candidates.find((p) => existsSync24(p));
|
|
24368
24419
|
if (!existing) return false;
|
|
24369
24420
|
console.log(" " + chalk27.dim("Pickup folder still on disk: ") + existing);
|
|
24370
24421
|
const reuse = await session.confirm("Reuse this pickup folder?", true);
|
|
@@ -24470,7 +24521,7 @@ __export(ingest_exports, {
|
|
|
24470
24521
|
handler: () => handler3
|
|
24471
24522
|
});
|
|
24472
24523
|
import chalk28 from "chalk";
|
|
24473
|
-
import { readFileSync as readFileSync20, existsSync as
|
|
24524
|
+
import { readFileSync as readFileSync20, existsSync as existsSync25 } from "fs";
|
|
24474
24525
|
import { basename as basename6 } from "path";
|
|
24475
24526
|
async function handler3(args, ctx) {
|
|
24476
24527
|
const { positional, flags } = parseArgs(args, [
|
|
@@ -24494,7 +24545,7 @@ async function handler3(args, ctx) {
|
|
|
24494
24545
|
console.error(chalk28.dim(" /ingest --demo [--scenario <name>]"));
|
|
24495
24546
|
process.exit(1);
|
|
24496
24547
|
}
|
|
24497
|
-
if (!
|
|
24548
|
+
if (!existsSync25(file)) {
|
|
24498
24549
|
console.error(chalk28.red(` File not found: ${file}`));
|
|
24499
24550
|
process.exit(1);
|
|
24500
24551
|
}
|
|
@@ -24876,39 +24927,54 @@ var ingest_chat_exports = {};
|
|
|
24876
24927
|
__export(ingest_chat_exports, {
|
|
24877
24928
|
extractFilePath: () => extractFilePath,
|
|
24878
24929
|
ingestFromChat: () => ingestFromChat,
|
|
24930
|
+
ingestPathFromChat: () => ingestPathFromChat,
|
|
24879
24931
|
isDemoIntent: () => isDemoIntent,
|
|
24932
|
+
listCsvsInFolder: () => listCsvsInFolder,
|
|
24880
24933
|
loadDemoFromChat: () => loadDemoFromChat,
|
|
24881
24934
|
looksLikeFilePath: () => looksLikeFilePath
|
|
24882
24935
|
});
|
|
24883
|
-
import { existsSync as
|
|
24884
|
-
import { basename as basename7, resolve as resolve9 } from "path";
|
|
24936
|
+
import { existsSync as existsSync26, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
24937
|
+
import { basename as basename7, join as join25, resolve as resolve9 } from "path";
|
|
24885
24938
|
import { homedir as homedir8 } from "os";
|
|
24886
24939
|
import chalk30 from "chalk";
|
|
24887
24940
|
function extractFilePath(input) {
|
|
24888
|
-
const trimmed = input.trim();
|
|
24941
|
+
const trimmed = input.trim().replace(/^["']|["']$/g, "");
|
|
24942
|
+
if (!trimmed) return null;
|
|
24889
24943
|
const patterns = [
|
|
24890
|
-
/^["'](
|
|
24891
|
-
/^@(
|
|
24892
|
-
/(?:here'?s|file|path|upload)[:\s]+["']?([^\s"']
|
|
24893
|
-
/^~\/\S
|
|
24894
|
-
/^\.\.?\/\S
|
|
24895
|
-
/^\/\S
|
|
24896
|
-
/^[A-Za-z]:\\[^\s]
|
|
24897
|
-
/^[^\s]
|
|
24944
|
+
/^["'](.+)["']$/i,
|
|
24945
|
+
/^@(.+)$/i,
|
|
24946
|
+
/(?:here'?s|file|path|upload|folder|dir)[:\s]+["']?([^\s"']+)["']?/i,
|
|
24947
|
+
/^~\/\S+$/i,
|
|
24948
|
+
/^\.\.?\/\S+$/i,
|
|
24949
|
+
/^\/\S+$/i,
|
|
24950
|
+
/^[A-Za-z]:\\[^\s]+$/i,
|
|
24951
|
+
/^[^\s]+$/i
|
|
24898
24952
|
];
|
|
24899
24953
|
for (const re of patterns) {
|
|
24900
24954
|
const m = trimmed.match(re);
|
|
24901
|
-
|
|
24902
|
-
|
|
24903
|
-
|
|
24904
|
-
|
|
24905
|
-
if (
|
|
24906
|
-
|
|
24907
|
-
|
|
24955
|
+
const candidate = (m?.[1] ?? (re.test(trimmed) ? trimmed : null))?.replace(/^["']|["']$/g, "");
|
|
24956
|
+
if (!candidate) continue;
|
|
24957
|
+
if (!looksLikePathToken(candidate)) continue;
|
|
24958
|
+
const p = expandPath(candidate);
|
|
24959
|
+
if (existsSync26(p)) {
|
|
24960
|
+
try {
|
|
24961
|
+
const st = statSync5(p);
|
|
24962
|
+
if (st.isFile() || st.isDirectory()) return p;
|
|
24963
|
+
} catch {
|
|
24964
|
+
}
|
|
24908
24965
|
}
|
|
24909
24966
|
}
|
|
24910
24967
|
return null;
|
|
24911
24968
|
}
|
|
24969
|
+
function looksLikePathToken(token) {
|
|
24970
|
+
if (token.length < 2) return false;
|
|
24971
|
+
if (/^(yes|no|y|n|back|b|prev|cancel|skip|help|demo)$/i.test(token)) return false;
|
|
24972
|
+
if (token.includes("/") || token.includes("\\")) return true;
|
|
24973
|
+
if (token.startsWith("~")) return true;
|
|
24974
|
+
if (/^[A-Za-z]:/.test(token)) return true;
|
|
24975
|
+
if (/\.[A-Za-z0-9]{1,8}$/.test(token)) return true;
|
|
24976
|
+
return false;
|
|
24977
|
+
}
|
|
24912
24978
|
function expandPath(p) {
|
|
24913
24979
|
if (p.startsWith("~/")) return resolve9(homedir8(), p.slice(2));
|
|
24914
24980
|
return resolve9(p);
|
|
@@ -24916,6 +24982,73 @@ function expandPath(p) {
|
|
|
24916
24982
|
function looksLikeFilePath(input) {
|
|
24917
24983
|
return extractFilePath(input) !== null;
|
|
24918
24984
|
}
|
|
24985
|
+
function listCsvsInFolder(dir) {
|
|
24986
|
+
try {
|
|
24987
|
+
if (!statSync5(dir).isDirectory()) return [];
|
|
24988
|
+
return readdirSync6(dir).filter((name) => name.toLowerCase().endsWith(".csv")).map((name) => join25(dir, name)).sort();
|
|
24989
|
+
} catch {
|
|
24990
|
+
return [];
|
|
24991
|
+
}
|
|
24992
|
+
}
|
|
24993
|
+
async function ingestPathFromChat(ctx, rawPath) {
|
|
24994
|
+
let st;
|
|
24995
|
+
try {
|
|
24996
|
+
st = statSync5(rawPath);
|
|
24997
|
+
} catch {
|
|
24998
|
+
console.log(" " + chalk30.red(`Path not found: ${rawPath}`));
|
|
24999
|
+
return false;
|
|
25000
|
+
}
|
|
25001
|
+
if (st.isDirectory()) {
|
|
25002
|
+
const csvs = listCsvsInFolder(rawPath);
|
|
25003
|
+
if (csvs.length === 0) {
|
|
25004
|
+
console.log();
|
|
25005
|
+
console.log(" " + paint("accent", "Folder noted") + chalk30.dim(` \u2014 ${rawPath}`));
|
|
25006
|
+
console.log(" " + chalk30.dim("No CSV files one level deep. Drop a .csv path, or put exports in that folder."));
|
|
25007
|
+
ctx.attachments = [
|
|
25008
|
+
...ctx.attachments ?? [],
|
|
25009
|
+
{ path: rawPath, ingested_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
25010
|
+
];
|
|
25011
|
+
saveSessionState(ctx);
|
|
25012
|
+
markProductionDataSeen();
|
|
25013
|
+
return false;
|
|
25014
|
+
}
|
|
25015
|
+
if (csvs.length === 1) {
|
|
25016
|
+
console.log(" " + chalk30.dim(`Found ${basename7(csvs[0])} in folder \u2014 ingesting.`));
|
|
25017
|
+
return ingestFromChat(ctx, csvs[0]);
|
|
25018
|
+
}
|
|
25019
|
+
if (!ctx.rl) {
|
|
25020
|
+
console.log(" " + chalk30.dim(`Found ${csvs.length} CSVs \u2014 re-run interactively to pick one.`));
|
|
25021
|
+
return false;
|
|
25022
|
+
}
|
|
25023
|
+
const prompts = createPromptSession(ctx.rl, ctx);
|
|
25024
|
+
try {
|
|
25025
|
+
const picked = await prompts.choose(
|
|
25026
|
+
"Which CSV in that folder?",
|
|
25027
|
+
csvs.map((p) => ({ value: p, label: basename7(p), description: p })),
|
|
25028
|
+
{ default: csvs[0] }
|
|
25029
|
+
);
|
|
25030
|
+
return ingestFromChat(ctx, picked);
|
|
25031
|
+
} finally {
|
|
25032
|
+
prompts.close();
|
|
25033
|
+
}
|
|
25034
|
+
}
|
|
25035
|
+
if (st.isFile()) {
|
|
25036
|
+
if (!rawPath.toLowerCase().endsWith(".csv")) {
|
|
25037
|
+
console.log();
|
|
25038
|
+
console.log(" " + paint("accent", "Path noted") + chalk30.dim(` \u2014 ${rawPath}`));
|
|
25039
|
+
console.log(" " + chalk30.dim("NTRP ingests CSV exports today. Drop a .csv from that location when ready."));
|
|
25040
|
+
ctx.attachments = [
|
|
25041
|
+
...ctx.attachments ?? [],
|
|
25042
|
+
{ path: rawPath, ingested_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
25043
|
+
];
|
|
25044
|
+
saveSessionState(ctx);
|
|
25045
|
+
markProductionDataSeen();
|
|
25046
|
+
return false;
|
|
25047
|
+
}
|
|
25048
|
+
return ingestFromChat(ctx, rawPath);
|
|
25049
|
+
}
|
|
25050
|
+
return false;
|
|
25051
|
+
}
|
|
24919
25052
|
async function ingestFromChat(ctx, filePath) {
|
|
24920
25053
|
if (!ctx.rl) {
|
|
24921
25054
|
console.log(" " + chalk30.red("Ingest confirm requires interactive mode."));
|
|
@@ -24980,6 +25113,7 @@ async function ingestFromChat(ctx, filePath) {
|
|
|
24980
25113
|
};
|
|
24981
25114
|
invalidateGapAudit(ctx);
|
|
24982
25115
|
saveSessionState(ctx);
|
|
25116
|
+
markProductionDataSeen();
|
|
24983
25117
|
console.log();
|
|
24984
25118
|
console.log(" " + paint("accent", "\u2713 Data loaded") + chalk30.dim(` \u2014 ${name}`));
|
|
24985
25119
|
recordMessage(ctx, "user", `[ingested ${name}]`);
|
|
@@ -25055,6 +25189,7 @@ async function loadDemoFromChat(ctx, scenario, opts = {}) {
|
|
|
25055
25189
|
counts,
|
|
25056
25190
|
ingested_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
25057
25191
|
};
|
|
25192
|
+
markDemoDataSeen();
|
|
25058
25193
|
if (!ctx.scope) {
|
|
25059
25194
|
const { proposeScopeFromIntent: proposeScopeFromIntent2 } = await Promise.resolve().then(() => (init_scope(), scope_exports));
|
|
25060
25195
|
const proposal = proposeScopeFromIntent2("demo pipeline and metrics");
|
|
@@ -25091,6 +25226,7 @@ var init_ingest_chat = __esm({
|
|
|
25091
25226
|
init_compute2();
|
|
25092
25227
|
init_theme();
|
|
25093
25228
|
init_demo();
|
|
25229
|
+
init_onboard_tiers();
|
|
25094
25230
|
}
|
|
25095
25231
|
});
|
|
25096
25232
|
|
|
@@ -25849,9 +25985,9 @@ async function handleGetSessionBrief(input) {
|
|
|
25849
25985
|
if (!target) {
|
|
25850
25986
|
return { error: `No session matching "${raw}".` };
|
|
25851
25987
|
}
|
|
25852
|
-
const { existsSync:
|
|
25988
|
+
const { existsSync: existsSync29, readFileSync: readFileSync23 } = await import("fs");
|
|
25853
25989
|
const briefPath = contextDocPathForSession2(target.id);
|
|
25854
|
-
if (!
|
|
25990
|
+
if (!existsSync29(briefPath)) {
|
|
25855
25991
|
return {
|
|
25856
25992
|
session_id: target.id,
|
|
25857
25993
|
error: "No context brief on disk for this session (created before brief storage existed).",
|
|
@@ -26866,14 +27002,14 @@ var init_ask = __esm({
|
|
|
26866
27002
|
});
|
|
26867
27003
|
|
|
26868
27004
|
// src/services/setup.ts
|
|
26869
|
-
import { existsSync as
|
|
26870
|
-
import { join as
|
|
27005
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync21, writeFileSync as writeFileSync16 } from "fs";
|
|
27006
|
+
import { join as join26 } from "path";
|
|
26871
27007
|
function setupCheck() {
|
|
26872
27008
|
const home = ntrpHome();
|
|
26873
27009
|
let writable = false;
|
|
26874
27010
|
try {
|
|
26875
27011
|
mkdirSync14(home, { recursive: true });
|
|
26876
|
-
const probe =
|
|
27012
|
+
const probe = join26(home, ".write-check");
|
|
26877
27013
|
writeFileSync16(probe, "ok\n");
|
|
26878
27014
|
writable = true;
|
|
26879
27015
|
} catch {
|
|
@@ -26925,11 +27061,11 @@ var init_setup = __esm({
|
|
|
26925
27061
|
});
|
|
26926
27062
|
|
|
26927
27063
|
// src/version.ts
|
|
26928
|
-
import { existsSync as
|
|
26929
|
-
import { dirname as dirname5, join as
|
|
27064
|
+
import { existsSync as existsSync28, readFileSync as readFileSync22 } from "fs";
|
|
27065
|
+
import { dirname as dirname5, join as join27 } from "path";
|
|
26930
27066
|
import { fileURLToPath } from "url";
|
|
26931
27067
|
function readVersionFromPackageJson(packageJsonPath) {
|
|
26932
|
-
if (!
|
|
27068
|
+
if (!existsSync28(packageJsonPath)) return null;
|
|
26933
27069
|
try {
|
|
26934
27070
|
const pkg = JSON.parse(readFileSync22(packageJsonPath, "utf-8"));
|
|
26935
27071
|
if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
|
|
@@ -26939,7 +27075,7 @@ function readVersionFromPackageJson(packageJsonPath) {
|
|
|
26939
27075
|
}
|
|
26940
27076
|
function readVersionNearEntry(entryPath) {
|
|
26941
27077
|
const start = dirname5(entryPath);
|
|
26942
|
-
for (const rel of [
|
|
27078
|
+
for (const rel of [join27(start, "..", "package.json"), join27(start, "../..", "package.json")]) {
|
|
26943
27079
|
const version = readVersionFromPackageJson(rel);
|
|
26944
27080
|
if (version) return version;
|
|
26945
27081
|
}
|