@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/obs-cli.mjs
CHANGED
|
@@ -5262,6 +5262,62 @@ function release(root, provider, session) {
|
|
|
5262
5262
|
deletePresenceRecord(root, provider, session);
|
|
5263
5263
|
}
|
|
5264
5264
|
|
|
5265
|
+
// src/core/pricing/pricing.freshness.ts
|
|
5266
|
+
var DEFAULT_TTL_DAYS = 7;
|
|
5267
|
+
var MS_PER_DAY = 86400000;
|
|
5268
|
+
function freshness(meta, now, ttlDays = DEFAULT_TTL_DAYS) {
|
|
5269
|
+
if (meta === null) {
|
|
5270
|
+
return { state: "absent" };
|
|
5271
|
+
}
|
|
5272
|
+
const stamp = meta.refreshedAt;
|
|
5273
|
+
if (stamp === undefined || Number.isNaN(Date.parse(stamp))) {
|
|
5274
|
+
return { state: "undated" };
|
|
5275
|
+
}
|
|
5276
|
+
const ageMs = now.getTime() - Date.parse(stamp);
|
|
5277
|
+
const ageDays = Math.max(0, ageMs / MS_PER_DAY);
|
|
5278
|
+
return ageDays > ttlDays ? { state: "stale", ageDays, refreshedAt: stamp } : { state: "fresh", ageDays, refreshedAt: stamp };
|
|
5279
|
+
}
|
|
5280
|
+
function shouldRefetch(state) {
|
|
5281
|
+
return state.state === "absent" || state.state === "undated" || state.state === "stale";
|
|
5282
|
+
}
|
|
5283
|
+
function freshnessMessage(state, catalogue) {
|
|
5284
|
+
switch (state.state) {
|
|
5285
|
+
case "absent":
|
|
5286
|
+
return `${catalogue}: not on this machine — run \`tlc harness prices refresh\``;
|
|
5287
|
+
case "undated":
|
|
5288
|
+
return `${catalogue}: present but carries no date — it will be refetched`;
|
|
5289
|
+
case "fresh":
|
|
5290
|
+
return `${catalogue}: ${describeAge(state.ageDays)} old`;
|
|
5291
|
+
default:
|
|
5292
|
+
return `${catalogue}: ${describeAge(state.ageDays)} old — run \`tlc harness prices refresh\``;
|
|
5293
|
+
}
|
|
5294
|
+
}
|
|
5295
|
+
var MIN_RETAINED_RATIO = 0.5;
|
|
5296
|
+
function mayReplace(existingCount, incomingCount, minRatio = MIN_RETAINED_RATIO) {
|
|
5297
|
+
if (incomingCount === 0) {
|
|
5298
|
+
return { replace: false, reason: "parsed no entries at all — the upstream format has changed" };
|
|
5299
|
+
}
|
|
5300
|
+
if (existingCount === 0) {
|
|
5301
|
+
return { replace: true, reason: `first catalogue, ${incomingCount} entries` };
|
|
5302
|
+
}
|
|
5303
|
+
if (incomingCount >= existingCount) {
|
|
5304
|
+
return { replace: true, reason: `${existingCount} → ${incomingCount} entries` };
|
|
5305
|
+
}
|
|
5306
|
+
const retained = incomingCount / existingCount;
|
|
5307
|
+
return retained >= minRatio ? { replace: true, reason: `${existingCount} → ${incomingCount} entries` } : {
|
|
5308
|
+
replace: false,
|
|
5309
|
+
reason: `would drop from ${existingCount} to ${incomingCount} entries, keeping the existing catalogue — the upstream format has probably changed`
|
|
5310
|
+
};
|
|
5311
|
+
}
|
|
5312
|
+
function describeAge(ageDays) {
|
|
5313
|
+
if (ageDays < 1) {
|
|
5314
|
+
const hours = Math.max(1, Math.round(ageDays * 24));
|
|
5315
|
+
return `${hours} hour${hours === 1 ? "" : "s"}`;
|
|
5316
|
+
}
|
|
5317
|
+
const days = Math.round(ageDays);
|
|
5318
|
+
return `${days} day${days === 1 ? "" : "s"}`;
|
|
5319
|
+
}
|
|
5320
|
+
|
|
5265
5321
|
// src/core/release/release.decisions.ts
|
|
5266
5322
|
import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync19 } from "node:fs";
|
|
5267
5323
|
import { join as join18 } from "node:path";
|
|
@@ -5611,7 +5667,8 @@ function linkHealth(target, runtimeHome2, probe) {
|
|
|
5611
5667
|
if (!probe.exists(resolved)) {
|
|
5612
5668
|
return { state: "dangling", target, resolved };
|
|
5613
5669
|
}
|
|
5614
|
-
const
|
|
5670
|
+
const resolveHome = probe.realpath ?? ((path) => path);
|
|
5671
|
+
const home = resolveHome(runtimeHome2).replace(/\/+$/, "");
|
|
5615
5672
|
return resolved === home || resolved.startsWith(`${home}/`) ? { state: "ok", target, resolved } : { state: "outside-runtime", target, resolved };
|
|
5616
5673
|
}
|
|
5617
5674
|
function linkHealthMessage(health) {
|
|
@@ -6758,6 +6815,12 @@ var coreFacade = {
|
|
|
6758
6815
|
coversHandler,
|
|
6759
6816
|
decideShim
|
|
6760
6817
|
},
|
|
6818
|
+
pricing: {
|
|
6819
|
+
freshness,
|
|
6820
|
+
freshnessMessage,
|
|
6821
|
+
mayReplace,
|
|
6822
|
+
shouldRefetch
|
|
6823
|
+
},
|
|
6761
6824
|
skill: {
|
|
6762
6825
|
linkHealth,
|
|
6763
6826
|
linkHealthMessage,
|
package/dist/price-lookup.mjs
CHANGED
|
@@ -38,16 +38,11 @@ function runtimeHome(env = process.env) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// src/platform/pricing.ts
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
47
|
-
} catch {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
41
|
+
var FALLBACK_PLANE = "litellm";
|
|
42
|
+
var MODEL_ALIASES = {
|
|
43
|
+
"cursor-grok-4.5": "grok-4.5",
|
|
44
|
+
auto: "auto-cost"
|
|
45
|
+
};
|
|
51
46
|
function stripMeta(table) {
|
|
52
47
|
if (!table) {
|
|
53
48
|
return {};
|
|
@@ -56,7 +51,7 @@ function stripMeta(table) {
|
|
|
56
51
|
return rest;
|
|
57
52
|
}
|
|
58
53
|
function slugifyModelName(name) {
|
|
59
|
-
return name.trim().toLowerCase().replace(
|
|
54
|
+
return name.trim().toLowerCase().replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").replace(/[[\]]/g, "").replace(/[^a-z0-9.+]+/g, "-").replace(/^-+|-+$/g, "");
|
|
60
55
|
}
|
|
61
56
|
function candidatesFor(model, aliases) {
|
|
62
57
|
const trimmed = model.trim();
|
|
@@ -93,28 +88,55 @@ function fuzzyFind(table, needle) {
|
|
|
93
88
|
}
|
|
94
89
|
return;
|
|
95
90
|
}
|
|
96
|
-
function
|
|
91
|
+
function cataloguePath() {
|
|
97
92
|
return join2(runtimeHome(), "model-prices.json");
|
|
98
93
|
}
|
|
99
|
-
function
|
|
100
|
-
return join2(runtimeHome(),
|
|
94
|
+
function overridesPath() {
|
|
95
|
+
return join2(runtimeHome(), "model-prices.local.json");
|
|
96
|
+
}
|
|
97
|
+
var cache = new Map;
|
|
98
|
+
function readCatalogue(path) {
|
|
99
|
+
if (!existsSync(path)) {
|
|
100
|
+
cache.delete(path);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
let text;
|
|
104
|
+
try {
|
|
105
|
+
text = readFileSync(path, "utf8");
|
|
106
|
+
} catch {
|
|
107
|
+
cache.delete(path);
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const hit = cache.get(path);
|
|
111
|
+
if (hit && hit.text === text) {
|
|
112
|
+
return hit.value;
|
|
113
|
+
}
|
|
114
|
+
let parsed;
|
|
115
|
+
try {
|
|
116
|
+
parsed = JSON.parse(text);
|
|
117
|
+
} catch {
|
|
118
|
+
cache.delete(path);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
cache.set(path, { text, value: parsed });
|
|
122
|
+
return parsed;
|
|
101
123
|
}
|
|
102
|
-
function
|
|
103
|
-
return
|
|
124
|
+
function loadCatalogue() {
|
|
125
|
+
return readCatalogue(cataloguePath()) ?? {};
|
|
104
126
|
}
|
|
105
|
-
function
|
|
106
|
-
return
|
|
127
|
+
function planeFor(catalogue, plane) {
|
|
128
|
+
return stripMeta(catalogue.planes?.[plane] ?? null);
|
|
107
129
|
}
|
|
108
130
|
function resolveModelPrice(provider, model) {
|
|
109
131
|
const trimmed = model.trim();
|
|
110
132
|
if (!trimmed) {
|
|
111
133
|
return;
|
|
112
134
|
}
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const candidates = candidatesFor(trimmed,
|
|
135
|
+
const catalogue = loadCatalogue();
|
|
136
|
+
const overrides = stripMeta(readCatalogue(overridesPath()));
|
|
137
|
+
const native = provider ? planeFor(catalogue, provider) : {};
|
|
138
|
+
const litellm = planeFor(catalogue, FALLBACK_PLANE);
|
|
139
|
+
const candidates = candidatesFor(trimmed, MODEL_ALIASES);
|
|
118
140
|
for (const id of candidates) {
|
|
119
141
|
const entry = overrides[id];
|
|
120
142
|
if (entry) {
|
package/dist/prompt-submit.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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,
|