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

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 (50) hide show
  1. package/README.md +22 -26
  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/dist/compact-before.mjs +66 -8
  6. package/dist/doctor.mjs +178 -41
  7. package/dist/help-topic.mjs +0 -0
  8. package/dist/init-project.mjs +2 -7
  9. package/dist/install-runtime.mjs +100 -17
  10. package/dist/lessons-cli.mjs +67 -1
  11. package/dist/obs-cli.mjs +64 -1
  12. package/dist/price-lookup.mjs +45 -23
  13. package/dist/prompt-submit.mjs +66 -8
  14. package/dist/refresh-model-prices.mjs +7190 -46
  15. package/dist/response-after.mjs +66 -8
  16. package/dist/run.mjs +66 -8
  17. package/dist/session-end.mjs +66 -8
  18. package/dist/session-start.mjs +66 -8
  19. package/dist/shim.mjs +66 -3
  20. package/dist/stop.mjs +66 -8
  21. package/dist/subagent-start.mjs +66 -8
  22. package/dist/subagent-stop.mjs +66 -8
  23. package/dist/support.mjs +64 -1
  24. package/dist/tlc-cli.mjs +193 -87
  25. package/dist/tool-after.mjs +111 -31
  26. package/dist/tool-before.mjs +66 -8
  27. package/dist/tool-failure.mjs +66 -8
  28. package/dist/uninstall-runtime.mjs +9 -10
  29. package/docs/log.md +2 -0
  30. package/docs/measure.md +35 -31
  31. package/package.json +4 -4
  32. package/src/core/core.facade.ts +7 -0
  33. package/src/core/index.ts +1 -0
  34. package/src/core/pricing/pricing.freshness.ts +118 -0
  35. package/src/core/skill/skill.link.ts +14 -3
  36. package/src/entrypoints/shim.ts +8 -2
  37. package/src/platform/links.ts +73 -0
  38. package/src/platform/pricing.ts +139 -31
  39. package/src/providers/cursor/cursor.wiring.ts +11 -8
  40. package/tools/doctor.ts +78 -8
  41. package/tools/init-project.ts +7 -7
  42. package/tools/install-runtime.ts +89 -6
  43. package/tools/refresh-model-prices.ts +242 -75
  44. package/tools/uninstall-runtime.ts +23 -19
  45. package/bin/tlc-build +0 -80
  46. package/bin/tlc-exec +0 -10
  47. package/bin/tlc-exec.cmd +0 -4
  48. package/model-aliases.json +0 -12
  49. package/model-prices.cursor.json +0 -410
  50. 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,
@@ -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/support.mjs CHANGED
@@ -5259,6 +5259,62 @@ function release(root, provider, session) {
5259
5259
  deletePresenceRecord(root, provider, session);
5260
5260
  }
5261
5261
 
5262
+ // src/core/pricing/pricing.freshness.ts
5263
+ var DEFAULT_TTL_DAYS = 7;
5264
+ var MS_PER_DAY = 86400000;
5265
+ function freshness(meta, now, ttlDays = DEFAULT_TTL_DAYS) {
5266
+ if (meta === null) {
5267
+ return { state: "absent" };
5268
+ }
5269
+ const stamp = meta.refreshedAt;
5270
+ if (stamp === undefined || Number.isNaN(Date.parse(stamp))) {
5271
+ return { state: "undated" };
5272
+ }
5273
+ const ageMs = now.getTime() - Date.parse(stamp);
5274
+ const ageDays = Math.max(0, ageMs / MS_PER_DAY);
5275
+ return ageDays > ttlDays ? { state: "stale", ageDays, refreshedAt: stamp } : { state: "fresh", ageDays, refreshedAt: stamp };
5276
+ }
5277
+ function shouldRefetch(state) {
5278
+ return state.state === "absent" || state.state === "undated" || state.state === "stale";
5279
+ }
5280
+ function freshnessMessage(state, catalogue) {
5281
+ switch (state.state) {
5282
+ case "absent":
5283
+ return `${catalogue}: not on this machine — run \`tlc harness prices refresh\``;
5284
+ case "undated":
5285
+ return `${catalogue}: present but carries no date — it will be refetched`;
5286
+ case "fresh":
5287
+ return `${catalogue}: ${describeAge(state.ageDays)} old`;
5288
+ default:
5289
+ return `${catalogue}: ${describeAge(state.ageDays)} old — run \`tlc harness prices refresh\``;
5290
+ }
5291
+ }
5292
+ var MIN_RETAINED_RATIO = 0.5;
5293
+ function mayReplace(existingCount, incomingCount, minRatio = MIN_RETAINED_RATIO) {
5294
+ if (incomingCount === 0) {
5295
+ return { replace: false, reason: "parsed no entries at all — the upstream format has changed" };
5296
+ }
5297
+ if (existingCount === 0) {
5298
+ return { replace: true, reason: `first catalogue, ${incomingCount} entries` };
5299
+ }
5300
+ if (incomingCount >= existingCount) {
5301
+ return { replace: true, reason: `${existingCount} → ${incomingCount} entries` };
5302
+ }
5303
+ const retained = incomingCount / existingCount;
5304
+ return retained >= minRatio ? { replace: true, reason: `${existingCount} → ${incomingCount} entries` } : {
5305
+ replace: false,
5306
+ reason: `would drop from ${existingCount} to ${incomingCount} entries, keeping the existing catalogue — the upstream format has probably changed`
5307
+ };
5308
+ }
5309
+ function describeAge(ageDays) {
5310
+ if (ageDays < 1) {
5311
+ const hours = Math.max(1, Math.round(ageDays * 24));
5312
+ return `${hours} hour${hours === 1 ? "" : "s"}`;
5313
+ }
5314
+ const days = Math.round(ageDays);
5315
+ return `${days} day${days === 1 ? "" : "s"}`;
5316
+ }
5317
+
5262
5318
  // src/core/release/release.decisions.ts
5263
5319
  import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync19 } from "node:fs";
5264
5320
  import { join as join18 } from "node:path";
@@ -5608,7 +5664,8 @@ function linkHealth(target, runtimeHome2, probe) {
5608
5664
  if (!probe.exists(resolved)) {
5609
5665
  return { state: "dangling", target, resolved };
5610
5666
  }
5611
- const home = runtimeHome2.replace(/\/+$/, "");
5667
+ const resolveHome = probe.realpath ?? ((path) => path);
5668
+ const home = resolveHome(runtimeHome2).replace(/\/+$/, "");
5612
5669
  return resolved === home || resolved.startsWith(`${home}/`) ? { state: "ok", target, resolved } : { state: "outside-runtime", target, resolved };
5613
5670
  }
5614
5671
  function linkHealthMessage(health) {
@@ -6755,6 +6812,12 @@ var coreFacade = {
6755
6812
  coversHandler,
6756
6813
  decideShim
6757
6814
  },
6815
+ pricing: {
6816
+ freshness,
6817
+ freshnessMessage,
6818
+ mayReplace,
6819
+ shouldRefetch
6820
+ },
6758
6821
  skill: {
6759
6822
  linkHealth,
6760
6823
  linkHealthMessage,