@sonnechasser/ntrp 1.5.2 → 1.5.3

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
@@ -15499,6 +15499,190 @@ var init_explore_mode = __esm({
15499
15499
  }
15500
15500
  });
15501
15501
 
15502
+ // src/config/update-check.ts
15503
+ import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync17, unlinkSync as unlinkSync4, writeFileSync as writeFileSync14 } from "fs";
15504
+ import { join as join21 } from "path";
15505
+ function cachePath2() {
15506
+ return join21(ntrpHome(), "update-check.json");
15507
+ }
15508
+ function ensureDir6() {
15509
+ const dir = ntrpHome();
15510
+ if (!existsSync21(dir)) {
15511
+ mkdirSync12(dir, { recursive: true });
15512
+ }
15513
+ }
15514
+ function loadUpdateCheckCache() {
15515
+ const path = cachePath2();
15516
+ if (!existsSync21(path)) return null;
15517
+ try {
15518
+ const parsed = JSON.parse(readFileSync17(path, "utf-8"));
15519
+ if (!parsed || typeof parsed !== "object" || typeof parsed.lastCheck !== "number" || typeof parsed.latestVersion !== "string") {
15520
+ return null;
15521
+ }
15522
+ return parsed;
15523
+ } catch {
15524
+ return null;
15525
+ }
15526
+ }
15527
+ function saveUpdateCheckCache(cache2) {
15528
+ ensureDir6();
15529
+ writeFileSync14(cachePath2(), JSON.stringify(cache2, null, 2) + "\n");
15530
+ }
15531
+ function isCacheFresh(cache2, ttlMs = CACHE_TTL_MS2) {
15532
+ if (!cache2) return false;
15533
+ return Date.now() - cache2.lastCheck < ttlMs;
15534
+ }
15535
+ function invalidateUpdateCheckCache() {
15536
+ const path = cachePath2();
15537
+ if (existsSync21(path)) {
15538
+ unlinkSync4(path);
15539
+ }
15540
+ }
15541
+ var CACHE_TTL_MS2;
15542
+ var init_update_check = __esm({
15543
+ "src/config/update-check.ts"() {
15544
+ "use strict";
15545
+ init_store();
15546
+ CACHE_TTL_MS2 = 864e5;
15547
+ }
15548
+ });
15549
+
15550
+ // src/version.ts
15551
+ import { existsSync as existsSync22, readFileSync as readFileSync18 } from "fs";
15552
+ import { dirname as dirname4, join as join22 } from "path";
15553
+ import { fileURLToPath } from "url";
15554
+ function readVersionFromPackageJson(packageJsonPath) {
15555
+ if (!existsSync22(packageJsonPath)) return null;
15556
+ try {
15557
+ const pkg = JSON.parse(readFileSync18(packageJsonPath, "utf-8"));
15558
+ if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
15559
+ } catch {
15560
+ }
15561
+ return null;
15562
+ }
15563
+ function readVersionNearEntry(entryPath) {
15564
+ const start = dirname4(entryPath);
15565
+ for (const rel of [join22(start, "..", "package.json"), join22(start, "../..", "package.json")]) {
15566
+ const version = readVersionFromPackageJson(rel);
15567
+ if (version) return version;
15568
+ }
15569
+ return null;
15570
+ }
15571
+ function readInstalledVersionFromDisk() {
15572
+ return readVersionNearEntry(fileURLToPath(import.meta.url));
15573
+ }
15574
+ function getInstalledVersion() {
15575
+ if (cachedVersion) return cachedVersion;
15576
+ cachedVersion = readInstalledVersionFromDisk() ?? "0.0.0";
15577
+ return cachedVersion;
15578
+ }
15579
+ var cachedVersion;
15580
+ var init_version = __esm({
15581
+ "src/version.ts"() {
15582
+ "use strict";
15583
+ }
15584
+ });
15585
+
15586
+ // src/update/registry.ts
15587
+ var registry_exports = {};
15588
+ __export(registry_exports, {
15589
+ NPM_PACKAGE: () => NPM_PACKAGE,
15590
+ applyUpdateCheckResult: () => applyUpdateCheckResult,
15591
+ checkForUpdate: () => checkForUpdate,
15592
+ fetchLatestVersion: () => fetchLatestVersion,
15593
+ formatUpdateNudge: () => formatUpdateNudge,
15594
+ hasAvailableUpdate: () => hasAvailableUpdate,
15595
+ hydrateUpdateAvailableFromCache: () => hydrateUpdateAvailableFromCache,
15596
+ isNewerVersion: () => isNewerVersion,
15597
+ startBackgroundUpdateCheck: () => startBackgroundUpdateCheck
15598
+ });
15599
+ function registryUrl() {
15600
+ return process.env.NTRP_REGISTRY_URL ?? "https://registry.npmjs.org/@sonnechasser/ntrp/latest";
15601
+ }
15602
+ function parseVersionParts(version) {
15603
+ const cleaned = version.trim().replace(/^v/i, "");
15604
+ const core = cleaned.split("-")[0] ?? cleaned;
15605
+ const parts = core.split(".").map((p) => parseInt(p, 10));
15606
+ return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
15607
+ }
15608
+ function isNewerVersion(latest, current) {
15609
+ const [lMaj, lMin, lPatch] = parseVersionParts(latest);
15610
+ const [cMaj, cMin, cPatch] = parseVersionParts(current);
15611
+ if (lMaj !== cMaj) return lMaj > cMaj;
15612
+ if (lMin !== cMin) return lMin > cMin;
15613
+ return lPatch > cPatch;
15614
+ }
15615
+ function formatUpdateNudge(current, latest) {
15616
+ return `\u26A1 NTRP v${latest} available (running v${current}) \u2014 \u23CE /update`;
15617
+ }
15618
+ function hasAvailableUpdate(update) {
15619
+ return Boolean(update && isNewerVersion(update.latest, update.current));
15620
+ }
15621
+ function hydrateUpdateAvailableFromCache(current) {
15622
+ const cached2 = loadUpdateCheckCache();
15623
+ if (!cached2?.latestVersion) return void 0;
15624
+ if (!isNewerVersion(cached2.latestVersion, current)) return void 0;
15625
+ return { current, latest: cached2.latestVersion };
15626
+ }
15627
+ function applyUpdateCheckResult(ctx, result) {
15628
+ if (result?.updateAvailable) {
15629
+ ctx.updateAvailable = { current: result.current, latest: result.latest };
15630
+ return;
15631
+ }
15632
+ if (result && !result.updateAvailable) {
15633
+ ctx.updateAvailable = void 0;
15634
+ }
15635
+ }
15636
+ function startBackgroundUpdateCheck(ctx) {
15637
+ const pending = checkForUpdate({ force: true, timeoutMs: 5e3 });
15638
+ ctx.pendingUpdateCheck = pending;
15639
+ void pending.then((result) => applyUpdateCheckResult(ctx, result)).catch(() => void 0);
15640
+ }
15641
+ async function fetchLatestVersion(timeoutMs = 5e3) {
15642
+ try {
15643
+ const res = await fetch(registryUrl(), { signal: AbortSignal.timeout(timeoutMs) });
15644
+ if (!res.ok) return null;
15645
+ const data = await res.json();
15646
+ return typeof data.version === "string" && data.version.length > 0 ? data.version : null;
15647
+ } catch {
15648
+ return null;
15649
+ }
15650
+ }
15651
+ function buildResult(current, latest) {
15652
+ return {
15653
+ current,
15654
+ latest,
15655
+ updateAvailable: isNewerVersion(latest, current)
15656
+ };
15657
+ }
15658
+ async function checkForUpdate(options) {
15659
+ const current = getInstalledVersion();
15660
+ const timeoutMs = options?.timeoutMs ?? 5e3;
15661
+ const cached2 = loadUpdateCheckCache();
15662
+ if (!options?.force && isCacheFresh(cached2)) {
15663
+ return buildResult(current, cached2.latestVersion);
15664
+ }
15665
+ const latest = await fetchLatestVersion(timeoutMs);
15666
+ if (!latest) {
15667
+ if (cached2?.latestVersion) {
15668
+ return buildResult(current, cached2.latestVersion);
15669
+ }
15670
+ return null;
15671
+ }
15672
+ const nextCache = { lastCheck: Date.now(), latestVersion: latest };
15673
+ saveUpdateCheckCache(nextCache);
15674
+ return buildResult(current, latest);
15675
+ }
15676
+ var NPM_PACKAGE;
15677
+ var init_registry = __esm({
15678
+ "src/update/registry.ts"() {
15679
+ "use strict";
15680
+ init_update_check();
15681
+ init_version();
15682
+ NPM_PACKAGE = "@sonnechasser/ntrp";
15683
+ }
15684
+ });
15685
+
15502
15686
  // src/conversation/recommended-action.ts
