@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
package/dist/tlc-cli.mjs CHANGED
@@ -4,17 +4,16 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
  // bin/tlc-cli.ts
5
5
  import { spawnSync } from "node:child_process";
6
6
  import {
7
- existsSync as existsSync25,
8
- lstatSync,
9
- mkdirSync as mkdirSync16,
7
+ existsSync as existsSync26,
8
+ lstatSync as lstatSync2,
9
+ mkdirSync as mkdirSync17,
10
10
  readdirSync as readdirSync6,
11
11
  readFileSync as readFileSync27,
12
12
  realpathSync,
13
- rmSync as rmSync4,
13
+ rmSync as rmSync5,
14
14
  writeFileSync as writeFileSync15
15
15
  } from "node:fs";
16
- import { homedir as homedir3 } from "node:os";
17
- import { delimiter, join as join26 } from "node:path";
16
+ import { delimiter, join as join27 } from "node:path";
18
17
 
19
18
  // src/core/attest/attest.service.ts
20
19
  import { createHash } from "node:crypto";
@@ -5284,6 +5283,62 @@ function release(root, provider, session) {
5284
5283
  deletePresenceRecord(root, provider, session);
5285
5284
  }
5286
5285
 
5286
+ // src/core/pricing/pricing.freshness.ts
5287
+ var DEFAULT_TTL_DAYS = 7;
5288
+ var MS_PER_DAY = 86400000;
5289
+ function freshness(meta, now, ttlDays = DEFAULT_TTL_DAYS) {
5290
+ if (meta === null) {
5291
+ return { state: "absent" };
5292
+ }
5293
+ const stamp = meta.refreshedAt;
5294
+ if (stamp === undefined || Number.isNaN(Date.parse(stamp))) {
5295
+ return { state: "undated" };
5296
+ }
5297
+ const ageMs = now.getTime() - Date.parse(stamp);
5298
+ const ageDays = Math.max(0, ageMs / MS_PER_DAY);
5299
+ return ageDays > ttlDays ? { state: "stale", ageDays, refreshedAt: stamp } : { state: "fresh", ageDays, refreshedAt: stamp };
5300
+ }
5301
+ function shouldRefetch(state) {
5302
+ return state.state === "absent" || state.state === "undated" || state.state === "stale";
5303
+ }
5304
+ function freshnessMessage(state, catalogue) {
5305
+ switch (state.state) {
5306
+ case "absent":
5307
+ return `${catalogue}: not on this machine — run \`tlc harness prices refresh\``;
5308
+ case "undated":
5309
+ return `${catalogue}: present but carries no date — it will be refetched`;
5310
+ case "fresh":
5311
+ return `${catalogue}: ${describeAge(state.ageDays)} old`;
5312
+ default:
5313
+ return `${catalogue}: ${describeAge(state.ageDays)} old — run \`tlc harness prices refresh\``;
5314
+ }
5315
+ }
5316
+ var MIN_RETAINED_RATIO = 0.5;
5317
+ function mayReplace(existingCount, incomingCount, minRatio = MIN_RETAINED_RATIO) {
5318
+ if (incomingCount === 0) {
5319
+ return { replace: false, reason: "parsed no entries at all — the upstream format has changed" };
5320
+ }
5321
+ if (existingCount === 0) {
5322
+ return { replace: true, reason: `first catalogue, ${incomingCount} entries` };
5323
+ }
5324
+ if (incomingCount >= existingCount) {
5325
+ return { replace: true, reason: `${existingCount} → ${incomingCount} entries` };
5326
+ }
5327
+ const retained = incomingCount / existingCount;
5328
+ return retained >= minRatio ? { replace: true, reason: `${existingCount} → ${incomingCount} entries` } : {
5329
+ replace: false,
5330
+ reason: `would drop from ${existingCount} to ${incomingCount} entries, keeping the existing catalogue — the upstream format has probably changed`
5331
+ };
5332
+ }
5333
+ function describeAge(ageDays) {
5334
+ if (ageDays < 1) {
5335
+ const hours = Math.max(1, Math.round(ageDays * 24));
5336
+ return `${hours} hour${hours === 1 ? "" : "s"}`;
5337
+ }
5338
+ const days = Math.round(ageDays);
5339
+ return `${days} day${days === 1 ? "" : "s"}`;
5340
+ }
5341
+
5287
5342
  // src/core/release/release.decisions.ts
