@sonnechasser/ntrp 1.4.9 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +729 -166
- package/dist/mcp/server.js +1116 -993
- package/package.json +2 -1
package/dist/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
|
{
|
|
@@ -20838,724 +20842,985 @@ var init_demo = __esm({
|
|
|
20838
20842
|
}
|
|
20839
20843
|
});
|
|
20840
20844
|
|
|
20841
|
-
// src/
|
|
20842
|
-
var
|
|
20843
|
-
__export(
|
|
20844
|
-
|
|
20845
|
-
|
|
20845
|
+
// src/demo/scenarios.ts
|
|
20846
|
+
var scenarios_exports = {};
|
|
20847
|
+
__export(scenarios_exports, {
|
|
20848
|
+
NAMED_DEMO_SCENARIOS: () => NAMED_DEMO_SCENARIOS,
|
|
20849
|
+
SCENARIOS: () => SCENARIOS,
|
|
20850
|
+
SCENARIO_LIST: () => SCENARIO_LIST,
|
|
20851
|
+
blendScenarios: () => blendScenarios,
|
|
20852
|
+
getScenario: () => getScenario,
|
|
20853
|
+
isNamedDemoScenario: () => isNamedDemoScenario,
|
|
20854
|
+
pickRandomScenario: () => pickRandomScenario,
|
|
20855
|
+
resolveScenarioInput: () => resolveScenarioInput
|
|
20846
20856
|
});
|
|
20847
|
-
|
|
20848
|
-
|
|
20849
|
-
|
|
20850
|
-
|
|
20851
|
-
|
|
20852
|
-
|
|
20853
|
-
});
|
|
20854
|
-
return {
|
|
20855
|
-
headers: result.meta.fields ?? [],
|
|
20856
|
-
rows: result.data,
|
|
20857
|
-
rowCount: result.data.length
|
|
20858
|
-
};
|
|
20857
|
+
function getScenario(key) {
|
|
20858
|
+
const scenario = SCENARIOS[key];
|
|
20859
|
+
if (!scenario) {
|
|
20860
|
+
throw new Error(`Unknown scenario: ${key}. Valid: ${Object.keys(SCENARIOS).join(", ")}`);
|
|
20861
|
+
}
|
|
20862
|
+
return scenario;
|
|
20859
20863
|
}
|
|
20860
|
-
function
|
|
20861
|
-
return
|
|
20864
|
+
function isNamedDemoScenario(raw) {
|
|
20865
|
+
return NAMED_DEMO_SCENARIOS.includes(raw);
|
|
20862
20866
|
}
|
|
20863
|
-
|
|
20864
|
-
|
|
20865
|
-
|
|
20867
|
+
function resolveScenarioInput(raw) {
|
|
20868
|
+
const input = raw?.trim();
|
|
20869
|
+
if (!input) return void 0;
|
|
20870
|
+
if (input === "research_blend") return "research_blend";
|
|
20871
|
+
if (NAMED_DEMO_SCENARIOS.includes(input)) {
|
|
20872
|
+
return input;
|
|
20866
20873
|
}
|
|
20867
|
-
|
|
20868
|
-
|
|
20869
|
-
|
|
20870
|
-
|
|
20871
|
-
return
|
|
20874
|
+
const n = Number(input);
|
|
20875
|
+
if (Number.isInteger(n) && n >= 1 && n <= NAMED_DEMO_SCENARIOS.length) {
|
|
20876
|
+
return NAMED_DEMO_SCENARIOS[n - 1];
|
|
20877
|
+
}
|
|
20878
|
+
return null;
|
|
20872
20879
|
}
|
|
20873
|
-
|
|
20874
|
-
|
|
20875
|
-
|
|
20880
|
+
function pickRandomScenario() {
|
|
20881
|
+
return RANDOM_POOL[Math.floor(Math.random() * RANDOM_POOL.length)];
|
|
20882
|
+
}
|
|
20883
|
+
function blendScenarios(_research) {
|
|
20884
|
+
const blended = {
|
|
20885
|
+
...BASELINE,
|
|
20886
|
+
key: "research_blend",
|
|
20887
|
+
label: "Research-Derived Blend",
|
|
20888
|
+
description: "Realistic data with mild-to-moderate problems across all vital signs.",
|
|
20889
|
+
story: "Generated with default research blend. Problems are seeded across all vital signs at realistic levels.",
|
|
20890
|
+
hook: "Mild-to-moderate problems seeded across all five vitals.",
|
|
20891
|
+
// Bump all problems slightly above baseline for discoverability
|
|
20892
|
+
staleContactRatio: 0.2,
|
|
20893
|
+
pastCloseDateRatio: 0.15,
|
|
20894
|
+
mqlDropRatio: 0.15,
|
|
20895
|
+
qualifiedNoOutreachRatio: 0.15,
|
|
20896
|
+
stuckDealRatio: 0.15,
|
|
20897
|
+
noiseActivityRatio: 0.2,
|
|
20898
|
+
singleThreadRatio: 0.25
|
|
20899
|
+
};
|
|
20900
|
+
return blended;
|
|
20901
|
+
}
|
|
20902
|
+
var BASELINE, SCENARIOS, SCENARIO_LIST, NAMED_DEMO_SCENARIOS, RANDOM_POOL;
|
|
20903
|
+
var init_scenarios = __esm({
|
|
20904
|
+
"src/demo/scenarios.ts"() {
|
|
20876
20905
|
"use strict";
|
|
20877
|
-
|
|
20878
|
-
|
|
20879
|
-
|
|
20880
|
-
|
|
20881
|
-
|
|
20882
|
-
|
|
20906
|
+
BASELINE = {
|
|
20907
|
+
enterpriseRatio: 0.2,
|
|
20908
|
+
midMarketRatio: 0.3,
|
|
20909
|
+
smbRatio: 0.5,
|
|
20910
|
+
staleContactRatio: 0.15,
|
|
20911
|
+
staleContactRatioEnterprise: 0.2,
|
|
20912
|
+
staleContactRatioSmb: 0.1,
|
|
20913
|
+
pastCloseDateRatio: 0.1,
|
|
20914
|
+
staleDays: 120,
|
|
20915
|
+
mqlDropRatio: 0.1,
|
|
20916
|
+
qualifiedNoOutreachRatio: 0.1,
|
|
20917
|
+
stuckDealRatio: 0.1,
|
|
20918
|
+
stuckInNegotiationDays: 45,
|
|
20919
|
+
avgDaysPerStageEnterprise: 25,
|
|
20920
|
+
avgDaysPerStageSmb: 8,
|
|
20921
|
+
activityVolumeMultiplier: 1,
|
|
20922
|
+
noiseActivityRatio: 0.15,
|
|
20923
|
+
singleThreadRatio: 0.2,
|
|
20924
|
+
loneWolfRepIndex: null,
|
|
20925
|
+
loneWolfSingleThreadRatio: 0,
|
|
20926
|
+
freshnessGapDays: 90
|
|
20927
|
+
};
|
|
20928
|
+
SCENARIOS = {
|
|
20929
|
+
hidden_crisis: {
|
|
20930
|
+
...BASELINE,
|
|
20931
|
+
key: "hidden_crisis",
|
|
20932
|
+
label: "The Hidden Crisis",
|
|
20933
|
+
description: "Overall health looks yellow but Enterprise is deep red, masked by strong SMB numbers.",
|
|
20934
|
+
story: "Your aggregate numbers look okay \u2014 but when you break it by segment, Enterprise is dying. 60% of enterprise contacts have gone dark, deals are single-threaded, and SMB is carrying the average.",
|
|
20935
|
+
hook: "SMB is carrying the average while Enterprise dies quietly.",
|
|
20936
|
+
staleContactRatio: 0.3,
|
|
20937
|
+
staleContactRatioEnterprise: 0.6,
|
|
20938
|
+
staleContactRatioSmb: 0.1,
|
|
20939
|
+
singleThreadRatio: 0.5,
|
|
20940
|
+
enterpriseRatio: 0.3,
|
|
20941
|
+
midMarketRatio: 0.3,
|
|
20942
|
+
smbRatio: 0.4
|
|
20883
20943
|
},
|
|
20884
|
-
|
|
20885
|
-
|
|
20886
|
-
|
|
20887
|
-
|
|
20944
|
+
leaky_bucket: {
|
|
20945
|
+
...BASELINE,
|
|
20946
|
+
key: "leaky_bucket",
|
|
20947
|
+
label: "The Leaky Bucket",
|
|
20948
|
+
description: "Marketing generates plenty of leads but 40% vanish at handoff to sales.",
|
|
20949
|
+
story: "Marketing is doing its job \u2014 MQLs are flowing. But 40% of qualified leads never show up in sales workflows. They're falling through the cracks at handoff, and nobody's noticing because marketing reports MQL count and sales reports pipeline value.",
|
|
20950
|
+
hook: "MQLs flow in, then 40% vanish at the sales handoff.",
|
|
20951
|
+
mqlDropRatio: 0.4,
|
|
20952
|
+
qualifiedNoOutreachRatio: 0.35,
|
|
20953
|
+
staleContactRatio: 0.2
|
|
20888
20954
|
},
|
|
20889
|
-
|
|
20890
|
-
|
|
20891
|
-
|
|
20955
|
+
stale_pipeline: {
|
|
20956
|
+
...BASELINE,
|
|
20957
|
+
key: "stale_pipeline",
|
|
20958
|
+
label: "The Stale Pipeline",
|
|
20959
|
+
description: "Big pipeline number but half the deals are zombies stuck in late stages.",
|
|
20960
|
+
story: "The pipeline report says $5M. But look closer: half those deals have close dates in the past, 40% are stuck in Negotiation for 120+ days, and nobody's touching them. You're forecasting on fiction.",
|
|
20961
|
+
hook: "Half the pipeline is zombies \u2014 you're forecasting on fiction.",
|
|
20962
|
+
pastCloseDateRatio: 0.5,
|
|
20963
|
+
stuckDealRatio: 0.4,
|
|
20964
|
+
stuckInNegotiationDays: 120,
|
|
20965
|
+
staleContactRatio: 0.25,
|
|
20966
|
+
staleDays: 90
|
|
20967
|
+
},
|
|
20968
|
+
lone_wolf: {
|
|
20969
|
+
...BASELINE,
|
|
20970
|
+
key: "lone_wolf",
|
|
20971
|
+
label: "The Lone Wolf",
|
|
20972
|
+
description: "One rep has great numbers but every single deal is single-threaded.",
|
|
20973
|
+
story: "Your top rep is crushing it on paper \u2014 biggest pipeline, highest close rate. But every deal has exactly one contact. One champion goes on vacation, gets promoted, or leaves, and the entire pipeline collapses.",
|
|
20974
|
+
hook: "Top rep, huge pipeline, one contact per deal \u2014 one exit from collapse.",
|
|
20975
|
+
loneWolfRepIndex: 0,
|
|
20976
|
+
loneWolfSingleThreadRatio: 1,
|
|
20977
|
+
singleThreadRatio: 0.15
|
|
20978
|
+
},
|
|
20979
|
+
busy_bees: {
|
|
20980
|
+
...BASELINE,
|
|
20981
|
+
key: "busy_bees",
|
|
20982
|
+
label: "The Busy Bees",
|
|
20983
|
+
description: "High activity volume across the team, but most of it hits dead ends.",
|
|
20984
|
+
story: "Your team is busy. Activity metrics look great \u2014 calls are up, emails are up, meetings are up. But 60% of that activity is aimed at contacts with no associated pipeline. Reps are spraying, not aiming.",
|
|
20985
|
+
hook: "Reps are spraying, not aiming.",
|
|
20986
|
+
activityVolumeMultiplier: 3,
|
|
20987
|
+
noiseActivityRatio: 0.6,
|
|
20988
|
+
staleContactRatio: 0.2
|
|
20989
|
+
},
|
|
20990
|
+
even_keel: {
|
|
20991
|
+
...BASELINE,
|
|
20992
|
+
key: "even_keel",
|
|
20993
|
+
label: "The Even Keel",
|
|
20994
|
+
description: "A reasonably healthy book \u2014 enough yellow to listen, not a five-alarm fire.",
|
|
20995
|
+
story: "Most numbers sit in a normal band. A few contacts have gone quiet, a handful of deals are slow, activity is mostly on-pipeline. This is what 'fine' looks like on the stethoscope \u2014 useful when you want to evaluate NTRP without a manufactured crisis.",
|
|
20996
|
+
hook: "Reasonably healthy \u2014 enough signal to listen, not a crisis."
|
|
20997
|
+
},
|
|
20998
|
+
compound_pain: {
|
|
20999
|
+
...BASELINE,
|
|
21000
|
+
key: "compound_pain",
|
|
21001
|
+
label: "The Compound Fracture",
|
|
21002
|
+
description: "Several vitals are red at once \u2014 stale pipeline, leaky handoff, noisy activity, thin threads.",
|
|
21003
|
+
story: "This isn't one problem. Enterprise contacts have gone dark, MQLs vanish at handoff, late-stage deals are zombies, and a lot of activity never touches pipeline. The gating logic has to pick a first red \u2014 that's the point of this book.",
|
|
21004
|
+
hook: "Several vitals red at once \u2014 the stethoscope has to pick a first listen.",
|
|
21005
|
+
enterpriseRatio: 0.3,
|
|
21006
|
+
midMarketRatio: 0.3,
|
|
21007
|
+
smbRatio: 0.4,
|
|
21008
|
+
staleContactRatio: 0.35,
|
|
21009
|
+
staleContactRatioEnterprise: 0.55,
|
|
21010
|
+
staleContactRatioSmb: 0.15,
|
|
21011
|
+
pastCloseDateRatio: 0.35,
|
|
21012
|
+
staleDays: 100,
|
|
21013
|
+
mqlDropRatio: 0.3,
|
|
21014
|
+
qualifiedNoOutreachRatio: 0.25,
|
|
21015
|
+
stuckDealRatio: 0.3,
|
|
21016
|
+
stuckInNegotiationDays: 90,
|
|
21017
|
+
activityVolumeMultiplier: 2,
|
|
21018
|
+
noiseActivityRatio: 0.4,
|
|
21019
|
+
singleThreadRatio: 0.4
|
|
20892
21020
|
}
|
|
20893
21021
|
};
|
|
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
|
-
};
|
|
21022
|
+
SCENARIO_LIST = Object.values(SCENARIOS);
|
|
21023
|
+
NAMED_DEMO_SCENARIOS = [
|
|
21024
|
+
"hidden_crisis",
|
|
21025
|
+
"leaky_bucket",
|
|
21026
|
+
"stale_pipeline",
|
|
21027
|
+
"lone_wolf",
|
|
21028
|
+
"busy_bees",
|
|
21029
|
+
"even_keel",
|
|
21030
|
+
"compound_pain"
|
|
21031
|
+
];
|
|
21032
|
+
RANDOM_POOL = [...NAMED_DEMO_SCENARIOS];
|
|
20914
21033
|
}
|
|
20915
21034
|
});
|
|
20916
21035
|
|
|
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
|
-
|
|
21036
|
+
// src/demo/scenario-fit.ts
|
|
21037
|
+
function dealBandFromCycleDays(days) {
|
|
21038
|
+
if (days == null || !Number.isFinite(days) || days <= 0) return void 0;
|
|
21039
|
+
if (days <= 21) return "velocity";
|
|
21040
|
+
if (days <= 45) return "core";
|
|
21041
|
+
if (days <= 90) return "mid";
|
|
21042
|
+
return "enterprise";
|
|
21043
|
+
}
|
|
21044
|
+
function dealBandFromAverageDealSize(raw) {
|
|
21045
|
+
if (!raw) return void 0;
|
|
21046
|
+
const t = raw.trim().toLowerCase();
|
|
21047
|
+
if (!t) return void 0;
|
|
21048
|
+
const match = t.match(/(\d+(?:\.\d+)?)\s*(k|m|million|thousand)?/i);
|
|
21049
|
+
if (!match) return void 0;
|
|
21050
|
+
let n = Number(match[1]);
|
|
21051
|
+
if (!Number.isFinite(n)) return void 0;
|
|
21052
|
+
const unit = (match[2] ?? "").toLowerCase();
|
|
21053
|
+
if (unit === "k" || unit === "thousand") n *= 1e3;
|
|
21054
|
+
else if (unit === "m" || unit === "million") n *= 1e6;
|
|
21055
|
+
else if (n > 0 && n < 500) n *= 1e3;
|
|
21056
|
+
if (n < 15e3) return "velocity";
|
|
21057
|
+
if (n < 5e4) return "core";
|
|
21058
|
+
if (n < 15e4) return "mid";
|
|
21059
|
+
return "enterprise";
|
|
21060
|
+
}
|
|
21061
|
+
function signalsFromProfile(profile) {
|
|
21062
|
+
if (!profile) return {};
|
|
21063
|
+
const text = [
|
|
21064
|
+
profile.industry,
|
|
21065
|
+
profile.product_description,
|
|
21066
|
+
profile.target_customer,
|
|
21067
|
+
profile.user_scope,
|
|
21068
|
+
profile.custom_context
|
|
21069
|
+
].filter(Boolean).join("\n");
|
|
20946
21070
|
return {
|
|
20947
|
-
|
|
20948
|
-
|
|
20949
|
-
|
|
21071
|
+
salesMotion: profile.sales_motion,
|
|
21072
|
+
dealBand: dealBandFromAverageDealSize(profile.average_deal_size) ?? dealBandFromCycleDays(profile.sales_cycle_days),
|
|
21073
|
+
cycleDays: profile.sales_cycle_days,
|
|
21074
|
+
text
|
|
20950
21075
|
};
|
|
20951
21076
|
}
|
|
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;
|
|
21077
|
+
function keywordHits(text) {
|
|
21078
|
+
const hits = {};
|
|
21079
|
+
const hay = text.toLowerCase();
|
|
21080
|
+
for (const { scenario, patterns } of KEYWORD_HINTS) {
|
|
21081
|
+
let n = 0;
|
|
21082
|
+
for (const re of patterns) {
|
|
21083
|
+
if (re.test(hay)) n++;
|
|
21084
|
+
}
|
|
21085
|
+
if (n > 0) hits[scenario] = n;
|
|
21007
21086
|
}
|
|
21008
|
-
return
|
|
21087
|
+
return hits;
|
|
21009
21088
|
}
|
|
21010
|
-
function
|
|
21011
|
-
|
|
21012
|
-
if (
|
|
21013
|
-
|
|
21014
|
-
return
|
|
21089
|
+
function matrixPick(motion, band) {
|
|
21090
|
+
if (motion && band) return MATRIX[motion][band];
|
|
21091
|
+
if (motion) return MOTION_ONLY[motion];
|
|
21092
|
+
if (band) return BAND_ONLY[band];
|
|
21093
|
+
return "even_keel";
|
|
21015
21094
|
}
|
|
21016
|
-
function
|
|
21017
|
-
const
|
|
21018
|
-
|
|
21019
|
-
|
|
21020
|
-
if (!mappedCols.has(key) && value !== void 0 && value !== "") {
|
|
21021
|
-
metadata[key] = value;
|
|
21022
|
-
}
|
|
21095
|
+
function reasonFor(scenario, signals, via) {
|
|
21096
|
+
const s = getScenario(scenario);
|
|
21097
|
+
if (via === "keywords") {
|
|
21098
|
+
return `Your notes sound like ${s.label} \u2014 ${s.hook}`;
|
|
21023
21099
|
}
|
|
21024
|
-
|
|
21100
|
+
const motion = signals.salesMotion;
|
|
21101
|
+
const band = signals.dealBand;
|
|
21102
|
+
if (motion && band) {
|
|
21103
|
+
return `${labelMotion(motion)} with ${labelBand(band)} deals maps to ${s.label}.`;
|
|
21104
|
+
}
|
|
21105
|
+
if (motion) return `${labelMotion(motion)} books usually show up as ${s.label}.`;
|
|
21106
|
+
if (band) return `${labelBand(band)} deals usually show up as ${s.label}.`;
|
|
21107
|
+
return `${s.label} is the even-keeled starting book when we don't know the motion yet.`;
|
|
21025
21108
|
}
|
|
21026
|
-
function
|
|
21027
|
-
return
|
|
21109
|
+
function labelMotion(m) {
|
|
21110
|
+
return MOTION_FIT_CHOICES.find((c) => c.value === m)?.label ?? m;
|
|
21028
21111
|
}
|
|
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;
|
|
21112
|
+
function labelBand(b) {
|
|
21113
|
+
return DEAL_BAND_CHOICES.find((c) => c.value === b)?.label ?? b;
|
|
21037
21114
|
}
|
|
21038
|
-
|
|
21039
|
-
const
|
|
21040
|
-
if (
|
|
21041
|
-
|
|
21042
|
-
|
|
21043
|
-
|
|
21044
|
-
|
|
21045
|
-
|
|
21046
|
-
|
|
21047
|
-
|
|
21048
|
-
|
|
21115
|
+
function inferDemoScenario(signals) {
|
|
21116
|
+
const text = signals.text?.trim() ?? "";
|
|
21117
|
+
if (text) {
|
|
21118
|
+
const hits = keywordHits(text);
|
|
21119
|
+
let best;
|
|
21120
|
+
let bestN = 0;
|
|
21121
|
+
for (const id of NAMED_DEMO_SCENARIOS) {
|
|
21122
|
+
const n = hits[id] ?? 0;
|
|
21123
|
+
if (n > bestN) {
|
|
21124
|
+
best = id;
|
|
21125
|
+
bestN = n;
|
|
21126
|
+
}
|
|
21127
|
+
}
|
|
21128
|
+
if (best && bestN > 0) {
|
|
21129
|
+
return { scenario: best, reason: reasonFor(best, signals, "keywords"), source: "heuristic" };
|
|
21130
|
+
}
|
|
21049
21131
|
}
|
|
21050
|
-
|
|
21132
|
+
const scenario = matrixPick(signals.salesMotion, signals.dealBand);
|
|
21133
|
+
return { scenario, reason: reasonFor(scenario, signals, "matrix"), source: "heuristic" };
|
|
21051
21134
|
}
|
|
21052
|
-
|
|
21053
|
-
const
|
|
21054
|
-
|
|
21055
|
-
|
|
21056
|
-
|
|
21057
|
-
|
|
21058
|
-
|
|
21059
|
-
|
|
21060
|
-
|
|
21061
|
-
|
|
21135
|
+
function getPreferredDemoScenario() {
|
|
21136
|
+
const raw = getConfigValue(PREF_SCENARIO_KEY);
|
|
21137
|
+
return raw && isNamedDemoScenario(raw) ? raw : void 0;
|
|
21138
|
+
}
|
|
21139
|
+
function saveDemoFit(opts) {
|
|
21140
|
+
setConfigValue(PREF_SCENARIO_KEY, opts.scenario);
|
|
21141
|
+
if (opts.motion) {
|
|
21142
|
+
setConfigValue(PREF_MOTION_KEY, opts.motion);
|
|
21143
|
+
if (opts.syncSalesMotion && !isProfileConfigured(loadProfile())) {
|
|
21144
|
+
setConfigValue("sales-motion", opts.motion);
|
|
21062
21145
|
}
|
|
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
21146
|
}
|
|
21075
|
-
|
|
21147
|
+
if (opts.dealBand) setConfigValue(PREF_BAND_KEY, opts.dealBand);
|
|
21076
21148
|
}
|
|
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 };
|
|
21149
|
+
function scenarioMenuChoices() {
|
|
21150
|
+
return NAMED_DEMO_SCENARIOS.map((id) => {
|
|
21151
|
+
const s = getScenario(id);
|
|
21152
|
+
return {
|
|
21153
|
+
value: id,
|
|
21154
|
+
label: s.label,
|
|
21155
|
+
description: s.hook
|
|
21156
|
+
};
|
|
21157
|
+
});
|
|
21109
21158
|
}
|
|
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
|
-
|
|
21159
|
+
var PREF_SCENARIO_KEY, PREF_MOTION_KEY, PREF_BAND_KEY, MOTION_FIT_CHOICES, DEAL_BAND_CHOICES, MATRIX, MOTION_ONLY, BAND_ONLY, KEYWORD_HINTS;
|
|
21160
|
+
var init_scenario_fit = __esm({
|
|
21161
|
+
"src/demo/scenario-fit.ts"() {
|
|
21162
|
+
"use strict";
|
|
21163
|
+
init_store();
|
|
21164
|
+
init_profile();
|
|
21165
|
+
init_scenarios();
|
|
21166
|
+
PREF_SCENARIO_KEY = "demo-scenario-preference";
|
|
21167
|
+
PREF_MOTION_KEY = "demo-fit-motion";
|
|
21168
|
+
PREF_BAND_KEY = "demo-fit-deal-band";
|
|
21169
|
+
MOTION_FIT_CHOICES = [
|
|
21170
|
+
{
|
|
21171
|
+
value: "plg",
|
|
21172
|
+
label: "Product-led / self-serve",
|
|
21173
|
+
description: "Users start themselves; sales assists or expands"
|
|
21174
|
+
},
|
|
21175
|
+
{
|
|
21176
|
+
value: "smb_velocity",
|
|
21177
|
+
label: "High-volume SMB",
|
|
21178
|
+
description: "Fast cycles, lots of small deals, outbound or inbound machine"
|
|
21179
|
+
},
|
|
21180
|
+
{
|
|
21181
|
+
value: "mid_market",
|
|
21182
|
+
label: "Mid-market, structured process",
|
|
21183
|
+
description: "A real sales cycle, a few stakeholders, moderate ACV"
|
|
21184
|
+
},
|
|
21185
|
+
{
|
|
21186
|
+
value: "enterprise",
|
|
21187
|
+
label: "Enterprise, long cycles",
|
|
21188
|
+
description: "Large deals, many buyers, quarters not weeks"
|
|
21189
|
+
}
|
|
21190
|
+
];
|
|
21191
|
+
DEAL_BAND_CHOICES = [
|
|
21192
|
+
{
|
|
21193
|
+
value: "velocity",
|
|
21194
|
+
label: "Under ~$15K, days to a couple of weeks",
|
|
21195
|
+
description: "Velocity / transactional"
|
|
21196
|
+
},
|
|
21197
|
+
{
|
|
21198
|
+
value: "core",
|
|
21199
|
+
label: "~$15K\u2013$50K, a few weeks",
|
|
21200
|
+
description: "Core SMB"
|
|
21201
|
+
},
|
|
21202
|
+
{
|
|
21203
|
+
value: "mid",
|
|
21204
|
+
label: "~$50K\u2013$150K, 1\u20133 months",
|
|
21205
|
+
description: "Classic mid-market"
|
|
21206
|
+
},
|
|
21207
|
+
{
|
|
21208
|
+
value: "enterprise",
|
|
21209
|
+
label: "$150K+, a quarter or more",
|
|
21210
|
+
description: "Enterprise / strategic"
|
|
21211
|
+
}
|
|
21212
|
+
];
|
|
21213
|
+
MATRIX = {
|
|
21214
|
+
plg: {
|
|
21215
|
+
velocity: "leaky_bucket",
|
|
21216
|
+
core: "leaky_bucket",
|
|
21217
|
+
mid: "hidden_crisis",
|
|
21218
|
+
enterprise: "hidden_crisis"
|
|
21219
|
+
},
|
|
21220
|
+
smb_velocity: {
|
|
21221
|
+
velocity: "busy_bees",
|
|
21222
|
+
core: "busy_bees",
|
|
21223
|
+
mid: "leaky_bucket",
|
|
21224
|
+
enterprise: "lone_wolf"
|
|
21225
|
+
},
|
|
21226
|
+
mid_market: {
|
|
21227
|
+
velocity: "busy_bees",
|
|
21228
|
+
core: "stale_pipeline",
|
|
21229
|
+
mid: "stale_pipeline",
|
|
21230
|
+
enterprise: "hidden_crisis"
|
|
21231
|
+
},
|
|
21232
|
+
enterprise: {
|
|
21233
|
+
velocity: "lone_wolf",
|
|
21234
|
+
core: "lone_wolf",
|
|
21235
|
+
mid: "hidden_crisis",
|
|
21236
|
+
enterprise: "hidden_crisis"
|
|
21237
|
+
}
|
|
21238
|
+
};
|
|
21239
|
+
MOTION_ONLY = {
|
|
21240
|
+
plg: "leaky_bucket",
|
|
21241
|
+
smb_velocity: "busy_bees",
|
|
21242
|
+
mid_market: "stale_pipeline",
|
|
21243
|
+
enterprise: "hidden_crisis"
|
|
21244
|
+
};
|
|
21245
|
+
BAND_ONLY = {
|
|
21246
|
+
velocity: "busy_bees",
|
|
21247
|
+
core: "leaky_bucket",
|
|
21248
|
+
mid: "stale_pipeline",
|
|
21249
|
+
enterprise: "hidden_crisis"
|
|
21250
|
+
};
|
|
21251
|
+
KEYWORD_HINTS = [
|
|
21252
|
+
{ scenario: "even_keel", patterns: [/\beven keel\b/, /\breasonably healthy\b/, /\bno crisis\b/, /\bjust evaluating\b/, /\bgreen[- ]field\b/] },
|
|
21253
|
+
{ scenario: "compound_pain", patterns: [/\beverything('?s| is) (on fire|red|broken)\b/, /\bcompound\b/, /\ball (five )?vitals\b/, /\bmultiple problems\b/] },
|
|
21254
|
+
{ scenario: "leaky_bucket", patterns: [/\bhandoff\b/, /\bmqls?\b/, /\bleak/, /\bdrop[- ]rate/, /\bvanish/, /\brouting\b/, /\bmarketing.?sales\b/] },
|
|
21255
|
+
{ scenario: "stale_pipeline", patterns: [/\bzombie/, /\bstale\b/, /\bpast[- ]due\b/, /\bforecast(ing)? on fiction\b/, /\bstuck in negotiation\b/, /\bquiet deals?\b/] },
|
|
21256
|
+
{ scenario: "lone_wolf", patterns: [/\bsingle[- ]thread/, /\blone wolf\b/, /\bone contact\b/, /\bchampion leaves\b/] },
|
|
21257
|
+
{ scenario: "busy_bees", patterns: [/\bspray/, /\bnois(e|y)\b/, /\bmisdirected\b/, /\bactivity (volume|metrics)\b/, /\bbusy bees\b/, /\bnot (on|hitting) pipeline\b/] },
|
|
21258
|
+
{ scenario: "hidden_crisis", patterns: [/\bhidden crisis\b/, /\benterprise (is )?(dying|red|stale)\b/, /\bsegment.{0,20}mask/, /\baverages? (look|looks) (fine|okay|yellow)\b/] }
|
|
21259
|
+
];
|
|
21147
21260
|
}
|
|
21148
|
-
|
|
21261
|
+
});
|
|
21262
|
+
|
|
21263
|
+
// src/conversation/onboard-tiers.ts
|
|
21264
|
+
import { existsSync as existsSync22, statSync as statSync4 } from "fs";
|
|
21265
|
+
function flagSet(tier) {
|
|
21266
|
+
return Boolean(getConfigValue(TIER_CONFIG_KEYS[tier]));
|
|
21149
21267
|
}
|
|
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++;
|
|
21268
|
+
function markOnboardTierComplete(...tiers) {
|
|
21269
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
21270
|
+
for (const tier of tiers) {
|
|
21271
|
+
if (!flagSet(tier)) setConfigValue(TIER_CONFIG_KEYS[tier], at);
|
|
21183
21272
|
}
|
|
21184
|
-
return { imported, errors };
|
|
21185
21273
|
}
|
|
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 };
|
|
21274
|
+
function markProductionDataSeen() {
|
|
21275
|
+
markOnboardTierComplete("production");
|
|
21210
21276
|
}
|
|
21211
|
-
|
|
21212
|
-
"
|
|
21277
|
+
function markDemoDataSeen() {
|
|
21278
|
+
markOnboardTierComplete("demo");
|
|
21279
|
+
}
|
|
21280
|
+
var TIER_CONFIG_KEYS;
|
|
21281
|
+
var init_onboard_tiers = __esm({
|
|
21282
|
+
"src/conversation/onboard-tiers.ts"() {
|
|
21213
21283
|
"use strict";
|
|
21214
|
-
|
|
21215
|
-
|
|
21216
|
-
|
|
21284
|
+
init_store();
|
|
21285
|
+
init_profile();
|
|
21286
|
+
init_repl_api();
|
|
21287
|
+
init_scenario_fit();
|
|
21288
|
+
TIER_CONFIG_KEYS = {
|
|
21289
|
+
profile: "onboard-tier-profile",
|
|
21290
|
+
domain: "onboard-tier-domain",
|
|
21291
|
+
demo: "onboard-tier-demo",
|
|
21292
|
+
production: "onboard-tier-production"
|
|
21293
|
+
};
|
|
21217
21294
|
}
|
|
21218
21295
|
});
|
|
21219
21296
|
|
|
21220
|
-
// src/pipeline/
|
|
21221
|
-
|
|
21222
|
-
|
|
21223
|
-
|
|
21224
|
-
|
|
21297
|
+
// src/pipeline/csv-parse.ts
|
|
21298
|
+
var csv_parse_exports = {};
|
|
21299
|
+
__export(csv_parse_exports, {
|
|
21300
|
+
getPreviewRows: () => getPreviewRows,
|
|
21301
|
+
parseCSV: () => parseCSV
|
|
21302
|
+
});
|
|
21303
|
+
import Papa from "papaparse";
|
|
21304
|
+
function parseCSV(content) {
|
|
21305
|
+
const result = Papa.parse(content, {
|
|
21306
|
+
header: true,
|
|
21307
|
+
skipEmptyLines: true,
|
|
21308
|
+
transformHeader: (h) => h.trim()
|
|
21309
|
+
});
|
|
21310
|
+
return {
|
|
21311
|
+
headers: result.meta.fields ?? [],
|
|
21312
|
+
rows: result.data,
|
|
21313
|
+
rowCount: result.data.length
|
|
21314
|
+
};
|
|
21225
21315
|
}
|
|
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;
|
|
21316
|
+
function getPreviewRows(rows, count = 5) {
|
|
21317
|
+
return rows.slice(0, count);
|
|
21281
21318
|
}
|
|
21282
|
-
var
|
|
21283
|
-
"src/pipeline/
|
|
21319
|
+
var init_csv_parse = __esm({
|
|
21320
|
+
"src/pipeline/csv-parse.ts"() {
|
|
21284
21321
|
"use strict";
|
|
21285
|
-
init_connection();
|
|
21286
21322
|
}
|
|
21287
21323
|
});
|
|
21288
21324
|
|
|
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;
|
|
21325
|
+
// src/pipeline/csv-mappings.ts
|
|
21326
|
+
function getDefaultMappings(sourceSystem, entityType) {
|
|
21327
|
+
return DEFAULT_MAPPINGS[sourceSystem]?.[entityType] ?? {};
|
|
21364
21328
|
}
|
|
21365
|
-
var
|
|
21366
|
-
|
|
21329
|
+
var DEFAULT_MAPPINGS, ACTIVITY_TYPE_MAP, CAMPAIGN_TYPE_MAP;
|
|
21330
|
+
var init_csv_mappings = __esm({
|
|
21331
|
+
"src/pipeline/csv-mappings.ts"() {
|
|
21367
21332
|
"use strict";
|
|
21333
|
+
DEFAULT_MAPPINGS = {
|
|
21334
|
+
salesforce: {
|
|
21335
|
+
organizations: { Name: "canonical_name", Website: "canonical_domain", Id: "source_id" },
|
|
21336
|
+
people: { Email: "canonical_email", FirstName: "first_name", LastName: "last_name", Id: "source_id", AccountId: "organization_source_id" },
|
|
21337
|
+
opportunities: { Name: "canonical_name", Amount: "amount", StageName: "current_stage", CloseDate: "close_date", Id: "source_id", AccountId: "organization_source_id", OwnerId: "owner_source_id" },
|
|
21338
|
+
activities: { TaskSubtype: "activity_type", ActivityDate: "occurred_at", Id: "source_id", WhoId: "person_source_id" }
|
|
21339
|
+
},
|
|
21340
|
+
hubspot: {
|
|
21341
|
+
organizations: { "Company name": "canonical_name", "Company Domain Name": "canonical_domain", "Company ID": "source_id" },
|
|
21342
|
+
people: { Email: "canonical_email", "First Name": "first_name", "Last Name": "last_name", "Contact ID": "source_id", "Associated Company ID": "organization_source_id" },
|
|
21343
|
+
activities: { Type: "activity_type", "Created At": "occurred_at", "Engagement ID": "source_id", "Contact ID": "person_source_id" }
|
|
21344
|
+
},
|
|
21345
|
+
outreach: {
|
|
21346
|
+
campaigns: { name: "canonical_name", id: "source_id" },
|
|
21347
|
+
activities: { type: "activity_type", created_at: "occurred_at", id: "source_id", prospect_id: "person_source_id" }
|
|
21348
|
+
}
|
|
21349
|
+
};
|
|
21350
|
+
ACTIVITY_TYPE_MAP = {
|
|
21351
|
+
Call: "call",
|
|
21352
|
+
Email: "email",
|
|
21353
|
+
Task: "custom",
|
|
21354
|
+
CALL: "call",
|
|
21355
|
+
EMAIL: "email",
|
|
21356
|
+
MEETING: "meeting",
|
|
21357
|
+
NOTE: "custom",
|
|
21358
|
+
TASK: "custom",
|
|
21359
|
+
INCOMING_EMAIL: "email",
|
|
21360
|
+
FORWARDED_EMAIL: "email",
|
|
21361
|
+
call: "call",
|
|
21362
|
+
email: "email",
|
|
21363
|
+
sequence_step: "email"
|
|
21364
|
+
};
|
|
21365
|
+
CAMPAIGN_TYPE_MAP = {
|
|
21366
|
+
outbound: "email_sequence",
|
|
21367
|
+
inbound: "email_sequence",
|
|
21368
|
+
trigger: "email_sequence"
|
|
21369
|
+
};
|
|
21368
21370
|
}
|
|
21369
21371
|
});
|
|
21370
21372
|
|
|
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
|
|
21373
|
+
// src/pipeline/csv-detect.ts
|
|
21374
|
+
var csv_detect_exports = {};
|
|
21375
|
+
__export(csv_detect_exports, {
|
|
21376
|
+
detectEntityType: () => detectEntityType
|
|
21382
21377
|
});
|
|
21383
|
-
function
|
|
21384
|
-
const
|
|
21385
|
-
if (!
|
|
21386
|
-
|
|
21387
|
-
|
|
21388
|
-
|
|
21389
|
-
|
|
21390
|
-
|
|
21391
|
-
|
|
21392
|
-
|
|
21393
|
-
|
|
21394
|
-
|
|
21395
|
-
|
|
21396
|
-
|
|
21397
|
-
|
|
21398
|
-
return input;
|
|
21378
|
+
function detectEntityType(headers, sourceSystem) {
|
|
21379
|
+
const rules = SIGNATURES[sourceSystem];
|
|
21380
|
+
if (!rules) return null;
|
|
21381
|
+
const headerSet = new Set(headers);
|
|
21382
|
+
let bestMatch = null;
|
|
21383
|
+
for (const rule of rules) {
|
|
21384
|
+
const requiredMatches = rule.required.filter((col) => headerSet.has(col)).length;
|
|
21385
|
+
const requiredTotal = rule.required.length;
|
|
21386
|
+
if (requiredMatches < Math.ceil(requiredTotal / 2)) continue;
|
|
21387
|
+
const optionalMatches = rule.optional.filter((col) => headerSet.has(col)).length;
|
|
21388
|
+
const optionalTotal = rule.optional.length;
|
|
21389
|
+
const score = requiredMatches / requiredTotal * 0.7 + (optionalTotal > 0 ? optionalMatches / optionalTotal * 0.3 : 0.3);
|
|
21390
|
+
if (!bestMatch || score > bestMatch.score) {
|
|
21391
|
+
bestMatch = { rule, score };
|
|
21392
|
+
}
|
|
21399
21393
|
}
|
|
21400
|
-
|
|
21401
|
-
|
|
21402
|
-
|
|
21394
|
+
if (!bestMatch) return null;
|
|
21395
|
+
const defaultMappings = getDefaultMappings(sourceSystem, bestMatch.rule.entityType);
|
|
21396
|
+
const mappings = {};
|
|
21397
|
+
for (const [csvCol, ontologyField] of Object.entries(defaultMappings)) {
|
|
21398
|
+
if (headerSet.has(csvCol)) {
|
|
21399
|
+
mappings[csvCol] = ontologyField;
|
|
21400
|
+
}
|
|
21403
21401
|
}
|
|
21404
|
-
return
|
|
21405
|
-
|
|
21406
|
-
|
|
21407
|
-
|
|
21408
|
-
}
|
|
21409
|
-
function blendScenarios(_research) {
|
|
21410
|
-
const blended = {
|
|
21411
|
-
...BASELINE,
|
|
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
|
|
21402
|
+
return {
|
|
21403
|
+
entityType: bestMatch.rule.entityType,
|
|
21404
|
+
confidence: Math.round(bestMatch.score * 100) / 100,
|
|
21405
|
+
mappings
|
|
21425
21406
|
};
|
|
21426
|
-
return blended;
|
|
21427
21407
|
}
|
|
21428
|
-
var
|
|
21429
|
-
var
|
|
21430
|
-
"src/
|
|
21408
|
+
var SIGNATURES;
|
|
21409
|
+
var init_csv_detect = __esm({
|
|
21410
|
+
"src/pipeline/csv-detect.ts"() {
|
|
21431
21411
|
"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
|
|
21412
|
+
init_csv_mappings();
|
|
21413
|
+
SIGNATURES = {
|
|
21414
|
+
salesforce: [
|
|
21415
|
+
{ entityType: "organizations", required: ["Name", "Industry"], optional: ["AnnualRevenue", "BillingCountry", "Website", "NumberOfEmployees"], weight: 1 },
|
|
21416
|
+
{ entityType: "people", required: ["FirstName", "LastName", "Email"], optional: ["AccountId", "Title", "Phone"], weight: 1 },
|
|
21417
|
+
{ entityType: "opportunities", required: ["StageName", "Amount", "CloseDate"], optional: ["AccountId", "OwnerId", "Probability"], weight: 1 },
|
|
21418
|
+
{ entityType: "activities", required: ["Subject", "ActivityDate", "TaskSubtype"], optional: ["WhoId", "WhatId", "Status"], weight: 1 }
|
|
21419
|
+
],
|
|
21420
|
+
hubspot: [
|
|
21421
|
+
{ entityType: "organizations", required: ["Company name", "Company Domain Name"], optional: ["Company ID", "Industry", "Annual Revenue"], weight: 1 },
|
|
21422
|
+
{ entityType: "people", required: ["First Name", "Last Name", "Email"], optional: ["Contact ID", "Associated Company ID", "Lifecycle Stage"], weight: 1 },
|
|
21423
|
+
{ entityType: "activities", required: ["Engagement ID", "Type"], optional: ["Timestamp", "Created At", "Contact ID"], weight: 1 }
|
|
21424
|
+
],
|
|
21425
|
+
outreach: [
|
|
21426
|
+
{ entityType: "campaigns", required: ["sequence_type", "step_count"], optional: ["name", "id", "enabled"], weight: 1 },
|
|
21427
|
+
{ entityType: "activities", required: ["prospect_id", "prospect_email"], optional: ["type", "created_at", "subject"], weight: 1 }
|
|
21428
|
+
]
|
|
21453
21429
|
};
|
|
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
|
-
|
|
21430
|
+
}
|
|
21431
|
+
});
|
|
21432
|
+
|
|
21433
|
+
// src/pipeline/importer.ts
|
|
21434
|
+
async function importRows(params) {
|
|
21435
|
+
let result;
|
|
21436
|
+
switch (params.entityType) {
|
|
21437
|
+
case "organizations":
|
|
21438
|
+
result = await importOrganizations(params);
|
|
21439
|
+
break;
|
|
21440
|
+
case "people":
|
|
21441
|
+
result = await importPeople(params);
|
|
21442
|
+
break;
|
|
21443
|
+
case "opportunities":
|
|
21444
|
+
result = await importOpportunities(params);
|
|
21445
|
+
break;
|
|
21446
|
+
case "activities":
|
|
21447
|
+
result = await importActivities(params);
|
|
21448
|
+
break;
|
|
21449
|
+
case "campaigns":
|
|
21450
|
+
result = await importCampaigns(params);
|
|
21451
|
+
break;
|
|
21452
|
+
default:
|
|
21453
|
+
result = { imported: 0, errors: [`Unknown entity type: ${params.entityType}`] };
|
|
21454
|
+
}
|
|
21455
|
+
const { invalidateLexiconSeed: invalidateLexiconSeed2 } = await Promise.resolve().then(() => (init_lexicon_seed(), lexicon_seed_exports));
|
|
21456
|
+
invalidateLexiconSeed2();
|
|
21457
|
+
return result;
|
|
21458
|
+
}
|
|
21459
|
+
function reverseMap(mappings) {
|
|
21460
|
+
const reversed = {};
|
|
21461
|
+
for (const [csvCol, ontologyField] of Object.entries(mappings)) {
|
|
21462
|
+
reversed[ontologyField] = csvCol;
|
|
21463
|
+
}
|
|
21464
|
+
return reversed;
|
|
21465
|
+
}
|
|
21466
|
+
function getMapped(row, fieldMap, field) {
|
|
21467
|
+
const csvCol = fieldMap[field];
|
|
21468
|
+
if (!csvCol) return void 0;
|
|
21469
|
+
const val = row[csvCol];
|
|
21470
|
+
return val !== void 0 && val !== "" ? val : void 0;
|
|
21471
|
+
}
|
|
21472
|
+
function buildMetadata(row, mappings) {
|
|
21473
|
+
const mappedCols = new Set(Object.keys(mappings));
|
|
21474
|
+
const metadata = {};
|
|
21475
|
+
for (const [key, value] of Object.entries(row)) {
|
|
21476
|
+
if (!mappedCols.has(key) && value !== void 0 && value !== "") {
|
|
21477
|
+
metadata[key] = value;
|
|
21478
|
+
}
|
|
21479
|
+
}
|
|
21480
|
+
return metadata;
|
|
21481
|
+
}
|
|
21482
|
+
function stripUrl(url) {
|
|
21483
|
+
return url.replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/$/, "");
|
|
21484
|
+
}
|
|
21485
|
+
function parseTimestamp(value) {
|
|
21486
|
+
if (!value) return null;
|
|
21487
|
+
const num2 = Number(value);
|
|
21488
|
+
if (!isNaN(num2) && num2 > 1e12) return new Date(num2).toISOString();
|
|
21489
|
+
if (!isNaN(num2) && num2 > 1e9) return new Date(num2 * 1e3).toISOString();
|
|
21490
|
+
const d = new Date(value);
|
|
21491
|
+
if (!isNaN(d.getTime())) return d.toISOString();
|
|
21492
|
+
return null;
|
|
21493
|
+
}
|
|
21494
|
+
async function resolveSourceIds(table, sourceSystem, sourceIds) {
|
|
21495
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
21496
|
+
if (sourceIds.size === 0) return idMap;
|
|
21497
|
+
const ids = Array.from(sourceIds);
|
|
21498
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
21499
|
+
const rows = await all(
|
|
21500
|
+
`SELECT id, source_id FROM ${table} WHERE source_system = ? AND source_id IN (${placeholders})`,
|
|
21501
|
+
[sourceSystem, ...ids]
|
|
21502
|
+
);
|
|
21503
|
+
for (const row of rows) {
|
|
21504
|
+
idMap.set(row.source_id, row.id);
|
|
21505
|
+
}
|
|
21506
|
+
return idMap;
|
|
21507
|
+
}
|
|
21508
|
+
async function importOrganizations(params) {
|
|
21509
|
+
const { rows, mappings, sourceSystem } = params;
|
|
21510
|
+
const errors = [];
|
|
21511
|
+
let imported = 0;
|
|
21512
|
+
const fieldMap = reverseMap(mappings);
|
|
21513
|
+
for (const row of rows) {
|
|
21514
|
+
const canonicalName = getMapped(row, fieldMap, "canonical_name");
|
|
21515
|
+
if (!canonicalName) {
|
|
21516
|
+
errors.push("Row missing canonical_name");
|
|
21517
|
+
continue;
|
|
21518
|
+
}
|
|
21519
|
+
const website = getMapped(row, fieldMap, "canonical_domain") ?? "";
|
|
21520
|
+
const canonicalDomain = stripUrl(website);
|
|
21521
|
+
await insertOrganization({
|
|
21522
|
+
canonical_name: canonicalName,
|
|
21523
|
+
canonical_domain: canonicalDomain || null,
|
|
21524
|
+
source_system: sourceSystem,
|
|
21525
|
+
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21526
|
+
raw_data: row,
|
|
21527
|
+
metadata: buildMetadata(row, mappings)
|
|
21528
|
+
});
|
|
21529
|
+
imported++;
|
|
21530
|
+
}
|
|
21531
|
+
return { imported, errors };
|
|
21532
|
+
}
|
|
21533
|
+
async function importPeople(params) {
|
|
21534
|
+
const { rows, mappings, sourceSystem } = params;
|
|
21535
|
+
const errors = [];
|
|
21536
|
+
let imported = 0;
|
|
21537
|
+
const fieldMap = reverseMap(mappings);
|
|
21538
|
+
const orgSourceIds = /* @__PURE__ */ new Set();
|
|
21539
|
+
for (const row of rows) {
|
|
21540
|
+
const orgSrcId = getMapped(row, fieldMap, "organization_source_id");
|
|
21541
|
+
if (orgSrcId) orgSourceIds.add(orgSrcId);
|
|
21542
|
+
}
|
|
21543
|
+
const orgIdMap = await resolveSourceIds("organizations", sourceSystem, orgSourceIds);
|
|
21544
|
+
for (const row of rows) {
|
|
21545
|
+
const firstName = getMapped(row, fieldMap, "first_name") ?? "";
|
|
21546
|
+
const lastName = getMapped(row, fieldMap, "last_name") ?? "";
|
|
21547
|
+
const canonicalName = [firstName, lastName].filter(Boolean).join(" ") || getMapped(row, fieldMap, "canonical_name") || "";
|
|
21548
|
+
if (!canonicalName) {
|
|
21549
|
+
errors.push("Row missing name fields");
|
|
21550
|
+
continue;
|
|
21551
|
+
}
|
|
21552
|
+
const orgSourceId = getMapped(row, fieldMap, "organization_source_id");
|
|
21553
|
+
await insertPerson({
|
|
21554
|
+
canonical_name: canonicalName,
|
|
21555
|
+
canonical_email: getMapped(row, fieldMap, "canonical_email") || null,
|
|
21556
|
+
organization_id: orgSourceId ? orgIdMap.get(orgSourceId) ?? null : null,
|
|
21557
|
+
source_system: sourceSystem,
|
|
21558
|
+
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21559
|
+
raw_data: row,
|
|
21560
|
+
metadata: buildMetadata(row, mappings)
|
|
21561
|
+
});
|
|
21562
|
+
imported++;
|
|
21563
|
+
}
|
|
21564
|
+
return { imported, errors };
|
|
21565
|
+
}
|
|
21566
|
+
async function importOpportunities(params) {
|
|
21567
|
+
const { rows, mappings, sourceSystem } = params;
|
|
21568
|
+
const errors = [];
|
|
21569
|
+
let imported = 0;
|
|
21570
|
+
const fieldMap = reverseMap(mappings);
|
|
21571
|
+
const orgSourceIds = /* @__PURE__ */ new Set();
|
|
21572
|
+
const ownerSourceIds = /* @__PURE__ */ new Set();
|
|
21573
|
+
for (const row of rows) {
|
|
21574
|
+
const o = getMapped(row, fieldMap, "organization_source_id");
|
|
21575
|
+
if (o) orgSourceIds.add(o);
|
|
21576
|
+
const w = getMapped(row, fieldMap, "owner_source_id");
|
|
21577
|
+
if (w) ownerSourceIds.add(w);
|
|
21578
|
+
}
|
|
21579
|
+
const orgIdMap = await resolveSourceIds("organizations", sourceSystem, orgSourceIds);
|
|
21580
|
+
const ownerIdMap = await resolveSourceIds("people", sourceSystem, ownerSourceIds);
|
|
21581
|
+
for (const row of rows) {
|
|
21582
|
+
const canonicalName = getMapped(row, fieldMap, "canonical_name");
|
|
21583
|
+
if (!canonicalName) {
|
|
21584
|
+
errors.push("Row missing canonical_name");
|
|
21585
|
+
continue;
|
|
21586
|
+
}
|
|
21587
|
+
const orgSourceId = getMapped(row, fieldMap, "organization_source_id");
|
|
21588
|
+
const ownerSourceId = getMapped(row, fieldMap, "owner_source_id");
|
|
21589
|
+
const amountStr = getMapped(row, fieldMap, "amount");
|
|
21590
|
+
await insertOpportunity({
|
|
21591
|
+
canonical_name: canonicalName,
|
|
21592
|
+
organization_id: orgSourceId ? orgIdMap.get(orgSourceId) ?? null : null,
|
|
21593
|
+
owner_id: ownerSourceId ? ownerIdMap.get(ownerSourceId) ?? null : null,
|
|
21594
|
+
current_stage: getMapped(row, fieldMap, "current_stage") || null,
|
|
21595
|
+
amount: amountStr ? parseFloat(amountStr) || null : null,
|
|
21596
|
+
close_date: getMapped(row, fieldMap, "close_date") || null,
|
|
21597
|
+
source_system: sourceSystem,
|
|
21598
|
+
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21599
|
+
raw_data: row,
|
|
21600
|
+
metadata: buildMetadata(row, mappings)
|
|
21601
|
+
});
|
|
21602
|
+
imported++;
|
|
21603
|
+
}
|
|
21604
|
+
return { imported, errors };
|
|
21605
|
+
}
|
|
21606
|
+
async function importActivities(params) {
|
|
21607
|
+
const { rows, mappings, sourceSystem } = params;
|
|
21608
|
+
const errors = [];
|
|
21609
|
+
let imported = 0;
|
|
21610
|
+
const fieldMap = reverseMap(mappings);
|
|
21611
|
+
const personSourceIds = /* @__PURE__ */ new Set();
|
|
21612
|
+
for (const row of rows) {
|
|
21613
|
+
const p = getMapped(row, fieldMap, "person_source_id");
|
|
21614
|
+
if (p) personSourceIds.add(p);
|
|
21615
|
+
}
|
|
21616
|
+
const personIdMap = await resolveSourceIds("people", sourceSystem, personSourceIds);
|
|
21617
|
+
for (const row of rows) {
|
|
21618
|
+
const rawType = getMapped(row, fieldMap, "activity_type") ?? "custom";
|
|
21619
|
+
const activityType = ACTIVITY_TYPE_MAP[rawType] ?? "custom";
|
|
21620
|
+
const rawOccurredAt = getMapped(row, fieldMap, "occurred_at") ?? "";
|
|
21621
|
+
const occurredAt = parseTimestamp(rawOccurredAt);
|
|
21622
|
+
if (!occurredAt) {
|
|
21623
|
+
errors.push("Row missing or invalid occurred_at");
|
|
21624
|
+
continue;
|
|
21625
|
+
}
|
|
21626
|
+
const personSourceId = getMapped(row, fieldMap, "person_source_id");
|
|
21627
|
+
await insertActivity({
|
|
21628
|
+
activity_type: activityType,
|
|
21629
|
+
occurred_at: occurredAt,
|
|
21630
|
+
person_id: personSourceId ? personIdMap.get(personSourceId) ?? null : null,
|
|
21631
|
+
organization_id: null,
|
|
21632
|
+
opportunity_id: null,
|
|
21633
|
+
source_system: sourceSystem,
|
|
21634
|
+
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21635
|
+
raw_data: row,
|
|
21636
|
+
metadata: buildMetadata(row, mappings)
|
|
21637
|
+
});
|
|
21638
|
+
imported++;
|
|
21639
|
+
}
|
|
21640
|
+
return { imported, errors };
|
|
21641
|
+
}
|
|
21642
|
+
async function importCampaigns(params) {
|
|
21643
|
+
const { rows, mappings, sourceSystem } = params;
|
|
21644
|
+
const errors = [];
|
|
21645
|
+
let imported = 0;
|
|
21646
|
+
const fieldMap = reverseMap(mappings);
|
|
21647
|
+
for (const row of rows) {
|
|
21648
|
+
const canonicalName = getMapped(row, fieldMap, "canonical_name");
|
|
21649
|
+
if (!canonicalName) {
|
|
21650
|
+
errors.push("Row missing canonical_name");
|
|
21651
|
+
continue;
|
|
21652
|
+
}
|
|
21653
|
+
const rawType = row["sequence_type"] ?? "";
|
|
21654
|
+
const campaignType = CAMPAIGN_TYPE_MAP[rawType] ?? "email_sequence";
|
|
21655
|
+
await insertCampaign({
|
|
21656
|
+
canonical_name: canonicalName,
|
|
21657
|
+
campaign_type: campaignType,
|
|
21658
|
+
source_system: sourceSystem,
|
|
21659
|
+
source_id: getMapped(row, fieldMap, "source_id") ?? "",
|
|
21660
|
+
raw_data: row,
|
|
21661
|
+
metadata: buildMetadata(row, mappings)
|
|
21662
|
+
});
|
|
21663
|
+
imported++;
|
|
21664
|
+
}
|
|
21665
|
+
return { imported, errors };
|
|
21666
|
+
}
|
|
21667
|
+
var init_importer = __esm({
|
|
21668
|
+
"src/pipeline/importer.ts"() {
|
|
21669
|
+
"use strict";
|
|
21670
|
+
init_csv_mappings();
|
|
21671
|
+
init_queries();
|
|
21672
|
+
init_connection();
|
|
21673
|
+
}
|
|
21674
|
+
});
|
|
21675
|
+
|
|
21676
|
+
// src/pipeline/resolver.ts
|
|
21677
|
+
async function resolveIdentities() {
|
|
21678
|
+
const orgsResolved = await resolveOrganizations();
|
|
21679
|
+
const peopleResolved = await resolvePeople();
|
|
21680
|
+
return { resolved: orgsResolved + peopleResolved, peopleResolved, orgsResolved };
|
|
21681
|
+
}
|
|
21682
|
+
async function resolveOrganizations() {
|
|
21683
|
+
let resolved = 0;
|
|
21684
|
+
const orgs = await all(
|
|
21685
|
+
`SELECT id, canonical_domain, created_at FROM organizations WHERE canonical_id IS NULL AND canonical_domain IS NOT NULL ORDER BY created_at ASC`
|
|
21686
|
+
);
|
|
21687
|
+
const groups = /* @__PURE__ */ new Map();
|
|
21688
|
+
for (const org of orgs) {
|
|
21689
|
+
const key = org.canonical_domain.toLowerCase();
|
|
21690
|
+
const group = groups.get(key);
|
|
21691
|
+
if (group) group.push(org);
|
|
21692
|
+
else groups.set(key, [org]);
|
|
21693
|
+
}
|
|
21694
|
+
for (const [, group] of groups) {
|
|
21695
|
+
if (group.length <= 1) continue;
|
|
21696
|
+
const canonical = group[0];
|
|
21697
|
+
const duplicateIds = group.slice(1).map((d) => d.id);
|
|
21698
|
+
const placeholders = duplicateIds.map(() => "?").join(", ");
|
|
21699
|
+
await run(`UPDATE organizations SET canonical_id = ? WHERE id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21700
|
+
await run(`UPDATE people SET organization_id = ? WHERE organization_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21701
|
+
await run(`UPDATE opportunities SET organization_id = ? WHERE organization_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21702
|
+
await run(`UPDATE activities SET organization_id = ? WHERE organization_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21703
|
+
resolved += duplicateIds.length;
|
|
21704
|
+
}
|
|
21705
|
+
return resolved;
|
|
21706
|
+
}
|
|
21707
|
+
async function resolvePeople() {
|
|
21708
|
+
let resolved = 0;
|
|
21709
|
+
const people = await all(
|
|
21710
|
+
`SELECT id, canonical_email, organization_id, created_at FROM people WHERE canonical_id IS NULL AND canonical_email IS NOT NULL ORDER BY created_at ASC`
|
|
21711
|
+
);
|
|
21712
|
+
const groups = /* @__PURE__ */ new Map();
|
|
21713
|
+
for (const person of people) {
|
|
21714
|
+
const key = person.canonical_email.toLowerCase();
|
|
21715
|
+
const group = groups.get(key);
|
|
21716
|
+
if (group) group.push(person);
|
|
21717
|
+
else groups.set(key, [person]);
|
|
21718
|
+
}
|
|
21719
|
+
for (const [, group] of groups) {
|
|
21720
|
+
if (group.length <= 1) continue;
|
|
21721
|
+
const canonical = group[0];
|
|
21722
|
+
const duplicates = group.slice(1);
|
|
21723
|
+
const duplicateIds = duplicates.map((d) => d.id);
|
|
21724
|
+
const placeholders = duplicateIds.map(() => "?").join(", ");
|
|
21725
|
+
if (!canonical.organization_id) {
|
|
21726
|
+
const withOrg = duplicates.find((d) => d.organization_id);
|
|
21727
|
+
if (withOrg) {
|
|
21728
|
+
await run(`UPDATE people SET organization_id = ? WHERE id = ?`, [withOrg.organization_id, canonical.id]);
|
|
21546
21729
|
}
|
|
21547
|
-
}
|
|
21548
|
-
|
|
21549
|
-
|
|
21550
|
-
|
|
21551
|
-
|
|
21552
|
-
|
|
21553
|
-
|
|
21554
|
-
|
|
21555
|
-
|
|
21556
|
-
|
|
21557
|
-
|
|
21558
|
-
|
|
21730
|
+
}
|
|
21731
|
+
await run(`UPDATE people SET canonical_id = ? WHERE id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21732
|
+
await run(`UPDATE activities SET person_id = ? WHERE person_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21733
|
+
await run(`UPDATE opportunities SET owner_id = ? WHERE owner_id IN (${placeholders})`, [canonical.id, ...duplicateIds]);
|
|
21734
|
+
resolved += duplicateIds.length;
|
|
21735
|
+
}
|
|
21736
|
+
return resolved;
|
|
21737
|
+
}
|
|
21738
|
+
var init_resolver2 = __esm({
|
|
21739
|
+
"src/pipeline/resolver.ts"() {
|
|
21740
|
+
"use strict";
|
|
21741
|
+
init_connection();
|
|
21742
|
+
}
|
|
21743
|
+
});
|
|
21744
|
+
|
|
21745
|
+
// src/demo/seed.ts
|
|
21746
|
+
function mulberry32(seed) {
|
|
21747
|
+
let a = seed | 0;
|
|
21748
|
+
return () => {
|
|
21749
|
+
a = a + 1831565813 | 0;
|
|
21750
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
21751
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
21752
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
21753
|
+
};
|
|
21754
|
+
}
|
|
21755
|
+
function createSeededRandom(seed) {
|
|
21756
|
+
const raw = mulberry32(seed);
|
|
21757
|
+
const rng = {
|
|
21758
|
+
next: raw,
|
|
21759
|
+
nextInt(min, max) {
|
|
21760
|
+
return Math.floor(raw() * (max - min + 1)) + min;
|
|
21761
|
+
},
|
|
21762
|
+
nextFloat(min, max) {
|
|
21763
|
+
return raw() * (max - min) + min;
|
|
21764
|
+
},
|
|
21765
|
+
pick(arr) {
|
|
21766
|
+
if (arr.length === 0) {
|
|
21767
|
+
throw new Error("Cannot pick from an empty array");
|
|
21768
|
+
}
|
|
21769
|
+
return arr[Math.floor(raw() * arr.length)];
|
|
21770
|
+
},
|
|
21771
|
+
pickN(arr, n) {
|
|
21772
|
+
const copy = [...arr];
|
|
21773
|
+
rng.shuffle(copy);
|
|
21774
|
+
return copy.slice(0, Math.min(n, copy.length));
|
|
21775
|
+
},
|
|
21776
|
+
shuffle(arr) {
|
|
21777
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
21778
|
+
const j = Math.floor(raw() * (i + 1));
|
|
21779
|
+
const current = arr[i];
|
|
21780
|
+
arr[i] = arr[j];
|
|
21781
|
+
arr[j] = current;
|
|
21782
|
+
}
|
|
21783
|
+
return arr;
|
|
21784
|
+
},
|
|
21785
|
+
chance(probability) {
|
|
21786
|
+
return raw() < probability;
|
|
21787
|
+
},
|
|
21788
|
+
uuid() {
|
|
21789
|
+
const bytes = Array.from({ length: 16 }, () => Math.floor(raw() * 256));
|
|
21790
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
21791
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
21792
|
+
const hex = bytes.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
21793
|
+
return [
|
|
21794
|
+
hex.slice(0, 8),
|
|
21795
|
+
hex.slice(8, 12),
|
|
21796
|
+
hex.slice(12, 16),
|
|
21797
|
+
hex.slice(16, 20),
|
|
21798
|
+
hex.slice(20, 32)
|
|
21799
|
+
].join("-");
|
|
21800
|
+
},
|
|
21801
|
+
date(start, end) {
|
|
21802
|
+
const s = start.getTime();
|
|
21803
|
+
const e = end.getTime();
|
|
21804
|
+
return new Date(s + raw() * (e - s));
|
|
21805
|
+
},
|
|
21806
|
+
weightedPick(items, weights) {
|
|
21807
|
+
if (items.length === 0) {
|
|
21808
|
+
throw new Error("Cannot pick from an empty weighted item list");
|
|
21809
|
+
}
|
|
21810
|
+
const total = weights.reduce((sum, w) => sum + w, 0);
|
|
21811
|
+
let r = raw() * total;
|
|
21812
|
+
for (let i = 0; i < items.length; i++) {
|
|
21813
|
+
r -= weights[i] ?? 0;
|
|
21814
|
+
if (r <= 0) return items[i];
|
|
21815
|
+
}
|
|
21816
|
+
return items[items.length - 1];
|
|
21817
|
+
}
|
|
21818
|
+
};
|
|
21819
|
+
return rng;
|
|
21820
|
+
}
|
|
21821
|
+
var init_seed = __esm({
|
|
21822
|
+
"src/demo/seed.ts"() {
|
|
21823
|
+
"use strict";
|
|
21559
21824
|
}
|
|
21560
21825
|
});
|
|
21561
21826
|
|
|
@@ -23542,320 +23807,93 @@ async function insertDirect(dataset) {
|
|
|
23542
23807
|
metadata: opp.metadata,
|
|
23543
23808
|
created_at: createdAt
|
|
23544
23809
|
});
|
|
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}`;
|
|
23810
|
+
oppIdMap.set(opp.localId, dbId);
|
|
23684
23811
|
}
|
|
23685
|
-
const
|
|
23686
|
-
|
|
23687
|
-
|
|
23688
|
-
|
|
23812
|
+
for (const act of dataset.activities) {
|
|
23813
|
+
await insertActivity({
|
|
23814
|
+
activity_type: act.activity_type,
|
|
23815
|
+
occurred_at: act.occurred_at,
|
|
23816
|
+
person_id: act.personLocalId ? personIdMap.get(act.personLocalId) ?? null : null,
|
|
23817
|
+
organization_id: act.organizationLocalId ? orgIdMap.get(act.organizationLocalId) ?? null : null,
|
|
23818
|
+
opportunity_id: act.opportunityLocalId ? oppIdMap.get(act.opportunityLocalId) ?? null : null,
|
|
23819
|
+
source_system: act.source_system,
|
|
23820
|
+
source_id: act.source_id,
|
|
23821
|
+
raw_data: act.raw_data,
|
|
23822
|
+
metadata: act.metadata
|
|
23823
|
+
});
|
|
23689
23824
|
}
|
|
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
|
-
}
|
|
23825
|
+
for (const campaign of dataset.campaigns) {
|
|
23826
|
+
await insertCampaign({
|
|
23827
|
+
canonical_name: campaign.canonical_name,
|
|
23828
|
+
campaign_type: campaign.campaign_type,
|
|
23829
|
+
source_system: campaign.source_system,
|
|
23830
|
+
source_id: campaign.source_id,
|
|
23831
|
+
raw_data: campaign.raw_data,
|
|
23832
|
+
metadata: campaign.metadata
|
|
23833
|
+
});
|
|
23716
23834
|
}
|
|
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
|
-
}
|
|
23835
|
+
for (const event of dataset.revenueEvents ?? []) {
|
|
23836
|
+
await insertRevenueEvent({
|
|
23837
|
+
organization_id: orgIdMap.get(event.organizationLocalId) ?? null,
|
|
23838
|
+
period: event.period,
|
|
23839
|
+
amount: event.amount,
|
|
23840
|
+
event_type: event.event_type,
|
|
23841
|
+
source_system: event.source_system,
|
|
23842
|
+
source_id: event.source_id,
|
|
23843
|
+
raw_data: event.raw_data
|
|
23844
|
+
});
|
|
23731
23845
|
}
|
|
23732
|
-
|
|
23846
|
+
const { invalidateLexiconSeed: invalidateLexiconSeed2 } = await Promise.resolve().then(() => (init_lexicon_seed(), lexicon_seed_exports));
|
|
23847
|
+
invalidateLexiconSeed2();
|
|
23848
|
+
return {
|
|
23849
|
+
mode: "direct",
|
|
23850
|
+
counts: {
|
|
23851
|
+
organizations: dataset.organizations.length,
|
|
23852
|
+
people: dataset.people.length,
|
|
23853
|
+
opportunities: dataset.opportunities.length,
|
|
23854
|
+
activities: dataset.activities.length,
|
|
23855
|
+
campaigns: dataset.campaigns.length,
|
|
23856
|
+
revenue_events: dataset.revenueEvents?.length ?? 0
|
|
23857
|
+
},
|
|
23858
|
+
health: null
|
|
23859
|
+
};
|
|
23733
23860
|
}
|
|
23734
|
-
function
|
|
23735
|
-
|
|
23736
|
-
|
|
23737
|
-
|
|
23738
|
-
|
|
23739
|
-
|
|
23740
|
-
|
|
23741
|
-
};
|
|
23742
|
-
});
|
|
23861
|
+
function hashString(str2) {
|
|
23862
|
+
let hash = 0;
|
|
23863
|
+
for (let i = 0; i < str2.length; i++) {
|
|
23864
|
+
const char = str2.charCodeAt(i);
|
|
23865
|
+
hash = (hash << 5) - hash + char | 0;
|
|
23866
|
+
}
|
|
23867
|
+
return Math.abs(hash);
|
|
23743
23868
|
}
|
|
23744
|
-
var
|
|
23745
|
-
|
|
23746
|
-
"src/demo/scenario-fit.ts"() {
|
|
23869
|
+
var init_generator = __esm({
|
|
23870
|
+
"src/demo/generator.ts"() {
|
|
23747
23871
|
"use strict";
|
|
23748
|
-
|
|
23749
|
-
|
|
23872
|
+
init_queries();
|
|
23873
|
+
init_seed();
|
|
23750
23874
|
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
|
-
];
|
|
23875
|
+
init_organizations();
|
|
23876
|
+
init_people();
|
|
23877
|
+
init_opportunities();
|
|
23878
|
+
init_activities();
|
|
23879
|
+
init_saas_enrichment();
|
|
23880
|
+
init_salesforce();
|
|
23881
|
+
init_hubspot();
|
|
23882
|
+
init_outreach();
|
|
23845
23883
|
}
|
|
23846
23884
|
});
|
|
23847
23885
|
|
|
23848
23886
|
// src/demo/taxonomy-cache.ts
|
|
23849
|
-
import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, existsSync as
|
|
23887
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, existsSync as existsSync23, mkdirSync as mkdirSync13, unlinkSync as unlinkSync3 } from "fs";
|
|
23850
23888
|
import { homedir as homedir7 } from "os";
|
|
23851
23889
|
import { join as join24 } from "path";
|
|
23852
23890
|
function ensureDir6() {
|
|
23853
|
-
if (!
|
|
23891
|
+
if (!existsSync23(NTRP_DIR4)) {
|
|
23854
23892
|
mkdirSync13(NTRP_DIR4, { recursive: true });
|
|
23855
23893
|
}
|
|
23856
23894
|
}
|
|
23857
23895
|
function loadCachedTaxonomy(profile) {
|
|
23858
|
-
if (!
|
|
23896
|
+
if (!existsSync23(TAXONOMY_PATH)) return null;
|
|
23859
23897
|
try {
|
|
23860
23898
|
const parsed = JSON.parse(readFileSync19(TAXONOMY_PATH, "utf-8"));
|
|
23861
23899
|
if (!parsed || typeof parsed !== "object") return null;
|
|
@@ -24332,7 +24370,7 @@ __export(inbox_setup_exports, {
|
|
|
24332
24370
|
shouldOfferInboxSkillSetup: () => shouldOfferInboxSkillSetup
|
|
24333
24371
|
});
|
|
24334
24372
|
import chalk27 from "chalk";
|
|
24335
|
-
import { existsSync as
|
|
24373
|
+
import { existsSync as existsSync24 } from "fs";
|
|
24336
24374
|
function markDemoOffered() {
|
|
24337
24375
|
setConfigValue("ai-inbox-nudge-seen", "true");
|
|
24338
24376
|
}
|
|
@@ -24364,7 +24402,7 @@ function printSkipHint(beat) {
|
|
|
24364
24402
|
async function reuseInboxFolderIfPresent(session, beat, folderPath) {
|
|
24365
24403
|
if (getAiInboxDir()) return false;
|
|
24366
24404
|
const candidates = folderPath ? [folderPath] : [.../* @__PURE__ */ new Set([defaultAiInboxDir(), legacyAiInboxDir()])];
|
|
24367
|
-
const existing = candidates.find((p) =>
|
|
24405
|
+
const existing = candidates.find((p) => existsSync24(p));
|
|
24368
24406
|
if (!existing) return false;
|
|
24369
24407
|
console.log(" " + chalk27.dim("Pickup folder still on disk: ") + existing);
|
|
24370
24408
|
const reuse = await session.confirm("Reuse this pickup folder?", true);
|
|
@@ -24470,7 +24508,7 @@ __export(ingest_exports, {
|
|
|
24470
24508
|
handler: () => handler3
|
|
24471
24509
|
});
|
|
24472
24510
|
import chalk28 from "chalk";
|
|
24473
|
-
import { readFileSync as readFileSync20, existsSync as
|
|
24511
|
+
import { readFileSync as readFileSync20, existsSync as existsSync25 } from "fs";
|
|
24474
24512
|
import { basename as basename6 } from "path";
|
|
24475
24513
|
async function handler3(args, ctx) {
|
|
24476
24514
|
const { positional, flags } = parseArgs(args, [
|
|
@@ -24494,7 +24532,7 @@ async function handler3(args, ctx) {
|
|
|
24494
24532
|
console.error(chalk28.dim(" /ingest --demo [--scenario <name>]"));
|
|
24495
24533
|
process.exit(1);
|
|
24496
24534
|
}
|
|
24497
|
-
if (!
|
|
24535
|
+
if (!existsSync25(file)) {
|
|
24498
24536
|
console.error(chalk28.red(` File not found: ${file}`));
|
|
24499
24537
|
process.exit(1);
|
|
24500
24538
|
}
|
|
@@ -24876,39 +24914,54 @@ var ingest_chat_exports = {};
|
|
|
24876
24914
|
__export(ingest_chat_exports, {
|
|
24877
24915
|
extractFilePath: () => extractFilePath,
|
|
24878
24916
|
ingestFromChat: () => ingestFromChat,
|
|
24917
|
+
ingestPathFromChat: () => ingestPathFromChat,
|
|
24879
24918
|
isDemoIntent: () => isDemoIntent,
|
|
24919
|
+
listCsvsInFolder: () => listCsvsInFolder,
|
|
24880
24920
|
loadDemoFromChat: () => loadDemoFromChat,
|
|
24881
24921
|
looksLikeFilePath: () => looksLikeFilePath
|
|
24882
24922
|
});
|
|
24883
|
-
import { existsSync as
|
|
24884
|
-
import { basename as basename7, resolve as resolve9 } from "path";
|
|
24923
|
+
import { existsSync as existsSync26, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
24924
|
+
import { basename as basename7, join as join25, resolve as resolve9 } from "path";
|
|
24885
24925
|
import { homedir as homedir8 } from "os";
|
|
24886
24926
|
import chalk30 from "chalk";
|
|
24887
24927
|
function extractFilePath(input) {
|
|
24888
|
-
const trimmed = input.trim();
|
|
24928
|
+
const trimmed = input.trim().replace(/^["']|["']$/g, "");
|
|
24929
|
+
if (!trimmed) return null;
|
|
24889
24930
|
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]
|
|
24931
|
+
/^["'](.+)["']$/i,
|
|
24932
|
+
/^@(.+)$/i,
|
|
24933
|
+
/(?:here'?s|file|path|upload|folder|dir)[:\s]+["']?([^\s"']+)["']?/i,
|
|
24934
|
+
/^~\/\S+$/i,
|
|
24935
|
+
/^\.\.?\/\S+$/i,
|
|
24936
|
+
/^\/\S+$/i,
|
|
24937
|
+
/^[A-Za-z]:\\[^\s]+$/i,
|
|
24938
|
+
/^[^\s]+$/i
|
|
24898
24939
|
];
|
|
24899
24940
|
for (const re of patterns) {
|
|
24900
24941
|
const m = trimmed.match(re);
|
|
24901
|
-
|
|
24902
|
-
|
|
24903
|
-
|
|
24904
|
-
|
|
24905
|
-
if (
|
|
24906
|
-
|
|
24907
|
-
|
|
24942
|
+
const candidate = (m?.[1] ?? (re.test(trimmed) ? trimmed : null))?.replace(/^["']|["']$/g, "");
|
|
24943
|
+
if (!candidate) continue;
|
|
24944
|
+
if (!looksLikePathToken(candidate)) continue;
|
|
24945
|
+
const p = expandPath(candidate);
|
|
24946
|
+
if (existsSync26(p)) {
|
|
24947
|
+
try {
|
|
24948
|
+
const st = statSync5(p);
|
|
24949
|
+
if (st.isFile() || st.isDirectory()) return p;
|
|
24950
|
+
} catch {
|
|
24951
|
+
}
|
|
24908
24952
|
}
|
|
24909
24953
|
}
|
|
24910
24954
|
return null;
|
|
24911
24955
|
}
|
|
24956
|
+
function looksLikePathToken(token) {
|
|
24957
|
+
if (token.length < 2) return false;
|
|
24958
|
+
if (/^(yes|no|y|n|back|b|prev|cancel|skip|help|demo)$/i.test(token)) return false;
|
|
24959
|
+
if (token.includes("/") || token.includes("\\")) return true;
|
|
24960
|
+
if (token.startsWith("~")) return true;
|
|
24961
|
+
if (/^[A-Za-z]:/.test(token)) return true;
|
|
24962
|
+
if (/\.[A-Za-z0-9]{1,8}$/.test(token)) return true;
|
|
24963
|
+
return false;
|
|
24964
|
+
}
|
|
24912
24965
|
function expandPath(p) {
|
|
24913
24966
|
if (p.startsWith("~/")) return resolve9(homedir8(), p.slice(2));
|
|
24914
24967
|
return resolve9(p);
|
|
@@ -24916,6 +24969,73 @@ function expandPath(p) {
|
|
|
24916
24969
|
function looksLikeFilePath(input) {
|
|
24917
24970
|
return extractFilePath(input) !== null;
|
|
24918
24971
|
}
|
|
24972
|
+
function listCsvsInFolder(dir) {
|
|
24973
|
+
try {
|
|
24974
|
+
if (!statSync5(dir).isDirectory()) return [];
|
|
24975
|
+
return readdirSync6(dir).filter((name) => name.toLowerCase().endsWith(".csv")).map((name) => join25(dir, name)).sort();
|
|
24976
|
+
} catch {
|
|
24977
|
+
return [];
|
|
24978
|
+
}
|
|
24979
|
+
}
|
|
24980
|
+
async function ingestPathFromChat(ctx, rawPath) {
|
|
24981
|
+
let st;
|
|
24982
|
+
try {
|
|
24983
|
+
st = statSync5(rawPath);
|
|
24984
|
+
} catch {
|
|
24985
|
+
console.log(" " + chalk30.red(`Path not found: ${rawPath}`));
|
|
24986
|
+
return false;
|
|
24987
|
+
}
|
|
24988
|
+
if (st.isDirectory()) {
|
|
24989
|
+
const csvs = listCsvsInFolder(rawPath);
|
|
24990
|
+
if (csvs.length === 0) {
|
|
24991
|
+
console.log();
|
|
24992
|
+
console.log(" " + paint("accent", "Folder noted") + chalk30.dim(` \u2014 ${rawPath}`));
|
|
24993
|
+
console.log(" " + chalk30.dim("No CSV files one level deep. Drop a .csv path, or put exports in that folder."));
|
|
24994
|
+
ctx.attachments = [
|
|
24995
|
+
...ctx.attachments ?? [],
|
|
24996
|
+
{ path: rawPath, ingested_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
24997
|
+
];
|
|
24998
|
+
saveSessionState(ctx);
|
|
24999
|
+
markProductionDataSeen();
|
|
25000
|
+
return false;
|
|
25001
|
+
}
|
|
25002
|
+
if (csvs.length === 1) {
|
|
25003
|
+
console.log(" " + chalk30.dim(`Found ${basename7(csvs[0])} in folder \u2014 ingesting.`));
|
|
25004
|
+
return ingestFromChat(ctx, csvs[0]);
|
|
25005
|
+
}
|
|
25006
|
+
if (!ctx.rl) {
|
|
25007
|
+
console.log(" " + chalk30.dim(`Found ${csvs.length} CSVs \u2014 re-run interactively to pick one.`));
|
|
25008
|
+
return false;
|
|
25009
|
+
}
|
|
25010
|
+
const prompts = createPromptSession(ctx.rl, ctx);
|
|
25011
|
+
try {
|
|
25012
|
+
const picked = await prompts.choose(
|
|
25013
|
+
"Which CSV in that folder?",
|
|
25014
|
+
csvs.map((p) => ({ value: p, label: basename7(p), description: p })),
|
|
25015
|
+
{ default: csvs[0] }
|
|
25016
|
+
);
|
|
25017
|
+
return ingestFromChat(ctx, picked);
|
|
25018
|
+
} finally {
|
|
25019
|
+
prompts.close();
|
|
25020
|
+
}
|
|
25021
|
+
}
|
|
25022
|
+
if (st.isFile()) {
|
|
25023
|
+
if (!rawPath.toLowerCase().endsWith(".csv")) {
|
|
25024
|
+
console.log();
|
|
25025
|
+
console.log(" " + paint("accent", "Path noted") + chalk30.dim(` \u2014 ${rawPath}`));
|
|
25026
|
+
console.log(" " + chalk30.dim("NTRP ingests CSV exports today. Drop a .csv from that location when ready."));
|
|
25027
|
+
ctx.attachments = [
|
|
25028
|
+
...ctx.attachments ?? [],
|
|
25029
|
+
{ path: rawPath, ingested_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
25030
|
+
];
|
|
25031
|
+
saveSessionState(ctx);
|
|
25032
|
+
markProductionDataSeen();
|
|
25033
|
+
return false;
|
|
25034
|
+
}
|
|
25035
|
+
return ingestFromChat(ctx, rawPath);
|
|
25036
|
+
}
|
|
25037
|
+
return false;
|
|
25038
|
+
}
|
|
24919
25039
|
async function ingestFromChat(ctx, filePath) {
|
|
24920
25040
|
if (!ctx.rl) {
|
|
24921
25041
|
console.log(" " + chalk30.red("Ingest confirm requires interactive mode."));
|
|
@@ -24980,6 +25100,7 @@ async function ingestFromChat(ctx, filePath) {
|
|
|
24980
25100
|
};
|
|
24981
25101
|
invalidateGapAudit(ctx);
|
|
24982
25102
|
saveSessionState(ctx);
|
|
25103
|
+
markProductionDataSeen();
|
|
24983
25104
|
console.log();
|
|
24984
25105
|
console.log(" " + paint("accent", "\u2713 Data loaded") + chalk30.dim(` \u2014 ${name}`));
|
|
24985
25106
|
recordMessage(ctx, "user", `[ingested ${name}]`);
|
|
@@ -25055,6 +25176,7 @@ async function loadDemoFromChat(ctx, scenario, opts = {}) {
|
|
|
25055
25176
|
counts,
|
|
25056
25177
|
ingested_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
25057
25178
|
};
|
|
25179
|
+
markDemoDataSeen();
|
|
25058
25180
|
if (!ctx.scope) {
|
|
25059
25181
|
const { proposeScopeFromIntent: proposeScopeFromIntent2 } = await Promise.resolve().then(() => (init_scope(), scope_exports));
|
|
25060
25182
|
const proposal = proposeScopeFromIntent2("demo pipeline and metrics");
|
|
@@ -25091,6 +25213,7 @@ var init_ingest_chat = __esm({
|
|
|
25091
25213
|
init_compute2();
|
|
25092
25214
|
init_theme();
|
|
25093
25215
|
init_demo();
|
|
25216
|
+
init_onboard_tiers();
|
|
25094
25217
|
}
|
|
25095
25218
|
});
|
|
25096
25219
|
|
|
@@ -25849,9 +25972,9 @@ async function handleGetSessionBrief(input) {
|
|
|
25849
25972
|
if (!target) {
|
|
25850
25973
|
return { error: `No session matching "${raw}".` };
|
|
25851
25974
|
}
|
|
25852
|
-
const { existsSync:
|
|
25975
|
+
const { existsSync: existsSync29, readFileSync: readFileSync23 } = await import("fs");
|
|
25853
25976
|
const briefPath = contextDocPathForSession2(target.id);
|
|
25854
|
-
if (!
|
|
25977
|
+
if (!existsSync29(briefPath)) {
|
|
25855
25978
|
return {
|
|
25856
25979
|
session_id: target.id,
|
|
25857
25980
|
error: "No context brief on disk for this session (created before brief storage existed).",
|
|
@@ -26866,14 +26989,14 @@ var init_ask = __esm({
|
|
|
26866
26989
|
});
|
|
26867
26990
|
|
|
26868
26991
|
// src/services/setup.ts
|
|
26869
|
-
import { existsSync as
|
|
26870
|
-
import { join as
|
|
26992
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync21, writeFileSync as writeFileSync16 } from "fs";
|
|
26993
|
+
import { join as join26 } from "path";
|
|
26871
26994
|
function setupCheck() {
|
|
26872
26995
|
const home = ntrpHome();
|
|
26873
26996
|
let writable = false;
|
|
26874
26997
|
try {
|
|
26875
26998
|
mkdirSync14(home, { recursive: true });
|
|
26876
|
-
const probe =
|
|
26999
|
+
const probe = join26(home, ".write-check");
|
|
26877
27000
|
writeFileSync16(probe, "ok\n");
|
|
26878
27001
|
writable = true;
|
|
26879
27002
|
} catch {
|
|
@@ -26925,11 +27048,11 @@ var init_setup = __esm({
|
|
|
26925
27048
|
});
|
|
26926
27049
|
|
|
26927
27050
|
// src/version.ts
|
|
26928
|
-
import { existsSync as
|
|
26929
|
-
import { dirname as dirname5, join as
|
|
27051
|
+
import { existsSync as existsSync28, readFileSync as readFileSync22 } from "fs";
|
|
27052
|
+
import { dirname as dirname5, join as join27 } from "path";
|
|
26930
27053
|
import { fileURLToPath } from "url";
|
|
26931
27054
|
function readVersionFromPackageJson(packageJsonPath) {
|
|
26932
|
-
if (!
|
|
27055
|
+
if (!existsSync28(packageJsonPath)) return null;
|
|
26933
27056
|
try {
|
|
26934
27057
|
const pkg = JSON.parse(readFileSync22(packageJsonPath, "utf-8"));
|
|
26935
27058
|
if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
|
|
@@ -26939,7 +27062,7 @@ function readVersionFromPackageJson(packageJsonPath) {
|
|
|
26939
27062
|
}
|
|
26940
27063
|
function readVersionNearEntry(entryPath) {
|
|
26941
27064
|
const start = dirname5(entryPath);
|
|
26942
|
-
for (const rel of [
|
|
27065
|
+
for (const rel of [join27(start, "..", "package.json"), join27(start, "../..", "package.json")]) {
|
|
26943
27066
|
const version = readVersionFromPackageJson(rel);
|
|
26944
27067
|
if (version) return version;
|
|
26945
27068
|
}
|