@tech-leads-club/harness-toolkit 0.2.4 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +68 -37
  2. package/bin/tlc-build.mjs +93 -0
  3. package/bin/tlc-cli.ts +110 -66
  4. package/bin/tlc-exec.mjs +16 -13
  5. package/config.example.json +1 -1
  6. package/dist/compact-before.mjs +66 -8
  7. package/dist/doctor.mjs +178 -41
  8. package/dist/help-topic.mjs +0 -0
  9. package/dist/init-project.mjs +2 -7
  10. package/dist/install-runtime.mjs +6594 -281
  11. package/dist/lessons-cli.mjs +67 -1
  12. package/dist/obs-cli.mjs +64 -1
  13. package/dist/price-lookup.mjs +45 -23
  14. package/dist/prompt-submit.mjs +66 -8
  15. package/dist/refresh-model-prices.mjs +7190 -46
  16. package/dist/response-after.mjs +66 -8
  17. package/dist/run.mjs +66 -8
  18. package/dist/session-end.mjs +66 -8
  19. package/dist/session-start.mjs +66 -8
  20. package/dist/shim.mjs +66 -3
  21. package/dist/stop.mjs +66 -8
  22. package/dist/subagent-start.mjs +66 -8
  23. package/dist/subagent-stop.mjs +66 -8
  24. package/dist/support.mjs +64 -1
  25. package/dist/tlc-cli.mjs +193 -87
  26. package/dist/tool-after.mjs +111 -31
  27. package/dist/tool-before.mjs +66 -8
  28. package/dist/tool-failure.mjs +66 -8
  29. package/dist/uninstall-runtime.mjs +9 -10
  30. package/docs/log.md +2 -0
  31. package/docs/measure.md +35 -31
  32. package/package.json +5 -5
  33. package/src/core/core.facade.ts +7 -0
  34. package/src/core/index.ts +1 -0
  35. package/src/core/pricing/pricing.freshness.ts +118 -0
  36. package/src/core/skill/skill.link.ts +14 -3
  37. package/src/entrypoints/shim.ts +8 -2
  38. package/src/platform/links.ts +73 -0
  39. package/src/platform/pricing.ts +139 -31
  40. package/src/providers/cursor/cursor.wiring.ts +11 -8
  41. package/tools/doctor.ts +78 -8
  42. package/tools/init-project.ts +7 -7
  43. package/tools/install-runtime.ts +110 -7
  44. package/tools/refresh-model-prices.ts +242 -75
  45. package/tools/uninstall-runtime.ts +23 -19
  46. package/bin/tlc-build +0 -80
  47. package/bin/tlc-exec +0 -10
  48. package/bin/tlc-exec.cmd +0 -4
  49. package/model-aliases.json +0 -12
  50. package/model-prices.cursor.json +0 -410
  51. package/model-prices.json +0 -1
@@ -5273,6 +5273,62 @@ function release(root, provider, session) {
5273
5273
  deletePresenceRecord(root, provider, session);
5274
5274
  }
5275
5275
 