15503
15687
  function resolveRecommendedAction(ctx) {
15504
15688
  if (!hasValidLicense()) return { submit: "/activate", hint: "/activate" };
@@ -15517,6 +15701,8 @@ function resolveRecommendedAction(ctx) {
15517
15701
  return ctx.strategistState?.step === "objective_confirm" ? { submit: "yes", hint: "yes" } : null;
15518
15702
  case "think":
15519
15703
  return null;
15704
+ case "orient":
15705
+ return hasAvailableUpdate(ctx.updateAvailable) ? { submit: "/update", hint: "/update" } : null;
15520
15706
  default:
15521
15707
  return null;
15522
15708
  }
@@ -15526,6 +15712,7 @@ var init_recommended_action = __esm({
15526
15712
  "use strict";
15527
15713
  init_repl_api();
15528
15714
  init_activation();
15715
+ init_registry();
15529
15716
  init_phase();
15530
15717
  }
15531
15718
  });
@@ -16324,17 +16511,17 @@ __export(play_outcomes_exports, {
16324
16511
  listPlayOutcomes: () => listPlayOutcomes,
16325
16512
  recordPlayOutcomes: () => recordPlayOutcomes
16326
16513
  });
16327
- import { existsSync as existsSync21, readFileSync as readFileSync17, appendFileSync as appendFileSync6 } from "fs";
16328
- import { join as join21 } from "path";
16514
+ import { existsSync as existsSync23, readFileSync as readFileSync19, appendFileSync as appendFileSync6 } from "fs";
16515
+ import { join as join23 } from "path";
16329
16516
  import { randomUUID as randomUUID7 } from "crypto";
16330
16517
  function outcomesPath() {
16331
- return join21(getMemoryDir(), OUTCOMES_FILE);
16518
+ return join23(getMemoryDir(), OUTCOMES_FILE);
16332
16519
  }
