@sonnechasser/ntrp 1.4.3 → 1.4.5

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 CHANGED
@@ -6279,6 +6279,15 @@ var init_pseudonymize = __esm({
6279
6279
  });
6280
6280
 
6281
6281
  // src/config/profile.ts
6282
+ var profile_exports = {};
6283
+ __export(profile_exports, {
6284
+ isProfileConfigured: () => isProfileConfigured,
6285
+ loadProfile: () => loadProfile,
6286
+ profileExists: () => profileExists,
6287
+ profilePath: () => profilePath,
6288
+ saveProfile: () => saveProfile,
6289
+ updateProfile: () => updateProfile
6290
+ });
6282
6291
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync10, existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
6283
6292
  import { join as join12 } from "path";
6284
6293
  function ensureDir5() {
@@ -13082,6 +13091,15 @@ var init_spinner = __esm({
13082
13091
  });
13083
13092
 
13084
13093
  // src/pipeline/segments.ts
13094
+ function resolveSegmentByName(query, segments) {
13095
+ const lower = query.toLowerCase();
13096
+ const exact = segments.find((s) => s.segment.name.toLowerCase() === lower);
13097
+ if (exact) return { type: "exact", segment: exact };
13098
+ const matches = segments.filter((s) => s.segment.name.toLowerCase().includes(lower));
13099
+ if (matches.length === 1) return { type: "exact", segment: matches[0] };
13100
+ if (matches.length > 1) return { type: "ambiguous", matches };
13101
+ return { type: "none" };
13102
+ }
13085
13103
  function resolveSegmentScopeFromSnapshot(segment, snapshot) {
13086
13104
  const orgIds = [];
13087
13105
  const peopleIds = [];
@@ -13394,8 +13412,9 @@ function resolveThresholds(salesMotion, computedBaselines) {
13394
13412
  return resolved;
13395
13413
  }
13396
13414
  async function getResolvedThresholds() {
13415
+ const { loadProfile: loadProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
13397
13416
  const { getConfigValue: getConfigValue2 } = await Promise.resolve().then(() => (init_store(), store_exports));
13398
- const motion = getConfigValue2("sales-motion");
13417
+ const motion = loadProfile2()?.sales_motion ?? getConfigValue2("sales-motion");
13399
13418
  return resolveThresholds(motion ?? null, {});
13400
13419
  }
13401
13420
  var init_resolve = __esm({
@@ -24173,52 +24192,82 @@ OUTPUT FORMAT \u2014 respond with STRICT JSON only, no preamble, no markdown fen
24173
24192
  });
24174
24193
 
24175
24194
  // src/commands/profile.ts
24176
- var profile_exports = {};
24177
- __export(profile_exports, {
24195
+ var profile_exports2 = {};
24196
+ __export(profile_exports2, {
24178
24197
  PRESET_DESCRIPTIONS: () => PRESET_DESCRIPTIONS,
24179
24198
  PRESET_LABELS: () => PRESET_LABELS,
24180
24199
  handler: () => handler4
24181
24200
  });
24182
24201
  import chalk23 from "chalk";
24183
- async function handler4(args, _ctx) {
24202
+ async function handler4(args, ctx) {
24184
24203
  const { positional } = parseArgs2(args);
24185
24204
  const sub = positional[0] ?? "show";
24186
- switch (sub) {
24187
- case "list":
24188
- return listProfiles();
24189
- case "set":
24190
- return setProfile(positional[1]);
24191
- case "show":
24192
- return showProfile();
24193
- default: {
24194
- console.error(chalk23.red(` Unknown subcommand: ${sub}`));
24195
- console.log(chalk23.dim(" Usage: /profile <list|set|show> [preset]"));
24196
- process.exit(1);
24205
+ const structured = isStructuredOutput(ctx.execution);
24206
+ try {
24207
+ switch (sub) {
24208
+ case "list":
24209
+ return listProfiles(structured);
24210
+ case "set":
24211
+ return setProfile(positional[1], structured);
24212
+ case "show":
24213
+ return showProfile(structured);
24214
+ default: {
24215
+ const err = new NtrpError("unknown_subcommand", `Unknown subcommand: ${sub}`, 2 /* Usage */);
24216
+ if (structured) emitError("profile", err);
24217
+ console.error(chalk23.red(` Unknown subcommand: ${sub}`));
24218
+ console.log(chalk23.dim(" Usage: /profile <list|set|show> [preset]"));
24219
+ process.exit(1);
24220
+ }
24197
24221
  }
24222
+ } catch (err) {
24223
+ if (structured) emitError("profile", err);
24224
+ throw err;
24198
24225
  }
24199
24226
  }
24200
- function listProfiles() {
24227
+ function listProfiles(structured) {
24228
+ const current = loadProfile()?.sales_motion ?? getConfigValue("sales-motion");
24229
+ const presets = Object.entries(PRESET_DESCRIPTIONS).map(([id, description]) => ({
24230
+ id,
24231
+ label: PRESET_LABELS[id],
24232
+ description,
24233
+ active: current === id
24234
+ }));
24235
+ if (structured) {
24236
+ emitResult("profile", { action: "list", presets, current_preset: current ?? null });
24237
+ return;
24238
+ }
24201
24239
  console.log();
24202
24240
  console.log(chalk23.bold(" Sales Motion Presets"));
24203
24241
  console.log();
24204
- const current = getConfigValue("sales-motion");
24205
- for (const [key, desc] of Object.entries(PRESET_DESCRIPTIONS)) {
24206
- const marker2 = current === key ? chalk23.green(" (active)") : "";
24207
- console.log(` ${chalk23.bold(PRESET_LABELS[key].padEnd(16))} ${chalk23.dim(desc)}${marker2}`);
24208
- console.log(chalk23.dim(` ${"".padEnd(16)} id: ${key}`));
24242
+ for (const p of presets) {
24243
+ const marker2 = p.active ? chalk23.green(" (active)") : "";
24244
+ console.log(` ${chalk23.bold(p.label.padEnd(16))} ${chalk23.dim(p.description)}${marker2}`);
24245
+ console.log(chalk23.dim(` ${"".padEnd(16)} id: ${p.id}`));
24209
24246
  }
24210
24247
  console.log();
24211
24248
  console.log(chalk23.dim(" Run /profile set <preset> to activate a profile."));
24212
24249
  console.log();
24213
24250
  }
24214
- function setProfile(preset) {
24251
+ function setProfile(preset, structured) {
24215
24252
  if (!preset) {
24253
+ const err = new NtrpError(
24254
+ "preset_required",
24255
+ `/profile set requires a preset. Valid options: ${Object.keys(PROFILE_PRESETS).join(", ")}`,
24256
+ 2 /* Usage */
24257
+ );
24258
+ if (structured) emitError("profile", err);
24216
24259
  console.error(chalk23.red(" /profile set requires a preset."));
24217
24260
  console.error(chalk23.dim(` Valid options: ${Object.keys(PROFILE_PRESETS).join(", ")}`));
24218
24261
  process.exit(1);
24219
24262
  }
24220
24263
  const validPresets = Object.keys(PROFILE_PRESETS);
24221
24264
  if (!validPresets.includes(preset)) {
24265
+ const err = new NtrpError(
24266
+ "unknown_preset",
24267
+ `Unknown preset: ${preset}. Valid options: ${validPresets.join(", ")}`,
24268
+ 2 /* Usage */
24269
+ );
24270
+ if (structured) emitError("profile", err);
24222
24271
  console.error(chalk23.red(`
24223
24272
  Unknown preset: ${preset}`));
24224
24273
  console.error(chalk23.dim(` Valid options: ${validPresets.join(", ")}
@@ -24227,26 +24276,76 @@ function setProfile(preset) {
24227
24276
  }
24228
24277
  const motion = preset;
24229
24278
  setConfigValue("sales-motion", motion);
24279
+ const hadProfile = Boolean(loadProfile());
24280
+ if (hadProfile) {
24281
+ updateProfile({ sales_motion: motion });
24282
+ }
24230
24283
  const resolved = resolveThresholds(motion, {});
24231
24284
  const defaults = DEFAULT_THRESHOLDS;
24232
- console.log();
24233
- console.log(chalk23.green(` Profile set to ${PRESET_LABELS[motion]}`));
24234
- console.log();
24235
- const changes = [];
24285
+ const threshold_changes = {};
24236
24286
  if (resolved.freshness.people_window_days !== defaults.freshness.people_window_days) {
24237
- changes.push(` Freshness window: ${defaults.freshness.people_window_days}d \u2192 ${resolved.freshness.people_window_days}d`);
24287
+ threshold_changes.freshness_window_days = {
24288
+ from: defaults.freshness.people_window_days,
24289
+ to: resolved.freshness.people_window_days
24290
+ };
24238
24291
  }
24239
24292
  if (resolved.flow_rate.green_days !== defaults.flow_rate.green_days) {
24240
- changes.push(` Flow rate green: ${defaults.flow_rate.green_days}d \u2192 ${resolved.flow_rate.green_days}d`);
24293
+ threshold_changes.flow_rate_green_days = {
24294
+ from: defaults.flow_rate.green_days,
24295
+ to: resolved.flow_rate.green_days
24296
+ };
24241
24297
  }
24242
24298
  if (resolved.flow_rate.stuck_days !== defaults.flow_rate.stuck_days) {
24243
- changes.push(` Stuck threshold: ${defaults.flow_rate.stuck_days}d \u2192 ${resolved.flow_rate.stuck_days}d`);
24299
+ threshold_changes.flow_rate_stuck_days = {
24300
+ from: defaults.flow_rate.stuck_days,
24301
+ to: resolved.flow_rate.stuck_days
24302
+ };
24244
24303
  }
24245
24304
  if (resolved.signal_to_noise.lookback_days !== defaults.signal_to_noise.lookback_days) {
24246
- changes.push(` S/N lookback: ${defaults.signal_to_noise.lookback_days}d \u2192 ${resolved.signal_to_noise.lookback_days}d`);
24305
+ threshold_changes.signal_to_noise_lookback_days = {
24306
+ from: defaults.signal_to_noise.lookback_days,
24307
+ to: resolved.signal_to_noise.lookback_days
24308
+ };
24247
24309
  }
24248
24310
  if (resolved.thread_depth.activity_window_days !== defaults.thread_depth.activity_window_days) {
24249
- changes.push(` Thread depth window: ${defaults.thread_depth.activity_window_days}d \u2192 ${resolved.thread_depth.activity_window_days}d`);
24311
+ threshold_changes.thread_depth_window_days = {
24312
+ from: defaults.thread_depth.activity_window_days,
24313
+ to: resolved.thread_depth.activity_window_days
24314
+ };
24315
+ }
24316
+ if (structured) {
24317
+ emitResult("profile", {
24318
+ action: "set",
24319
+ motion,
24320
+ label: PRESET_LABELS[motion],
24321
+ threshold_changes,
24322
+ profile_synced: hadProfile
24323
+ });
24324
+ return;
24325
+ }
24326
+ console.log();
24327
+ console.log(chalk23.green(` Profile set to ${PRESET_LABELS[motion]}`));
24328
+ console.log();
24329
+ const changes = [];
24330
+ if (threshold_changes.freshness_window_days) {
24331
+ const c = threshold_changes.freshness_window_days;
24332
+ changes.push(` Freshness window: ${c.from}d \u2192 ${c.to}d`);
24333
+ }
24334
+ if (threshold_changes.flow_rate_green_days) {
24335
+ const c = threshold_changes.flow_rate_green_days;
24336
+ changes.push(` Flow rate green: ${c.from}d \u2192 ${c.to}d`);
24337
+ }
24338
+ if (threshold_changes.flow_rate_stuck_days) {
24339
+ const c = threshold_changes.flow_rate_stuck_days;
24340
+ changes.push(` Stuck threshold: ${c.from}d \u2192 ${c.to}d`);
24341
+ }
24342
+ if (threshold_changes.signal_to_noise_lookback_days) {
24343
+ const c = threshold_changes.signal_to_noise_lookback_days;
24344
+ changes.push(` S/N lookback: ${c.from}d \u2192 ${c.to}d`);
24345
+ }
24346
+ if (threshold_changes.thread_depth_window_days) {
24347
+ const c = threshold_changes.thread_depth_window_days;
24348
+ changes.push(` Thread depth window: ${c.from}d \u2192 ${c.to}d`);
24250
24349
  }
24251
24350
  if (changes.length > 0) {
24252
24351
  console.log(chalk23.dim(" Threshold changes:"));
@@ -24258,9 +24357,27 @@ function setProfile(preset) {
24258
24357
  console.log(chalk23.dim(" Run /diagnose to see results with the new profile."));
24259
24358
  console.log();
24260
24359
  }
24261
- function showProfile() {
24360
+ function showProfile(structured) {
24262
24361
  const companyProfile = loadProfile();
24263
24362
  const current = companyProfile?.sales_motion ?? getConfigValue("sales-motion");
24363
+ const resolved = resolveThresholds(current ?? null, {});
24364
+ if (structured) {
24365
+ emitResult("profile", {
24366
+ action: "show",
24367
+ motion: current ?? null,
24368
+ label: current && PRESET_LABELS[current] ? PRESET_LABELS[current] : null,
24369
+ company_profile: companyProfile,
24370
+ thresholds: {
24371
+ freshness_window_days: resolved.freshness.people_window_days,
24372
+ flow_rate_green_days: resolved.flow_rate.green_days,
24373
+ flow_rate_stuck_days: resolved.flow_rate.stuck_days,
24374
+ signal_to_noise_lookback_days: resolved.signal_to_noise.lookback_days,
24375
+ thread_depth_window_days: resolved.thread_depth.activity_window_days,
24376
+ multi_thread_min: resolved.thread_depth.multi_thread_threshold
24377
+ }
24378
+ });
24379
+ return;
24380
+ }
24264
24381
  if (companyProfile) {
24265
24382
  const lines = [];
24266
24383
  lines.push(`### ${companyProfile.company_name}`);
@@ -24290,7 +24407,6 @@ function showProfile() {
24290
24407
  console.log(chalk23.dim(" No sales motion set. Using default thresholds."));
24291
24408
  }
24292
24409
  console.log();
24293
- const resolved = resolveThresholds(current ?? null, {});
24294
24410
  console.log(chalk23.dim(" Key Thresholds:"));
24295
24411
  console.log(chalk23.dim(` Freshness window: ${resolved.freshness.people_window_days} days`));
24296
24412
  console.log(chalk23.dim(` Flow rate (green): ${resolved.flow_rate.green_days} days`));
@@ -24312,6 +24428,9 @@ var init_profile2 = __esm({
24312
24428
  init_argparse();
24313
24429
  init_markdown();
24314
24430
  init_theme();
24431
+ init_emit();
24432
+ init_errors2();
24433
+ init_types2();
24315
24434
  PRESET_DESCRIPTIONS = {
24316
24435
  plg: "Product-Led Growth \u2014 shorter cycles, high volume, self-serve focus",
24317
24436
  smb_velocity: "SMB Velocity \u2014 fast sales cycles, quick close, volume-oriented",
@@ -25784,22 +25903,19 @@ async function loadReportData(segmentName) {
25784
25903
  let { health, segments, findings, entityCounts } = diagnosis;
25785
25904
  let scopedSegment = null;
25786
25905
  if (segmentName) {
25787
- const lower = segmentName.toLowerCase();
25788
- let match = segments.find((s) => s.segment.name.toLowerCase() === lower);
25789
- if (!match) {
25790
- const subs = segments.filter((s) => s.segment.name.toLowerCase().includes(lower));
25791
- if (subs.length === 1) match = subs[0];
25792
- else if (subs.length > 1) {
25793
- throw new NtrpError("ambiguous_segment", `"${segmentName}" matches multiple segments.`, 2 /* Usage */, {
25794
- matches: subs.map((s) => s.segment.name)
25795
- });
25796
- }
25906
+ const resolved = resolveSegmentByName(segmentName, segments);
25907
+ if (resolved.type === "ambiguous") {
25908
+ throw new NtrpError("ambiguous_segment", `"${segmentName}" matches multiple segments.`, 2 /* Usage */, {
25909
+ matches: resolved.matches.map((s) => s.segment.name)
25910
+ });
25797
25911
  }
25798
- if (!match) {
25912
+ if (resolved.type === "none") {
25799
25913
  throw new NtrpError("segment_not_found", `No segment matching "${segmentName}".`, 2 /* Usage */, {
25800
25914
  available_segments: segments.map((s) => s.segment.name)
25801
25915
  });
25802
25916
  }
25917
+ const match = resolved.segment;
25918
+ const lower = segmentName.toLowerCase();
25803
25919
  scopedSegment = match;
25804
25920
  health = match.result;
25805
25921
  segments = [match];
@@ -25825,6 +25941,7 @@ var init_report = __esm({
25825
25941
  init_errors2();
25826
25942
  init_types2();
25827
25943
  init_session_analysis();
25944
+ init_segments();
25828
25945
  }
25829
25946
  });
25830
25947
 
@@ -26188,20 +26305,15 @@ async function handler10(args, _ctx) {
26188
26305
  let exportFindings = diagnosis.findings;
26189
26306
  let exportHealth = diagnosis.health;
26190
26307
  if (segmentName) {
26191
- const lower = segmentName.toLowerCase();
26192
- let match = diagnosis.segments.find((s) => s.segment.name.toLowerCase() === lower);
26193
- if (!match) {
26194
- const subs = diagnosis.segments.filter((s) => s.segment.name.toLowerCase().includes(lower));
26195
- if (subs.length === 1) match = subs[0];
26196
- else if (subs.length > 1) {
26197
- console.error(chalk30.yellow(`
26308
+ const resolved = resolveSegmentByName(segmentName, diagnosis.segments);
26309
+ if (resolved.type === "ambiguous") {
26310
+ console.error(chalk30.yellow(`
26198
26311
  "${segmentName}" matches multiple segments:`));
26199
- for (const s of subs) console.log(chalk30.dim(` - ${s.segment.name}`));
26200
- console.log();
26201
- process.exit(1);
26202
- }
26312
+ for (const s of resolved.matches) console.log(chalk30.dim(` - ${s.segment.name}`));
26313
+ console.log();
26314
+ process.exit(1);
26203
26315
  }
26204
- if (!match) {
26316
+ if (resolved.type === "none") {
26205
26317
  console.error(chalk30.red(`
26206
26318
  No segment matching "${segmentName}".`));
26207
26319
  if (diagnosis.segments.length > 0) {
@@ -26211,6 +26323,8 @@ async function handler10(args, _ctx) {
26211
26323
  console.log();
26212
26324
  process.exit(1);
26213
26325
  }
26326
+ const match = resolved.segment;
26327
+ const lower = segmentName.toLowerCase();
26214
26328
  exportHealth = match.result;
26215
26329
  exportSegments = [match];
26216
26330
  exportFindings = diagnosis.findings.filter((f) => f.segment.toLowerCase().includes(lower));
@@ -26267,6 +26381,7 @@ var init_export = __esm({
26267
26381
  init_path_safety();
26268
26382
  init_argparse();
26269
26383
  init_exports_registry();
26384
+ init_segments();
26270
26385
  }
26271
26386
  });
26272
26387
 
@@ -27619,15 +27734,6 @@ __export(segment_exports, {
27619
27734
  handler: () => handler15
27620
27735
  });
27621
27736
  import chalk35 from "chalk";
27622
- function resolveSegmentByName(query, segments) {
27623
- const lower = query.toLowerCase();
27624
- const exact = segments.find((s) => s.segment.name.toLowerCase() === lower);
27625
- if (exact) return { type: "exact", segment: exact };
27626
- const matches = segments.filter((s) => s.segment.name.toLowerCase().includes(lower));
27627
- if (matches.length === 1) return { type: "exact", segment: matches[0] };
27628
- if (matches.length > 1) return { type: "ambiguous", matches };
27629
- return { type: "none" };
27630
- }
27631
27737
  function handleNoMatch(query, segments) {
27632
27738
  console.error(chalk35.red(` No segment matching "${query}".`));
27633
27739
  console.log();
@@ -27951,6 +28057,7 @@ var init_segment = __esm({
27951
28057
  init_types2();
27952
28058
  init_serialize();
27953
28059
  init_session_analysis();
28060
+ init_segments();
27954
28061
  OPERATOR_ALIASES = {
27955
28062
  eq: "equals",
27956
28063
  equals: "equals",
@@ -30565,42 +30672,85 @@ __export(reset_exports, {
30565
30672
  handler: () => handler20
30566
30673
  });
30567
30674
  import chalk43 from "chalk";
30568
- async function handler20(args, _ctx) {
30675
+ async function countRowsByTable() {
30676
+ const counts = {};
30677
+ for (const table of TABLES) {
30678
+ try {
30679
+ const rows = await all(`SELECT COUNT(*) AS cnt FROM ${table}`);
30680
+ counts[table] = Number(rows[0]?.cnt ?? 0);
30681
+ } catch {
30682
+ counts[table] = 0;
30683
+ }
30684
+ }
30685
+ return counts;
30686
+ }
30687
+ async function handler20(args, ctx) {
30569
30688
  const { flags } = parseArgs2(args, ["force"]);
30570
30689
  const force = getBool(flags, "force");
30690
+ const structured = isStructuredOutput(ctx.execution);
30571
30691
  if (!force) {
30692
+ const message = "This will delete ALL data from the local database. Re-run with --force to confirm.";
30693
+ if (structured) {
30694
+ emitError("reset", new NtrpError("force_required", message, 2 /* Usage */));
30695
+ }
30572
30696
  console.log(chalk43.yellow("\n This will delete ALL data from the local database."));
30573
30697
  console.log(chalk43.dim(" Re-run with --force to confirm.\n"));
30574
30698
  process.exit(1);
30575
30699
  }
30576
- const spinner = makeSpinner("Resetting database\u2026");
30700
+ const spinner = structured ? null : makeSpinner("Resetting database\u2026");
30577
30701
  try {
30578
30702
  await initSchema();
30703
+ const counts_by_table = await countRowsByTable();
30704
+ const total_rows_deleted = Object.values(counts_by_table).reduce((a, b) => a + b, 0);
30579
30705
  for (const table of TABLES) {
30580
30706
  await run(`DELETE FROM ${table}`);
30581
30707
  }
30582
- spinner.succeed("All data cleared");
30583
30708
  const { wipePrivacyStore: wipePrivacyStore3 } = await Promise.resolve().then(() => (init_lexicon_seed(), lexicon_seed_exports));
30584
30709
  wipePrivacyStore3();
30710
+ if (structured) {
30711
+ emitResult("reset", {
30712
+ status: "cleared",
30713
+ tables_cleared: TABLES,
30714
+ total_rows_deleted,
30715
+ counts_by_table
30716
+ });
30717
+ return;
30718
+ }
30719
+ spinner.succeed("All data cleared");
30585
30720
  console.log(chalk43.dim("\n Run ") + chalk43.cyan("/new") + chalk43.dim(" \u2192 pick Demo, then choose your analysis type.\n"));
30586
30721
  } catch (err) {
30587
30722
  if (force && isClosedConnectionError(err)) {
30588
30723
  try {
30589
- spinner.text = "Recreating database\u2026";
30724
+ if (spinner) spinner.text = "Recreating database\u2026";
30590
30725
  await recreateDatabaseFile();
30591
30726
  await initSchema();
30592
- spinner.succeed("Database recreated");
30593
30727
  const { wipePrivacyStore: wipePrivacyStore3 } = await Promise.resolve().then(() => (init_lexicon_seed(), lexicon_seed_exports));
30594
30728
  wipePrivacyStore3();
30729
+ if (structured) {
30730
+ emitResult("reset", {
30731
+ status: "recreated",
30732
+ tables_cleared: TABLES,
30733
+ total_rows_deleted: 0,
30734
+ counts_by_table: Object.fromEntries(TABLES.map((t) => [t, 0]))
30735
+ });
30736
+ return;
30737
+ }
30738
+ spinner.succeed("Database recreated");
30595
30739
  console.log(chalk43.dim("\n Run ") + chalk43.cyan("/new") + chalk43.dim(" \u2192 pick Demo, then choose your analysis type.\n"));
30596
30740
  return;
30597
30741
  } catch (recreateErr) {
30598
- spinner.fail("Reset failed");
30742
+ if (structured) {
30743
+ emitError("reset", recreateErr instanceof NtrpError ? recreateErr : new NtrpError("reset_failed", String(recreateErr), 1 /* RuntimeError */));
30744
+ }
30745
+ spinner?.fail("Reset failed");
30599
30746
  console.error(chalk43.red(String(recreateErr)));
30600
30747
  process.exit(1);
30601
30748
  }
30602
30749
  }
30603
- spinner.fail("Reset failed");
30750
+ if (structured) {
30751
+ emitError("reset", err instanceof NtrpError ? err : new NtrpError("reset_failed", String(err), 1 /* RuntimeError */));
30752
+ }
30753
+ spinner?.fail("Reset failed");
30604
30754
  console.error(chalk43.red(String(err)));
30605
30755
  process.exit(1);
30606
30756
  }
@@ -30613,6 +30763,9 @@ var init_reset = __esm({
30613
30763
  init_connection();
30614
30764
  init_schema();
30615
30765
  init_argparse();
30766
+ init_emit();
30767
+ init_errors2();
30768
+ init_types2();
30616
30769
  TABLES = [
30617
30770
  "findings",
30618
30771
  "health_readings",
@@ -30893,7 +31046,10 @@ function usage() {
30893
31046
  console.log(chalk45.dim(" Tip: ") + paint("accent", "/config set api-key") + chalk45.dim(" opens a hidden prompt (no inline paste)."));
30894
31047
  console.log(chalk45.dim(" Tip: ") + paint("accent", "/connect") + chalk45.dim(" auto-detects the provider from any pasted key."));
30895
31048
  }
30896
- function fail(message, ctx) {
31049
+ function fail(message, ctx, code = "usage_error") {
31050
+ if (isStructuredOutput(ctx.execution)) {
31051
+ emitError("config", new NtrpError(code, message, 2 /* Usage */));
31052
+ }
30897
31053
  console.error(chalk45.red(` ${message}`));
30898
31054
  if (ctx.oneShot) process.exit(1);
30899
31055
  }
@@ -30910,9 +31066,18 @@ async function promptSecretValue(key, ctx) {
30910
31066
  prompts.close();
30911
31067
  }
30912
31068
  }
31069
+ function redactConfig(config) {
31070
+ const secrets = secretKeys();
31071
+ const out = {};
31072
+ for (const [k, v] of Object.entries(config)) {
31073
+ out[k] = secrets.has(k) ? display(k, v) : v;
31074
+ }
31075
+ return out;
31076
+ }
30913
31077
  async function handler22(args, ctx) {
30914
31078
  const { positional } = parseArgs2(args);
30915
31079
  const sub = positional[0] ?? "list";
31080
+ const structured = isStructuredOutput(ctx.execution);
30916
31081
  switch (sub) {
30917
31082
  case "set": {
30918
31083
  const key = positional[1];
@@ -30924,6 +31089,10 @@ async function handler22(args, ctx) {
30924
31089
  }
30925
31090
  let value = inlineValue;
30926
31091
  if (!value && secretKeys().has(key)) {
31092
+ if (structured) {
31093
+ fail(`/config set ${key} requires an inline value in --json mode`, ctx, "value_required");
31094
+ return;
31095
+ }
30927
31096
  try {
30928
31097
  value = await promptSecretValue(key, ctx);
30929
31098
  } catch (err) {
@@ -30938,7 +31107,11 @@ async function handler22(args, ctx) {
30938
31107
  setConfigValue(key, value);
30939
31108
  const saved = getConfigValue(key);
30940
31109
  if (saved !== value) {
30941
- fail(`Failed to save ${key} \u2014 check permissions on ~/.ntrp/config.json`, ctx);
31110
+ fail(`Failed to save ${key} \u2014 check permissions on ~/.ntrp/config.json`, ctx, "save_failed");
31111
+ return;
31112
+ }
31113
+ if (structured) {
31114
+ emitResult("config", { action: "set", key, value: display(key, value) });
30942
31115
  return;
30943
31116
  }
30944
31117
  console.log();
@@ -30972,6 +31145,15 @@ async function handler22(args, ctx) {
30972
31145
  return;
30973
31146
  }
30974
31147
  const value = getConfigValue(key);
31148
+ if (structured) {
31149
+ emitResult("config", {
31150
+ action: "get",
31151
+ key,
31152
+ value: value === void 0 ? null : display(key, value),
31153
+ is_set: value !== void 0
31154
+ });
31155
+ return;
31156
+ }
30975
31157
  if (value === void 0) console.log(chalk45.dim(` ${key} is not set`));
30976
31158
  else console.log(` ${key} = ${display(key, value)}`);
30977
31159
  return;
@@ -30979,6 +31161,14 @@ async function handler22(args, ctx) {
30979
31161
  case "list": {
30980
31162
  const config = loadConfig();
30981
31163
  const entries2 = Object.entries(config);
31164
+ if (structured) {
31165
+ emitResult("config", {
31166
+ action: "list",
31167
+ config: redactConfig(config),
31168
+ count: entries2.length
31169
+ });
31170
+ return;
31171
+ }
30982
31172
  if (entries2.length === 0) {
30983
31173
  console.log(chalk45.dim(" No configuration set."));
30984
31174
  return;
@@ -30993,11 +31183,15 @@ async function handler22(args, ctx) {
30993
31183
  return;
30994
31184
  }
30995
31185
  deleteConfigValue(key);
31186
+ if (structured) {
31187
+ emitResult("config", { action: "delete", key });
31188
+ return;
31189
+ }
30996
31190
  console.log(chalk45.green(` Deleted ${key}`));
30997
31191
  return;
30998
31192
  }
30999
31193
  default: {
31000
- fail(`Unknown subcommand: ${sub}`, ctx);
31194
+ fail(`Unknown subcommand: ${sub}`, ctx, "unknown_subcommand");
31001
31195
  usage();
31002
31196
  return;
31003
31197
  }
@@ -31012,6 +31206,9 @@ var init_config = __esm({
31012
31206
  init_argparse();
31013
31207
  init_prompts();
31014
31208
  init_theme();
31209
+ init_emit();
31210
+ init_errors2();
31211
+ init_types2();
31015
31212
  }
31016
31213
  });
31017
31214
 
@@ -31029,8 +31226,13 @@ async function afterLicenseSuccess(ctx) {
31029
31226
  async function handler23(args, ctx) {
31030
31227
  const { positional } = parseArgs2(args);
31031
31228
  const key = positional[0];
31229
+ const structured = isStructuredOutput(ctx.execution);
31032
31230
  if (!key) {
31033
- if (ctx.oneShot || !process.stdin.isTTY) {
31231
+ if (ctx.oneShot || !process.stdin.isTTY || structured) {
31232
+ const message = "Usage: /activate <key>. Or type /activate in ntrp to paste a key.";
31233
+ if (structured) {
31234
+ emitError("activate", new NtrpError("key_required", message, 2 /* Usage */));
31235
+ }
31034
31236
  console.error(chalk46.red("\n Usage: /activate <key>"));
31035
31237
  console.error(chalk46.dim(" Or type /activate in ntrp to paste a key.\n"));
31036
31238
  if (ctx.oneShot) process.exit(1);
@@ -31056,18 +31258,29 @@ async function handler23(args, ctx) {
31056
31258
  try {
31057
31259
  const result = await activateLicenseKey(key);
31058
31260
  if (!result.valid) {
31261
+ if (structured) {
31262
+ emitResult("activate", { valid: false, message: result.message });
31263
+ process.exit(1);
31264
+ }
31059
31265
  console.error(chalk46.red(`
31060
31266
  ${result.message}
31061
31267
  `));
31062
31268
  if (ctx.oneShot) process.exit(1);
31063
31269
  return;
31064
31270
  }
31271
+ if (structured) {
31272
+ emitResult("activate", { valid: true, message: result.message });
31273
+ return;
31274
+ }
31065
31275
  console.log(chalk46.green(`
31066
31276
  License activated: ${result.message}
31067
31277
  `));
31068
31278
  await afterLicenseSuccess(ctx);
31069
31279
  } catch (err) {
31070
31280
  const message = err instanceof Error ? err.message : "License activation failed";
31281
+ if (structured) {
31282
+ emitError("activate", new NtrpError("activation_failed", message, 3 /* Auth */));
31283
+ }
31071
31284
  console.error(chalk46.red(`
31072
31285
  ${message}
31073
31286
  `));
@@ -31083,6 +31296,9 @@ var init_activate = __esm({
31083
31296
  init_activation();
31084
31297
  init_upgrade();
31085
31298
  init_theme();
31299
+ init_emit();
31300
+ init_errors2();
31301
+ init_types2();
31086
31302
  }
31087
31303
  });
31088
31304
 
@@ -35705,7 +35921,7 @@ async function importHandler(runtimePath) {
35705
35921
  case "../commands/publish.js":
35706
35922
  return Promise.resolve().then(() => (init_publish2(), publish_exports));
35707
35923
  case "../commands/profile.js":
35708
- return Promise.resolve().then(() => (init_profile2(), profile_exports));
35924
+ return Promise.resolve().then(() => (init_profile2(), profile_exports2));
35709
35925
  case "../commands/config.js":
35710
35926
  return Promise.resolve().then(() => (init_config(), config_exports));
35711
35927
  case "../commands/activate.js":
@@ -36429,6 +36645,7 @@ Manage CLI configuration stored at \`~/.ntrp/config.json\`. Useful keys:
36429
36645
  \`api-key\` (Anthropic), \`openai-api-key\` (and \`groq-api-key\`, \`google-api-key\`, ...),
36430
36646
  \`llm-primary\` (default provider), \`llm-tier\`, \`llm-auto-failover\`,
36431
36647
  \`voice-personality\`, \`voice-roast\`,
36648
+ \`sales-motion\`, \`rep_hourly_cost\`, \`hours_per_activity\`,
36432
36649
  \`default-format\`, \`export-dir\`, \`ai-inbox-dir\` (or type \`/inbox set\`).
36433
36650
 
36434
36651
  Setting a provider key opens a hidden prompt and auto-discovers that provider's models.
@@ -5742,8 +5742,22 @@ var init_pseudonymize = __esm({
5742
5742
  });
5743
5743
 
5744
5744
  // src/config/profile.ts
5745
+ var profile_exports = {};
5746
+ __export(profile_exports, {
5747
+ isProfileConfigured: () => isProfileConfigured,
5748
+ loadProfile: () => loadProfile,
5749
+ profileExists: () => profileExists,
5750
+ profilePath: () => profilePath,
5751
+ saveProfile: () => saveProfile,
5752
+ updateProfile: () => updateProfile
5753
+ });
5745
5754
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync10, existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
5746
5755
  import { join as join12 } from "path";
5756
+ function ensureDir5() {
5757
+ if (!existsSync12(NTRP_DIR3)) {
5758
+ mkdirSync9(NTRP_DIR3, { recursive: true });
5759
+ }
5760
+ }
5747
5761
  function profilePath() {
5748
5762
  return PROFILE_PATH;
5749
5763
  }
@@ -5764,6 +5778,35 @@ function loadProfile() {
5764
5778
  return null;
5765
5779
  }
5766
5780
  }
5781
+ function saveProfile(profile) {
5782
+ ensureDir5();
5783
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
5784
+ const toWrite = {
5785
+ ...profile,
5786
+ schema_version: 1,
5787
+ created_at: profile.created_at || now2,
5788
+ updated_at: now2
5789
+ };
5790
+ writeFileSync10(PROFILE_PATH, JSON.stringify(toWrite, null, 2) + "\n");
5791
+ }
5792
+ function updateProfile(patch) {
5793
+ const existing = loadProfile();
5794
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
5795
+ const merged = {
5796
+ schema_version: 1,
5797
+ company_name: "",
5798
+ industry: "",
5799
+ product_description: "",
5800
+ target_customer: "",
5801
+ sales_motion: "mid_market",
5802
+ created_at: now2,
5803
+ updated_at: now2,
5804
+ ...existing ?? {},
5805
+ ...patch
5806
+ };
5807
+ saveProfile(merged);
5808
+ return merged;
5809
+ }
5767
5810
  var NTRP_DIR3, PROFILE_PATH;
5768
5811
  var init_profile = __esm({
5769
5812
  "src/config/profile.ts"() {
@@ -8598,6 +8641,15 @@ var init_context2 = __esm({
8598
8641
  });
8599
8642
 
8600
8643
  // src/pipeline/segments.ts
8644
+ function resolveSegmentByName(query, segments) {
8645
+ const lower = query.toLowerCase();
8646
+ const exact = segments.find((s) => s.segment.name.toLowerCase() === lower);
8647
+ if (exact) return { type: "exact", segment: exact };
8648
+ const matches = segments.filter((s) => s.segment.name.toLowerCase().includes(lower));
8649
+ if (matches.length === 1) return { type: "exact", segment: matches[0] };
8650
+ if (matches.length > 1) return { type: "ambiguous", matches };
8651
+ return { type: "none" };
8652
+ }
8601
8653
  function resolveSegmentScopeFromSnapshot(segment, snapshot) {
8602
8654
  const orgIds = [];
8603
8655
  const peopleIds = [];
@@ -8945,8 +8997,9 @@ function resolveThresholds(salesMotion, computedBaselines) {
8945
8997
  return resolved;
8946
8998
  }
8947
8999
  async function getResolvedThresholds() {
9000
+ const { loadProfile: loadProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
8948
9001
  const { getConfigValue: getConfigValue2 } = await Promise.resolve().then(() => (init_store(), store_exports));
8949
- const motion = getConfigValue2("sales-motion");
9002
+ const motion = loadProfile2()?.sales_motion ?? getConfigValue2("sales-motion");
8950
9003
  return resolveThresholds(motion ?? null, {});
8951
9004
  }
8952
9005
  var init_resolve = __esm({
@@ -10462,6 +10515,7 @@ Manage CLI configuration stored at \`~/.ntrp/config.json\`. Useful keys:
10462
10515
  \`api-key\` (Anthropic), \`openai-api-key\` (and \`groq-api-key\`, \`google-api-key\`, ...),
10463
10516
  \`llm-primary\` (default provider), \`llm-tier\`, \`llm-auto-failover\`,
10464
10517
  \`voice-personality\`, \`voice-roast\`,
10518
+ \`sales-motion\`, \`rep_hourly_cost\`, \`hours_per_activity\`,
10465
10519
  \`default-format\`, \`export-dir\`, \`ai-inbox-dir\` (or type \`/inbox set\`).
10466
10520
 
10467
10521
  Setting a provider key opens a hidden prompt and auto-discovers that provider's models.
@@ -23291,7 +23345,7 @@ var init_scenario_fit = __esm({
23291
23345
  import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, existsSync as existsSync22, mkdirSync as mkdirSync13, unlinkSync as unlinkSync3 } from "fs";
23292
23346
  import { homedir as homedir7 } from "os";
23293
23347
  import { join as join24 } from "path";
23294
- function ensureDir5() {
23348
+ function ensureDir6() {
23295
23349
  if (!existsSync22(NTRP_DIR4)) {
23296
23350
  mkdirSync13(NTRP_DIR4, { recursive: true });
23297
23351
  }
@@ -23308,7 +23362,7 @@ function loadCachedTaxonomy(profile) {
23308
23362
  }
23309
23363
  }
23310
23364
  function saveCachedTaxonomy(taxonomy) {
23311
- ensureDir5();
23365
+ ensureDir6();
23312
23366
  writeFileSync15(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
23313
23367
  }
23314
23368
  var NTRP_DIR4, TAXONOMY_PATH;
@@ -26096,22 +26150,19 @@ async function loadReportData(segmentName) {
26096
26150
  let { health, segments, findings, entityCounts } = diagnosis;
26097
26151
  let scopedSegment = null;
26098
26152
  if (segmentName) {
26099
- const lower = segmentName.toLowerCase();
26100
- let match = segments.find((s) => s.segment.name.toLowerCase() === lower);
26101
- if (!match) {
26102
- const subs = segments.filter((s) => s.segment.name.toLowerCase().includes(lower));
26103
- if (subs.length === 1) match = subs[0];
26104
- else if (subs.length > 1) {
26105
- throw new NtrpError("ambiguous_segment", `"${segmentName}" matches multiple segments.`, 2 /* Usage */, {
26106
- matches: subs.map((s) => s.segment.name)
26107
- });
26108
- }
26153
+ const resolved = resolveSegmentByName(segmentName, segments);
26154
+ if (resolved.type === "ambiguous") {
26155
+ throw new NtrpError("ambiguous_segment", `"${segmentName}" matches multiple segments.`, 2 /* Usage */, {
26156
+ matches: resolved.matches.map((s) => s.segment.name)
26157
+ });
26109
26158
  }
26110
- if (!match) {
26159
+ if (resolved.type === "none") {
26111
26160
  throw new NtrpError("segment_not_found", `No segment matching "${segmentName}".`, 2 /* Usage */, {
26112
26161
  available_segments: segments.map((s) => s.segment.name)
26113
26162
  });
26114
26163
  }
26164
+ const match = resolved.segment;
26165
+ const lower = segmentName.toLowerCase();
26115
26166
  scopedSegment = match;
26116
26167
  health = match.result;
26117
26168
  segments = [match];
@@ -26137,6 +26188,7 @@ var init_report = __esm({
26137
26188
  init_errors2();
26138
26189
  init_types2();
26139
26190
  init_session_analysis();
26191
+ init_segments();
26140
26192
  }
26141
26193
  });
26142
26194
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonnechasser/ntrp",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
4
4
  "description": "GTM Health Diagnostic CLI — local pipeline analysis tool",
5
5
  "homepage": "https://ntrp.sonnechasser.com",
6
6
  "repository": {