5288
5343
  import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync19 } from "node:fs";
5289
5344
  import { join as join18 } from "node:path";
@@ -5633,7 +5688,8 @@ function linkHealth(target, runtimeHome2, probe) {
5633
5688
  if (!probe.exists(resolved)) {
5634
5689
  return { state: "dangling", target, resolved };
5635
5690
  }
5636
- const home = runtimeHome2.replace(/\/+$/, "");
5691
+ const resolveHome = probe.realpath ?? ((path) => path);
5692
+ const home = resolveHome(runtimeHome2).replace(/\/+$/, "");
5637
5693
  return resolved === home || resolved.startsWith(`${home}/`) ? { state: "ok", target, resolved } : { state: "outside-runtime", target, resolved };
5638
5694
  }
5639
5695
  function linkHealthMessage(health) {
@@ -6780,6 +6836,12 @@ var coreFacade = {
6780
6836
  coversHandler,
6781
6837
  decideShim
6782
6838
  },
6839
+ pricing: {
6840
+ freshness,
6841
+ freshnessMessage,
6842
+ mayReplace,
6843
+ shouldRefetch
6844
+ },
6783
6845
  skill: {
6784
6846
  linkHealth,
6785
6847
  linkHealthMessage,
@@ -6991,6 +7053,43 @@ function unknownFlags(args) {
6991
7053
  return args.filter((arg) => arg.startsWith("--"));
6992
7054
  }
6993
7055
 
7056
+ // src/platform/links.ts
7057
+ import { copyFileSync, existsSync as existsSync25, lstatSync, mkdirSync as mkdirSync16, rmSync as rmSync4, symlinkSync } from "node:fs";
7058
+ import { dirname as dirname7, join as join26 } from "node:path";
7059
+ var LINK_TYPE = "junction";
7060
+ function linkDir(source, target) {
7061
+ let replaced = false;
7062
+ if (isLink(target)) {
7063
+ rmSync4(target, { recursive: true, force: true });
7064
+ replaced = true;
7065
+ } else if (existsSync25(target)) {
7066
+ return {
7067
+ kind: "refused",
7068
+ target,
7069
+ reason: `${target} exists and is not a link — move it aside and re-run`
7070
+ };
7071
+ }
7072
+ mkdirSync16(dirname7(target), { recursive: true });
7073
+ symlinkSync(source, target, LINK_TYPE);
7074
+ return { kind: replaced ? "relinked" : "linked", target, source };
7075
+ }
7076
+ function isLink(path) {
7077
+ try {
7078
+ return lstatSync(path).isSymbolicLink();
7079
+ } catch {
7080
+ return false;
7081
+ }
7082
+ }
7083
+ function seedConfig(dest) {
7084
+ const path = join26(dest, "config.json");
7085
+ const example = join26(dest, "config.example.json");
7086
+ if (existsSync25(path) || !existsSync25(example)) {
7087
+ return { seeded: false, path };
7088
+ }
7089
+ copyFileSync(example, path);
7090
+ return { seeded: true, path };
7091
+ }
7092
+
6994
7093
  // bin/tlc-cli.ts
6995
7094
  class UsageError extends Error {
6996
7095
  }
@@ -6998,22 +7097,22 @@ function resolveProjectRoot() {
6998
7097
  return process.env.TLC_PROJECT_DIR ?? process.cwd();
6999
7098
  }
7000
7099
  function modeFilePath(root) {
7001
- return join26(projectStateDir(root), "harness-mode");
7100
+ return join27(projectStateDir(root), "harness-mode");
7002
7101
  }
7003
7102
  function grindFlagPath(root) {
7004
- return join26(flagsDir(root), "grind-on");
7103
+ return join27(flagsDir(root), "grind-on");
7005
7104
  }
7006
7105
  function skipFlagPath(root) {
7007
- return join26(flagsDir(root), "skip-verify");
7106
+ return join27(flagsDir(root), "skip-verify");
7008
7107
  }
7009
7108
  function focusFlagPath(root) {
7010
- return join26(flagsDir(root), "focus");
7109
+ return join27(flagsDir(root), "focus");
7011
7110
  }
7012
7111
  function pairedFlagPath(root) {
7013
- return join26(flagsDir(root), "paired");
7112
+ return join27(flagsDir(root), "paired");
7014
7113
  }
7015
7114
  function ensureFlagsDir(root) {
7016
- mkdirSync16(flagsDir(root), { recursive: true });
7115
+ mkdirSync17(flagsDir(root), { recursive: true });
7017
7116
  }
7018
7117
  function readMode(root) {
7019
7118
  return coreFacade.policy.loadPolicy(root).mode;
@@ -7022,7 +7121,7 @@ function grindOn(root) {
7022
7121
  return coreFacade.policy.loadPolicy(root).grind.enabled;
7023
7122
  }
7024
7123
  function gatesPaused(root) {
7025
- return existsSync25(skipFlagPath(root));
7124
+ return existsSync26(skipFlagPath(root));
7026
7125
  }
7027
7126
  function acceptedModes() {
7028
7127
  return coreFacade.policy.OPERATOR_MODES.join(" | ");
@@ -7084,8 +7183,8 @@ function setGrind(root, on) {
7084
7183
  coreFacade.policy.refreshPolicyBaselines(root);
7085
7184
  return "grind ON — stop hook will lint/test and auto-retry on failure";
7086
7185
  }
7087
- if (existsSync25(path)) {
7088
- rmSync4(path);
7186
+ if (existsSync26(path)) {
7187
+ rmSync5(path);
7089
7188
  }
7090
7189
  coreFacade.policy.refreshPolicyBaselines(root);
7091
7190
  return "grind OFF — no auto fix loops";
@@ -7098,8 +7197,8 @@ function setPaused(root, on) {
7098
7197
  coreFacade.policy.refreshPolicyBaselines(root);
7099
7198
  return "gates PAUSED — stop checks disabled until `tlc harness resume`";
7100
7199
  }
7101
- if (existsSync25(path)) {
7102
- rmSync4(path);
7200
+ if (existsSync26(path)) {
7201
+ rmSync5(path);
7103
7202
  }
7104
7203
  coreFacade.policy.refreshPolicyBaselines(root);
7105
7204
  return "gates ACTIVE again";
@@ -7321,30 +7420,30 @@ function classifyRuntimePath(dest, probe) {
7321
7420
  if (!probe.exists(dest)) {
7322
7421
  return "absent";
7323
7422
  }
7324
- if (probe.exists(join26(dest, ".git"))) {
7423
+ if (probe.exists(join27(dest, ".git"))) {
7325
7424
  return "managed";
7326
7425
  }
7327
- return probe.exists(join26(dest, NPM_MARKER)) ? "npm" : "unmanaged";
7426
+ return probe.exists(join27(dest, NPM_MARKER)) ? "npm" : "unmanaged";
7328
7427
  }
7329
7428
  function runtimePathKind(dest) {
7330
7429
  return classifyRuntimePath(dest, {
7331
7430
  isSymlink: (path) => {
7332
7431
  try {
7333
- return lstatSync(path).isSymbolicLink();
7432
+ return lstatSync2(path).isSymbolicLink();
7334
7433
  } catch {
7335
7434
  return false;
7336
7435
  }
7337
7436
  },
7338
- exists: existsSync25
7437
+ exists: existsSync26
7339
7438
  });
7340
7439
  }
7341
7440
  function missingBundles(dest) {
7342
- const entrypoints = join26(dest, "src", "entrypoints");
7343
- if (!existsSync25(entrypoints)) {
7441
+ const entrypoints = join27(dest, "src", "entrypoints");
7442
+ if (!existsSync26(entrypoints)) {
7344
7443
  return [];
7345
7444
  }
7346
7445
  const expected = readdirSync6(entrypoints).filter((name) => name.endsWith(".ts") && !name.endsWith(".test.ts")).map((name) => `${name.slice(0, -3)}.mjs`);
7347
- return expected.filter((bundle) => !existsSync25(join26(dest, "dist", bundle)));
7446
+ return expected.filter((bundle) => !existsSync26(join27(dest, "dist", bundle)));
7348
7447
  }
7349
7448
  function linkedRuntimeMessage(dest, target) {
7350
7449
  return [
@@ -7392,7 +7491,7 @@ function resetFailureMessage(dest, mergeRef, gitOutput) {
7392
7491
  `);
7393
7492
  }
7394
7493
  function runtimeRevision(dest) {
7395
- if (!existsSync25(join26(dest, ".git"))) {
7494
+ if (!existsSync26(join27(dest, ".git"))) {
7396
7495
  return { revision: null, date: null };
7397
7496
  }
7398
7497
  const read = (args) => {
@@ -7435,7 +7534,7 @@ function versionText(root, style = PLAIN) {
7435
7534
  return render(versionScreen(root), style);
7436
7535
  }
7437
7536
  function pendingUpdate(dest, mergeRef) {
7438
- if (!existsSync25(join26(dest, ".git"))) {
7537
+ if (!existsSync26(join27(dest, ".git"))) {
7439
7538
  return { ok: false, reason: "the runtime path is not a git checkout", commits: 0, decisions: [] };
7440
7539
  }
7441
7540
  const fetch = spawnSync("git", ["-C", dest, "fetch", "origin"], { stdio: "inherit", env: process.env });
@@ -7486,17 +7585,17 @@ var GATE_FIELDS = {
7486
7585
  "test-command": "test",
7487
7586
  "lint-command": "lint"
7488
7587
  };
7489
- function resolveExecutable(name, env = process.env, platform = process.platform) {
7490
- const extensions = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
7491
- const candidates = (base) => [base, ...extensions.map((ext) => `${base}${ext}`)];
7588
+ var EXECUTABLE_EXTENSIONS = ["", ".exe", ".cmd", ".bat", ".ps1"];
7589
+ function resolveExecutable(name, env = process.env) {
7590
+ const candidates = (base) => EXECUTABLE_EXTENSIONS.map((ext) => `${base}${ext}`);
7492
7591
  if (name.includes("/") || name.includes("\\")) {
7493
- return candidates(name).find((candidate) => existsSync25(candidate)) ?? null;
7592
+ return candidates(name).find((candidate) => existsSync26(candidate)) ?? null;
7494
7593
  }
7495
7594
  for (const dir of (env.PATH ?? "").split(delimiter)) {
7496
7595
  if (!dir) {
7497
7596
  continue;
7498
7597
  }
7499
- const found = candidates(join26(dir, name)).find((candidate) => existsSync25(candidate));
7598
+ const found = candidates(join27(dir, name)).find((candidate) => existsSync26(candidate));
7500
7599
  if (found) {
7501
7600
  return found;
7502
7601
  }
@@ -7515,11 +7614,11 @@ function setGateCommand(root, field, argv, interactive) {
7515
7614
  throw new UsageError(`\`${binary}\` was not found on PATH, and a gate command that cannot run is a config fault ([/decisions/ad-021.md](/decisions/ad-021.md)).`);
7516
7615
  }
7517
7616
  const path = projectConfigPath(root);
7518
- const parsed = existsSync25(path) ? JSON.parse(readFileSync27(path, "utf8")) : {};
7617
+ const parsed = existsSync26(path) ? JSON.parse(readFileSync27(path, "utf8")) : {};
7519
7618
  const grind = { ...parsed.grind ?? {} };
7520
7619
  grind[field === "test" ? "testCommand" : "lintCommand"] = argv;
7521
7620
  parsed.grind = grind;
7522
- mkdirSync16(join26(root, ".tlc", "harness"), { recursive: true });
7621
+ mkdirSync17(join27(root, ".tlc", "harness"), { recursive: true });
7523
7622
  writeFileSync15(path, `${JSON.stringify(parsed, null, 2)}
7524
7623
  `, "utf8");
7525
7624
  coreFacade.policy.refreshPolicyBaselines(root);
@@ -7575,23 +7674,25 @@ function helpText(style = PLAIN) {
7575
7674
  }
7576
7675
  function pricesHelpScreen() {
7577
7676
  return {
7578
- title: "price catalogs",
7677
+ title: "prices",
7579
7678
  sections: [
7580
7679
  {
7581
- lines: ` tlc harness prices refresh [all|cursor|litellm]
7680
+ lines: ` tlc harness prices refresh [all|cursor|litellm] [--if-stale]
7582
7681
  tlc harness prices lookup <model-id>
7583
7682
 
7584
- refresh / refresh all Cursor catalog + LiteLLM fallback
7585
- refresh cursor model-prices.cursor.json (tracked)
7586
- refresh litellm model-prices.litellm.json (local)
7683
+ refresh / refresh all both planes of model-prices.json
7684
+ refresh cursor the provider's own rates
7685
+ refresh litellm the vendors' list prices
7686
+ --if-stale fetch only past the 7-day TTL
7587
7687
  lookup <model-id> catalog key, pool, USD for 1M in + 1M out
7588
7688
 
7589
- Resolution: overrides Cursor LiteLLM null
7689
+ Catalogue: <runtime home>/model-prices.json fetched per machine, never versioned
7690
+ Overrides: <runtime home>/model-prices.local.json — yours, hand-written
7590
7691
  Documentation: tlc harness help prices`.split(`
7591
7692
  `)
7592
7693
  }
7593
7694
  ],
7594
- footer: "resolution: local overrides → the provider's own catalog LiteLLM → null"
7695
+ footer: "resolution: your overrides → the asking provider's planethe vendor plane → null"
7595
7696
  };
7596
7697
  }
7597
7698
  function pricesHelpText(style = PLAIN) {
@@ -7605,11 +7706,37 @@ function resolveHarnessRoot() {
7605
7706
  return home;
7606
7707
  }
7607
7708
  }
7709
+ function wireRuntime(dest, home) {
7710
+ const lines = [];
7711
+ const seeded = seedConfig(dest);
7712
+ if (seeded.seeded) {
7713
+ lines.push(`config seeded → ${seeded.path}`);
7714
+ }
7715
+ if (!existsSync26(join27(dest, "skills", "harness-init"))) {
7716
+ return { lines, missingSkill: true };
7717
+ }
7718
+ const links = coreFacade.skill.skillLinks(dest, providerConfigDirs(), existsSync26);
7719
+ if (links.length === 0) {
7720
+ lines.push("no provider config dir found — skill not linked");
7721
+ }
7722
+ for (const link of links) {
7723
+ const outcome = linkDir(link.source, link.target);
7724
+ lines.push(outcome.kind === "refused" ? `skill not linked — ${outcome.reason}` : `skill → ${outcome.target}`);
7725
+ }
7726
+ const hooks = spawnSync(process.execPath, [join27(dest, "bin", "write-user-hooks.mjs")], {
7727
+ stdio: "inherit",
7728
+ env: { ...process.env, TLC_HOME: home }
7729
+ });
7730
+ if ((hooks.status ?? 1) !== 0) {
7731
+ lines.push("hooks unchanged (merge manually or: node bin/write-user-hooks.mjs --force)");
7732
+ }
7733
+ return { lines, missingSkill: false };
7734
+ }
7608
7735
  function execBinPath() {
7609
- return join26(resolveHarnessRoot(), "bin", "tlc-exec");
7736
+ return join27(resolveHarnessRoot(), "bin", "tlc-exec.mjs");
7610
7737
  }
7611
7738
  function buildBinPath() {
7612
- return join26(resolveHarnessRoot(), "bin", "tlc-build");
7739
+ return join27(resolveHarnessRoot(), "bin", "tlc-build.mjs");
7613
7740
  }
7614
7741
  function route(args) {
7615
7742
  const cmd = (args[0] ?? "status").toLowerCase();
@@ -7815,7 +7942,7 @@ function runUpdate(root) {
7815
7942
  const revisionBefore = runtimeRevision(dest).revision;
7816
7943
  const home = runtimeHome();
7817
7944
  console.log(`update: runtime → ${dest}`);
7818
- if (!existsSync25(join26(dest, "bin", "tlc-exec.mjs"))) {
7945
+ if (!existsSync26(join27(dest, "bin", "tlc-exec.mjs"))) {
7819
7946
  console.error(`update: missing install at ${home}`);
7820
7947
  console.error(`update: install once with \`npm i -g ${NPM_PACKAGE}\`, then \`tlc harness install\`, then retry.`);
7821
7948
  process.exit(1);
@@ -7827,13 +7954,16 @@ function runUpdate(root) {
7827
7954
  const bump = spawnSync("npm", ["install", "-g", `${NPM_PACKAGE}@latest`], {
7828
7955
  stdio: "inherit",
7829
7956
  env: process.env,
7830
- shell: process.platform === "win32"
7957
+ shell: true
7831
7958
  });
7832
7959
  if ((bump.status ?? 1) !== 0) {
7833
7960
  console.error(npmUpdateFailureMessage());
7834
7961
  process.exit(bump.status ?? 1);
7835
7962
  }
7836
- const sync = spawnSync(execBinPath(), ["install-runtime"], { stdio: "inherit", env: process.env });
7963
+ const sync = spawnSync(process.execPath, [execBinPath(), "install-runtime"], {
7964
+ stdio: "inherit",
7965
+ env: process.env
7966
+ });
7837
7967
  if ((sync.status ?? 1) !== 0) {
7838
7968
  process.exit(sync.status ?? 1);
7839
7969
  }
@@ -7860,57 +7990,32 @@ function runUpdate(root) {
7860
7990
  const after = runtimeRevision(dest).revision;
7861
7991
  console.log(revisionBefore === after ? `update: runtime already at ${after ?? "unknown"} — nothing to move` : `update: runtime ${revisionBefore ?? "unknown"} → ${after ?? "unknown"}`);
7862
7992
  }
7863
- const binDir = process.env.TLC_BIN_DIR || join26(homedir3(), ".local", "bin");
7864
- mkdirSync16(binDir, { recursive: true });
7865
- if (process.platform === "win32") {
7866
- const installPs1 = join26(dest, "install.ps1");
7867
- const r = spawnSync("powershell", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", installPs1], {
7868
- stdio: "inherit",
7869
- env: { ...process.env, TLC_HOME: home },
7870
- cwd: dest
7871
- });
7872
- if ((r.status ?? 1) !== 0) {
7873
- process.exit(r.status ?? 1);
7874
- }
7875
- } else {
7876
- const tlcBin = join26(dest, "bin", "tlc");
7877
- const skillSrc = join26(dest, "skills", "harness-init");
7878
- spawnSync("ln", ["-sfn", tlcBin, join26(binDir, "tlc")], { stdio: "inherit" });
7879
- if (!existsSync25(skillSrc)) {
7880
- console.error(`update: missing skill at ${skillSrc}`);
7881
- process.exit(1);
7882
- }
7883
- const links = coreFacade.skill.skillLinks(dest, providerConfigDirs(), existsSync25);
7884
- if (links.length === 0) {
7885
- console.log("update: no provider config dir found — skill not linked");
7886
- }
7887
- for (const link of links) {
7888
- mkdirSync16(join26(link.providerDir, "skills"), { recursive: true });
7889
- spawnSync("ln", ["-sfn", link.source, link.target], { stdio: "inherit" });
7890
- console.log(`update: skill → ${link.target}`);
7891
- }
7892
- const hooks = spawnSync(process.execPath, [join26(dest, "bin", "write-user-hooks.mjs")], {
7893
- stdio: "inherit",
7894
- env: { ...process.env, TLC_HOME: home }
7895
- });
7896
- if ((hooks.status ?? 1) !== 0) {
7897
- console.log("update: hooks unchanged (merge manually or: node bin/write-user-hooks.mjs --force)");
7898
- }
7993
+ const wired = wireRuntime(dest, home);
7994
+ for (const line of wired.lines) {
7995
+ console.log(`update: ${line}`);
7996
+ }
7997
+ if (wired.missingSkill) {
7998
+ console.error(`update: missing skill at ${join27(dest, "skills", "harness-init")}`);
7999
+ process.exit(1);
7899
8000
  }
7900
8001
  const missing = missingBundles(dest);
7901
8002
  if (missing.length === 0) {
7902
8003
  console.log("update: dist/ complete — no rebuild, so the runtime path stays clean");
7903
- } else if (existsSync25(buildBinPath())) {
8004
+ } else if (existsSync26(buildBinPath())) {
7904
8005
  console.log(`update: ${missing.length} bundle(s) missing — building`);
7905
- const build = spawnSync(buildBinPath(), [], { stdio: "inherit", env: process.env });
8006
+ const build = spawnSync(process.execPath, [buildBinPath()], { stdio: "inherit", env: process.env });
7906
8007
  if ((build.status ?? 1) !== 0) {
7907
8008
  console.log(`update: build failed — ${missing.length} bundle(s) still missing from dist/`);
7908
8009
  }
7909
8010
  }
7910
8011
  announceNewCapabilities(root, dest);
7911
8012
  announceLandedDecisions(root, dest, revisionBefore);
8013
+ spawnSync(process.execPath, [execBinPath(), "refresh-model-prices", "all", "--if-stale"], {
8014
+ stdio: "inherit",
8015
+ env: { ...process.env, TLC_PROJECT_DIR: root }
8016
+ });
7912
8017
  console.log("update: running doctor…");
7913
- const doctor = spawnSync(execBinPath(), ["doctor"], {
8018
+ const doctor = spawnSync(process.execPath, [execBinPath(), "doctor"], {
7914
8019
  stdio: "inherit",
7915
8020
  env: { ...process.env, TLC_PROJECT_DIR: root }
7916
8021
  });
@@ -7918,7 +8023,7 @@ function runUpdate(root) {
7918
8023
  process.exit(doctor.status ?? 0);
7919
8024
  }
7920
8025
  function runEntry(entry, toolArgs, root) {
7921
- const r = spawnSync(execBinPath(), [entry, ...toolArgs], {
8026
+ const r = spawnSync(process.execPath, [execBinPath(), entry, ...toolArgs], {
7922
8027
  stdio: "inherit",
7923
8028
  env: { ...process.env, TLC_PROJECT_DIR: root }
7924
8029
  });
@@ -8013,7 +8118,7 @@ function main(argv) {
8013
8118
  console.log(helpText(createStyle()));
8014
8119
  break;
8015
8120
  case "build": {
8016
- const r = spawnSync(buildBinPath(), [], { stdio: "inherit", env: process.env });
8121
+ const r = spawnSync(process.execPath, [buildBinPath()], { stdio: "inherit", env: process.env });
8017
8122
  process.exit(r.status ?? 1);
8018
8123
  break;
8019
8124
  }
@@ -8095,6 +8200,7 @@ if (__require.main == __require.module) {
8095
8200
  main(process.argv.slice(2));
8096
8201
  }
8097
8202
  export {
8203
+ wireRuntime,
8098
8204
  versionText,
8099
8205
  versionScreen,
8100
8206
  versionJson,