16333
16520
  function listPlayOutcomes() {
16334
16521
  const path = outcomesPath();
16335
- if (!existsSync21(path)) return [];
16522
+ if (!existsSync23(path)) return [];
16336
16523
  const out = [];
16337
- for (const line of readFileSync17(path, "utf-8").split("\n")) {
16524
+ for (const line of readFileSync19(path, "utf-8").split("\n")) {
16338
16525
  const trimmed = line.trim();
16339
16526
  if (!trimmed) continue;
16340
16527
  try {
@@ -19801,18 +19988,18 @@ var init_terminal = __esm({
19801
19988
  });
19802
19989
 
19803
19990
  // src/demo/taxonomy-cache.ts
19804
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync14, existsSync as existsSync22, mkdirSync as mkdirSync12, unlinkSync as unlinkSync4 } from "fs";
19991
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync15, existsSync as existsSync24, mkdirSync as mkdirSync13, unlinkSync as unlinkSync5 } from "fs";
19805
19992
  import { homedir as homedir7 } from "os";
19806
- import { join as join22 } from "path";
19807
- function ensureDir6() {
19808
- if (!existsSync22(NTRP_DIR4)) {
19809
- mkdirSync12(NTRP_DIR4, { recursive: true });
19993
+ import { join as join24 } from "path";
19994
+ function ensureDir7() {
19995
+ if (!existsSync24(NTRP_DIR4)) {
19996
+ mkdirSync13(NTRP_DIR4, { recursive: true });
19810
19997
  }
19811
19998
  }
19812
19999
  function loadCachedTaxonomy(profile) {
19813
- if (!existsSync22(TAXONOMY_PATH)) return null;
20000
+ if (!existsSync24(TAXONOMY_PATH)) return null;
19814
20001
  try {
19815
- const parsed = JSON.parse(readFileSync18(TAXONOMY_PATH, "utf-8"));
20002
+ const parsed = JSON.parse(readFileSync20(TAXONOMY_PATH, "utf-8"));
19816
20003
  if (!parsed || typeof parsed !== "object") return null;
19817
20004
  if (parsed.profile_updated_at !== profile.updated_at) return null;
19818
20005
  return parsed;
@@ -19821,13 +20008,13 @@ function loadCachedTaxonomy(profile) {
19821
20008
  }
19822
20009
  }
19823
20010
  function saveCachedTaxonomy(taxonomy) {
19824
- ensureDir6();
19825
- writeFileSync14(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
20011
+ ensureDir7();
20012
+ writeFileSync15(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
19826
20013
  }
19827
20014
  function invalidateTaxonomy() {
19828
- if (existsSync22(TAXONOMY_PATH)) {
20015
+ if (existsSync24(TAXONOMY_PATH)) {
19829
20016
  try {
19830
- unlinkSync4(TAXONOMY_PATH);
20017
+ unlinkSync5(TAXONOMY_PATH);
19831
20018
  } catch {
19832
20019
  }
19833
20020
  }
@@ -19836,8 +20023,8 @@ var NTRP_DIR4, TAXONOMY_PATH;
19836
20023
  var init_taxonomy_cache = __esm({
19837
20024
  "src/demo/taxonomy-cache.ts"() {
19838
20025
  "use strict";
19839
- NTRP_DIR4 = join22(homedir7(), ".ntrp");
19840
- TAXONOMY_PATH = join22(NTRP_DIR4, "demo-taxonomy.json");
20026
+ NTRP_DIR4 = join24(homedir7(), ".ntrp");
20027
+ TAXONOMY_PATH = join24(NTRP_DIR4, "demo-taxonomy.json");
19841
20028
  }
19842
20029
  });
19843
20030
 
@@ -20295,7 +20482,7 @@ __export(inbox_setup_exports, {
20295
20482
  shouldOfferInboxSkillSetup: () => shouldOfferInboxSkillSetup
20296
20483
  });
20297
20484
  import chalk19 from "chalk";
20298
- import { existsSync as existsSync23 } from "fs";
20485
+ import { existsSync as existsSync25 } from "fs";
20299
20486
  function markDemoOffered() {
20300
20487
  setConfigValue("ai-inbox-nudge-seen", "true");
20301
20488
  }
@@ -20327,7 +20514,7 @@ function printSkipHint(beat) {
20327
20514
  async function reuseInboxFolderIfPresent(session, beat, folderPath) {
20328
20515
  if (getAiInboxDir()) return false;
20329
20516
  const candidates = folderPath ? [folderPath] : [.../* @__PURE__ */ new Set([defaultAiInboxDir(), legacyAiInboxDir()])];
20330
- const existing = candidates.find((p) => existsSync23(p));
20517
+ const existing = candidates.find((p) => existsSync25(p));
20331
20518
  if (!existing) return false;
20332
20519
  console.log(" " + chalk19.dim("Pickup folder still on disk: ") + existing);
20333
20520
  const reuse = await session.confirm("Reuse this pickup folder?", true);
@@ -20433,7 +20620,7 @@ __export(ingest_exports, {
20433
20620
  handler: () => handler2
20434
20621
  });
20435
20622
  import chalk20 from "chalk";
20436
- import { readFileSync as readFileSync19, existsSync as existsSync24 } from "fs";
20623
+ import { readFileSync as readFileSync21, existsSync as existsSync26 } from "fs";
20437
20624
  import { basename as basename6 } from "path";
20438
20625
  async function handler2(args, ctx) {
20439
20626
  const { positional, flags } = parseArgs2(args, [
@@ -20457,7 +20644,7 @@ async function handler2(args, ctx) {
20457
20644
  console.error(chalk20.dim(" /ingest --demo [--scenario <name>]"));
20458
20645
  process.exit(1);
20459
20646
  }
20460
- if (!existsSync24(file)) {
20647
+ if (!existsSync26(file)) {
20461
20648
  console.error(chalk20.red(` File not found: ${file}`));
20462
20649
  process.exit(1);
20463
20650
  }
@@ -20475,7 +20662,7 @@ async function handler2(args, ctx) {
20475
20662
  try {
20476
20663
  await initSchema();
20477
20664
  spinner.text = "Parsing CSV\u2026";
20478
- const content = readFileSync19(file, "utf-8");
20665
+ const content = readFileSync21(file, "utf-8");
20479
20666
  const { rows, headers } = parseCSV(content);
20480
20667
  if (rows.length === 0) {
20481
20668
  spinner.fail("CSV is empty");
@@ -23388,7 +23575,9 @@ function buildSituationalAwarenessBlock(ctx, opts = {}) {
23388
23575
  );
23389
23576
  } else if (phase === "orient") {
23390
23577
  lines.push("Already done: nothing locked yet");
23391
- lines.push("Available now: help the user name a focus; CLI will propose scope from their words");
23578
+ lines.push(
23579
+ "Available now: help the user name a focus; CLI will propose scope from their words. If \u23CE /update is armed, the CLI owns the install confirm \u2014 do not re-ask about updating"
23580
+ );
23392
23581
  } else if (phase === "think") {
23393
23582
  lines.push("Already done: analysis complete; think channel open");
23394
23583
  lines.push("Available now: socratic exploration; draft_strategy / draft_handoff when ready to graduate");
@@ -25159,7 +25348,7 @@ __export(onboard_tiers_exports, {
25159
25348
  resetOnboardTierProgress: () => resetOnboardTierProgress,
25160
25349
  resolveNextOnboardTier: () => resolveNextOnboardTier
25161
25350
  });
25162
- import { existsSync as existsSync25, statSync as statSync4 } from "fs";
25351
+ import { existsSync as existsSync27, statSync as statSync4 } from "fs";
25163
25352
  function flagSet(tier) {
25164
25353
  return Boolean(getConfigValue(TIER_CONFIG_KEYS[tier]));
25165
25354
  }
@@ -25186,7 +25375,7 @@ function hasProductionDataset(ctx) {
25186
25375
  if (source.startsWith("/") || /^[A-Za-z]:[\\/]/.test(source) || source.startsWith("~")) {
25187
25376
  return true;
25188
25377
  }
25189
- if (!source.includes(":") && existsSync25(source)) return true;
25378
+ if (!source.includes(":") && existsSync27(source)) return true;
25190
25379
  if (source.startsWith("csv:") || source.startsWith("file:") || source.startsWith("folder:")) {
25191
25380
  return true;
25192
25381
  }
@@ -25245,7 +25434,7 @@ function markDemoDataSeen() {
25245
25434
  }
25246
25435
  function pathLooksPresent(raw) {
25247
25436
  try {
25248
- return existsSync25(raw) && (statSync4(raw).isFile() || statSync4(raw).isDirectory());
25437
+ return existsSync27(raw) && (statSync4(raw).isFile() || statSync4(raw).isDirectory());
25249
25438
  } catch {
25250
25439
  return false;
25251
25440
  }
@@ -25650,7 +25839,7 @@ __export(onboard_exports, {
25650
25839
  profileExists: () => profileExists
25651
25840
  });
25652
25841
  import chalk25 from "chalk";
25653
- import { existsSync as existsSync26 } from "fs";
25842
+ import { existsSync as existsSync28 } from "fs";
25654
25843
  import { basename as basename7 } from "path";
25655
25844
  async function handler5(args, ctx) {
25656
25845
  const { flags } = parseArgs2(args, ["force", "skip-brand"]);
@@ -25925,7 +26114,7 @@ async function runProductionTier(session, ctx) {
25925
26114
  return "Production data skipped";
25926
26115
  }
25927
26116
  const resolved = resolveUserPath(trimmed);
25928
- if (!existsSync26(resolved)) {
26117
+ if (!existsSync28(resolved)) {
25929
26118
  console.log(" " + chalk25.red(`Path not found: ${resolved}`));
25930
26119
  console.log(" " + chalk25.dim("Try again with /onboard, or drop the path into the REPL."));
25931
26120
  return "Production path not found";
@@ -26332,7 +26521,7 @@ __export(new_exports, {
26332
26521
  handler: () => handler6
26333
26522
  });
26334
26523
  import chalk26 from "chalk";
26335
- import { existsSync as existsSync27 } from "fs";
26524
+ import { existsSync as existsSync29 } from "fs";
26336
26525
  import { basename as basename8 } from "path";
26337
26526
  async function handler6(args, ctx) {
26338
26527
  const { positional, flags } = parseArgs2(args, ["demo", "empty", "list-scenarios", "regen-taxonomy"]);
@@ -26354,7 +26543,7 @@ async function handler6(args, ctx) {
26354
26543
  console.error(chalk26.red(" Usage: /new <file.csv> | --demo [--scenario <name>] | --empty [--lens health|metrics]"));
26355
26544
  return;
26356
26545
  }
26357
- if (source.kind === "file" && !existsSync27(source.path)) {
26546
+ if (source.kind === "file" && !existsSync29(source.path)) {
26358
26547
  console.error(chalk26.red(` File not found: ${source.path}`));
26359
26548
  return;
26360
26549
  }
@@ -26581,7 +26770,7 @@ __export(end_exports, {
26581
26770
  handler: () => handler7
26582
26771
  });
26583
26772
  import chalk27 from "chalk";
26584
- import { existsSync as existsSync28 } from "fs";
26773
+ import { existsSync as existsSync30 } from "fs";
26585
26774
  async function handler7(args, ctx) {
26586
26775
  if (args.length > 0) {
26587
26776
  console.error(chalk27.red(" Usage: /end"));
@@ -26618,10 +26807,10 @@ async function handler7(args, ctx) {
26618
26807
  if (summary) {
26619
26808
  console.log(" " + chalk27.dim(summary));
26620
26809
  }
26621
- if (existsSync28(transcriptPathForSession(endedId))) {
26810
+ if (existsSync30(transcriptPathForSession(endedId))) {
26622
26811
  console.log(" " + chalk27.dim("Transcript: ") + chalk27.dim(transcriptPathForSession(endedId)));
26623
26812
  }
26624
- if (existsSync28(contextDocPathForSession(endedId))) {
26813
+ if (existsSync30(contextDocPathForSession(endedId))) {
26625
26814
  console.log(" " + chalk27.dim("Context brief: ") + chalk27.dim(contextDocPathForSession(endedId)));
26626
26815
  }
26627
26816
  console.log();
@@ -26642,8 +26831,8 @@ __export(session_exports, {
26642
26831
  handler: () => handler8
26643
26832
  });
26644
26833
  import chalk28 from "chalk";
26645
- import { join as join23 } from "path";
26646
- import { existsSync as existsSync29 } from "fs";
26834
+ import { join as join25 } from "path";
26835
+ import { existsSync as existsSync31 } from "fs";
26647
26836
  async function handler8(args, ctx) {
26648
26837
  const sub = args[0];
26649
26838
  if (!sub) return listSessionsView(ctx);
@@ -26746,7 +26935,7 @@ async function pickUp(idArg, ctx) {
26746
26935
  }
26747
26936
  resetContextForSwitch(ctx, {
26748
26937
  sessionId: target.id,
26749
- sessionFile: join23(getSessionsDir(), `${target.id}.json`),
26938
+ sessionFile: join25(getSessionsDir(), `${target.id}.json`),
26750
26939
  sessionName: session.name,
26751
26940
  messages: [...session.messages],
26752
26941
  conversation: session.thread ? [...session.thread] : [],
@@ -26789,7 +26978,7 @@ async function pickUp(idArg, ctx) {
26789
26978
  );
26790
26979
  }
26791
26980
  const contextPath = contextDocPathForSession(target.id);
26792
- if (existsSync29(contextPath)) {
26981
+ if (existsSync31(contextPath)) {
26793
26982
  console.log(" " + chalk28.dim("Context brief: ") + chalk28.dim(contextPath));
26794
26983
  }
26795
26984
  console.log();
@@ -27080,8 +27269,8 @@ __export(report_exports, {
27080
27269
  handler: () => handler9
27081
27270
  });
27082
27271
  import chalk29 from "chalk";
27083
- import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync15 } from "fs";
27084
- import { dirname as dirname4 } from "path";
27272
+ import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync16 } from "fs";
27273
+ import { dirname as dirname5 } from "path";
27085
27274
  async function handler9(args, ctx) {
27086
27275
  const { flags } = parseArgs2(args);
27087
27276
  const format = getString(flags, "format", "f") ?? getConfigValue("default-format") ?? "terminal";
@@ -27174,10 +27363,10 @@ async function handler9(args, ctx) {
27174
27363
  if (output) {
27175
27364
  const resolvedOutput = resolveUserPath(output);
27176
27365
  if (!isInsideNtrp(resolvedOutput)) {
27177
- console.warn(chalk29.yellow(` Warning: writing report outside NTRP home (${dirname4(resolvedOutput)})`));
27366
+ console.warn(chalk29.yellow(` Warning: writing report outside NTRP home (${dirname5(resolvedOutput)})`));
27178
27367
  }
27179
- mkdirSync13(dirname4(resolvedOutput), { recursive: true });
27180
- writeFileSync15(resolvedOutput, rendered);
27368
+ mkdirSync14(dirname5(resolvedOutput), { recursive: true });
27369
+ writeFileSync16(resolvedOutput, rendered);
27181
27370
  console.log(chalk29.green(` Report written to ${resolvedOutput}`));
27182
27371
  } else if (rendered) {
27183
27372
  console.log(rendered);
@@ -27208,8 +27397,8 @@ var init_report2 = __esm({
27208
27397
  });
27209
27398
 
27210
27399
  // src/output/notes-export.ts
27211
- import { mkdirSync as mkdirSync14 } from "fs";
27212
- import { join as join24 } from "path";
27400
+ import { mkdirSync as mkdirSync15 } from "fs";
27401
+ import { join as join26 } from "path";
27213
27402
  function exportToNotes(data) {
27214
27403
  const { computeResult, divergences, findings, exchanges } = data;
27215
27404
  const { aggregate, segments } = computeResult;
@@ -27218,8 +27407,8 @@ function exportToNotes(data) {
27218
27407
  const timeStr = formatTime(now2);
27219
27408
  const filename = `${dateStr}-${timeStr}-gtm-health.md`;
27220
27409
  const dir = data.dir ?? getArchiveKindDir("notes");
27221
- mkdirSync14(dir, { recursive: true });
27222
- const filepath = join24(dir, filename);
27410
+ mkdirSync15(dir, { recursive: true });
27411
+ const filepath = join26(dir, filename);
27223
27412
  const severityTags = /* @__PURE__ */ new Set();
27224
27413
  for (const f of findings) severityTags.add(f.severity);
27225
27414
  const tags = ["ntrp", "gtm-health", ...severityTags];
@@ -27464,8 +27653,8 @@ __export(backmeup_exports, {
27464
27653
  });
27465
27654
  import chalk31 from "chalk";
27466
27655
  import Papa5 from "papaparse";
27467
- import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync16 } from "fs";
27468
- import { join as join25 } from "path";
27656
+ import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync17 } from "fs";
27657
+ import { join as join27 } from "path";
27469
27658
  function sanitizeCsvValue(value) {
27470
27659
  if (typeof value !== "string") return value;
27471
27660
  return CSV_FORMULA_RE.test(value) ? `'${value}` : value;
@@ -27501,8 +27690,8 @@ async function handler11(args, _ctx) {
27501
27690
  if (!isInsideNtrp(baseDir)) {
27502
27691
  console.warn(chalk31.yellow(` Warning: writing backup outside NTRP home (${baseDir})`));
27503
27692
  }
27504
- const folder = join25(baseDir, folderName);
27505
- mkdirSync15(folder, { recursive: true });
27693
+ const folder = join27(baseDir, folderName);
27694
+ mkdirSync16(folder, { recursive: true });
27506
27695
  const generatedAt = now2.toISOString();
27507
27696
  let fileCount = 0;
27508
27697
  const coverRows = health.vital_signs.map((vs) => ({
@@ -27516,7 +27705,7 @@ async function handler11(args, _ctx) {
27516
27705
  "Total At Risk": health.total_value_at_risk != null ? formatCurrency(health.total_value_at_risk) : "N/A",
27517
27706
  "Generated At": generatedAt
27518
27707
  }));
27519
- writeFileSync16(join25(folder, "cover-sheet.csv"), Papa5.unparse(sanitizeCsvRows(coverRows)), "utf-8");
27708
+ writeFileSync17(join27(folder, "cover-sheet.csv"), Papa5.unparse(sanitizeCsvRows(coverRows)), "utf-8");
27520
27709
  fileCount++;
27521
27710
  if (findings.length > 0) {
27522
27711
  const findingsRows = findings.map((f) => ({
@@ -27526,7 +27715,7 @@ async function handler11(args, _ctx) {
27526
27715
  Finding: f.finding,
27527
27716
  "Recommended Plays": f.recommended_plays ? f.recommended_plays.map((p) => p.play_name).join("; ") : ""
27528
27717
  }));
27529
- writeFileSync16(join25(folder, "findings.csv"), Papa5.unparse(sanitizeCsvRows(findingsRows)), "utf-8");
27718
+ writeFileSync17(join27(folder, "findings.csv"), Papa5.unparse(sanitizeCsvRows(findingsRows)), "utf-8");
27530
27719
  fileCount++;
27531
27720
  }
27532
27721
  for (const vs of health.vital_signs) {
@@ -27536,7 +27725,7 @@ async function handler11(args, _ctx) {
27536
27725
  ...detail
27537
27726
  }));
27538
27727
  const filename = EVIDENCE_FILENAMES[vs.vital_sign] ?? `${vs.vital_sign}.csv`;
27539
- writeFileSync16(join25(folder, filename), Papa5.unparse(sanitizeCsvRows(rows)), "utf-8");
27728
+ writeFileSync17(join27(folder, filename), Papa5.unparse(sanitizeCsvRows(rows)), "utf-8");
27540
27729
  fileCount++;
27541
27730
  }
27542
27731
  const event = recordExportWrite({
@@ -27740,8 +27929,8 @@ var init_bundle = __esm({
27740
27929
  });
27741
27930
 
27742
27931
  // src/repositories/markdown.ts
27743
- import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync17 } from "fs";
27744
- import { basename as basename9, dirname as dirname5, join as join26, resolve as resolve8 } from "path";
27932
+ import { mkdirSync as mkdirSync17, writeFileSync as writeFileSync18 } from "fs";
27933
+ import { basename as basename9, dirname as dirname6, join as join28, resolve as resolve8 } from "path";
27745
27934
  import { stringify as stringifyYaml2 } from "yaml";
27746
27935
  function renderMarkdownFiles(pkg) {
27747
27936
  const bundleJson = JSON.stringify(pkg, null, 2) + "\n";
@@ -27961,12 +28150,12 @@ var init_markdown3 = __esm({
27961
28150
  write(pkg) {
27962
28151
  const root = getRootPath(pkg.target);
27963
28152
  const files = renderMarkdownFiles(pkg);
27964
- mkdirSync16(root, { recursive: true });
28153
+ mkdirSync17(root, { recursive: true });
27965
28154
  const written = [];
27966
28155
  for (const file of files) {
27967
- const absolutePath = join26(root, file.relativePath);
27968
- mkdirSync16(dirname5(absolutePath), { recursive: true });
27969
- writeFileSync17(absolutePath, file.contents, "utf-8");
28156
+ const absolutePath = join28(root, file.relativePath);
28157
+ mkdirSync17(dirname6(absolutePath), { recursive: true });
28158
+ writeFileSync18(absolutePath, file.contents, "utf-8");
27970
28159
  written.push(absolutePath);
27971
28160
  }
27972
28161
  return {
@@ -28270,7 +28459,7 @@ __export(handoff_exports, {
28270
28459
  handler: () => handler13
28271
28460
  });
28272
28461
  import chalk33 from "chalk";
28273
- import { join as join27 } from "path";
28462
+ import { join as join29 } from "path";
28274
28463
  async function handler13(args, ctx) {
28275
28464
  const sub = args[0];
28276
28465
  if (!sub) {
@@ -28370,7 +28559,7 @@ async function runPublish2(args, ctx) {
28370
28559
  );
28371
28560
  if (sub === "propose" && !hasDir) {
28372
28561
  const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
28373
- const dir = join27(getArchiveKindDir("publish"), `ntrp-repository-${stamp}`);
28562
+ const dir = join29(getArchiveKindDir("publish"), `ntrp-repository-${stamp}`);
28374
28563
  publishArgs.push("--dir", dir);
28375
28564
  }
28376
28565
  const result = await publish(publishArgs, ctx);
@@ -30756,8 +30945,8 @@ __export(strategy_review_exports, {
30756
30945
  persistStrategyReview: () => persistStrategyReview,
30757
30946
  resolveReviewableStrategy: () => resolveReviewableStrategy
30758
30947
  });
30759
- import { writeFileSync as writeFileSync18 } from "fs";
30760
- import { join as join28 } from "path";
30948
+ import { writeFileSync as writeFileSync19 } from "fs";
30949
+ import { join as join30 } from "path";
30761
30950
  function groupByBatch(vitals, metrics) {
30762
30951
  const map = /* @__PURE__ */ new Map();
30763
30952
  const order = [];
@@ -31047,7 +31236,7 @@ async function persistStrategyReview(report, milestoneVerdicts, notes) {
31047
31236
  }
31048
31237
  function logWin(strategy, hits, milestoneWins) {
31049
31238
  const date = isoToday();
31050
- const path = join28(getWinsDir(), `${strategy.slug}-${date}.md`);
31239
+ const path = join30(getWinsDir(), `${strategy.slug}-${date}.md`);
31051
31240
  const lines = [
31052
31241
  `# Win \u2014 ${strategy.title}`,
31053
31242
  "",
@@ -31064,7 +31253,7 @@ function logWin(strategy, hits, milestoneWins) {
31064
31253
  for (const label of milestoneWins) lines.push(`- ${label}`);
31065
31254
  }
31066
31255
  lines.push("", "_Logged by /strategy review._", "");
31067
- writeFileSync18(path, lines.join("\n"));
31256
+ writeFileSync19(path, lines.join("\n"));
31068
31257
  return path;
31069
31258
  }
31070
31259
  var VITAL_TOKENS, COMPONENT_TOKENS;
@@ -32461,15 +32650,15 @@ var init_checkout = __esm({
32461
32650
  });
32462
32651
 
32463
32652
  // src/services/setup.ts
32464
- import { existsSync as existsSync30, mkdirSync as mkdirSync17, readFileSync as readFileSync20, writeFileSync as writeFileSync19 } from "fs";
32465
- import { join as join29 } from "path";
32653
+ import { existsSync as existsSync32, mkdirSync as mkdirSync18, readFileSync as readFileSync22, writeFileSync as writeFileSync20 } from "fs";
32654
+ import { join as join31 } from "path";
32466
32655
  function setupCheck() {
32467
32656
  const home = ntrpHome();
32468
32657
  let writable = false;
32469
32658
  try {
32470
- mkdirSync17(home, { recursive: true });
32471
- const probe = join29(home, ".write-check");
32472
- writeFileSync19(probe, "ok\n");
32659
+ mkdirSync18(home, { recursive: true });
32660
+ const probe = join31(home, ".write-check");
32661
+ writeFileSync20(probe, "ok\n");
32473
32662
  writable = true;
32474
32663
  } catch {
32475
32664
  writable = false;
@@ -32508,7 +32697,7 @@ function setupCheck() {
32508
32697
  };
32509
32698
  }
32510
32699
  function readProfileInput(pathOrDash) {
32511
- const raw = pathOrDash === "-" ? readFileSync20(0, "utf-8") : readFileSync20(pathOrDash, "utf-8");
32700
+ const raw = pathOrDash === "-" ? readFileSync22(0, "utf-8") : readFileSync22(pathOrDash, "utf-8");
32512
32701
  return JSON.parse(raw);
32513
32702
  }
32514
32703
  function writeAgentProfile(input) {
@@ -33298,7 +33487,7 @@ var init_orchestrator = __esm({
33298
33487
  });
33299
33488
 
33300
33489
  // src/services/smoke-protocol.ts
33301
- import { join as join30 } from "path";
33490
+ import { join as join32 } from "path";
33302
33491
  function isSmokeProtocolTrigger(input) {
33303
33492
  return normalize2(input).includes(SMOKE_TRIGGER_PHRASE);
33304
33493
  }
@@ -33334,7 +33523,7 @@ async function runSmokeProtocol(_input, ctx) {
33334
33523
  });
33335
33524
  const proposalResult = await proposeRepositoryExport({
33336
33525
  target: "markdown",
33337
- directory: join30(getExportsDir(), "repository-smoke"),
33526
+ directory: join32(getExportsDir(), "repository-smoke"),
33338
33527
  source: "smoke_protocol",
33339
33528
  modelOrFixture: "smoke-protocol-v1"
33340
33529
  });
@@ -34266,7 +34455,7 @@ var init_recall = __esm({
34266
34455
 
34267
34456
  // src/memory/feedback.ts
34268
34457
  import { appendFileSync as appendFileSync7 } from "fs";
34269
- import { join as join31 } from "path";
34458
+ import { join as join33 } from "path";
34270
34459
  import { randomUUID as randomUUID8 } from "crypto";
34271
34460
  function summarize(text) {
34272
34461
  return text.replace(/[#*`>_]/g, "").replace(/\s+/g, " ").trim().slice(0, 200);
@@ -34298,7 +34487,7 @@ function recordFeedback(input) {
34298
34487
  created_at: (/* @__PURE__ */ new Date()).toISOString()
34299
34488
  };
34300
34489
  try {
34301
- appendFileSync7(join31(getMemoryDir(), "feedback.jsonl"), JSON.stringify(entry) + "\n");
34490
+ appendFileSync7(join33(getMemoryDir(), "feedback.jsonl"), JSON.stringify(entry) + "\n");
34302
34491
  } catch {
34303
34492
  }
34304
34493
  if (input.rating === "positive") {
@@ -34468,7 +34657,7 @@ __export(sessions_exports, {
34468
34657
  handler: () => handler35
34469
34658
  });
34470
34659
  import chalk62 from "chalk";
34471
- import { existsSync as existsSync31 } from "fs";
34660
+ import { existsSync as existsSync33 } from "fs";
34472
34661
  async function handler35(args, _ctx) {
34473
34662
  const sub = args[0] ?? "list";
34474
34663
  if (sub === "list" || !args[0]) {
@@ -34575,12 +34764,12 @@ function showSession(idArg) {
34575
34764
  console.log();
34576
34765
  const transcriptPath = transcriptPathForSession(session.id);
34577
34766
  const contextPath = contextDocPathForSession(session.id);
34578
- if (existsSync31(transcriptPath) || existsSync31(contextPath)) {
34767
+ if (existsSync33(transcriptPath) || existsSync33(contextPath)) {
34579
34768
  console.log(" " + chalk62.dim("\u2500".repeat(40)));
34580
- if (existsSync31(contextPath)) {
34769
+ if (existsSync33(contextPath)) {
34581
34770
  console.log(" " + chalk62.dim("Context brief: ") + chalk62.dim(contextPath));
34582
34771
  }
34583
- if (existsSync31(transcriptPath)) {
34772
+ if (existsSync33(transcriptPath)) {
34584
34773
  console.log(" " + chalk62.dim("Full transcript: ") + chalk62.dim(transcriptPath));
34585
34774
  }
34586
34775
  console.log();
@@ -34733,7 +34922,7 @@ var switch_exports = {};
34733
34922
  __export(switch_exports, {
34734
34923
  handler: () => handler38
34735
34924
  });
34736
- import { join as join32 } from "path";
34925
+ import { join as join34 } from "path";
34737
34926
  import chalk65 from "chalk";
34738
34927
  async function handler38(args, ctx) {
34739
34928
  if (args.length === 0) {
@@ -34763,7 +34952,7 @@ async function handler38(args, ctx) {
34763
34952
  }
34764
34953
  const context = buildSwitchContext(session);
34765
34954
  const newId = makeSessionId();
34766
- const newFile = join32(getSessionsDir(), `${newId}.json`);
34955
+ const newFile = join34(getSessionsDir(), `${newId}.json`);
34767
34956
  resetContextForSwitch(ctx, {
34768
34957
  sessionId: newId,
34769
34958
  sessionFile: newFile,
@@ -34789,7 +34978,7 @@ async function handler38(args, ctx) {
34789
34978
  return `Switched to "${targetName}"`;
34790
34979
  } else {
34791
34980
  const newId = makeSessionId();
34792
- const newFile = join32(getSessionsDir(), `${newId}.json`);
34981
+ const newFile = join34(getSessionsDir(), `${newId}.json`);
34793
34982
  resetContextForSwitch(ctx, {
34794
34983
  sessionId: newId,
34795
34984
  sessionFile: newFile,
@@ -35916,186 +36105,6 @@ var init_model = __esm({
35916
36105
  }
35917
36106
  });
35918
36107
 
35919
- // src/config/update-check.ts
35920
- import { existsSync as existsSync32, mkdirSync as mkdirSync18, readFileSync as readFileSync21, unlinkSync as unlinkSync5, writeFileSync as writeFileSync20 } from "fs";
35921
- import { join as join33 } from "path";
35922
- function cachePath2() {
35923
- return join33(ntrpHome(), "update-check.json");
35924
- }
35925
- function ensureDir7() {
35926
- const dir = ntrpHome();
35927
- if (!existsSync32(dir)) {
35928
- mkdirSync18(dir, { recursive: true });
35929
- }
35930
- }
35931
- function loadUpdateCheckCache() {
35932
- const path = cachePath2();
35933
- if (!existsSync32(path)) return null;
35934
- try {
35935
- const parsed = JSON.parse(readFileSync21(path, "utf-8"));
35936
- if (!parsed || typeof parsed !== "object" || typeof parsed.lastCheck !== "number" || typeof parsed.latestVersion !== "string") {
35937
- return null;
35938
- }
35939
- return parsed;
35940
- } catch {
35941
- return null;
35942
- }
35943
- }
35944
- function saveUpdateCheckCache(cache2) {
35945
- ensureDir7();
35946
- writeFileSync20(cachePath2(), JSON.stringify(cache2, null, 2) + "\n");
35947
- }
35948
- function isCacheFresh(cache2, ttlMs = CACHE_TTL_MS2) {
35949
- if (!cache2) return false;
35950
- return Date.now() - cache2.lastCheck < ttlMs;
35951
- }
35952
- function invalidateUpdateCheckCache() {
35953
- const path = cachePath2();
35954
- if (existsSync32(path)) {
35955
- unlinkSync5(path);
35956
- }
35957
- }
35958
- var CACHE_TTL_MS2;
35959
- var init_update_check = __esm({
35960
- "src/config/update-check.ts"() {
35961
- "use strict";
35962
- init_store();
35963
- CACHE_TTL_MS2 = 864e5;
35964
- }
35965
- });
35966
-
35967
- // src/version.ts
35968
- import { existsSync as existsSync33, readFileSync as readFileSync22 } from "fs";
35969
- import { dirname as dirname6, join as join34 } from "path";
35970
- import { fileURLToPath } from "url";
35971
- function readVersionFromPackageJson(packageJsonPath) {
35972
- if (!existsSync33(packageJsonPath)) return null;
35973
- try {
35974
- const pkg = JSON.parse(readFileSync22(packageJsonPath, "utf-8"));
35975
- if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
35976
- } catch {
35977
- }
35978
- return null;
35979
- }
35980
- function readVersionNearEntry(entryPath) {
35981
- const start = dirname6(entryPath);
35982
- for (const rel of [join34(start, "..", "package.json"), join34(start, "../..", "package.json")]) {
35983
- const version = readVersionFromPackageJson(rel);
35984
- if (version) return version;
35985
- }
35986
- return null;
35987
- }
35988
- function readInstalledVersionFromDisk() {
35989
- return readVersionNearEntry(fileURLToPath(import.meta.url));
35990
- }
35991
- function getInstalledVersion() {
35992
- if (cachedVersion) return cachedVersion;
35993
- cachedVersion = readInstalledVersionFromDisk() ?? "0.0.0";
35994
- return cachedVersion;
35995
- }
35996
- var cachedVersion;
35997
- var init_version = __esm({
35998
- "src/version.ts"() {
35999
- "use strict";
36000
- }
36001
- });
36002
-
36003
- // src/update/registry.ts
36004
- var registry_exports = {};
36005
- __export(registry_exports, {
36006
- NPM_PACKAGE: () => NPM_PACKAGE,
36007
- applyUpdateCheckResult: () => applyUpdateCheckResult,
36008
- checkForUpdate: () => checkForUpdate,
36009
- fetchLatestVersion: () => fetchLatestVersion,
36010
- formatUpdateNudge: () => formatUpdateNudge,
36011
- hydrateUpdateAvailableFromCache: () => hydrateUpdateAvailableFromCache,
36012
- isNewerVersion: () => isNewerVersion,
36013
- startBackgroundUpdateCheck: () => startBackgroundUpdateCheck
36014
- });
36015
- function registryUrl() {
36016
- return process.env.NTRP_REGISTRY_URL ?? "https://registry.npmjs.org/@sonnechasser/ntrp/latest";
36017
- }
36018
- function parseVersionParts(version) {
36019
- const cleaned = version.trim().replace(/^v/i, "");
36020
- const core = cleaned.split("-")[0] ?? cleaned;
36021
- const parts = core.split(".").map((p) => parseInt(p, 10));
36022
- return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
36023
- }
36024
- function isNewerVersion(latest, current) {
36025
- const [lMaj, lMin, lPatch] = parseVersionParts(latest);
36026
- const [cMaj, cMin, cPatch] = parseVersionParts(current);
36027
- if (lMaj !== cMaj) return lMaj > cMaj;
36028
- if (lMin !== cMin) return lMin > cMin;
36029
- return lPatch > cPatch;
36030
- }
36031
- function formatUpdateNudge(current, latest) {
36032
- return `\u26A1 NTRP v${latest} available (running v${current}) \u2014 type /update`;
36033
- }
36034
- function hydrateUpdateAvailableFromCache(current) {
36035
- const cached2 = loadUpdateCheckCache();
36036
- if (!cached2?.latestVersion) return void 0;
36037
- if (!isNewerVersion(cached2.latestVersion, current)) return void 0;
36038
- return { current, latest: cached2.latestVersion };
36039
- }
36040
- function applyUpdateCheckResult(ctx, result) {
36041
- if (result?.updateAvailable) {
36042
- ctx.updateAvailable = { current: result.current, latest: result.latest };
36043
- return;
36044
- }
36045
- if (result && !result.updateAvailable) {
36046
- ctx.updateAvailable = void 0;
36047
- }
36048
- }
36049
- function startBackgroundUpdateCheck(ctx) {
36050
- const pending = checkForUpdate({ force: true, timeoutMs: 5e3 });
36051
- ctx.pendingUpdateCheck = pending;
36052
- void pending.then((result) => applyUpdateCheckResult(ctx, result)).catch(() => void 0);
36053
- }
36054
- async function fetchLatestVersion(timeoutMs = 5e3) {
36055
- try {
36056
- const res = await fetch(registryUrl(), { signal: AbortSignal.timeout(timeoutMs) });
36057
- if (!res.ok) return null;
36058
- const data = await res.json();
36059
- return typeof data.version === "string" && data.version.length > 0 ? data.version : null;
36060
- } catch {
36061
- return null;
36062
- }
36063
- }
36064
- function buildResult(current, latest) {
36065
- return {
36066
- current,
36067
- latest,
36068
- updateAvailable: isNewerVersion(latest, current)
36069
- };
36070
- }
36071
- async function checkForUpdate(options) {
36072
- const current = getInstalledVersion();
36073
- const timeoutMs = options?.timeoutMs ?? 5e3;
36074
- const cached2 = loadUpdateCheckCache();
36075
- if (!options?.force && isCacheFresh(cached2)) {
36076
- return buildResult(current, cached2.latestVersion);
36077
- }
36078
- const latest = await fetchLatestVersion(timeoutMs);
36079
- if (!latest) {
36080
- if (cached2?.latestVersion) {
36081
- return buildResult(current, cached2.latestVersion);
36082
- }
36083
- return null;
36084
- }
36085
- const nextCache = { lastCheck: Date.now(), latestVersion: latest };
36086
- saveUpdateCheckCache(nextCache);
36087
- return buildResult(current, latest);
36088
- }
36089
- var NPM_PACKAGE;
36090
- var init_registry = __esm({
36091
- "src/update/registry.ts"() {
36092
- "use strict";
36093
- init_update_check();
36094
- init_version();
36095
- NPM_PACKAGE = "@sonnechasser/ntrp";
36096
- }
36097
- });
36098
-
36099
36108
  // src/update/relaunch.ts
36100
36109
  var relaunch_exports = {};
36101
36110
  __export(relaunch_exports, {
@@ -36242,6 +36251,22 @@ async function handler44(_args, ctx) {
36242
36251
  console.log();
36243
36252
  return;
36244
36253
  }
36254
+ if (!ctx.oneShot) {
36255
+ const prompts = createPromptSession(ctx.rl, ctx);
36256
+ let ok2 = false;
36257
+ try {
36258
+ console.log();
36259
+ ok2 = await prompts.confirm(`Install NTRP v${current} \u2192 v${latest}?`, true);
36260
+ } finally {
36261
+ prompts.close();
36262
+ }
36263
+ if (!ok2) {
36264
+ console.log();
36265
+ console.log(chalk73.dim(" Update cancelled."));
36266
+ console.log();
36267
+ return "Update cancelled";
36268
+ }
36269
+ }
36245
36270
  console.log();
36246
36271
  console.log(` Updating NTRP v${current} \u2192 v${latest}...`);
36247
36272
  const { ok, output } = runGlobalInstall();
@@ -36288,6 +36313,7 @@ var PERMISSIONS_URL;
36288
36313
  var init_update = __esm({
36289
36314
  "src/commands/update.ts"() {
36290
36315
  "use strict";
36316
+ init_prompts();
36291
36317
  init_update_check();
36292
36318
  init_registry();
36293
36319
  init_relaunch();
@@ -37247,7 +37273,9 @@ section: Settings
37247
37273
  handler: ../commands/update.ts
37248
37274
  ---
37249
37275
 
37250
- Install the latest global NTRP package via npm.`
37276
+ Install the latest global NTRP package via npm.
37277
+ Interactive confirms with \u23CE yes, then re-execs onto home.
37278
+ One-shot installs immediately and prints a restart hint.`
37251
37279
  },
37252
37280
  {
37253
37281
  name: "resume",
@@ -39917,7 +39945,7 @@ function ntrpStatusRow(version, update) {
39917
39945
  return {
39918
39946
  label: "ntrp",
39919
39947
  state: badge("UPDATE", "warning"),
39920
- detail: `v${update.latest} \xB7 type /update`
39948
+ detail: `v${update.latest} \xB7 \u23CE /update`
39921
39949
  };
39922
39950
  }
39923
39951
  if (disk && isNewerVersion(disk, getInstalledVersion())) {
@@ -40798,7 +40826,7 @@ function printHelp() {
40798
40826
  console.log(" " + sectionHeading("Keys"));
40799
40827
  console.log(
40800
40828
  " " + paint("accent", "\u23CE") + chalk88.dim(
40801
- " Accepts the default. At the main prompt it runs the armed action (yes, use demo data, go ahead, /connect)."
40829
+ " Accepts the default. At the main prompt it runs the armed action (yes, use demo data, go ahead, /connect, /update)."
40802
40830
  )
40803
40831
  );
40804
40832
  console.log(
@@ -10025,7 +10025,9 @@ section: Settings
10025
10025
  handler: ../commands/update.ts
10026
10026
  ---
10027
10027
 
10028
- Install the latest global NTRP package via npm.`
10028
+ Install the latest global NTRP package via npm.
10029
+ Interactive confirms with \u23CE yes, then re-execs onto home.
10030
+ One-shot installs immediately and prints a restart hint.`
10029
10031
  },
10030
10032
  {
10031
10033
  name: "resume",
@@ -14156,6 +14158,77 @@ var init_activation = __esm({
14156
14158
  }
14157
14159
  });
14158
14160
 
14161
+ // src/config/update-check.ts
14162
+ import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync19, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "fs";
14163
+ import { join as join22 } from "path";
14164
+ var init_update_check = __esm({
14165
+ "src/config/update-check.ts"() {
14166
+ "use strict";
14167
+ init_store();
14168
+ }
14169
+ });
14170
+
14171
+ // src/version.ts
14172
+ import { existsSync as existsSync23, readFileSync as readFileSync20 } from "fs";
14173
+ import { dirname as dirname4, join as join23 } from "path";
14174
+ import { fileURLToPath } from "url";
14175
+ function readVersionFromPackageJson(packageJsonPath) {
14176
+ if (!existsSync23(packageJsonPath)) return null;
14177
+ try {
14178
+ const pkg = JSON.parse(readFileSync20(packageJsonPath, "utf-8"));
14179
+ if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
14180
+ } catch {
14181
+ }
14182
+ return null;
14183
+ }
14184
+ function readVersionNearEntry(entryPath) {
14185
+ const start = dirname4(entryPath);
14186
+ for (const rel of [join23(start, "..", "package.json"), join23(start, "../..", "package.json")]) {
14187
+ const version = readVersionFromPackageJson(rel);
14188
+ if (version) return version;
14189
+ }
14190
+ return null;
14191
+ }
14192
+ function readInstalledVersionFromDisk() {
14193
+ return readVersionNearEntry(fileURLToPath(import.meta.url));
14194
+ }
14195
+ function getInstalledVersion() {
14196
+ if (cachedVersion) return cachedVersion;
14197
+ cachedVersion = readInstalledVersionFromDisk() ?? "0.0.0";
14198
+ return cachedVersion;
14199
+ }
14200
+ var cachedVersion;
14201
+ var init_version = __esm({
14202
+ "src/version.ts"() {
14203
+ "use strict";
14204
+ }
14205
+ });
14206
+
14207
+ // src/update/registry.ts
14208
+ function parseVersionParts(version) {
14209
+ const cleaned = version.trim().replace(/^v/i, "");
14210
+ const core = cleaned.split("-")[0] ?? cleaned;
14211
+ const parts = core.split(".").map((p) => parseInt(p, 10));
14212
+ return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
14213
+ }
14214
+ function isNewerVersion(latest, current) {
14215
+ const [lMaj, lMin, lPatch] = parseVersionParts(latest);
14216
+ const [cMaj, cMin, cPatch] = parseVersionParts(current);
14217
+ if (lMaj !== cMaj) return lMaj > cMaj;
14218
+ if (lMin !== cMin) return lMin > cMin;
14219
+ return lPatch > cPatch;
14220
+ }
14221
+ function hasAvailableUpdate(update) {
14222
+ return Boolean(update && isNewerVersion(update.latest, update.current));
14223
+ }
14224
+ var init_registry2 = __esm({
14225
+ "src/update/registry.ts"() {
14226
+ "use strict";
14227
+ init_update_check();
14228
+ init_version();
14229
+ }
14230
+ });
14231
+
14159
14232
  // src/conversation/recommended-action.ts
14160
14233
  function resolveRecommendedAction(ctx) {
14161
14234
  if (!hasValidLicense()) return { submit: "/activate", hint: "/activate" };
@@ -14174,6 +14247,8 @@ function resolveRecommendedAction(ctx) {
14174
14247
  return ctx.strategistState?.step === "objective_confirm" ? { submit: "yes", hint: "yes" } : null;
14175
14248
  case "think":
14176
14249
  return null;
14250
+ case "orient":
14251
+ return hasAvailableUpdate(ctx.updateAvailable) ? { submit: "/update", hint: "/update" } : null;
14177
14252
  default:
14178
14253
  return null;
14179
14254
  }
@@ -14183,6 +14258,7 @@ var init_recommended_action = __esm({
14183
14258
  "use strict";
14184
14259
  init_repl_api();
14185
14260
  init_activation();
14261
+ init_registry2();
14186
14262
  init_phase();
14187
14263
  }
14188
14264
  });
@@ -19252,7 +19328,9 @@ function buildSituationalAwarenessBlock(ctx, opts = {}) {
19252
19328
  );
19253
19329
  } else if (phase === "orient") {
19254
19330
  lines.push("Already done: nothing locked yet");
19255
- lines.push("Available now: help the user name a focus; CLI will propose scope from their words");
19331
+ lines.push(
19332
+ "Available now: help the user name a focus; CLI will propose scope from their words. If \u23CE /update is armed, the CLI owns the install confirm \u2014 do not re-ask about updating"
19333
+ );
19256
19334
  } else if (phase === "think") {
19257
19335
  lines.push("Already done: analysis complete; think channel open");
19258
19336
  lines.push("Available now: socratic exploration; draft_strategy / draft_handoff when ready to graduate");
@@ -20169,8 +20247,8 @@ var init_bundle = __esm({
20169
20247
  });
20170
20248
 
20171
20249
  // src/repositories/markdown.ts
20172
- import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync14 } from "fs";
20173
- import { basename as basename5, dirname as dirname4, join as join22, resolve as resolve8 } from "path";
20250
+ import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync15 } from "fs";
20251
+ import { basename as basename5, dirname as dirname5, join as join24, resolve as resolve8 } from "path";
20174
20252
  import { stringify as stringifyYaml2 } from "yaml";
20175
20253
  function renderMarkdownFiles(pkg) {
20176
20254
  const bundleJson = JSON.stringify(pkg, null, 2) + "\n";
@@ -20390,12 +20468,12 @@ var init_markdown2 = __esm({
20390
20468
  write(pkg) {
20391
20469
  const root = getRootPath(pkg.target);
20392
20470
  const files = renderMarkdownFiles(pkg);
20393
- mkdirSync12(root, { recursive: true });
20471
+ mkdirSync13(root, { recursive: true });
20394
20472
  const written = [];
20395
20473
  for (const file of files) {
20396
- const absolutePath = join22(root, file.relativePath);
20397
- mkdirSync12(dirname4(absolutePath), { recursive: true });
20398
- writeFileSync14(absolutePath, file.contents, "utf-8");
20474
+ const absolutePath = join24(root, file.relativePath);
20475
+ mkdirSync13(dirname5(absolutePath), { recursive: true });
20476
+ writeFileSync15(absolutePath, file.contents, "utf-8");
20399
20477
  written.push(absolutePath);
20400
20478
  }
20401
20479
  return {
@@ -20498,7 +20576,7 @@ var init_publish = __esm({
20498
20576
  });
20499
20577
 
20500
20578
  // src/services/smoke-protocol.ts
20501
- import { join as join23 } from "path";
20579
+ import { join as join25 } from "path";
20502
20580
  function isSmokeProtocolTrigger(input) {
20503
20581
  return normalize(input).includes(SMOKE_TRIGGER_PHRASE);
20504
20582
  }
@@ -20534,7 +20612,7 @@ async function runSmokeProtocol(_input, ctx) {
20534
20612
  });
20535
20613
  const proposalResult = await proposeRepositoryExport({
20536
20614
  target: "markdown",
20537
- directory: join23(getExportsDir(), "repository-smoke"),
20615
+ directory: join25(getExportsDir(), "repository-smoke"),
20538
20616
  source: "smoke_protocol",
20539
20617
  modelOrFixture: "smoke-protocol-v1"
20540
20618
  });
@@ -21274,7 +21352,7 @@ var init_scenario_fit = __esm({
21274
21352
  });
21275
21353
 
21276
21354
  // src/conversation/onboard-tiers.ts
21277
- import { existsSync as existsSync22, statSync as statSync4 } from "fs";
21355
+ import { existsSync as existsSync24, statSync as statSync4 } from "fs";
21278
21356
  function flagSet(tier) {
21279
21357
  return Boolean(getConfigValue(TIER_CONFIG_KEYS[tier]));
21280
21358
  }
@@ -23897,18 +23975,18 @@ var init_generator = __esm({
23897
23975
  });
23898
23976
 
23899
23977
  // src/demo/taxonomy-cache.ts
23900
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, existsSync as existsSync23, mkdirSync as mkdirSync13, unlinkSync as unlinkSync3 } from "fs";
23978
+ import { readFileSync as readFileSync21, writeFileSync as writeFileSync16, existsSync as existsSync25, mkdirSync as mkdirSync14, unlinkSync as unlinkSync4 } from "fs";
23901
23979
  import { homedir as homedir7 } from "os";
23902
- import { join as join24 } from "path";
23980
+ import { join as join26 } from "path";
23903
23981
  function ensureDir6() {
23904
- if (!existsSync23(NTRP_DIR4)) {
23905
- mkdirSync13(NTRP_DIR4, { recursive: true });
23982
+ if (!existsSync25(NTRP_DIR4)) {
23983
+ mkdirSync14(NTRP_DIR4, { recursive: true });
23906
23984
  }
23907
23985
  }
23908
23986
  function loadCachedTaxonomy(profile) {
23909
- if (!existsSync23(TAXONOMY_PATH)) return null;
23987
+ if (!existsSync25(TAXONOMY_PATH)) return null;
23910
23988
  try {
23911
- const parsed = JSON.parse(readFileSync19(TAXONOMY_PATH, "utf-8"));
23989
+ const parsed = JSON.parse(readFileSync21(TAXONOMY_PATH, "utf-8"));
23912
23990
  if (!parsed || typeof parsed !== "object") return null;
23913
23991
  if (parsed.profile_updated_at !== profile.updated_at) return null;
23914
23992
  return parsed;
@@ -23918,14 +23996,14 @@ function loadCachedTaxonomy(profile) {
23918
23996
  }
23919
23997
  function saveCachedTaxonomy(taxonomy) {
23920
23998
  ensureDir6();
23921
- writeFileSync15(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
23999
+ writeFileSync16(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
23922
24000
  }
23923
24001
  var NTRP_DIR4, TAXONOMY_PATH;
23924
24002
  var init_taxonomy_cache = __esm({
23925
24003
  "src/demo/taxonomy-cache.ts"() {
23926
24004
  "use strict";
23927
- NTRP_DIR4 = join24(homedir7(), ".ntrp");
23928
- TAXONOMY_PATH = join24(NTRP_DIR4, "demo-taxonomy.json");
24005
+ NTRP_DIR4 = join26(homedir7(), ".ntrp");
24006
+ TAXONOMY_PATH = join26(NTRP_DIR4, "demo-taxonomy.json");
23929
24007
  }
23930
24008
  });
23931
24009
 
@@ -24383,7 +24461,7 @@ __export(inbox_setup_exports, {
24383
24461
  shouldOfferInboxSkillSetup: () => shouldOfferInboxSkillSetup
24384
24462
  });
24385
24463
  import chalk27 from "chalk";
24386
- import { existsSync as existsSync24 } from "fs";
24464
+ import { existsSync as existsSync26 } from "fs";
24387
24465
  function markDemoOffered() {
24388
24466
  setConfigValue("ai-inbox-nudge-seen", "true");
24389
24467
  }
@@ -24415,7 +24493,7 @@ function printSkipHint(beat) {
24415
24493
  async function reuseInboxFolderIfPresent(session, beat, folderPath) {
24416
24494
  if (getAiInboxDir()) return false;
24417
24495
  const candidates = folderPath ? [folderPath] : [.../* @__PURE__ */ new Set([defaultAiInboxDir(), legacyAiInboxDir()])];
24418
- const existing = candidates.find((p) => existsSync24(p));
24496
+ const existing = candidates.find((p) => existsSync26(p));
24419
24497
  if (!existing) return false;
24420
24498
  console.log(" " + chalk27.dim("Pickup folder still on disk: ") + existing);
24421
24499
  const reuse = await session.confirm("Reuse this pickup folder?", true);
@@ -24521,7 +24599,7 @@ __export(ingest_exports, {
24521
24599
  handler: () => handler3
24522
24600
  });
24523
24601
  import chalk28 from "chalk";
24524
- import { readFileSync as readFileSync20, existsSync as existsSync25 } from "fs";
24602
+ import { readFileSync as readFileSync22, existsSync as existsSync27 } from "fs";
24525
24603
  import { basename as basename6 } from "path";
24526
24604
  async function handler3(args, ctx) {
24527
24605
  const { positional, flags } = parseArgs(args, [
@@ -24545,7 +24623,7 @@ async function handler3(args, ctx) {
24545
24623
  console.error(chalk28.dim(" /ingest --demo [--scenario <name>]"));
24546
24624
  process.exit(1);
24547
24625
  }
24548
- if (!existsSync25(file)) {
24626
+ if (!existsSync27(file)) {
24549
24627
  console.error(chalk28.red(` File not found: ${file}`));
24550
24628
  process.exit(1);
24551
24629
  }
@@ -24563,7 +24641,7 @@ async function handler3(args, ctx) {
24563
24641
  try {
24564
24642
  await initSchema();
24565
24643
  spinner.text = "Parsing CSV\u2026";
24566
- const content = readFileSync20(file, "utf-8");
24644
+ const content = readFileSync22(file, "utf-8");
24567
24645
  const { rows, headers } = parseCSV(content);
24568
24646
  if (rows.length === 0) {
24569
24647
  spinner.fail("CSV is empty");
@@ -24933,8 +25011,8 @@ __export(ingest_chat_exports, {
24933
25011
  loadDemoFromChat: () => loadDemoFromChat,
24934
25012
  looksLikeFilePath: () => looksLikeFilePath
24935
25013
  });
24936
- import { existsSync as existsSync26, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
24937
- import { basename as basename7, join as join25, resolve as resolve9 } from "path";
25014
+ import { existsSync as existsSync28, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
25015
+ import { basename as basename7, join as join27, resolve as resolve9 } from "path";
24938
25016
  import { homedir as homedir8 } from "os";
24939
25017
  import chalk30 from "chalk";
24940
25018
  function extractFilePath(input) {
@@ -24956,7 +25034,7 @@ function extractFilePath(input) {
24956
25034
  if (!candidate) continue;
24957
25035
  if (!looksLikePathToken(candidate)) continue;
24958
25036
  const p = expandPath(candidate);
24959
- if (existsSync26(p)) {
25037
+ if (existsSync28(p)) {
24960
25038
  try {
24961
25039
  const st = statSync5(p);
24962
25040
  if (st.isFile() || st.isDirectory()) return p;
@@ -24985,7 +25063,7 @@ function looksLikeFilePath(input) {
24985
25063
  function listCsvsInFolder(dir) {
24986
25064
  try {
24987
25065
  if (!statSync5(dir).isDirectory()) return [];
24988
- return readdirSync6(dir).filter((name) => name.toLowerCase().endsWith(".csv")).map((name) => join25(dir, name)).sort();
25066
+ return readdirSync6(dir).filter((name) => name.toLowerCase().endsWith(".csv")).map((name) => join27(dir, name)).sort();
24989
25067
  } catch {
24990
25068
  return [];
24991
25069
  }
@@ -25067,12 +25145,12 @@ async function ingestFromChat(ctx, filePath) {
25067
25145
  }
25068
25146
  const { handler: ingest } = await Promise.resolve().then(() => (init_ingest(), ingest_exports));
25069
25147
  const { detectEntityType: detectEntityType2 } = await Promise.resolve().then(() => (init_csv_detect(), csv_detect_exports));
25070
- const { readFileSync: readFileSync23 } = await import("fs");
25148
+ const { readFileSync: readFileSync24 } = await import("fs");
25071
25149
  const { parseCSV: parseCSV2 } = await Promise.resolve().then(() => (init_csv_parse(), csv_parse_exports));
25072
25150
  const { getStoredApiKey } = await Promise.resolve().then(() => (init_repl_api(), repl_api_exports));
25073
25151
  let headerCheckFailed = false;
25074
25152
  try {
25075
- const raw = readFileSync23(filePath, "utf-8");
25153
+ const raw = readFileSync24(filePath, "utf-8");
25076
25154
  const { headers } = parseCSV2(raw);
25077
25155
  const detected = detectEntityType2(headers, "unknown");
25078
25156
  if (!detected) headerCheckFailed = true;
@@ -25985,9 +26063,9 @@ async function handleGetSessionBrief(input) {
25985
26063
  if (!target) {
25986
26064
  return { error: `No session matching "${raw}".` };
25987
26065
  }
25988
- const { existsSync: existsSync29, readFileSync: readFileSync23 } = await import("fs");
26066
+ const { existsSync: existsSync30, readFileSync: readFileSync24 } = await import("fs");
25989
26067
  const briefPath = contextDocPathForSession2(target.id);
25990
- if (!existsSync29(briefPath)) {
26068
+ if (!existsSync30(briefPath)) {
25991
26069
  return {
25992
26070
  session_id: target.id,
25993
26071
  error: "No context brief on disk for this session (created before brief storage existed).",
@@ -25998,7 +26076,7 @@ async function handleGetSessionBrief(input) {
25998
26076
  return {
25999
26077
  session_id: target.id,
26000
26078
  security_notice: UNTRUSTED_CONTENT_NOTICE,
26001
- brief: wrapUntrustedContent(readFileSync23(briefPath, "utf-8"))
26079
+ brief: wrapUntrustedContent(readFileSync24(briefPath, "utf-8"))
26002
26080
  };
26003
26081
  }
26004
26082
  function auditDenied(name, input, resultJson, start) {
@@ -27002,15 +27080,15 @@ var init_ask = __esm({
27002
27080
  });
27003
27081
 
27004
27082
  // src/services/setup.ts
27005
- import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync21, writeFileSync as writeFileSync16 } from "fs";
27006
- import { join as join26 } from "path";
27083
+ import { existsSync as existsSync29, mkdirSync as mkdirSync15, readFileSync as readFileSync23, writeFileSync as writeFileSync17 } from "fs";
27084
+ import { join as join28 } from "path";
27007
27085
  function setupCheck() {
27008
27086
  const home = ntrpHome();
27009
27087
  let writable = false;
27010
27088
  try {
27011
- mkdirSync14(home, { recursive: true });
27012
- const probe = join26(home, ".write-check");
27013
- writeFileSync16(probe, "ok\n");
27089
+ mkdirSync15(home, { recursive: true });
27090
+ const probe = join28(home, ".write-check");
27091
+ writeFileSync17(probe, "ok\n");
27014
27092
  writable = true;
27015
27093
  } catch {
27016
27094
  writable = false;
@@ -27060,42 +27138,6 @@ var init_setup = __esm({
27060
27138
  }
27061
27139
  });
27062
27140
 
27063
- // src/version.ts
27064
- import { existsSync as existsSync28, readFileSync as readFileSync22 } from "fs";
27065
- import { dirname as dirname5, join as join27 } from "path";
27066
- import { fileURLToPath } from "url";
27067
- function readVersionFromPackageJson(packageJsonPath) {
27068
- if (!existsSync28(packageJsonPath)) return null;
27069
- try {
27070
- const pkg = JSON.parse(readFileSync22(packageJsonPath, "utf-8"));
27071
- if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
27072
- } catch {
27073
- }
27074
- return null;
27075
- }
27076
- function readVersionNearEntry(entryPath) {
27077
- const start = dirname5(entryPath);
27078
- for (const rel of [join27(start, "..", "package.json"), join27(start, "../..", "package.json")]) {
27079
- const version = readVersionFromPackageJson(rel);
27080
- if (version) return version;
27081
- }
27082
- return null;
27083
- }
27084
- function readInstalledVersionFromDisk() {
27085
- return readVersionNearEntry(fileURLToPath(import.meta.url));
27086
- }
27087
- function getInstalledVersion() {
27088
- if (cachedVersion) return cachedVersion;
27089
- cachedVersion = readInstalledVersionFromDisk() ?? "0.0.0";
27090
- return cachedVersion;
27091
- }
27092
- var cachedVersion;
27093
- var init_version = __esm({
27094
- "src/version.ts"() {
27095
- "use strict";
27096
- }
27097
- });
27098
-
27099
27141
  // src/mcp/server.ts
27100
27142
  init_context2();
27101
27143
  init_diagnosis();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonnechasser/ntrp",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "description": "GTM Health Diagnostic CLI — local pipeline analysis tool",
5
5
  "homepage": "https://ntrp.sonnechasser.com",
6
6
  "repository": {