@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.
- package/README.md +22 -26
- package/bin/tlc-build.mjs +93 -0
- package/bin/tlc-cli.ts +110 -66
- package/bin/tlc-exec.mjs +16 -13
- package/dist/compact-before.mjs +66 -8
- package/dist/doctor.mjs +178 -41
- package/dist/help-topic.mjs +0 -0
- package/dist/init-project.mjs +2 -7
- package/dist/install-runtime.mjs +100 -17
- package/dist/lessons-cli.mjs +67 -1
- package/dist/obs-cli.mjs +64 -1
- package/dist/price-lookup.mjs +45 -23
- package/dist/prompt-submit.mjs +66 -8
- package/dist/refresh-model-prices.mjs +7190 -46
- package/dist/response-after.mjs +66 -8
- package/dist/run.mjs +66 -8
- package/dist/session-end.mjs +66 -8
- package/dist/session-start.mjs +66 -8
- package/dist/shim.mjs +66 -3
- package/dist/stop.mjs +66 -8
- package/dist/subagent-start.mjs +66 -8
- package/dist/subagent-stop.mjs +66 -8
- package/dist/support.mjs +64 -1
- package/dist/tlc-cli.mjs +193 -87
- package/dist/tool-after.mjs +111 -31
- package/dist/tool-before.mjs +66 -8
- package/dist/tool-failure.mjs +66 -8
- package/dist/uninstall-runtime.mjs +9 -10
- package/docs/log.md +2 -0
- package/docs/measure.md +35 -31
- package/package.json +4 -4
- package/src/core/core.facade.ts +7 -0
- package/src/core/index.ts +1 -0
- package/src/core/pricing/pricing.freshness.ts +118 -0
- package/src/core/skill/skill.link.ts +14 -3
- package/src/entrypoints/shim.ts +8 -2
- package/src/platform/links.ts +73 -0
- package/src/platform/pricing.ts +139 -31
- package/src/providers/cursor/cursor.wiring.ts +11 -8
- package/tools/doctor.ts +78 -8
- package/tools/init-project.ts +7 -7
- package/tools/install-runtime.ts +89 -6
- package/tools/refresh-model-prices.ts +242 -75
- package/tools/uninstall-runtime.ts +23 -19
- package/bin/tlc-build +0 -80
- package/bin/tlc-exec +0 -10
- package/bin/tlc-exec.cmd +0 -4
- package/model-aliases.json +0 -12
- package/model-prices.cursor.json +0 -410
- 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
|
|
8
|
-
lstatSync,
|
|
9
|
-
mkdirSync as
|
|
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
|
|
13
|
+
rmSync as rmSync5,
|
|
14
14
|
writeFileSync as writeFileSync15
|
|
15
15
|
} from "node:fs";
|
|
16
|
-
import {
|
|
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
|
|
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
|
|
7100
|
+
return join27(projectStateDir(root), "harness-mode");
|
|
7002
7101
|
}
|
|
7003
7102
|
function grindFlagPath(root) {
|
|
7004
|
-
return
|
|
7103
|
+
return join27(flagsDir(root), "grind-on");
|
|
7005
7104
|
}
|
|
7006
7105
|
function skipFlagPath(root) {
|
|
7007
|
-
return
|
|
7106
|
+
return join27(flagsDir(root), "skip-verify");
|
|
7008
7107
|
}
|
|
7009
7108
|
function focusFlagPath(root) {
|
|
7010
|
-
return
|
|
7109
|
+
return join27(flagsDir(root), "focus");
|
|
7011
7110
|
}
|
|
7012
7111
|
function pairedFlagPath(root) {
|
|
7013
|
-
return
|
|
7112
|
+
return join27(flagsDir(root), "paired");
|
|
7014
7113
|
}
|
|
7015
7114
|
function ensureFlagsDir(root) {
|
|
7016
|
-
|
|
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
|
|
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 (
|
|
7088
|
-
|
|
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 (
|
|
7102
|
-
|
|
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(
|
|
7423
|
+
if (probe.exists(join27(dest, ".git"))) {
|
|
7325
7424
|
return "managed";
|
|
7326
7425
|
}
|
|
7327
|
-
return probe.exists(
|
|
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
|
|
7432
|
+
return lstatSync2(path).isSymbolicLink();
|
|
7334
7433
|
} catch {
|
|
7335
7434
|
return false;
|
|
7336
7435
|
}
|
|
7337
7436
|
},
|
|
7338
|
-
exists:
|
|
7437
|
+
exists: existsSync26
|
|
7339
7438
|
});
|
|
7340
7439
|
}
|
|
7341
7440
|
function missingBundles(dest) {
|
|
7342
|
-
const entrypoints =
|
|
7343
|
-
if (!
|
|
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) => !
|
|
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 (!
|
|
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 (!
|
|
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
|
-
|
|
7490
|
-
|
|
7491
|
-
const candidates = (base) =>
|
|
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) =>
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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: "
|
|
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
|
|
7585
|
-
refresh cursor
|
|
7586
|
-
refresh litellm
|
|
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
|
-
|
|
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:
|
|
7695
|
+
footer: "resolution: your overrides → the asking provider's plane → the 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
|
|
7736
|
+
return join27(resolveHarnessRoot(), "bin", "tlc-exec.mjs");
|
|
7610
7737
|
}
|
|
7611
7738
|
function buildBinPath() {
|
|
7612
|
-
return
|
|
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 (!
|
|
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:
|
|
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(),
|
|
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
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
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 (
|
|
8004
|
+
} else if (existsSync26(buildBinPath())) {
|
|
7904
8005
|
console.log(`update: ${missing.length} bundle(s) missing — building`);
|
|
7905
|
-
const build = spawnSync(buildBinPath()
|
|
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(),
|
|
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(),
|
|
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()
|
|
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,
|