@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/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
|
|
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
|
|
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
|
|
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
|
-
|
|
7030
|
-
|
|
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
|
|
7040
|
-
|
|
7041
|
-
|
|
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
|
|
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:
|
|
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) =>
|
|
7992
|
-
const health = coreFacade.skill.linkHealth(
|
|
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 =
|
|
8002
|
-
const distSample =
|
|
8003
|
-
const
|
|
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:
|
|
8140
|
+
{ level: existsSync30(launcher) ? "ok" : "fail", name: "global runtime", detail: home },
|
|
8007
8141
|
runtimeOwnershipCheck(home),
|
|
8008
8142
|
{
|
|
8009
|
-
level:
|
|
8143
|
+
level: existsSync30(distSample) ? "ok" : "fail",
|
|
8010
8144
|
name: "dist bundles",
|
|
8011
|
-
detail:
|
|
8145
|
+
detail: existsSync30(distSample) ? join31(home, "dist") : "missing — run: tlc harness build"
|
|
8012
8146
|
},
|
|
8013
|
-
{ level:
|
|
8147
|
+
{ level: existsSync30(launcher) ? "ok" : "fail", name: "portable launcher", detail: launcher },
|
|
8014
8148
|
{
|
|
8015
|
-
level:
|
|
8149
|
+
level: onPath === null ? "fail" : "ok",
|
|
8016
8150
|
name: "CLI on PATH",
|
|
8017
|
-
detail:
|
|
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 =
|
|
8037
|
-
return cursorWiringProblems(text, { launcherPath: launcherPathOf(wiring) },
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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:
|
|
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:
|
|
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 =
|
|
8259
|
-
if (!
|
|
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,
|
package/dist/help-topic.mjs
CHANGED
|
File without changes
|
package/dist/init-project.mjs
CHANGED
|
@@ -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
|
-
|
|
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 } =
|
|
835
|
+
const { command, argsPrefix } = SHIM_COMMAND;
|
|
841
836
|
return CURSOR_SHIM_SPECS.map((spec) => ({
|
|
842
837
|
hookEvent: spec.hookEvent,
|
|
843
838
|
handler: spec.handler,
|
package/dist/install-runtime.mjs
CHANGED
|
@@ -2,8 +2,9 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
3
|
|
|
4
4
|
// tools/install-runtime.ts
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { cpSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, rmSync as rmSync2, writeFileSync } from "node:fs";
|
|
7
|
+
import { join as join3, relative, resolve, sep } from "node:path";
|
|
7
8
|
|
|
8
9
|
// src/platform/paths.ts
|
|
9
10
|
import { homedir } from "node:os";
|
|
@@ -849,6 +850,34 @@ var TOOL_KINDS = new Set([
|
|
|
849
850
|
"file.edit",
|
|
850
851
|
"file.read"
|
|
851
852
|
]);
|
|
853
|
+
// src/platform/links.ts
|
|
854
|
+
import { copyFileSync, existsSync, lstatSync, mkdirSync, rmSync, symlinkSync } from "node:fs";
|
|
855
|
+
import { dirname, join as join2 } from "node:path";
|
|
856
|
+
var LINK_TYPE = "junction";
|
|
857
|
+
function linkDir(source, target) {
|
|
858
|
+
let replaced = false;
|
|
859
|
+
if (isLink(target)) {
|
|
860
|
+
rmSync(target, { recursive: true, force: true });
|
|
861
|
+
replaced = true;
|
|
862
|
+
} else if (existsSync(target)) {
|
|
863
|
+
return {
|
|
864
|
+
kind: "refused",
|
|
865
|
+
target,
|
|
866
|
+
reason: `${target} exists and is not a link — move it aside and re-run`
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
870
|
+
symlinkSync(source, target, LINK_TYPE);
|
|
871
|
+
return { kind: replaced ? "relinked" : "linked", target, source };
|
|
872
|
+
}
|
|
873
|
+
function isLink(path) {
|
|
874
|
+
try {
|
|
875
|
+
return lstatSync(path).isSymbolicLink();
|
|
876
|
+
} catch {
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
852
881
|
// bin/tlc-cli.ts
|
|
853
882
|
var NPM_PACKAGE = "@tech-leads-club/harness-toolkit";
|
|
854
883
|
var NPM_MARKER = "installed-from-npm";
|
|
@@ -864,13 +893,10 @@ var RUNTIME_PAYLOAD = [
|
|
|
864
893
|
"src",
|
|
865
894
|
"tools",
|
|
866
895
|
"config.example.json",
|
|
867
|
-
"model-aliases.json",
|
|
868
|
-
"model-prices.cursor.json",
|
|
869
|
-
"model-prices.json",
|
|
870
896
|
"package.json"
|
|
871
897
|
];
|
|
872
898
|
var OPERATOR_OWNED = ["config.json", "state", "flags"];
|
|
873
|
-
var NOT_SHIPPED = [
|
|
899
|
+
var NOT_SHIPPED = [join3("tools", "dev"), join3("tools", "__test__")];
|
|
874
900
|
function isShipped(relativePath) {
|
|
875
901
|
const normalised = relativePath.split(sep).join("/");
|
|
876
902
|
return !NOT_SHIPPED.some((excluded) => {
|
|
@@ -890,34 +916,75 @@ function installRuntime(source, dest) {
|
|
|
890
916
|
if (resolve(source) === resolve(dest)) {
|
|
891
917
|
return { kind: "in-place", source, dest, entries: [], missing: [] };
|
|
892
918
|
}
|
|
893
|
-
|
|
919
|
+
mkdirSync2(dest, { recursive: true });
|
|
894
920
|
const entries = [];
|
|
895
921
|
const missing = [];
|
|
896
922
|
for (const entry of RUNTIME_PAYLOAD) {
|
|
897
|
-
const from =
|
|
898
|
-
if (!
|
|
923
|
+
const from = join3(source, entry);
|
|
924
|
+
if (!existsSync2(from)) {
|
|
899
925
|
missing.push(entry);
|
|
900
926
|
continue;
|
|
901
927
|
}
|
|
902
|
-
const to =
|
|
903
|
-
|
|
928
|
+
const to = join3(dest, entry);
|
|
929
|
+
rmSync2(to, { recursive: true, force: true });
|
|
904
930
|
cpSync(from, to, {
|
|
905
931
|
recursive: true,
|
|
906
932
|
filter: (src) => isShipped(relative(source, src))
|
|
907
933
|
});
|
|
908
934
|
entries.push(entry);
|
|
909
935
|
}
|
|
910
|
-
writeFileSync(
|
|
936
|
+
writeFileSync(join3(dest, NPM_MARKER), `Installed by \`tlc harness install\` from ${source}.
|
|
911
937
|
Update with: npm i -g ${NPM_PACKAGE}@latest && tlc harness install
|
|
912
938
|
`, "utf8");
|
|
913
|
-
const config =
|
|
914
|
-
const example =
|
|
915
|
-
if (!
|
|
939
|
+
const config = join3(dest, "config.json");
|
|
940
|
+
const example = join3(dest, "config.example.json");
|
|
941
|
+
if (!existsSync2(config) && existsSync2(example)) {
|
|
916
942
|
writeFileSync(config, readFileSync(example, "utf8"), "utf8");
|
|
917
943
|
}
|
|
918
944
|
return { kind: "copied", source, dest, entries, missing };
|
|
919
945
|
}
|
|
946
|
+
function linkRuntime(source, dest) {
|
|
947
|
+
if (resolve(source) === resolve(dest)) {
|
|
948
|
+
return { kind: "in-place", source, dest, entries: [], missing: [] };
|
|
949
|
+
}
|
|
950
|
+
const outcome = linkDir(resolve(source), dest);
|
|
951
|
+
if (outcome.kind === "refused") {
|
|
952
|
+
return { kind: "refused", source, dest, entries: [], missing: [], reason: outcome.reason };
|
|
953
|
+
}
|
|
954
|
+
const missing = RUNTIME_PAYLOAD.filter((entry) => !existsSync2(join3(dest, entry)));
|
|
955
|
+
return { kind: outcome.kind === "relinked" ? "relinked" : "linked", source, dest, entries: [], missing };
|
|
956
|
+
}
|
|
920
957
|
function installScreen(report) {
|
|
958
|
+
if (report.kind === "refused") {
|
|
959
|
+
return {
|
|
960
|
+
title: "harness install",
|
|
961
|
+
sections: [{ rows: [{ label: "refused", value: report.reason ?? "", level: "fail" }] }]
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
if (report.kind === "linked" || report.kind === "relinked") {
|
|
965
|
+
return {
|
|
966
|
+
title: "harness install",
|
|
967
|
+
sections: [
|
|
968
|
+
{
|
|
969
|
+
rows: [
|
|
970
|
+
{
|
|
971
|
+
label: report.kind === "linked" ? "linked" : "relinked",
|
|
972
|
+
value: `${report.dest} → ${report.source}`,
|
|
973
|
+
level: "ok"
|
|
974
|
+
},
|
|
975
|
+
...report.missing.length > 0 ? [
|
|
976
|
+
{
|
|
977
|
+
label: "incomplete",
|
|
978
|
+
value: `the checkout has no ${report.missing.join(", ")} — run the build`,
|
|
979
|
+
level: "fail"
|
|
980
|
+
}
|
|
981
|
+
] : []
|
|
982
|
+
]
|
|
983
|
+
}
|
|
984
|
+
],
|
|
985
|
+
footer: "an edit in the checkout is live in the next hook · `npm link` puts `tlc` on PATH"
|
|
986
|
+
};
|
|
987
|
+
}
|
|
921
988
|
if (report.kind === "in-place") {
|
|
922
989
|
return {
|
|
923
990
|
title: "harness install",
|
|
@@ -949,20 +1016,36 @@ function installDest(env = process.env) {
|
|
|
949
1016
|
}
|
|
950
1017
|
return runtimeHomeWasChosen(env) ? runtimeHome(env) : conventionalRuntimeHome();
|
|
951
1018
|
}
|
|
1019
|
+
function fetchPrices(dest, spawn = spawnSync) {
|
|
1020
|
+
const result = spawn(process.execPath, [join3(dest, "bin", "tlc-exec.mjs"), "refresh-model-prices"], {
|
|
1021
|
+
stdio: "inherit",
|
|
1022
|
+
env: { ...process.env, TLC_HOME: dest }
|
|
1023
|
+
});
|
|
1024
|
+
if ((result.status ?? 1) !== 0) {
|
|
1025
|
+
console.log("install: prices not fetched — cost estimates stay empty until `tlc harness prices refresh`");
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
952
1028
|
if (__require.main == __require.module) {
|
|
953
|
-
const
|
|
1029
|
+
const link = process.argv.includes("--link");
|
|
1030
|
+
const source = link ? process.cwd() : originRoot();
|
|
954
1031
|
const dest = installDest();
|
|
955
|
-
const report = installRuntime(source, dest);
|
|
1032
|
+
const report = link ? linkRuntime(source, dest) : installRuntime(source, dest);
|
|
956
1033
|
console.log(installReportText(report, createStyle()));
|
|
1034
|
+
if (report.kind === "refused") {
|
|
1035
|
+
process.exit(1);
|
|
1036
|
+
}
|
|
1037
|
+
fetchPrices(dest);
|
|
957
1038
|
process.exit(report.missing.length > 0 ? 1 : 0);
|
|
958
1039
|
}
|
|
959
1040
|
export {
|
|
960
1041
|
originRoot,
|
|
1042
|
+
linkRuntime,
|
|
961
1043
|
isShipped,
|
|
962
1044
|
installScreen,
|
|
963
1045
|
installRuntime,
|
|
964
1046
|
installReportText,
|
|
965
1047
|
installDest,
|
|
1048
|
+
fetchPrices,
|
|
966
1049
|
RUNTIME_PAYLOAD,
|
|
967
1050
|
OPERATOR_OWNED,
|
|
968
1051
|
NOT_SHIPPED
|
package/dist/lessons-cli.mjs
CHANGED
|
@@ -5258,6 +5258,62 @@ function release(root, provider, session) {
|
|
|
5258
5258
|
deletePresenceRecord(root, provider, session);
|
|
5259
5259
|
}
|
|
5260
5260
|
|
|
5261
|
+
// src/core/pricing/pricing.freshness.ts
|
|
5262
|
+
var DEFAULT_TTL_DAYS = 7;
|
|
5263
|
+
var MS_PER_DAY = 86400000;
|
|
5264
|
+
function freshness(meta, now, ttlDays = DEFAULT_TTL_DAYS) {
|
|
5265
|
+
if (meta === null) {
|
|
5266
|
+
return { state: "absent" };
|
|
5267
|
+
}
|
|
5268
|
+
const stamp = meta.refreshedAt;
|
|
5269
|
+
if (stamp === undefined || Number.isNaN(Date.parse(stamp))) {
|
|
5270
|
+
return { state: "undated" };
|
|
5271
|
+
}
|
|
5272
|
+
const ageMs = now.getTime() - Date.parse(stamp);
|
|
5273
|
+
const ageDays = Math.max(0, ageMs / MS_PER_DAY);
|
|
5274
|
+
return ageDays > ttlDays ? { state: "stale", ageDays, refreshedAt: stamp } : { state: "fresh", ageDays, refreshedAt: stamp };
|
|
5275
|
+
}
|
|
5276
|
+
function shouldRefetch(state) {
|
|
5277
|
+
return state.state === "absent" || state.state === "undated" || state.state === "stale";
|
|
5278
|
+
}
|
|
5279
|
+
function freshnessMessage(state, catalogue) {
|
|
5280
|
+
switch (state.state) {
|
|
5281
|
+
case "absent":
|
|
5282
|
+
return `${catalogue}: not on this machine — run \`tlc harness prices refresh\``;
|
|
5283
|
+
case "undated":
|
|
5284
|
+
return `${catalogue}: present but carries no date — it will be refetched`;
|
|
5285
|
+
case "fresh":
|
|
5286
|
+
return `${catalogue}: ${describeAge(state.ageDays)} old`;
|
|
5287
|
+
default:
|
|
5288
|
+
return `${catalogue}: ${describeAge(state.ageDays)} old — run \`tlc harness prices refresh\``;
|
|
5289
|
+
}
|
|
5290
|
+
}
|
|
5291
|
+
var MIN_RETAINED_RATIO = 0.5;
|
|
5292
|
+
function mayReplace(existingCount, incomingCount, minRatio = MIN_RETAINED_RATIO) {
|
|
5293
|
+
if (incomingCount === 0) {
|
|
5294
|
+
return { replace: false, reason: "parsed no entries at all — the upstream format has changed" };
|
|
5295
|
+
}
|
|
5296
|
+
if (existingCount === 0) {
|
|
5297
|
+
return { replace: true, reason: `first catalogue, ${incomingCount} entries` };
|
|
5298
|
+
}
|
|
5299
|
+
if (incomingCount >= existingCount) {
|
|
5300
|
+
return { replace: true, reason: `${existingCount} → ${incomingCount} entries` };
|
|
5301
|
+
}
|
|
5302
|
+
const retained = incomingCount / existingCount;
|
|
5303
|
+
return retained >= minRatio ? { replace: true, reason: `${existingCount} → ${incomingCount} entries` } : {
|
|
5304
|
+
replace: false,
|
|
5305
|
+
reason: `would drop from ${existingCount} to ${incomingCount} entries, keeping the existing catalogue — the upstream format has probably changed`
|
|
5306
|
+
};
|
|
5307
|
+
}
|
|
5308
|
+
function describeAge(ageDays) {
|
|
5309
|
+
if (ageDays < 1) {
|
|
5310
|
+
const hours = Math.max(1, Math.round(ageDays * 24));
|
|
5311
|
+
return `${hours} hour${hours === 1 ? "" : "s"}`;
|
|
5312
|
+
}
|
|
5313
|
+
const days = Math.round(ageDays);
|
|
5314
|
+
return `${days} day${days === 1 ? "" : "s"}`;
|
|
5315
|
+
}
|
|
5316
|
+
|
|
5261
5317
|
// src/core/release/release.decisions.ts
|
|
5262
5318
|
import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync19 } from "node:fs";
|
|
5263
5319
|
import { join as join18 } from "node:path";
|
|
@@ -5607,7 +5663,8 @@ function linkHealth(target, runtimeHome2, probe) {
|
|
|
5607
5663
|
if (!probe.exists(resolved)) {
|
|
5608
5664
|
return { state: "dangling", target, resolved };
|
|
5609
5665
|
}
|
|
5610
|
-
const
|
|
5666
|
+
const resolveHome = probe.realpath ?? ((path) => path);
|
|
5667
|
+
const home = resolveHome(runtimeHome2).replace(/\/+$/, "");
|
|
5611
5668
|
return resolved === home || resolved.startsWith(`${home}/`) ? { state: "ok", target, resolved } : { state: "outside-runtime", target, resolved };
|
|
5612
5669
|
}
|
|
5613
5670
|
function linkHealthMessage(health) {
|
|
@@ -6754,6 +6811,12 @@ var coreFacade = {
|
|
|
6754
6811
|
coversHandler,
|
|
6755
6812
|
decideShim
|
|
6756
6813
|
},
|
|
6814
|
+
pricing: {
|
|
6815
|
+
freshness,
|
|
6816
|
+
freshnessMessage,
|
|
6817
|
+
mayReplace,
|
|
6818
|
+
shouldRefetch
|
|
6819
|
+
},
|
|
6757
6820
|
skill: {
|
|
6758
6821
|
linkHealth,
|
|
6759
6822
|
linkHealthMessage,
|
|
@@ -6972,6 +7035,9 @@ var NO_HUMAN_MODES = new Set(["bypassPermissions", "dontAsk"]);
|
|
|
6972
7035
|
// bin/write-user-hooks.mjs
|
|
6973
7036
|
if (false) {}
|
|
6974
7037
|
|
|
7038
|
+
// src/platform/pricing.ts
|
|
7039
|
+
var cache = new Map;
|
|
7040
|
+
|
|
6975
7041
|
// tools/doctor.ts
|
|
6976
7042
|
function plural(count, word) {
|
|
6977
7043
|
return `${count} ${word}${count === 1 ? "" : "s"}`;
|