@sonnechasser/ntrp 1.3.5 → 1.3.8

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.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/demo/seed.ts","../../src/demo/whimsy-names.ts","../../src/demo/scenarios.ts","../../src/config/store.ts","../../src/config/profile.ts","../../src/demo/scenario-fit.ts","../../src/ai/json-response.ts","../../src/demo/whimsy-smoke.ts"],"sourcesContent":["/**\n * Seeded PRNG using mulberry32 algorithm.\n * All randomness in the generator flows through this — no Math.random().\n */\n\nexport interface SeededRandom {\n /** Returns a float in [0, 1) */\n next(): number;\n /** Returns an integer in [min, max] inclusive */\n nextInt(min: number, max: number): number;\n /** Pick a random element from an array */\n pick<T>(arr: readonly T[]): T;\n /** Shuffle an array in place (Fisher-Yates) */\n shuffle<T>(arr: T[]): T[];\n /** Returns true with the given probability (0-1) */\n chance(probability: number): boolean;\n /** Generate a deterministic UUID v4 */\n uuid(): string;\n /** Generate a random date between start and end */\n date(start: Date, end: Date): Date;\n /** Pick N unique elements from an array */\n pickN<T>(arr: readonly T[], n: number): T[];\n /** Generate a float in [min, max) */\n nextFloat(min: number, max: number): number;\n /** Weighted pick: items with associated weights */\n weightedPick<T>(items: readonly T[], weights: readonly number[]): T;\n}\n\nfunction mulberry32(seed: number): () => number {\n let a = seed | 0;\n return () => {\n a = (a + 0x6d2b79f5) | 0;\n let t = Math.imul(a ^ (a >>> 15), 1 | a);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\nexport function createSeededRandom(seed: number): SeededRandom {\n const raw = mulberry32(seed);\n\n const rng: SeededRandom = {\n next: raw,\n\n nextInt(min: number, max: number): number {\n return Math.floor(raw() * (max - min + 1)) + min;\n },\n\n nextFloat(min: number, max: number): number {\n return raw() * (max - min) + min;\n },\n\n pick<T>(arr: readonly T[]): T {\n if (arr.length === 0) {\n throw new Error(\"Cannot pick from an empty array\");\n }\n return arr[Math.floor(raw() * arr.length)]!;\n },\n\n pickN<T>(arr: readonly T[], n: number): T[] {\n const copy = [...arr];\n rng.shuffle(copy);\n return copy.slice(0, Math.min(n, copy.length));\n },\n\n shuffle<T>(arr: T[]): T[] {\n for (let i = arr.length - 1; i > 0; i--) {\n const j = Math.floor(raw() * (i + 1));\n const current = arr[i]!;\n arr[i] = arr[j]!;\n arr[j] = current;\n }\n return arr;\n },\n\n chance(probability: number): boolean {\n return raw() < probability;\n },\n\n uuid(): string {\n const bytes = Array.from({ length: 16 }, () => Math.floor(raw() * 256));\n bytes[6] = (bytes[6]! & 0x0f) | 0x40; // version 4\n bytes[8] = (bytes[8]! & 0x3f) | 0x80; // variant 1\n const hex = bytes.map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return [\n hex.slice(0, 8),\n hex.slice(8, 12),\n hex.slice(12, 16),\n hex.slice(16, 20),\n hex.slice(20, 32),\n ].join(\"-\");\n },\n\n date(start: Date, end: Date): Date {\n const s = start.getTime();\n const e = end.getTime();\n return new Date(s + raw() * (e - s));\n },\n\n weightedPick<T>(items: readonly T[], weights: readonly number[]): T {\n if (items.length === 0) {\n throw new Error(\"Cannot pick from an empty weighted item list\");\n }\n const total = weights.reduce((sum, w) => sum + w, 0);\n let r = raw() * total;\n for (let i = 0; i < items.length; i++) {\n r -= weights[i] ?? 0;\n if (r <= 0) return items[i]!;\n }\n return items[items.length - 1]!;\n },\n };\n\n return rng;\n}\n","/**\n * Whimsy name pool — a curated, hand-picked roster of recognizable real\n * figures from SAFE cultural domains (music, sports, film/TV). Used to give\n * generated reps / account owners a little delight instead of \"John Smith\".\n *\n * Mirrors the GOODBYES pattern in src/cli/repl.ts: a flat, audited static\n * list. Names are deliberately NOT AI-generated (hallucination / safety risk)\n * and NOT derived from the company config — this is a purely cosmetic layer\n * that sits on top of the config-driven data \"shape\".\n *\n * Curation rules:\n * - Safe domains only: music, sports, film/TV. NO politics, NO religion.\n * - Real names only (no stage names) so first/last split cleanly for emails.\n * - Single-word last names only — `toEmail` concatenates `first.last`, so a\n * space (e.g. \"Van Halen\") would produce an invalid email. Keep it clean.\n * - Paired as atomic units: \"James Hetfield\" stays together, never mixed into\n * \"James Hammett\".\n */\n\nimport type { SeededRandom } from \"./seed.js\";\n\nexport type WhimsyCategory = \"music\" | \"sports\" | \"film\";\n\nexport interface WhimsyFigure {\n first: string;\n last: string;\n category: WhimsyCategory;\n}\n\nexport const WHIMSY_FIGURES: readonly WhimsyFigure[] = [\n // ── Music (band members, real names — no stage names) ──\n { first: \"James\", last: \"Hetfield\", category: \"music\" },\n { first: \"Lars\", last: \"Ulrich\", category: \"music\" },\n { first: \"Kirk\", last: \"Hammett\", category: \"music\" },\n { first: \"Robert\", last: \"Trujillo\", category: \"music\" },\n { first: \"Myles\", last: \"Kennedy\", category: \"music\" },\n { first: \"Mark\", last: \"Tremonti\", category: \"music\" },\n { first: \"Scott\", last: \"Stapp\", category: \"music\" },\n { first: \"Dave\", last: \"Grohl\", category: \"music\" },\n { first: \"Taylor\", last: \"Hawkins\", category: \"music\" },\n { first: \"Eddie\", last: \"Vedder\", category: \"music\" },\n { first: \"Mike\", last: \"McCready\", category: \"music\" },\n { first: \"Stone\", last: \"Gossard\", category: \"music\" },\n { first: \"Geddy\", last: \"Lee\", category: \"music\" },\n { first: \"Alex\", last: \"Lifeson\", category: \"music\" },\n { first: \"Neil\", last: \"Peart\", category: \"music\" },\n { first: \"Robert\", last: \"Plant\", category: \"music\" },\n { first: \"Jimmy\", last: \"Page\", category: \"music\" },\n { first: \"Brian\", last: \"May\", category: \"music\" },\n { first: \"Roger\", last: \"Taylor\", category: \"music\" },\n { first: \"Adam\", last: \"Jones\", category: \"music\" },\n { first: \"Danny\", last: \"Carey\", category: \"music\" },\n { first: \"Chris\", last: \"Cornell\", category: \"music\" },\n { first: \"Angus\", last: \"Young\", category: \"music\" },\n { first: \"Chad\", last: \"Smith\", category: \"music\" },\n { first: \"Sammy\", last: \"Hagar\", category: \"music\" },\n { first: \"Maynard\", last: \"Keenan\", category: \"music\" },\n { first: \"Thom\", last: \"Yorke\", category: \"music\" },\n { first: \"Tony\", last: \"Iommi\", category: \"music\" },\n { first: \"Billie\", last: \"Armstrong\", category: \"music\" },\n { first: \"Bruce\", last: \"Dickinson\", category: \"music\" },\n { first: \"Stevie\", last: \"Nicks\", category: \"music\" },\n { first: \"Jon\", last: \"Jovi\", category: \"music\" },\n { first: \"Richie\", last: \"Sambora\", category: \"music\" },\n\n // ── Sports (legends, broadly uncontroversial) ──\n { first: \"Michael\", last: \"Jordan\", category: \"sports\" },\n { first: \"Wayne\", last: \"Gretzky\", category: \"sports\" },\n { first: \"Roger\", last: \"Federer\", category: \"sports\" },\n { first: \"Serena\", last: \"Williams\", category: \"sports\" },\n { first: \"Lionel\", last: \"Messi\", category: \"sports\" },\n { first: \"Peyton\", last: \"Manning\", category: \"sports\" },\n { first: \"Derek\", last: \"Jeter\", category: \"sports\" },\n { first: \"Sidney\", last: \"Crosby\", category: \"sports\" },\n { first: \"Mia\", last: \"Hamm\", category: \"sports\" },\n { first: \"Larry\", last: \"Bird\", category: \"sports\" },\n { first: \"Steph\", last: \"Curry\", category: \"sports\" },\n { first: \"Patrick\", last: \"Mahomes\", category: \"sports\" },\n { first: \"Jackie\", last: \"Robinson\", category: \"sports\" },\n { first: \"Bonnie\", last: \"Blair\", category: \"sports\" },\n { first: \"Peggy\", last: \"Fleming\", category: \"sports\" },\n { first: \"Tiger\", last: \"Woods\", category: \"sports\" },\n { first: \"Simone\", last: \"Biles\", category: \"sports\" },\n { first: \"Venus\", last: \"Williams\", category: \"sports\" },\n { first: \"Tom\", last: \"Brady\", category: \"sports\" },\n { first: \"Naomi\", last: \"Osaka\", category: \"sports\" },\n { first: \"Rafael\", last: \"Nadal\", category: \"sports\" },\n\n // ── Film / TV (well-known actors) ──\n { first: \"Tom\", last: \"Hanks\", category: \"film\" },\n { first: \"Meryl\", last: \"Streep\", category: \"film\" },\n { first: \"Denzel\", last: \"Washington\", category: \"film\" },\n { first: \"Harrison\", last: \"Ford\", category: \"film\" },\n { first: \"Sigourney\", last: \"Weaver\", category: \"film\" },\n { first: \"Keanu\", last: \"Reeves\", category: \"film\" },\n { first: \"Morgan\", last: \"Freeman\", category: \"film\" },\n { first: \"Bryan\", last: \"Cranston\", category: \"film\" },\n { first: \"Jeff\", last: \"Goldblum\", category: \"film\" },\n { first: \"Cate\", last: \"Blanchett\", category: \"film\" },\n { first: \"Viola\", last: \"Davis\", category: \"film\" },\n { first: \"Jodie\", last: \"Foster\", category: \"film\" },\n { first: \"Sandra\", last: \"Bullock\", category: \"film\" },\n { first: \"Idris\", last: \"Elba\", category: \"film\" },\n { first: \"Emma\", last: \"Stone\", category: \"film\" },\n { first: \"Steve\", last: \"Carell\", category: \"film\" },\n { first: \"Jennifer\", last: \"Aniston\", category: \"film\" },\n { first: \"Matt\", last: \"Damon\", category: \"film\" },\n { first: \"Julia\", last: \"Roberts\", category: \"film\" },\n { first: \"Leonardo\", last: \"DiCaprio\", category: \"film\" },\n { first: \"Aaron\", last: \"Paul\", category: \"film\" },\n { first: \"Gillian\", last: \"Anderson\", category: \"film\" },\n];\n\n/**\n * A stateful, deterministic, draw-without-replacement cursor over the whimsy\n * pool. Each `next()` returns a unique figure so the same celebrity never\n * shows up twice in one dataset. Returns `null` once the (optionally\n * category-filtered) pool is exhausted, so callers can fall back to the\n * generic name pools for any overflow.\n */\nexport interface FigureDrawer {\n next(): WhimsyFigure | null;\n}\n\nexport function createFigureDrawer(\n rng: SeededRandom,\n categories?: readonly WhimsyCategory[],\n): FigureDrawer {\n const pool = categories && categories.length > 0\n ? WHIMSY_FIGURES.filter((f) => categories.includes(f.category))\n : [...WHIMSY_FIGURES];\n const shuffled = rng.shuffle([...pool]);\n let idx = 0;\n\n return {\n next(): WhimsyFigure | null {\n if (idx >= shuffled.length) return null;\n return shuffled[idx++]!;\n },\n };\n}\n","/**\n * 7 scenario presets for the sample data generator.\n * Five amplify a specific problem; even_keel is a healthy baseline;\n * compound_pain stacks several reds so gating has to choose.\n */\n\nimport type { DemoScenario } from \"../types.js\";\nimport type { ScenarioParams } from \"./types.js\";\n\nconst BASELINE: Omit<ScenarioParams, \"key\" | \"label\" | \"description\" | \"story\" | \"hook\"> = {\n enterpriseRatio: 0.2,\n midMarketRatio: 0.3,\n smbRatio: 0.5,\n\n staleContactRatio: 0.15,\n staleContactRatioEnterprise: 0.2,\n staleContactRatioSmb: 0.1,\n pastCloseDateRatio: 0.1,\n staleDays: 120,\n\n mqlDropRatio: 0.1,\n qualifiedNoOutreachRatio: 0.1,\n\n stuckDealRatio: 0.1,\n stuckInNegotiationDays: 45,\n avgDaysPerStageEnterprise: 25,\n avgDaysPerStageSmb: 8,\n\n activityVolumeMultiplier: 1.0,\n noiseActivityRatio: 0.15,\n\n singleThreadRatio: 0.2,\n loneWolfRepIndex: null,\n loneWolfSingleThreadRatio: 0.0,\n\n freshnessGapDays: 90,\n};\n\nexport const SCENARIOS: Record<string, ScenarioParams> = {\n hidden_crisis: {\n ...BASELINE,\n key: \"hidden_crisis\",\n label: \"The Hidden Crisis\",\n description: \"Overall health looks yellow but Enterprise is deep red, masked by strong SMB numbers.\",\n story: \"Your aggregate numbers look okay — 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.\",\n hook: \"SMB is carrying the average while Enterprise dies quietly.\",\n\n staleContactRatio: 0.3,\n staleContactRatioEnterprise: 0.6,\n staleContactRatioSmb: 0.1,\n singleThreadRatio: 0.5,\n enterpriseRatio: 0.3,\n midMarketRatio: 0.3,\n smbRatio: 0.4,\n },\n\n leaky_bucket: {\n ...BASELINE,\n key: \"leaky_bucket\",\n label: \"The Leaky Bucket\",\n description: \"Marketing generates plenty of leads but 40% vanish at handoff to sales.\",\n story: \"Marketing is doing its job — 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.\",\n hook: \"MQLs flow in, then 40% vanish at the sales handoff.\",\n\n mqlDropRatio: 0.4,\n qualifiedNoOutreachRatio: 0.35,\n staleContactRatio: 0.2,\n },\n\n stale_pipeline: {\n ...BASELINE,\n key: \"stale_pipeline\",\n label: \"The Stale Pipeline\",\n description: \"Big pipeline number but half the deals are zombies stuck in late stages.\",\n 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.\",\n hook: \"Half the pipeline is zombies — you're forecasting on fiction.\",\n\n pastCloseDateRatio: 0.5,\n stuckDealRatio: 0.4,\n stuckInNegotiationDays: 120,\n staleContactRatio: 0.25,\n staleDays: 90,\n },\n\n lone_wolf: {\n ...BASELINE,\n key: \"lone_wolf\",\n label: \"The Lone Wolf\",\n description: \"One rep has great numbers but every single deal is single-threaded.\",\n story: \"Your top rep is crushing it on paper — 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.\",\n hook: \"Top rep, huge pipeline, one contact per deal — one exit from collapse.\",\n\n loneWolfRepIndex: 0,\n loneWolfSingleThreadRatio: 1.0,\n singleThreadRatio: 0.15,\n },\n\n busy_bees: {\n ...BASELINE,\n key: \"busy_bees\",\n label: \"The Busy Bees\",\n description: \"High activity volume across the team, but most of it hits dead ends.\",\n story: \"Your team is busy. Activity metrics look great — 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.\",\n hook: \"Reps are spraying, not aiming.\",\n\n activityVolumeMultiplier: 3.0,\n noiseActivityRatio: 0.6,\n staleContactRatio: 0.2,\n },\n\n even_keel: {\n ...BASELINE,\n key: \"even_keel\",\n label: \"The Even Keel\",\n description: \"A reasonably healthy book — enough yellow to listen, not a five-alarm fire.\",\n 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 — useful when you want to evaluate NTRP without a manufactured crisis.\",\n hook: \"Reasonably healthy — enough signal to listen, not a crisis.\",\n },\n\n compound_pain: {\n ...BASELINE,\n key: \"compound_pain\",\n label: \"The Compound Fracture\",\n description: \"Several vitals are red at once — stale pipeline, leaky handoff, noisy activity, thin threads.\",\n 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 — that's the point of this book.\",\n hook: \"Several vitals red at once — the stethoscope has to pick a first listen.\",\n\n enterpriseRatio: 0.3,\n midMarketRatio: 0.3,\n smbRatio: 0.4,\n staleContactRatio: 0.35,\n staleContactRatioEnterprise: 0.55,\n staleContactRatioSmb: 0.15,\n pastCloseDateRatio: 0.35,\n staleDays: 100,\n mqlDropRatio: 0.3,\n qualifiedNoOutreachRatio: 0.25,\n stuckDealRatio: 0.3,\n stuckInNegotiationDays: 90,\n activityVolumeMultiplier: 2.0,\n noiseActivityRatio: 0.4,\n singleThreadRatio: 0.4,\n },\n};\n\nexport const SCENARIO_LIST = Object.values(SCENARIOS);\n\nexport function getScenario(key: string): ScenarioParams {\n const scenario = SCENARIOS[key];\n if (!scenario) {\n throw new Error(`Unknown scenario: ${key}. Valid: ${Object.keys(SCENARIOS).join(\", \")}`);\n }\n return scenario;\n}\n\n/** Scenario keys accepted by the demo generator (excludes research_blend). */\nexport const NAMED_DEMO_SCENARIOS = [\n \"hidden_crisis\",\n \"leaky_bucket\",\n \"stale_pipeline\",\n \"lone_wolf\",\n \"busy_bees\",\n \"even_keel\",\n \"compound_pain\",\n] as const satisfies readonly DemoScenario[];\n\nexport function isNamedDemoScenario(raw: string): raw is DemoScenario {\n return (NAMED_DEMO_SCENARIOS as readonly string[]).includes(raw);\n}\n\n/**\n * Resolve a --scenario flag or wizard answer into a scenario key.\n * - blank → undefined (caller picks random)\n * - \"hidden_crisis\" etc. → that key\n * - \"1\"-\"7\" → NAMED_DEMO_SCENARIOS[n-1] (menu-style input)\n * - anything else → null (invalid)\n */\nexport function resolveScenarioInput(\n raw: string | undefined,\n): DemoScenario | \"research_blend\" | undefined | null {\n const input = raw?.trim();\n if (!input) return undefined;\n if (input === \"research_blend\") return \"research_blend\";\n if ((NAMED_DEMO_SCENARIOS as readonly string[]).includes(input)) {\n return input as DemoScenario;\n }\n const n = Number(input);\n if (Number.isInteger(n) && n >= 1 && n <= NAMED_DEMO_SCENARIOS.length) {\n return NAMED_DEMO_SCENARIOS[n - 1]!;\n }\n return null;\n}\n\n/**\n * Named-scenario pool used by `pickRandomScenario`. Deliberately excludes\n * `research_blend` — the 7 named presets are the books of business that\n * /demo randomizes across when the operator has not fitted one.\n */\nconst RANDOM_POOL: DemoScenario[] = [...NAMED_DEMO_SCENARIOS];\n\n/**\n * Pick a fresh random scenario per /demo invocation. Uses Math.random() at\n * the CLI boundary (not the seeded RNG) so successive runs differ.\n */\nexport function pickRandomScenario(): DemoScenario {\n return RANDOM_POOL[Math.floor(Math.random() * RANDOM_POOL.length)]!;\n}\n\n/**\n * Creates a composite ScenarioParams by mixing BASELINE with moderate\n * problem seeding across all vital signs. CLI version always returns\n * the baseline blend (no company research integration).\n */\nexport function blendScenarios(_research: null): ScenarioParams {\n // Start from baseline with moderate problem seeding\n const blended: ScenarioParams = {\n ...BASELINE,\n key: \"research_blend\",\n label: \"Research-Derived Blend\",\n description: \"Realistic data with mild-to-moderate problems across all vital signs.\",\n story: \"Generated with default research blend. Problems are seeded across all vital signs at realistic levels.\",\n hook: \"Mild-to-moderate problems seeded across all five vitals.\",\n\n // Bump all problems slightly above baseline for discoverability\n staleContactRatio: 0.2,\n pastCloseDateRatio: 0.15,\n mqlDropRatio: 0.15,\n qualifiedNoOutreachRatio: 0.15,\n stuckDealRatio: 0.15,\n noiseActivityRatio: 0.2,\n singleThreadRatio: 0.25,\n };\n\n return blended;\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join, resolve } from \"path\";\nimport type { CLIConfig } from \"../types.js\";\n\nconst NTRP_DIR = process.env.NTRP_HOME ? resolve(process.env.NTRP_HOME) : join(homedir(), \".ntrp\");\nconst CONFIG_PATH = join(NTRP_DIR, \"config.json\");\nlet cachedConfig: CLIConfig | null = null;\n\nexport function ntrpHome(): string {\n return NTRP_DIR;\n}\n\nfunction chmodQuiet(path: string, mode: number): void {\n try {\n chmodSync(path, mode);\n } catch {\n // Windows, or we don't own the file — never fail a read/write over this.\n }\n}\n\nfunction ensureDir(): void {\n if (!existsSync(NTRP_DIR)) {\n mkdirSync(NTRP_DIR, { recursive: true, mode: 0o700 });\n }\n chmodQuiet(NTRP_DIR, 0o700);\n}\n\n/** Ensure ~/.ntrp exists and is mode 700. Used by DuckDB and other writers. */\nexport function secureNtrpHome(): string {\n ensureDir();\n return NTRP_DIR;\n}\n\n/** Write a file that may hold secrets (config, provider metadata). */\nexport function writePrivateFile(path: string, contents: string): void {\n ensureDir();\n writeFileSync(path, contents, { encoding: \"utf-8\", mode: 0o600 });\n chmodQuiet(path, 0o600);\n}\n\nexport function loadConfig(): CLIConfig {\n if (cachedConfig) return cachedConfig;\n ensureDir();\n if (!existsSync(CONFIG_PATH)) {\n cachedConfig = {};\n return cachedConfig;\n }\n try {\n cachedConfig = JSON.parse(readFileSync(CONFIG_PATH, \"utf-8\")) as CLIConfig;\n } catch {\n cachedConfig = {};\n }\n return cachedConfig;\n}\n\nexport function saveConfig(config: CLIConfig): void {\n ensureDir();\n writePrivateFile(CONFIG_PATH, JSON.stringify(config, null, 2) + \"\\n\");\n cachedConfig = config;\n}\n\n/** Clear the in-memory config cache (e.g. after deleting config.json on disk). */\nexport function resetConfigCache(): void {\n cachedConfig = null;\n}\n\nexport function getConfigValue(key: string): string | undefined {\n // api-key is config-file only; env vars are never picked up automatically (see ai/repl-api.ts).\n if (key === \"api-key\") return loadConfig()[\"api-key\"];\n if (key === \"license-key\") return process.env.NTRP_LICENSE_KEY ?? (loadConfig() as Record<string, string | undefined>)[\"license-key\"];\n const config = loadConfig();\n return (config as Record<string, string | undefined>)[key];\n}\n\nexport function setConfigValue(key: string, value: string): void {\n const config = loadConfig();\n (config as Record<string, string>)[key] = value;\n saveConfig(config);\n}\n\nexport function deleteConfigValue(key: string): void {\n const config = loadConfig();\n delete (config as Record<string, unknown>)[key];\n saveConfig(config);\n}\n\nexport function getExportsDir(): string {\n const config = loadConfig();\n const dir = resolve(config[\"export-dir\"] ?? join(NTRP_DIR, \"exports\"));\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n return dir;\n}\n\n/** Optional desktop-AI inbox path from config (no mkdir). Null when unset. */\nexport function getConfiguredAiInboxDir(): string | null {\n const raw = loadConfig()[\"ai-inbox-dir\"];\n return raw ? resolve(raw) : null;\n}\n\nexport function getStrategiesDir(): string {\n const dir = join(NTRP_DIR, \"strategies\");\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n writeFileSync(join(dir, \"README.md\"), `# Strategies\n\nThis directory holds your GTM strategy files. Each file describes a strategy you're executing.\n\n## How to use\n\n1. Create a markdown file for each active strategy (e.g., \\`multi-thread-q2.md\\`)\n2. Describe the goal, target segment, and success criteria\n3. Reference playbook plays that support this strategy\n4. After diagnosis, check if vital signs improved in the targeted area\n\n## Example\n\n\\`\\`\\`markdown\n# Multi-Thread Enterprise Deals — Q2\n\n**Goal:** Reduce single-threaded deals from 65% to under 30%\n**Segment:** Enterprise accounts > $100K\n**Play:** Multi-Thread Your Deals\n**Success metric:** Thread depth score > 70\n\\`\\`\\`\n`);\n }\n return dir;\n}\n\nexport function getMemoryDir(): string {\n const dir = join(NTRP_DIR, \"memory\");\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n return dir;\n}\n\nexport function getKnowledgeDir(): string {\n const dir = join(NTRP_DIR, \"knowledge\");\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n writeFileSync(join(dir, \"README.md\"), `# Knowledge Packs\n\nDrop case studies, GTM frameworks, benchmark reports, or playbooks here as\nmarkdown, text, or PDF. NTRP ingests them with \\`/knowledge add <file>\\` and\nreferences the most relevant passages during analysis — so the agent can learn\nfrom work done outside this platform.\n\n## How to use\n\n1. Add a file: \\`/knowledge add ~/Downloads/plg-benchmarks-2026.pdf\\`\n2. List what's indexed: \\`/knowledge list\\`\n3. Ask a question — relevant passages are pulled in automatically.\n`);\n }\n return dir;\n}\n\nexport function getWinsDir(): string {\n const dir = join(NTRP_DIR, \"wins\");\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n writeFileSync(join(dir, \"README.md\"), `# Wins\n\nThis directory logs outcomes when a strategy or play succeeds. Each win creates a record that future diagnoses can reference.\n\n## How to use\n\n1. After executing a play, log the result here (e.g., \\`2026-04-clean-pipeline.md\\`)\n2. Include: what you did, what changed, before/after scores\n3. Future AI findings will reference wins to track improvement over time\n\n## Example\n\n\\`\\`\\`markdown\n# Pipeline Cleanup — April 2026\n\n**Play:** Clean Dead Pipeline\n**Before:** Freshness 29/100, $3.1M stale pipeline\n**After:** Freshness 72/100, removed 45 zombie deals\n**Impact:** Forecast accuracy improved from 62% to 84%\n\\`\\`\\`\n`);\n }\n return dir;\n}\n","/**\n * Company profile storage — mirrors the store.ts pattern but dedicated to\n * the structured business profile at ~/.ntrp/profile.json.\n *\n * Held separate from the flat key/value config.json so the existing\n * config-get/set path stays simple and the profile schema can evolve on\n * its own cadence.\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { CompanyProfile } from \"../types.js\";\nimport { ntrpHome } from \"./store.js\";\n\nconst NTRP_DIR = ntrpHome();\nconst PROFILE_PATH = join(NTRP_DIR, \"profile.json\");\n\nfunction ensureDir(): void {\n if (!existsSync(NTRP_DIR)) {\n mkdirSync(NTRP_DIR, { recursive: true });\n }\n}\n\nexport function profilePath(): string {\n return PROFILE_PATH;\n}\n\nexport function profileExists(): boolean {\n return existsSync(PROFILE_PATH);\n}\n\n/** True when a saved profile has the minimum fields needed for lens gates and AI context. */\nexport function isProfileConfigured(profile: CompanyProfile | null = loadProfile()): boolean {\n if (!profile) return false;\n return profile.company_name.trim().length > 0;\n}\n\nexport function loadProfile(): CompanyProfile | null {\n if (!existsSync(PROFILE_PATH)) return null;\n try {\n const parsed = JSON.parse(readFileSync(PROFILE_PATH, \"utf-8\")) as CompanyProfile;\n if (!parsed || typeof parsed !== \"object\") return null;\n return parsed;\n } catch {\n return null;\n }\n}\n\nexport function saveProfile(profile: CompanyProfile): void {\n ensureDir();\n const now = new Date().toISOString();\n const toWrite: CompanyProfile = {\n ...profile,\n schema_version: 1,\n created_at: profile.created_at || now,\n updated_at: now,\n };\n writeFileSync(PROFILE_PATH, JSON.stringify(toWrite, null, 2) + \"\\n\");\n}\n\nexport function updateProfile(patch: Partial<CompanyProfile>): CompanyProfile {\n const existing = loadProfile();\n const now = new Date().toISOString();\n const merged: CompanyProfile = {\n schema_version: 1,\n company_name: \"\",\n industry: \"\",\n product_description: \"\",\n target_customer: \"\",\n sales_motion: \"mid_market\",\n created_at: now,\n updated_at: now,\n ...(existing ?? {}),\n ...patch,\n };\n saveProfile(merged);\n return merged;\n}\n","/**\n * Fit a demo book of business to the operator — heuristics, catalog copy,\n * and preference persistence. No LLM, no readline (those live in\n * conversation/demo-fit.ts) so smokes can import this file cold.\n */\n\nimport type { CompanyProfile, DemoScenario, SalesMotion } from \"../types.js\";\nimport { getConfigValue, setConfigValue } from \"../config/store.js\";\nimport { isProfileConfigured, loadProfile } from \"../config/profile.js\";\nimport {\n NAMED_DEMO_SCENARIOS,\n getScenario,\n isNamedDemoScenario,\n} from \"./scenarios.js\";\n\nexport type DealBand = \"velocity\" | \"core\" | \"mid\" | \"enterprise\";\n\nexport interface DemoFitSignals {\n salesMotion?: SalesMotion | null;\n dealBand?: DealBand | null;\n /** Free text: industry, ICP, product, user_scope, notes. */\n text?: string | null;\n cycleDays?: number | null;\n}\n\nexport interface DemoFitResult {\n scenario: DemoScenario;\n reason: string;\n source: \"heuristic\" | \"llm\" | \"preference\";\n}\n\nconst PREF_SCENARIO_KEY = \"demo-scenario-preference\";\nconst PREF_MOTION_KEY = \"demo-fit-motion\";\nconst PREF_BAND_KEY = \"demo-fit-deal-band\";\n\nexport const MOTION_FIT_CHOICES: { value: SalesMotion; label: string; description: string }[] = [\n {\n value: \"plg\",\n label: \"Product-led / self-serve\",\n description: \"Users start themselves; sales assists or expands\",\n },\n {\n value: \"smb_velocity\",\n label: \"High-volume SMB\",\n description: \"Fast cycles, lots of small deals, outbound or inbound machine\",\n },\n {\n value: \"mid_market\",\n label: \"Mid-market, structured process\",\n description: \"A real sales cycle, a few stakeholders, moderate ACV\",\n },\n {\n value: \"enterprise\",\n label: \"Enterprise, long cycles\",\n description: \"Large deals, many buyers, quarters not weeks\",\n },\n];\n\nexport const DEAL_BAND_CHOICES: { value: DealBand; label: string; description: string }[] = [\n {\n value: \"velocity\",\n label: \"Under ~$15K, days to a couple of weeks\",\n description: \"Velocity / transactional\",\n },\n {\n value: \"core\",\n label: \"~$15K–$50K, a few weeks\",\n description: \"Core SMB\",\n },\n {\n value: \"mid\",\n label: \"~$50K–$150K, 1–3 months\",\n description: \"Classic mid-market\",\n },\n {\n value: \"enterprise\",\n label: \"$150K+, a quarter or more\",\n description: \"Enterprise / strategic\",\n },\n];\n\n/** Motion × deal-band → recommended book. */\nconst MATRIX: Record<SalesMotion, Record<DealBand, DemoScenario>> = {\n plg: {\n velocity: \"leaky_bucket\",\n core: \"leaky_bucket\",\n mid: \"hidden_crisis\",\n enterprise: \"hidden_crisis\",\n },\n smb_velocity: {\n velocity: \"busy_bees\",\n core: \"busy_bees\",\n mid: \"leaky_bucket\",\n enterprise: \"lone_wolf\",\n },\n mid_market: {\n velocity: \"busy_bees\",\n core: \"stale_pipeline\",\n mid: \"stale_pipeline\",\n enterprise: \"hidden_crisis\",\n },\n enterprise: {\n velocity: \"lone_wolf\",\n core: \"lone_wolf\",\n mid: \"hidden_crisis\",\n enterprise: \"hidden_crisis\",\n },\n};\n\nconst MOTION_ONLY: Record<SalesMotion, DemoScenario> = {\n plg: \"leaky_bucket\",\n smb_velocity: \"busy_bees\",\n mid_market: \"stale_pipeline\",\n enterprise: \"hidden_crisis\",\n};\n\nconst BAND_ONLY: Record<DealBand, DemoScenario> = {\n velocity: \"busy_bees\",\n core: \"leaky_bucket\",\n mid: \"stale_pipeline\",\n enterprise: \"hidden_crisis\",\n};\n\nconst KEYWORD_HINTS: { scenario: DemoScenario; patterns: RegExp[] }[] = [\n { scenario: \"even_keel\", patterns: [/\\beven keel\\b/, /\\breasonably healthy\\b/, /\\bno crisis\\b/, /\\bjust evaluating\\b/, /\\bgreen[- ]field\\b/] },\n { scenario: \"compound_pain\", patterns: [/\\beverything('?s| is) (on fire|red|broken)\\b/, /\\bcompound\\b/, /\\ball (five )?vitals\\b/, /\\bmultiple problems\\b/] },\n { scenario: \"leaky_bucket\", patterns: [/\\bhandoff\\b/, /\\bmqls?\\b/, /\\bleak/, /\\bdrop[- ]rate/, /\\bvanish/, /\\brouting\\b/, /\\bmarketing.?sales\\b/] },\n { scenario: \"stale_pipeline\", patterns: [/\\bzombie/, /\\bstale\\b/, /\\bpast[- ]due\\b/, /\\bforecast(ing)? on fiction\\b/, /\\bstuck in negotiation\\b/, /\\bquiet deals?\\b/] },\n { scenario: \"lone_wolf\", patterns: [/\\bsingle[- ]thread/, /\\blone wolf\\b/, /\\bone contact\\b/, /\\bchampion leaves\\b/] },\n { scenario: \"busy_bees\", patterns: [/\\bspray/, /\\bnois(e|y)\\b/, /\\bmisdirected\\b/, /\\bactivity (volume|metrics)\\b/, /\\bbusy bees\\b/, /\\bnot (on|hitting) pipeline\\b/] },\n { scenario: \"hidden_crisis\", patterns: [/\\bhidden crisis\\b/, /\\benterprise (is )?(dying|red|stale)\\b/, /\\bsegment.{0,20}mask/, /\\baverages? (look|looks) (fine|okay|yellow)\\b/] },\n];\n\nexport function dealBandFromCycleDays(days?: number | null): DealBand | undefined {\n if (days == null || !Number.isFinite(days) || days <= 0) return undefined;\n if (days <= 21) return \"velocity\";\n if (days <= 45) return \"core\";\n if (days <= 90) return \"mid\";\n return \"enterprise\";\n}\n\n/** Parse a free-form ACV string (\"$45K–$120K\", \"250k+\", \"1.2M\") into a band. */\nexport function dealBandFromAverageDealSize(raw?: string | null): DealBand | undefined {\n if (!raw) return undefined;\n const t = raw.trim().toLowerCase();\n if (!t) return undefined;\n const match = t.match(/(\\d+(?:\\.\\d+)?)\\s*(k|m|million|thousand)?/i);\n if (!match) return undefined;\n let n = Number(match[1]);\n if (!Number.isFinite(n)) return undefined;\n const unit = (match[2] ?? \"\").toLowerCase();\n if (unit === \"k\" || unit === \"thousand\") n *= 1_000;\n else if (unit === \"m\" || unit === \"million\") n *= 1_000_000;\n else if (n > 0 && n < 500) n *= 1_000; // bare \"45\" in a deal-size field → $45K\n if (n < 15_000) return \"velocity\";\n if (n < 50_000) return \"core\";\n if (n < 150_000) return \"mid\";\n return \"enterprise\";\n}\n\nexport function signalsFromProfile(profile: CompanyProfile | null | undefined): DemoFitSignals {\n if (!profile) return {};\n const text = [\n profile.industry,\n profile.product_description,\n profile.target_customer,\n profile.user_scope,\n profile.custom_context,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n return {\n salesMotion: profile.sales_motion,\n dealBand:\n dealBandFromAverageDealSize(profile.average_deal_size) ??\n dealBandFromCycleDays(profile.sales_cycle_days),\n cycleDays: profile.sales_cycle_days,\n text,\n };\n}\n\nfunction keywordHits(text: string): Partial<Record<DemoScenario, number>> {\n const hits: Partial<Record<DemoScenario, number>> = {};\n const hay = text.toLowerCase();\n for (const { scenario, patterns } of KEYWORD_HINTS) {\n let n = 0;\n for (const re of patterns) {\n if (re.test(hay)) n++;\n }\n if (n > 0) hits[scenario] = n;\n }\n return hits;\n}\n\nfunction matrixPick(motion?: SalesMotion | null, band?: DealBand | null): DemoScenario {\n if (motion && band) return MATRIX[motion][band];\n if (motion) return MOTION_ONLY[motion];\n if (band) return BAND_ONLY[band];\n return \"even_keel\";\n}\n\nfunction reasonFor(scenario: DemoScenario, signals: DemoFitSignals, via: \"keywords\" | \"matrix\"): string {\n const s = getScenario(scenario);\n if (via === \"keywords\") {\n return `Your notes sound like ${s.label} — ${s.hook}`;\n }\n const motion = signals.salesMotion;\n const band = signals.dealBand;\n if (motion && band) {\n return `${labelMotion(motion)} with ${labelBand(band)} deals maps to ${s.label}.`;\n }\n if (motion) return `${labelMotion(motion)} books usually show up as ${s.label}.`;\n if (band) return `${labelBand(band)} deals usually show up as ${s.label}.`;\n return `${s.label} is the even-keeled starting book when we don't know the motion yet.`;\n}\n\nfunction labelMotion(m: SalesMotion): string {\n return MOTION_FIT_CHOICES.find((c) => c.value === m)?.label ?? m;\n}\n\nfunction labelBand(b: DealBand): string {\n return DEAL_BAND_CHOICES.find((c) => c.value === b)?.label ?? b;\n}\n\n/** Deterministic fit from motion, deal shape, and optional free text. */\nexport function inferDemoScenario(signals: DemoFitSignals): DemoFitResult {\n const text = signals.text?.trim() ?? \"\";\n if (text) {\n const hits = keywordHits(text);\n let best: DemoScenario | undefined;\n let bestN = 0;\n for (const id of NAMED_DEMO_SCENARIOS) {\n const n = hits[id] ?? 0;\n if (n > bestN) {\n best = id;\n bestN = n;\n }\n }\n if (best && bestN > 0) {\n return { scenario: best, reason: reasonFor(best, signals, \"keywords\"), source: \"heuristic\" };\n }\n }\n const scenario = matrixPick(signals.salesMotion, signals.dealBand);\n return { scenario, reason: reasonFor(scenario, signals, \"matrix\"), source: \"heuristic\" };\n}\n\nexport function getPreferredDemoScenario(): DemoScenario | undefined {\n const raw = getConfigValue(PREF_SCENARIO_KEY);\n return raw && isNamedDemoScenario(raw) ? raw : undefined;\n}\n\nexport function saveDemoFit(opts: {\n scenario: DemoScenario;\n motion?: SalesMotion;\n dealBand?: DealBand;\n /** When true (quiz, no company profile), also set the global sales-motion key. */\n syncSalesMotion?: boolean;\n}): void {\n setConfigValue(PREF_SCENARIO_KEY, opts.scenario);\n if (opts.motion) {\n setConfigValue(PREF_MOTION_KEY, opts.motion);\n if (opts.syncSalesMotion && !isProfileConfigured(loadProfile())) {\n setConfigValue(\"sales-motion\", opts.motion);\n }\n }\n if (opts.dealBand) setConfigValue(PREF_BAND_KEY, opts.dealBand);\n}\n\nexport function scenarioMenuChoices(): {\n value: DemoScenario;\n label: string;\n description: string;\n}[] {\n return NAMED_DEMO_SCENARIOS.map((id) => {\n const s = getScenario(id);\n return {\n value: id,\n label: s.label,\n description: s.hook,\n };\n });\n}\n","/**\n * Shared helpers for parsing JSON from LLM text responses.\n * Models often wrap arrays in markdown fences or add a short preamble —\n * always extract the outermost balanced `[...]` before parsing.\n */\n\n/** Strip ```json fences and trim. */\nexport function stripJsonFences(text: string): string {\n const trimmed = text.trim();\n const fenced = trimmed.match(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/i);\n if (fenced) return fenced[1]!.trim();\n return trimmed.replace(/```(?:json)?\\s*/gi, \"\").replace(/```/g, \"\").trim();\n}\n\n/**\n * Parse a JSON array from free-form model output.\n * Returns null when no valid array can be extracted (distinct from `[]`).\n */\nexport function parseJsonArrayFromText(text: string): unknown[] | null {\n const cleaned = stripJsonFences(text);\n const start = cleaned.indexOf(\"[\");\n if (start === -1) return null;\n\n let depth = 0;\n let end = -1;\n for (let i = start; i < cleaned.length; i++) {\n if (cleaned[i] === \"[\") depth++;\n else if (cleaned[i] === \"]\") {\n depth--;\n if (depth === 0) {\n end = i;\n break;\n }\n }\n }\n if (end === -1) return null;\n\n try {\n const parsed = JSON.parse(cleaned.slice(start, end + 1));\n return Array.isArray(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n","/**\n * Smoke tests for the whimsy name drawer — run via `npm run test:whimsy`.\n * Kept as a separate tsup entry so the main CLI bundle stays single-file.\n */\n\nimport { createSeededRandom } from \"./seed.js\";\nimport { createFigureDrawer, WHIMSY_FIGURES, type WhimsyCategory } from \"./whimsy-names.js\";\nimport { NAMED_DEMO_SCENARIOS, resolveScenarioInput } from \"./scenarios.js\";\nimport { inferDemoScenario, dealBandFromAverageDealSize, dealBandFromCycleDays, signalsFromProfile } from \"./scenario-fit.js\";\nimport { parseJsonArrayFromText, stripJsonFences } from \"../ai/json-response.js\";\n\nfunction assert(condition: boolean, message: string): void {\n if (!condition) {\n console.error(`FAIL: ${message}`);\n process.exit(1);\n }\n}\n\nfunction testNoRepeats(): void {\n const rng = createSeededRandom(42);\n const drawer = createFigureDrawer(rng);\n const seen = new Set<string>();\n let count = 0;\n for (let fig = drawer.next(); fig !== null; fig = drawer.next()) {\n const key = `${fig.first}|${fig.last}`;\n assert(!seen.has(key), `duplicate figure drawn: ${fig.first} ${fig.last}`);\n seen.add(key);\n count++;\n }\n assert(count === WHIMSY_FIGURES.length, `expected ${WHIMSY_FIGURES.length} unique draws, got ${count}`);\n}\n\nfunction testDeterministicOrder(): void {\n const draw = (seed: number) => {\n const drawer = createFigureDrawer(createSeededRandom(seed));\n return Array.from({ length: 5 }, () => {\n const f = drawer.next();\n return f ? `${f.first} ${f.last}` : \"\";\n });\n };\n assert(\n JSON.stringify(draw(7)) === JSON.stringify(draw(7)),\n \"same seed should produce identical rep name order\",\n );\n assert(\n JSON.stringify(draw(7)) !== JSON.stringify(draw(8)),\n \"different seeds should shuffle differently\",\n );\n}\n\nfunction testCategoryFilter(): void {\n const rng = createSeededRandom(123);\n const drawer = createFigureDrawer(rng, [\"music\"]);\n for (let fig = drawer.next(); fig !== null; fig = drawer.next()) {\n assert(fig.category === \"music\", `expected music, got ${fig.category} for ${fig.first} ${fig.last}`);\n }\n const musicCount = WHIMSY_FIGURES.filter((f) => f.category === \"music\").length;\n assert(musicCount > 0, \"music pool should not be empty\");\n}\n\nfunction testCategoriesPresent(): void {\n const categories: WhimsyCategory[] = [\"music\", \"sports\", \"film\"];\n for (const cat of categories) {\n assert(\n WHIMSY_FIGURES.some((f) => f.category === cat),\n `pool missing category: ${cat}`,\n );\n }\n}\n\nfunction testEmailSafeLastNames(): void {\n for (const fig of WHIMSY_FIGURES) {\n assert(!fig.last.includes(\" \"), `${fig.first} ${fig.last} has multi-word last name`);\n assert(fig.first.length > 0 && fig.last.length > 0, \"empty name part\");\n }\n}\n\nfunction testResolveScenarioInput(): void {\n assert(resolveScenarioInput(undefined) === undefined, \"blank -> random\");\n assert(resolveScenarioInput(\"hidden_crisis\") === \"hidden_crisis\", \"key passthrough\");\n assert(resolveScenarioInput(\"2\") === \"leaky_bucket\", \"2 -> second scenario\");\n assert(resolveScenarioInput(\"6\") === \"even_keel\", \"6 -> even_keel\");\n assert(resolveScenarioInput(\"7\") === \"compound_pain\", \"7 -> compound_pain\");\n assert(resolveScenarioInput(\"99\") === null, \"out of range -> invalid\");\n assert(resolveScenarioInput(\"not-a-scenario\") === null, \"garbage -> invalid\");\n}\n\nfunction testInferDemoScenario(): void {\n assert(NAMED_DEMO_SCENARIOS.length === 7, \"seven named books of business\");\n assert(dealBandFromCycleDays(14) === \"velocity\", \"short cycle → velocity\");\n assert(dealBandFromCycleDays(60) === \"mid\", \"60d → mid\");\n assert(dealBandFromCycleDays(120) === \"enterprise\", \"120d → enterprise\");\n assert(dealBandFromAverageDealSize(\"$12K\") === \"velocity\", \"12k → velocity\");\n assert(dealBandFromAverageDealSize(\"$80K ACV\") === \"mid\", \"80k → mid\");\n assert(dealBandFromAverageDealSize(\"$250K+\") === \"enterprise\", \"250k → enterprise\");\n\n const plg = inferDemoScenario({ salesMotion: \"plg\", dealBand: \"velocity\" });\n assert(plg.scenario === \"leaky_bucket\", `plg+velocity → leaky_bucket, got ${plg.scenario}`);\n\n const ent = inferDemoScenario({ salesMotion: \"enterprise\", dealBand: \"enterprise\" });\n assert(ent.scenario === \"hidden_crisis\", `enterprise+enterprise → hidden_crisis, got ${ent.scenario}`);\n\n const smb = inferDemoScenario({ salesMotion: \"smb_velocity\", dealBand: \"velocity\" });\n assert(smb.scenario === \"busy_bees\", `smb velocity → busy_bees, got ${smb.scenario}`);\n\n const mm = inferDemoScenario({ salesMotion: \"mid_market\", dealBand: \"mid\" });\n assert(mm.scenario === \"stale_pipeline\", `mid-market → stale_pipeline, got ${mm.scenario}`);\n\n const none = inferDemoScenario({});\n assert(none.scenario === \"even_keel\", `empty signals → even_keel, got ${none.scenario}`);\n\n const handoff = inferDemoScenario({\n salesMotion: \"enterprise\",\n dealBand: \"enterprise\",\n text: \"MQLs vanish at the marketing-sales handoff\",\n });\n assert(handoff.scenario === \"leaky_bucket\", `handoff keywords beat enterprise matrix, got ${handoff.scenario}`);\n\n const wolf = inferDemoScenario({ text: \"every deal is single-threaded with one contact\" });\n assert(wolf.scenario === \"lone_wolf\", `single-thread text → lone_wolf, got ${wolf.scenario}`);\n\n const fromProfile = inferDemoScenario(\n signalsFromProfile({\n schema_version: 1,\n company_name: \"Acme\",\n industry: \"B2B SaaS\",\n product_description: \"Workflow software\",\n target_customer: \"Enterprise IT\",\n sales_motion: \"enterprise\",\n average_deal_size: \"$200K\",\n sales_cycle_days: 120,\n created_at: \"\",\n updated_at: \"\",\n }),\n );\n assert(fromProfile.scenario === \"hidden_crisis\", `enterprise profile → hidden_crisis, got ${fromProfile.scenario}`);\n}\n\nfunction testParseJsonArrayFromText(): void {\n assert(parseJsonArrayFromText(\"[]\")?.length === 0, \"empty array\");\n const fenced = parseJsonArrayFromText('Here you go:\\n```json\\n[{\"a\":1}]\\n```');\n assert(Array.isArray(fenced) && (fenced[0] as { a: number }).a === 1, \"fenced array\");\n assert(parseJsonArrayFromText(\"not json\") === null, \"garbage -> null\");\n const balanced = parseJsonArrayFromText('Note:\\n[{\"text\":\"$727K pipeline at risk\"}]');\n assert(Array.isArray(balanced) && balanced.length === 1, \"preamble + array\");\n assert(stripJsonFences(\"```json\\n[]\\n```\") === \"[]\", \"strip fences\");\n}\n\ntestNoRepeats();\ntestDeterministicOrder();\ntestCategoryFilter();\ntestCategoriesPresent();\ntestEmailSafeLastNames();\ntestResolveScenarioInput();\ntestInferDemoScenario();\ntestParseJsonArrayFromText();\nconsole.log(`whimsy smoke passed (${WHIMSY_FIGURES.length} figures)`);\n"],"mappings":"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","names":["join","NTRP_DIR","join"]}