@tech-leads-club/harness-toolkit 0.2.4 → 0.3.1
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 +68 -37
- package/bin/tlc-build.mjs +93 -0
- package/bin/tlc-cli.ts +110 -66
- package/bin/tlc-exec.mjs +16 -13
- package/config.example.json +1 -1
- 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 +6594 -281
- 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 +5 -5
- 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 +110 -7
- 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/compact-before.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,
|
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,
|