5276
+ // src/core/pricing/pricing.freshness.ts
5277
+ var DEFAULT_TTL_DAYS = 7;
5278
+ var MS_PER_DAY = 86400000;
5279
+ function freshness(meta, now, ttlDays = DEFAULT_TTL_DAYS) {
5280
+ if (meta === null) {
5281
+ return { state: "absent" };
5282
+ }
5283
+ const stamp = meta.refreshedAt;
5284
+ if (stamp === undefined || Number.isNaN(Date.parse(stamp))) {
5285
+ return { state: "undated" };
5286
+ }
5287
+ const ageMs = now.getTime() - Date.parse(stamp);
5288
+ const ageDays = Math.max(0, ageMs / MS_PER_DAY);
5289
+ return ageDays > ttlDays ? { state: "stale", ageDays, refreshedAt: stamp } : { state: "fresh", ageDays, refreshedAt: stamp };
5290
+ }
5291
+ function shouldRefetch(state) {
5292
+ return state.state === "absent" || state.state === "undated" || state.state === "stale";
5293
+ }
5294
+ function freshnessMessage(state, catalogue) {
5295
+ switch (state.state) {
5296
+ case "absent":
5297
+ return `${catalogue}: not on this machine — run \`tlc harness prices refresh\``;
5298
+ case "undated":
5299
+ return `${catalogue}: present but carries no date — it will be refetched`;
5300
+ case "fresh":
5301
+ return `${catalogue}: ${describeAge(state.ageDays)} old`;
5302
+ default:
5303
+ return `${catalogue}: ${describeAge(state.ageDays)} old — run \`tlc harness prices refresh\``;
5304
+ }
5305
+ }
5306
+ var MIN_RETAINED_RATIO = 0.5;
5307
+ function mayReplace(existingCount, incomingCount, minRatio = MIN_RETAINED_RATIO) {
5308
+ if (incomingCount === 0) {
5309
+ return { replace: false, reason: "parsed no entries at all — the upstream format has changed" };
5310
+ }
5311
+ if (existingCount === 0) {
5312
+ return { replace: true, reason: `first catalogue, ${incomingCount} entries` };
5313
+ }
5314
+ if (incomingCount >= existingCount) {
5315
+ return { replace: true, reason: `${existingCount} → ${incomingCount} entries` };
5316
+ }
5317
+ const retained = incomingCount / existingCount;
5318
+ return retained >= minRatio ? { replace: true, reason: `${existingCount} → ${incomingCount} entries` } : {
5319
+ replace: false,
5320
+ reason: `would drop from ${existingCount} to ${incomingCount} entries, keeping the existing catalogue — the upstream format has probably changed`
5321
+ };
5322
+ }
5323
+ function describeAge(ageDays) {
5324
+ if (ageDays < 1) {
5325
+ const hours = Math.max(1, Math.round(ageDays * 24));
5326
+ return `${hours} hour${hours === 1 ? "" : "s"}`;
5327
+ }
5328
+ const days = Math.round(ageDays);
5329
+ return `${days} day${days === 1 ? "" : "s"}`;
5330
+ }
5331
+
5276
5332
  // src/core/release/release.decisions.ts
5277
5333
  import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync19 } from "node:fs";
5278
5334
  import { join as join18 } from "node:path";
@@ -5622,7 +5678,8 @@ function linkHealth(target, runtimeHome2, probe) {
5622
5678
  if (!probe.exists(resolved)) {
5623
5679
  return { state: "dangling", target, resolved };
5624
5680
  }
5625
- const home = runtimeHome2.replace(/\/+$/, "");
5681
+ const resolveHome = probe.realpath ?? ((path) => path);
5682
+ const home = resolveHome(runtimeHome2).replace(/\/+$/, "");
5626
5683
  return resolved === home || resolved.startsWith(`${home}/`) ? { state: "ok", target, resolved } : { state: "outside-runtime", target, resolved };
5627
5684
  }
5628
5685
  function linkHealthMessage(health) {
@@ -6769,6 +6826,12 @@ var coreFacade = {
6769
6826
  coversHandler,
6770
6827
  decideShim
6771
6828
  },
6829
+ pricing: {
6830
+ freshness,
6831
+ freshnessMessage,
6832
+ mayReplace,
6833
+ shouldRefetch
6834
+ },
6772
6835
  skill: {
6773
6836
  linkHealth,
6774
6837
  linkHealthMessage,
@@ -7719,14 +7782,9 @@ var ENTRY_SPECS2 = [
7719
7782
  { hookEvent: "stop", handler: "stop", timeoutSeconds: 120, loopLimit: 5 },
7720
7783
  { hookEvent: "afterAgentResponse", handler: "response-after", timeoutSeconds: 5, matcher: "AgentResponse" }
7721
7784
  ];
7722
- function commandFor(runtime) {
7723
- if (process.platform === "win32") {
7724
- return { command: "cmd", argsPrefix: ["/c", "node", runtime.launcherPath] };
7725
- }
7726
- return { command: "node", argsPrefix: [runtime.launcherPath] };
7727
- }
7728
7785
  function cursorWiring(runtime) {
7729
- const { command, argsPrefix } = commandFor(runtime);
7786
+ const command = "node";
7787
+ const argsPrefix = [runtime.launcherPath];
7730
7788
  const entries = ENTRY_SPECS2.map((spec) => ({
7731
7789
  hookEvent: spec.hookEvent,
7732
7790
  handler: spec.handler,
package/dist/doctor.mjs CHANGED
@@ -3,9 +3,9 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
4
  // tools/doctor.ts
5
5
  import { spawnSync } from "node:child_process";
6
- import { existsSync as existsSync29, lstatSync as lstatSync2, readFileSync as readFileSync31, readlinkSync, realpathSync as realpathSync4 } from "node:fs";
6
+ import { existsSync as existsSync30, lstatSync as lstatSync2, readFileSync as readFileSync32, readlinkSync, realpathSync as realpathSync4 } from "node:fs";
7
7
  import { homedir as homedir3, platform as osPlatform } from "node:os";
8
- import { basename as basename5, dirname as dirname9, join as join30 } from "node:path";
8
+ import { basename as basename5, delimiter as delimiter3, dirname as dirname9, join as join31 } from "node:path";
9
9
 
10
10
  // bin/tlc-cli.ts
11
11
  import {
@@ -5288,6 +5288,62 @@ function release(root, provider, session) {
5288
5288
  deletePresenceRecord(root, provider, session);
5289
5289
  }
5290
5290
 
5291
+ // src/core/pricing/pricing.freshness.ts
5292
+ var DEFAULT_TTL_DAYS = 7;
5293
+ var MS_PER_DAY = 86400000;
5294
+ function freshness(meta, now, ttlDays = DEFAULT_TTL_DAYS) {
5295
+ if (meta === null) {
5296
+ return { state: "absent" };
5297
+ }
5298
+ const stamp = meta.refreshedAt;
5299
+ if (stamp === undefined || Number.isNaN(Date.parse(stamp))) {
5300
+ return { state: "undated" };
5301
+ }
5302
+ const ageMs = now.getTime() - Date.parse(stamp);
5303
+ const ageDays = Math.max(0, ageMs / MS_PER_DAY);
5304
+ return ageDays > ttlDays ? { state: "stale", ageDays, refreshedAt: stamp } : { state: "fresh", ageDays, refreshedAt: stamp };
5305
+ }
5306
+ function shouldRefetch(state) {
5307
+ return state.state === "absent" || state.state === "undated" || state.state === "stale";
5308
+ }
5309
+ function freshnessMessage(state, catalogue) {
5310
+ switch (state.state) {
5311
+ case "absent":
5312
+ return `${catalogue}: not on this machine — run \`tlc harness prices refresh\``;
5313
+ case "undated":
5314
+ return `${catalogue}: present but carries no date — it will be refetched`;
5315
+ case "fresh":
5316
+ return `${catalogue}: ${describeAge(state.ageDays)} old`;
5317
+ default:
5318
+ return `${catalogue}: ${describeAge(state.ageDays)} old — run \`tlc harness prices refresh\``;
5319
+ }
5320
+ }
5321
+ var MIN_RETAINED_RATIO = 0.5;
5322
+ function mayReplace(existingCount, incomingCount, minRatio = MIN_RETAINED_RATIO) {
5323
+ if (incomingCount === 0) {
5324
+ return { replace: false, reason: "parsed no entries at all — the upstream format has changed" };
5325
+ }
5326
+ if (existingCount === 0) {
5327
+ return { replace: true, reason: `first catalogue, ${incomingCount} entries` };
5328
+ }
5329
+ if (incomingCount >= existingCount) {
5330
+ return { replace: true, reason: `${existingCount} → ${incomingCount} entries` };
5331
+ }
5332
+ const retained = incomingCount / existingCount;
5333
+ return retained >= minRatio ? { replace: true, reason: `${existingCount} → ${incomingCount} entries` } : {
5334
+ replace: false,
5335
+ reason: `would drop from ${existingCount} to ${incomingCount} entries, keeping the existing catalogue — the upstream format has probably changed`
5336
+ };
5337
+ }
5338
+ function describeAge(ageDays) {
5339
+ if (ageDays < 1) {
5340
+ const hours = Math.max(1, Math.round(ageDays * 24));
5341
+ return `${hours} hour${hours === 1 ? "" : "s"}`;
5342
+ }
5343
+ const days = Math.round(ageDays);
5344
+ return `${days} day${days === 1 ? "" : "s"}`;
5345
+ }
5346
+
5291
5347
  // src/core/release/release.decisions.ts
5292
5348
  import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync19 } from "node:fs";
5293
5349
  import { join as join18 } from "node:path";
@@ -5637,7 +5693,8 @@ function linkHealth(target, runtimeHome2, probe) {
5637
5693
  if (!probe.exists(resolved)) {
5638
5694
  return { state: "dangling", target, resolved };
5639
5695
  }
5640
- const home = runtimeHome2.replace(/\/+$/, "");
5696
+ const resolveHome = probe.realpath ?? ((path) => path);
5697
+ const home = resolveHome(runtimeHome2).replace(/\/+$/, "");
5641
5698
  return resolved === home || resolved.startsWith(`${home}/`) ? { state: "ok", target, resolved } : { state: "outside-runtime", target, resolved };
5642
5699
  }
5643
5700
  function linkHealthMessage(health) {
@@ -6784,6 +6841,12 @@ var coreFacade = {
6784
6841
  coversHandler,
6785
6842
  decideShim
6786
6843
  },
6844
+ pricing: {
6845
+ freshness,
6846
+ freshnessMessage,
6847
+ mayReplace,
6848
+ shouldRefetch
6849
+ },
6787
6850
  skill: {
6788
6851
  linkHealth,
6789
6852
  linkHealthMessage,
@@ -6993,6 +7056,7 @@ function writeStdout(text) {
6993
7056
  }
6994
7057
 
6995
7058
  // bin/tlc-cli.ts
7059
+ var NPM_PACKAGE = "@tech-leads-club/harness-toolkit";
6996
7060
  var NPM_MARKER = "installed-from-npm";
6997
7061
  function classifyRuntimePath(dest, probe) {
6998
7062
  if (probe.isSymlink(dest)) {
@@ -7026,19 +7090,17 @@ import { delimiter as delimiter2, dirname as dirname7, join as join27 } from "no
7026
7090
  function isPackagedCopy(candidate) {
7027
7091
  return candidate.split(/[/\\]/).includes("node_modules");
7028
7092
  }
7029
- function bunExecutableName(platform = process.platform) {
7030
- return platform === "win32" ? "bun.exe" : "bun";
7031
- }
7032
- function findBunOnPath(env = process.env, platform = process.platform) {
7033
- const pathValue = env.PATH ?? "";
7034
- const bunName = bunExecutableName(platform);
7035
- for (const dir of pathValue.split(delimiter2)) {
7093
+ var BUN_EXECUTABLE_NAMES = ["bun", "bun.exe"];
7094
+ function findBunOnPath(env = process.env) {
7095
+ for (const dir of (env.PATH ?? "").split(delimiter2)) {
7036
7096
  if (!dir) {
7037
7097
  continue;
7038
7098
  }
7039
- const candidate = join27(dir, bunName);
7040
- if (existsSync26(candidate)) {
7041
- return candidate;
7099
+ for (const name of BUN_EXECUTABLE_NAMES) {
7100
+ const candidate = join27(dir, name);
7101
+ if (existsSync26(candidate)) {
7102
+ return candidate;
7103
+ }
7042
7104
  }
7043
7105
  }
7044
7106
  return null;
@@ -7814,14 +7876,9 @@ var ENTRY_SPECS2 = [
7814
7876
  { hookEvent: "stop", handler: "stop", timeoutSeconds: 120, loopLimit: 5 },
7815
7877
  { hookEvent: "afterAgentResponse", handler: "response-after", timeoutSeconds: 5, matcher: "AgentResponse" }
7816
7878
  ];
7817
- function commandFor(runtime) {
7818
- if (process.platform === "win32") {
7819
- return { command: "cmd", argsPrefix: ["/c", "node", runtime.launcherPath] };
7820
- }
7821
- return { command: "node", argsPrefix: [runtime.launcherPath] };
7822
- }
7823
7879
  function cursorWiring(runtime) {
7824
- const { command, argsPrefix } = commandFor(runtime);
7880
+ const command = "node";
7881
+ const argsPrefix = [runtime.launcherPath];
7825
7882
  const entries = ENTRY_SPECS2.map((spec) => ({
7826
7883
  hookEvent: spec.hookEvent,
7827
7884
  handler: spec.handler,
@@ -7913,6 +7970,50 @@ function isCursorWired(targetPath) {
7913
7970
  }
7914
7971
  if (false) {}
7915
7972
 
7973
+ // src/platform/pricing.ts
7974
+ import { existsSync as existsSync29, readFileSync as readFileSync31 } from "node:fs";
7975
+ import { join as join30 } from "node:path";
7976
+ function cataloguePath() {
7977
+ return join30(runtimeHome(), "model-prices.json");
7978
+ }
7979
+ var cache = new Map;
7980
+ function readCatalogue(path) {
7981
+ if (!existsSync29(path)) {
7982
+ cache.delete(path);
7983
+ return null;
7984
+ }
7985
+ let text;
7986
+ try {
7987
+ text = readFileSync31(path, "utf8");
7988
+ } catch {
7989
+ cache.delete(path);
7990
+ return null;
7991
+ }
7992
+ const hit = cache.get(path);
7993
+ if (hit && hit.text === text) {
7994
+ return hit.value;
7995
+ }
7996
+ let parsed;
7997
+ try {
7998
+ parsed = JSON.parse(text);
7999
+ } catch {
8000
+ cache.delete(path);
8001
+ return null;
8002
+ }
8003
+ cache.set(path, { text, value: parsed });
8004
+ return parsed;
8005
+ }
8006
+ function loadCatalogue() {
8007
+ return readCatalogue(cataloguePath()) ?? {};
8008
+ }
8009
+ function planeMeta() {
8010
+ return loadCatalogue()._meta?.planes ?? {};
8011
+ }
8012
+ function catalogueMeta() {
8013
+ const parsed = readCatalogue(cataloguePath());
8014
+ return parsed === null ? null : parsed._meta ?? {};
8015
+ }
8016
+
7916
8017
  // tools/doctor.ts
7917
8018
  function plural(count, word) {
7918
8019
  return `${count} ${word}${count === 1 ? "" : "s"}`;
@@ -7986,10 +8087,17 @@ function checkSkillLinks(home, providerDirs = providerConfigDirs(), probe = {
7986
8087
  }
7987
8088
  }
7988
8089
  },
7989
- exists: existsSync29
8090
+ exists: existsSync30,
8091
+ realpath: (path) => {
8092
+ try {
8093
+ return realpathSync4(path);
8094
+ } catch {
8095
+ return path;
8096
+ }
8097
+ }
7990
8098
  }) {
7991
- return providerDirs.filter((dir) => existsSync29(dir)).map((dir) => {
7992
- const health = coreFacade.skill.linkHealth(join30(dir, "skills", "harness-init"), home, probe);
8099
+ return providerDirs.filter((dir) => existsSync30(dir)).map((dir) => {
8100
+ const health = coreFacade.skill.linkHealth(join31(dir, "skills", "harness-init"), home, probe);
7993
8101
  return {
7994
8102
  level: health.state === "ok" ? "ok" : "fail",
7995
8103
  name: `init skill (${basename5(dir)})`,
@@ -7997,24 +8105,50 @@ function checkSkillLinks(home, providerDirs = providerConfigDirs(), probe = {
7997
8105
  };
7998
8106
  });
7999
8107
  }
8108
+ function checkPrices(now = new Date, read = { meta: catalogueMeta, planes: planeMeta }) {
8109
+ const state = coreFacade.pricing.freshness(read.meta(), now);
8110
+ const planes = read.planes();
8111
+ const named = Object.entries(planes).map(([plane, meta]) => `${plane} ${meta.count ?? 0}`).join(", ");
8112
+ return [
8113
+ {
8114
+ level: state.state === "fresh" ? "ok" : "warn",
8115
+ name: "prices",
8116
+ detail: state.state === "fresh" ? `${coreFacade.pricing.freshnessMessage(state, "catalogue")}${named ? ` (${named})` : ""}` : coreFacade.pricing.freshnessMessage(state, "catalogue")
8117
+ }
8118
+ ];
8119
+ }
8120
+ function resolveOnPath(command, env = process.env, exists = existsSync30) {
8121
+ for (const dir of (env.PATH ?? "").split(delimiter3)) {
8122
+ if (!dir) {
8123
+ continue;
8124
+ }
8125
+ for (const name of [command, `${command}.cmd`, `${command}.exe`, `${command}.ps1`]) {
8126
+ const candidate = join31(dir, name);
8127
+ if (exists(candidate)) {
8128
+ return candidate;
8129
+ }
8130
+ }
8131
+ }
8132
+ return null;
8133
+ }
8000
8134
  function checkRuntimePaths(home, platform) {
8001
- const launcher = join30(home, "bin", "tlc-exec.mjs");
8002
- const distSample = join30(home, "dist", "stop.mjs");
8003
- const cliLink = join30(homedir3(), ".local", "bin", platform === "win32" ? "tlc.cmd" : "tlc");
8135
+ const launcher = join31(home, "bin", "tlc-exec.mjs");
8136
+ const distSample = join31(home, "dist", "stop.mjs");
8137
+ const onPath = resolveOnPath("tlc");
8004
8138
  return [
8005
8139
  { level: "ok", name: "platform", detail: platform },
8006
- { level: existsSync29(launcher) ? "ok" : "fail", name: "global runtime", detail: home },
8140
+ { level: existsSync30(launcher) ? "ok" : "fail", name: "global runtime", detail: home },
8007
8141
  runtimeOwnershipCheck(home),
8008
8142
  {
8009
- level: existsSync29(distSample) ? "ok" : "fail",
8143
+ level: existsSync30(distSample) ? "ok" : "fail",
8010
8144
  name: "dist bundles",
8011
- detail: existsSync29(distSample) ? join30(home, "dist") : "missing — run: tlc harness build"
8145
+ detail: existsSync30(distSample) ? join31(home, "dist") : "missing — run: tlc harness build"
8012
8146
  },
8013
- { level: existsSync29(launcher) ? "ok" : "fail", name: "portable launcher", detail: launcher },
8147
+ { level: existsSync30(launcher) ? "ok" : "fail", name: "portable launcher", detail: launcher },
8014
8148
  {
8015
- level: existsSync29(cliLink) || existsSync29(join30(home, "bin", platform === "win32" ? "tlc.cmd" : "tlc")) ? "ok" : "fail",
8149
+ level: onPath === null ? "fail" : "ok",
8016
8150
  name: "CLI on PATH",
8017
- detail: cliLink
8151
+ detail: onPath ?? `no \`tlc\` on PATH — npm i -g ${NPM_PACKAGE}, or \`npm link\` from a clone`
8018
8152
  }
8019
8153
  ];
8020
8154
  }
@@ -8033,15 +8167,15 @@ function wiringProblems(wiring) {
8033
8167
  if (wiring.strategy !== "replace") {
8034
8168
  return [];
8035
8169
  }
8036
- const text = existsSync29(wiring.target) ? readFileSync31(wiring.target, "utf8") : null;
8037
- return cursorWiringProblems(text, { launcherPath: launcherPathOf(wiring) }, existsSync29);
8170
+ const text = existsSync30(wiring.target) ? readFileSync32(wiring.target, "utf8") : null;
8171
+ return cursorWiringProblems(text, { launcherPath: launcherPathOf(wiring) }, existsSync30);
8038
8172
  }
8039
8173
  function launcherPathOf(wiring) {
8040
8174
  const first = wiring.entries[0];
8041
8175
  return first?.args.find((arg) => arg.endsWith(".mjs")) ?? "";
8042
8176
  }
8043
8177
  function providerWiringStatus(wiring) {
8044
- if (!existsSync29(dirname9(wiring.target))) {
8178
+ if (!existsSync30(dirname9(wiring.target))) {
8045
8179
  return "not-installed";
8046
8180
  }
8047
8181
  if (wiring.strategy === "replace") {
@@ -8050,12 +8184,12 @@ function providerWiringStatus(wiring) {
8050
8184
  }
8051
8185
  return wiringProblems(wiring).length === 0 ? "wired" : "detected-but-unwired";
8052
8186
  }
8053
- const existingText = existsSync29(wiring.target) ? readFileSync31(wiring.target, "utf8") : null;
8187
+ const existingText = existsSync30(wiring.target) ? readFileSync32(wiring.target, "utf8") : null;
8054
8188
  const result = mergeClaudeSettings(existingText, wiring.entries);
8055
8189
  return result.ok && !result.changed ? "wired" : "detected-but-unwired";
8056
8190
  }
8057
8191
  function checkProviders(registry, home) {
8058
- const launcherPath = join30(home, "bin", "tlc-exec.mjs");
8192
+ const launcherPath = join31(home, "bin", "tlc-exec.mjs");
8059
8193
  return registry.map((provider) => {
8060
8194
  const wiring = provider.wiring({ launcherPath });
8061
8195
  const status = providerWiringStatus(wiring);
@@ -8239,12 +8373,12 @@ function checkProjectPolicy(root) {
8239
8373
  {
8240
8374
  level: "ok",
8241
8375
  name: "project policy",
8242
- detail: existsSync29(configPath) ? configPath : "missing — run: tlc harness init"
8376
+ detail: existsSync30(configPath) ? configPath : "missing — run: tlc harness init"
8243
8377
  },
8244
8378
  {
8245
8379
  level: "ok",
8246
8380
  name: "state dir",
8247
- detail: existsSync29(stateDir) ? stateDir : `${stateDir} (created on first session)`
8381
+ detail: existsSync30(stateDir) ? stateDir : `${stateDir} (created on first session)`
8248
8382
  },
8249
8383
  checkPosture(root),
8250
8384
  ...checkObservedRails(root),
@@ -8255,8 +8389,8 @@ function checkProjectPolicy(root) {
8255
8389
  ];
8256
8390
  }
8257
8391
  function checkGlobalCommands(home) {
8258
- const globalCommands = join30(home, ".cursor", "commands");
8259
- if (!existsSync29(globalCommands)) {
8392
+ const globalCommands = join31(home, ".cursor", "commands");
8393
+ if (!existsSync30(globalCommands)) {
8260
8394
  return {
8261
8395
  level: "ok",
8262
8396
  name: "global commands dir",
@@ -8280,6 +8414,7 @@ function runChecks(ctx) {
8280
8414
  ...checkProviders(ctx.registry, ctx.runtimeHome),
8281
8415
  ...checkProjectPolicy(ctx.root),
8282
8416
  ...checkCapabilities(ctx.root, ctx.runtimeHome),
8417
+ ...checkPrices(),
8283
8418
  checkGlobalCommands(ctx.home)
8284
8419
  ];
8285
8420
  }
@@ -8349,6 +8484,7 @@ export {
8349
8484
  toReport,
8350
8485
  runtimeOwnershipCheck,
8351
8486
  runChecks,
8487
+ resolveOnPath,
8352
8488
  providerWiringStatus,
8353
8489
  plural,
8354
8490
  medianMs,
@@ -8360,6 +8496,7 @@ export {
8360
8496
  checkRuntimePaths,
8361
8497
  checkProviders,
8362
8498
  checkProjectPolicy,
8499
+ checkPrices,
8363
8500
  checkNodeVersion,
8364
8501
  checkLessonHealth,
8365
8502
  checkId,
File without changes
@@ -811,12 +811,7 @@ function usageText(style = PLAIN) {
811
811
  function launcherPath(home = runtimeHome()) {
812
812
  return join4(home, "bin", "tlc-exec.mjs");
813
813
  }
814
- function shimCommand(platform = process.platform) {
815
- if (platform === "win32") {
816
- return { command: "cmd", argsPrefix: ["/c", "node"] };
817
- }
818
- return { command: "node", argsPrefix: [] };
819
- }
814
+ var SHIM_COMMAND = { command: "node", argsPrefix: [] };
820
815
  var CURSOR_SHIM_SPECS = [
821
816
  { hookEvent: "sessionStart", handler: "session-start", timeoutSeconds: 10 },
822
817
  { hookEvent: "sessionEnd", handler: "session-end", timeoutSeconds: 10 },
@@ -837,7 +832,7 @@ var CLAUDE_SHIM_SPECS = [
837
832
  { hookEvent: "MessageDisplay", handler: "response-after", timeoutSeconds: 5 }
838
833
  ];
839
834
  function cursorShimEntries(launcher) {
840
- const { command, argsPrefix } = shimCommand();
835
+ const { command, argsPrefix } = SHIM_COMMAND;
841
836
  return CURSOR_SHIM_SPECS.map((spec) => ({
842
837
  hookEvent: spec.hookEvent,
843
838
  handler: spec.handler,