@tech-leads-club/harness-toolkit 0.2.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/CHANGELOG.md +95 -0
- package/LICENSE +84 -0
- package/NOTICE +9 -0
- package/README.md +480 -0
- package/bin/tlc +10 -0
- package/bin/tlc-build +80 -0
- package/bin/tlc-cli.ts +1509 -0
- package/bin/tlc-exec +10 -0
- package/bin/tlc-exec.cmd +4 -0
- package/bin/tlc-exec.d.mts +60 -0
- package/bin/tlc-exec.mjs +248 -0
- package/bin/tlc.cmd +4 -0
- package/bin/tlc.mjs +7 -0
- package/bin/write-user-hooks.d.mts +40 -0
- package/bin/write-user-hooks.mjs +123 -0
- package/capabilities/catalog.json +418 -0
- package/config.example.json +51 -0
- package/dist/compact-before.mjs +7839 -0
- package/dist/doctor.mjs +8263 -0
- package/dist/help-topic.mjs +82 -0
- package/dist/init-project.mjs +993 -0
- package/dist/install-runtime.mjs +969 -0
- package/dist/lessons-cli.mjs +7258 -0
- package/dist/obs-cli.mjs +7025 -0
- package/dist/price-lookup.mjs +221 -0
- package/dist/prompt-submit.mjs +7860 -0
- package/dist/refresh-model-prices.mjs +158 -0
- package/dist/response-after.mjs +7879 -0
- package/dist/run.mjs +7830 -0
- package/dist/session-end.mjs +7937 -0
- package/dist/session-start.mjs +8056 -0
- package/dist/shim.mjs +52 -0
- package/dist/stop.mjs +8494 -0
- package/dist/subagent-start.mjs +7861 -0
- package/dist/subagent-stop.mjs +7851 -0
- package/dist/support.mjs +7045 -0
- package/dist/tlc-cli.mjs +8063 -0
- package/dist/tool-after.mjs +8113 -0
- package/dist/tool-before.mjs +7985 -0
- package/dist/tool-failure.mjs +7850 -0
- package/dist/uninstall-runtime.mjs +1307 -0
- package/docs/architecture.md +270 -0
- package/docs/concepts.md +562 -0
- package/docs/coverage.md +55 -0
- package/docs/decisions/ad-001.md +32 -0
- package/docs/decisions/ad-002.md +51 -0
- package/docs/decisions/ad-003.md +30 -0
- package/docs/decisions/ad-004.md +37 -0
- package/docs/decisions/ad-005.md +36 -0
- package/docs/decisions/ad-006.md +49 -0
- package/docs/decisions/ad-007.md +36 -0
- package/docs/decisions/ad-008.md +54 -0
- package/docs/decisions/ad-009.md +61 -0
- package/docs/decisions/ad-010.md +45 -0
- package/docs/decisions/ad-011.md +59 -0
- package/docs/decisions/ad-012.md +71 -0
- package/docs/decisions/ad-013.md +87 -0
- package/docs/decisions/ad-014.md +56 -0
- package/docs/decisions/ad-015.md +33 -0
- package/docs/decisions/ad-016.md +98 -0
- package/docs/decisions/ad-017.md +65 -0
- package/docs/decisions/ad-018.md +77 -0
- package/docs/decisions/ad-019.md +75 -0
- package/docs/decisions/ad-020.md +88 -0
- package/docs/decisions/ad-021.md +57 -0
- package/docs/decisions/ad-022.md +120 -0
- package/docs/decisions/ad-023.md +87 -0
- package/docs/decisions/ad-024.md +85 -0
- package/docs/decisions/ad-025.md +92 -0
- package/docs/decisions/ad-026.md +131 -0
- package/docs/decisions/ad-027.md +90 -0
- package/docs/decisions/ad-028.md +90 -0
- package/docs/decisions/ad-029.md +73 -0
- package/docs/decisions/ad-030.md +97 -0
- package/docs/decisions/ad-031.md +94 -0
- package/docs/decisions/ad-032.md +83 -0
- package/docs/decisions/ad-033.md +89 -0
- package/docs/decisions/ad-034.md +86 -0
- package/docs/decisions/ad-035.md +86 -0
- package/docs/decisions/ad-036.md +68 -0
- package/docs/decisions/ad-037.md +47 -0
- package/docs/decisions/ad-038.md +52 -0
- package/docs/decisions/ad-039.md +69 -0
- package/docs/decisions/ad-040.md +89 -0
- package/docs/decisions/ad-041.md +98 -0
- package/docs/decisions/ad-042.md +82 -0
- package/docs/decisions/ad-043.md +79 -0
- package/docs/decisions/ad-044.md +61 -0
- package/docs/decisions/ad-045.md +94 -0
- package/docs/decisions/ad-046.md +111 -0
- package/docs/decisions/ad-047.md +96 -0
- package/docs/decisions/ad-048.md +85 -0
- package/docs/decisions/ad-049.md +66 -0
- package/docs/decisions/ad-050.md +94 -0
- package/docs/decisions/ad-051.md +69 -0
- package/docs/decisions/ad-052.md +69 -0
- package/docs/decisions/ad-053.md +78 -0
- package/docs/decisions/ad-054.md +98 -0
- package/docs/decisions/ad-055.md +74 -0
- package/docs/decisions/ad-056.md +85 -0
- package/docs/decisions/ad-057.md +68 -0
- package/docs/decisions/ad-058.md +97 -0
- package/docs/decisions/ad-059.md +82 -0
- package/docs/decisions/ad-060.md +75 -0
- package/docs/decisions/ad-061.md +68 -0
- package/docs/decisions/ad-062.md +72 -0
- package/docs/decisions/ad-063.md +84 -0
- package/docs/decisions/ad-064.md +79 -0
- package/docs/decisions/ad-065.md +81 -0
- package/docs/decisions/ad-066.md +111 -0
- package/docs/decisions/ad-067.md +64 -0
- package/docs/decisions/ad-068.md +79 -0
- package/docs/decisions/ad-069.md +74 -0
- package/docs/decisions/ad-070.md +86 -0
- package/docs/decisions/ad-071.md +93 -0
- package/docs/decisions/ad-072.md +82 -0
- package/docs/decisions/ad-073.md +102 -0
- package/docs/decisions/ad-074.md +91 -0
- package/docs/decisions/ad-075.md +79 -0
- package/docs/decisions/ad-076.md +102 -0
- package/docs/decisions/ad-077.md +94 -0
- package/docs/decisions/ad-078.md +84 -0
- package/docs/decisions/ad-079.md +73 -0
- package/docs/decisions/ad-080.md +86 -0
- package/docs/decisions/ad-081.md +70 -0
- package/docs/decisions/ad-082.md +79 -0
- package/docs/decisions/ad-083.md +88 -0
- package/docs/decisions/index.md +111 -0
- package/docs/diagnose.md +248 -0
- package/docs/index.md +52 -0
- package/docs/init.md +101 -0
- package/docs/lessons.md +294 -0
- package/docs/log.md +139 -0
- package/docs/measure.md +178 -0
- package/docs/providers/claude-code.md +118 -0
- package/docs/providers/cursor.md +110 -0
- package/docs/providers/index.md +71 -0
- package/docs/troubleshooting.md +138 -0
- package/model-aliases.json +12 -0
- package/model-prices.cursor.json +410 -0
- package/model-prices.json +1 -0
- package/package.json +57 -0
- package/skills/harness-init/SKILL.md +241 -0
- package/skills/harness-init/evals/trigger_evals.json +86 -0
- package/skills/harness-init/references/capabilities.md +113 -0
- package/src/contracts/capabilities.ts +30 -0
- package/src/contracts/decision.ts +21 -0
- package/src/contracts/effort.ts +15 -0
- package/src/contracts/harness-event.ts +87 -0
- package/src/contracts/index.ts +6 -0
- package/src/contracts/policy-defaults.ts +14 -0
- package/src/contracts/wiring.ts +20 -0
- package/src/core/attest/attest.service.ts +0 -0
- package/src/core/capability/capability.service.ts +62 -0
- package/src/core/capability/capability.store.ts +51 -0
- package/src/core/capability/capability.types.ts +56 -0
- package/src/core/comment-policy/comment-policy.service.ts +307 -0
- package/src/core/comment-policy/comment-policy.types.ts +6 -0
- package/src/core/comment-policy/comment-resolvability.ts +134 -0
- package/src/core/comment-policy/comment-syntax.catalog.ts +310 -0
- package/src/core/comment-policy/comment-syntax.store.ts +65 -0
- package/src/core/comment-policy/comment-syntax.types.ts +24 -0
- package/src/core/core.facade.ts +461 -0
- package/src/core/duplication/duplication.service.ts +219 -0
- package/src/core/floor/floor.catalog.ts +54 -0
- package/src/core/floor/floor.paths.ts +90 -0
- package/src/core/floor/floor.policy-surface.ts +311 -0
- package/src/core/floor/floor.service.ts +306 -0
- package/src/core/floor/floor.tokenize.ts +179 -0
- package/src/core/floor/floor.verb.ts +28 -0
- package/src/core/floor/index.ts +1 -0
- package/src/core/gate/gate.artifact.ts +157 -0
- package/src/core/gate/gate.command.ts +130 -0
- package/src/core/gate/gate.findings.ts +201 -0
- package/src/core/gate/gate.inputs.ts +0 -0
- package/src/core/gate/gate.lock.ts +283 -0
- package/src/core/gate/gate.service.ts +28 -0
- package/src/core/gate/gate.types.ts +67 -0
- package/src/core/handoff/handoff.service.ts +43 -0
- package/src/core/handoff/handoff.store.ts +59 -0
- package/src/core/handoff/handoff.types.ts +89 -0
- package/src/core/index.ts +52 -0
- package/src/core/integrity/state-seal.ts +81 -0
- package/src/core/lesson/lesson.authored.ts +96 -0
- package/src/core/lesson/lesson.credit.ts +51 -0
- package/src/core/lesson/lesson.garden.ts +283 -0
- package/src/core/lesson/lesson.link.ts +93 -0
- package/src/core/lesson/lesson.score.ts +61 -0
- package/src/core/lesson/lesson.select.ts +209 -0
- package/src/core/lesson/lesson.service.ts +110 -0
- package/src/core/lesson/lesson.store.ts +317 -0
- package/src/core/lesson/lesson.sync.ts +70 -0
- package/src/core/lesson/lesson.types.ts +92 -0
- package/src/core/lesson/lesson.validity.ts +44 -0
- package/src/core/observability/observability.report.ts +326 -0
- package/src/core/observability/observability.service.ts +328 -0
- package/src/core/observability/observability.store.ts +269 -0
- package/src/core/observability/observability.types.ts +193 -0
- package/src/core/observability/observability.why.ts +206 -0
- package/src/core/observe/observe.service.ts +76 -0
- package/src/core/plan/plan.detect.ts +47 -0
- package/src/core/plan/plan.service.ts +81 -0
- package/src/core/plan/plan.types.ts +25 -0
- package/src/core/policy/policy.defaults.ts +103 -0
- package/src/core/policy/policy.guard.ts +34 -0
- package/src/core/policy/policy.integrity.ts +238 -0
- package/src/core/policy/policy.loader.ts +118 -0
- package/src/core/policy/policy.operator.ts +82 -0
- package/src/core/policy/policy.posture.ts +79 -0
- package/src/core/policy/policy.rails.ts +45 -0
- package/src/core/policy/policy.types.ts +157 -0
- package/src/core/presence/presence.service.ts +111 -0
- package/src/core/presence/presence.store.ts +59 -0
- package/src/core/presence/presence.types.ts +9 -0
- package/src/core/release/release.decisions.ts +147 -0
- package/src/core/release/release.seen.ts +38 -0
- package/src/core/shell-policy/shell-policy.service.ts +215 -0
- package/src/core/shell-policy/shell-policy.stall.ts +57 -0
- package/src/core/shell-policy/shell-policy.types.ts +26 -0
- package/src/core/ship/ship.ledger.ts +76 -0
- package/src/core/ship/ship.service.ts +137 -0
- package/src/core/ship/ship.types.ts +19 -0
- package/src/core/stagnation/stagnation.resolution.ts +87 -0
- package/src/core/stagnation/stagnation.service.ts +20 -0
- package/src/core/stagnation/stagnation.store.ts +49 -0
- package/src/core/stagnation/stagnation.types.ts +12 -0
- package/src/core/subagent-policy/subagent-policy.parent-model.ts +202 -0
- package/src/core/subagent-policy/subagent-policy.service.ts +139 -0
- package/src/core/subagent-policy/subagent-policy.types.ts +22 -0
- package/src/core/supply-chain/supply-chain.catalog.ts +51 -0
- package/src/core/supply-chain/supply-chain.service.ts +217 -0
- package/src/core/turn/turn.activity.ts +94 -0
- package/src/core/turn/turn.autopilot.ts +131 -0
- package/src/core/turn/turn.failure-signals.ts +193 -0
- package/src/core/turn/turn.loop-counter.ts +71 -0
- package/src/core/turn/turn.types.ts +14 -0
- package/src/core/untrusted/untrusted.detect.ts +53 -0
- package/src/core/untrusted/untrusted.recall.ts +87 -0
- package/src/core/untrusted/untrusted.service.ts +129 -0
- package/src/core/untrusted/untrusted.store.ts +75 -0
- package/src/core/untrusted/untrusted.types.ts +42 -0
- package/src/entrypoints/compact-before.ts +14 -0
- package/src/entrypoints/prompt-submit.ts +25 -0
- package/src/entrypoints/response-after.ts +60 -0
- package/src/entrypoints/run.ts +179 -0
- package/src/entrypoints/session-end.ts +58 -0
- package/src/entrypoints/session-start.ts +172 -0
- package/src/entrypoints/shim.ts +46 -0
- package/src/entrypoints/stop.ts +876 -0
- package/src/entrypoints/subagent-start.ts +16 -0
- package/src/entrypoints/subagent-stop.ts +32 -0
- package/src/entrypoints/support.ts +182 -0
- package/src/entrypoints/tool-after.ts +111 -0
- package/src/entrypoints/tool-before.ts +186 -0
- package/src/entrypoints/tool-failure.ts +26 -0
- package/src/platform/backoff.ts +49 -0
- package/src/platform/cli-output.ts +30 -0
- package/src/platform/env-scope.ts +22 -0
- package/src/platform/fs-atomic.ts +142 -0
- package/src/platform/fs-jsonl.ts +24 -0
- package/src/platform/git.ts +187 -0
- package/src/platform/paths.ts +80 -0
- package/src/platform/pricing.ts +229 -0
- package/src/platform/process.ts +64 -0
- package/src/platform/sanitize.ts +23 -0
- package/src/platform/screen.ts +46 -0
- package/src/platform/style.ts +103 -0
- package/src/providers/claude/claude.capabilities.ts +25 -0
- package/src/providers/claude/claude.detect.ts +14 -0
- package/src/providers/claude/claude.inbound.ts +252 -0
- package/src/providers/claude/claude.lessons-view.ts +33 -0
- package/src/providers/claude/claude.outbound.ts +65 -0
- package/src/providers/claude/claude.policy-defaults.ts +9 -0
- package/src/providers/claude/claude.transcript.ts +62 -0
- package/src/providers/claude/claude.wiring.ts +245 -0
- package/src/providers/claude/index.ts +17 -0
- package/src/providers/cursor/cursor.capabilities.ts +28 -0
- package/src/providers/cursor/cursor.detect.ts +14 -0
- package/src/providers/cursor/cursor.inbound.ts +203 -0
- package/src/providers/cursor/cursor.lessons-view.ts +30 -0
- package/src/providers/cursor/cursor.outbound.ts +43 -0
- package/src/providers/cursor/cursor.policy-defaults.ts +9 -0
- package/src/providers/cursor/cursor.wiring.ts +214 -0
- package/src/providers/cursor/index.ts +17 -0
- package/src/providers/index.ts +9 -0
- package/src/providers/provider.degrade.ts +152 -0
- package/src/providers/provider.port.ts +20 -0
- package/src/providers/provider.registry.ts +28 -0
- package/tools/doctor.ts +631 -0
- package/tools/help-topic.ts +74 -0
- package/tools/init-project.ts +277 -0
- package/tools/install-runtime.ts +164 -0
- package/tools/lessons-cli.ts +474 -0
- package/tools/obs-cli.ts +175 -0
- package/tools/price-lookup.ts +57 -0
- package/tools/refresh-model-prices.ts +210 -0
- package/tools/test-env.mjs +28 -0
- package/tools/test-env.names.d.mts +1 -0
- package/tools/test-env.names.mjs +14 -0
- package/tools/uninstall-runtime.ts +463 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { emitJson, takeJsonFlag } from "../src/platform/cli-output.ts";
|
|
2
|
+
import {
|
|
3
|
+
type CostEstimate,
|
|
4
|
+
estimateCostUsd,
|
|
5
|
+
type PriceResolution,
|
|
6
|
+
resolveModelPrice,
|
|
7
|
+
} from "../src/platform/pricing.ts";
|
|
8
|
+
|
|
9
|
+
export type PriceLookupArgs = { model: string; provider: string };
|
|
10
|
+
export type PriceLookupResult = {
|
|
11
|
+
model: string;
|
|
12
|
+
provider: string;
|
|
13
|
+
resolved: PriceResolution | undefined;
|
|
14
|
+
per1M: CostEstimate;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function parsePriceLookupArgs(argv: readonly string[]): PriceLookupArgs | null {
|
|
18
|
+
const model = argv[0];
|
|
19
|
+
if (!model) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return { model, provider: argv[1] ?? "" };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function lookupPrice(args: PriceLookupArgs): PriceLookupResult {
|
|
26
|
+
const resolved = resolveModelPrice(args.provider, args.model);
|
|
27
|
+
const per1M = estimateCostUsd(args.provider, args.model, {
|
|
28
|
+
inputTokens: 1_000_000,
|
|
29
|
+
outputTokens: 1_000_000,
|
|
30
|
+
});
|
|
31
|
+
return { model: args.model, provider: args.provider, resolved, per1M };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function main(): void {
|
|
35
|
+
const { json, rest } = takeJsonFlag(process.argv.slice(2));
|
|
36
|
+
const args = parsePriceLookupArgs(rest);
|
|
37
|
+
if (!args) {
|
|
38
|
+
console.error("usage: tlc harness prices lookup <model-id> [provider]");
|
|
39
|
+
console.error(
|
|
40
|
+
" or: node --experimental-strip-types tools/price-lookup.ts <model-id> [provider] (dev)",
|
|
41
|
+
);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const result = lookupPrice(args);
|
|
45
|
+
if (json) {
|
|
46
|
+
emitJson(result);
|
|
47
|
+
} else {
|
|
48
|
+
console.log(JSON.stringify(result, null, 2));
|
|
49
|
+
}
|
|
50
|
+
if (!result.resolved) {
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (import.meta.main) {
|
|
56
|
+
main();
|
|
57
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const HARNESS_HOME = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
const CURSOR_DOCS_URL = "https://cursor.com/docs/models-and-pricing.md";
|
|
8
|
+
const LITELLM_URL =
|
|
9
|
+
"https://raw.githubusercontent.com/BerriAI/litellm/main/litellm/model_prices_and_context_window_backup.json";
|
|
10
|
+
|
|
11
|
+
type PriceEntry = {
|
|
12
|
+
displayName?: string;
|
|
13
|
+
provider?: string;
|
|
14
|
+
promptPer1M?: number;
|
|
15
|
+
completionPer1M?: number;
|
|
16
|
+
cacheWritePer1M?: number;
|
|
17
|
+
cacheReadPer1M?: number;
|
|
18
|
+
pool?: "cursor_models" | "other_models" | "auto" | "unknown";
|
|
19
|
+
billing?: "metered" | "included" | "unknown";
|
|
20
|
+
contextWindow?: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function slugify(name: string): string {
|
|
24
|
+
return name
|
|
25
|
+
.trim()
|
|
26
|
+
.toLowerCase()
|
|
27
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
|
|
28
|
+
.replace(/\(.*?\)/g, "")
|
|
29
|
+
.replace(/[^a-z0-9.+]+/g, "-")
|
|
30
|
+
.replace(/^-+|-+$/g, "");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseMoney(cell: string): number | undefined {
|
|
34
|
+
const t = cell.trim();
|
|
35
|
+
if (!t || t === "-" || t === "—" || t.toLowerCase() === "n/a") {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const m = t.replace(/,/g, "").match(/\$?\s*([0-9]*\.?[0-9]+)/);
|
|
39
|
+
if (!m) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
return Number(m[1]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function stripCell(cell: string): string {
|
|
46
|
+
return cell
|
|
47
|
+
.trim()
|
|
48
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
|
|
49
|
+
.replace(/\*\*/g, "")
|
|
50
|
+
.trim();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function inferPool(displayName: string, provider: string): PriceEntry["pool"] {
|
|
54
|
+
const n = displayName.toLowerCase();
|
|
55
|
+
if (n === "auto cost" || n.startsWith("auto ")) {
|
|
56
|
+
return "auto";
|
|
57
|
+
}
|
|
58
|
+
if (provider.toLowerCase() === "cursor" || n.includes("composer") || n.includes("grok 4.5")) {
|
|
59
|
+
return "cursor_models";
|
|
60
|
+
}
|
|
61
|
+
return "other_models";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseCursorDocs(md: string): Record<string, PriceEntry> {
|
|
65
|
+
const out: Record<string, PriceEntry> = {};
|
|
66
|
+
const lines = md.split("\n");
|
|
67
|
+
let inTable = false;
|
|
68
|
+
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
if (!line.startsWith("|")) {
|
|
71
|
+
if (inTable) {
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const cells = line
|
|
77
|
+
.split("|")
|
|
78
|
+
.slice(1, -1)
|
|
79
|
+
.map((c) => c.trim());
|
|
80
|
+
if (cells.length < 6) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const [cell0 = "", cell1 = "", cell2 = "", cell3 = "", cell4 = "", cell5 = ""] = cells;
|
|
84
|
+
if (cell0.toLowerCase() === "model" || cell0.startsWith("---") || cell0.includes("---")) {
|
|
85
|
+
inTable = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (!inTable) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const displayName = stripCell(cell0);
|
|
93
|
+
const provider = stripCell(cell1);
|
|
94
|
+
const promptPer1M = parseMoney(cell2);
|
|
95
|
+
const cacheWritePer1M = parseMoney(cell3);
|
|
96
|
+
const cacheReadPer1M = parseMoney(cell4);
|
|
97
|
+
const completionPer1M = parseMoney(cell5);
|
|
98
|
+
if (!displayName || promptPer1M === undefined || completionPer1M === undefined) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const key = slugify(displayName);
|
|
103
|
+
out[key] = {
|
|
104
|
+
displayName,
|
|
105
|
+
provider,
|
|
106
|
+
promptPer1M,
|
|
107
|
+
completionPer1M,
|
|
108
|
+
cacheWritePer1M,
|
|
109
|
+
cacheReadPer1M,
|
|
110
|
+
pool: inferPool(displayName, provider),
|
|
111
|
+
billing: "metered",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
type LiteLlmEntry = {
|
|
119
|
+
max_input_tokens?: number;
|
|
120
|
+
max_tokens?: number;
|
|
121
|
+
input_cost_per_token?: number;
|
|
122
|
+
output_cost_per_token?: number;
|
|
123
|
+
cache_read_input_token_cost?: number;
|
|
124
|
+
cache_creation_input_token_cost?: number;
|
|
125
|
+
litellm_provider?: string;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
function parseLiteLlm(raw: Record<string, LiteLlmEntry>): Record<string, PriceEntry> {
|
|
129
|
+
const out: Record<string, PriceEntry> = {};
|
|
130
|
+
for (const [id, entry] of Object.entries(raw)) {
|
|
131
|
+
if (id === "sample_spec") {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const contextWindow =
|
|
135
|
+
typeof entry.max_input_tokens === "number" ? entry.max_input_tokens : entry.max_tokens;
|
|
136
|
+
const compact: PriceEntry = {
|
|
137
|
+
displayName: id,
|
|
138
|
+
provider: entry.litellm_provider,
|
|
139
|
+
pool: "unknown",
|
|
140
|
+
billing: "metered",
|
|
141
|
+
};
|
|
142
|
+
if (typeof contextWindow === "number" && contextWindow > 0) {
|
|
143
|
+
compact.contextWindow = contextWindow;
|
|
144
|
+
}
|
|
145
|
+
if (typeof entry.input_cost_per_token === "number" && typeof entry.output_cost_per_token === "number") {
|
|
146
|
+
compact.promptPer1M = entry.input_cost_per_token * 1_000_000;
|
|
147
|
+
compact.completionPer1M = entry.output_cost_per_token * 1_000_000;
|
|
148
|
+
} else {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (typeof entry.cache_read_input_token_cost === "number") {
|
|
152
|
+
compact.cacheReadPer1M = entry.cache_read_input_token_cost * 1_000_000;
|
|
153
|
+
}
|
|
154
|
+
if (typeof entry.cache_creation_input_token_cost === "number") {
|
|
155
|
+
compact.cacheWritePer1M = entry.cache_creation_input_token_cost * 1_000_000;
|
|
156
|
+
}
|
|
157
|
+
out[id] = compact;
|
|
158
|
+
const slug = slugify(id);
|
|
159
|
+
if (slug !== id && !out[slug]) {
|
|
160
|
+
out[slug] = { ...compact, displayName: id };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const mode = (process.argv[2] ?? "all").toLowerCase();
|
|
167
|
+
|
|
168
|
+
if (mode === "all" || mode === "cursor") {
|
|
169
|
+
const res = await fetch(CURSOR_DOCS_URL);
|
|
170
|
+
if (!res.ok) {
|
|
171
|
+
console.error(`Failed to fetch Cursor docs: ${res.status}`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
const md = await res.text();
|
|
175
|
+
const cursor = parseCursorDocs(md);
|
|
176
|
+
const count = Object.keys(cursor).length;
|
|
177
|
+
if (count === 0) {
|
|
178
|
+
console.error("Parsed 0 Cursor models — docs table format may have changed");
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
const path = join(HARNESS_HOME, "model-prices.cursor.json");
|
|
182
|
+
writeFileSync(
|
|
183
|
+
path,
|
|
184
|
+
`${JSON.stringify({ _meta: { source: CURSOR_DOCS_URL, refreshedAt: new Date().toISOString() }, ...cursor }, null, 2)}\n`,
|
|
185
|
+
);
|
|
186
|
+
console.log(`Cursor catalog: ${count} models → ${path}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (mode === "all" || mode === "litellm") {
|
|
190
|
+
const res = await fetch(LITELLM_URL);
|
|
191
|
+
if (!res.ok) {
|
|
192
|
+
console.error(`Failed to fetch LiteLLM prices: ${res.status}`);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
const raw = (await res.json()) as Record<string, LiteLlmEntry>;
|
|
196
|
+
const litellm = parseLiteLlm(raw);
|
|
197
|
+
const path = join(HARNESS_HOME, "model-prices.litellm.json");
|
|
198
|
+
writeFileSync(
|
|
199
|
+
path,
|
|
200
|
+
`${JSON.stringify({
|
|
201
|
+
_meta: {
|
|
202
|
+
source: LITELLM_URL,
|
|
203
|
+
refreshedAt: new Date().toISOString(),
|
|
204
|
+
count: Object.keys(litellm).length,
|
|
205
|
+
},
|
|
206
|
+
...litellm,
|
|
207
|
+
})}\n`,
|
|
208
|
+
);
|
|
209
|
+
console.log(`LiteLLM catalog: ${Object.keys(litellm).length} models → ${path}`);
|
|
210
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loaded with `node --import` before every test file, so the suite answers from its own fixtures instead of
|
|
3
|
+
* from the shell that happened to start it.
|
|
4
|
+
*
|
|
5
|
+
* hazard: `projectDirFor` prefers `CLAUDE_PROJECT_DIR` over the payload's `cwd`, which is correct in
|
|
6
|
+
* production — the env var is the project root and `cwd` can be a subdirectory. Inside a Claude Code hook that
|
|
7
|
+
* variable is always set, so 22 tests that build a fixture in a temp directory silently read policy and state
|
|
8
|
+
* from the real repository instead. The suite passed from a shell and failed from inside a hook.
|
|
9
|
+
*/
|
|
10
|
+
import { mkdtempSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { PROJECT_SCOPED_ENV } from "./test-env.names.mjs";
|
|
14
|
+
|
|
15
|
+
for (const name of PROJECT_SCOPED_ENV) {
|
|
16
|
+
delete process.env[name];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* hazard: `TLC_HOME` was left alone on the reasoning that it names which runtime and CI sets it deliberately. That
|
|
21
|
+
* held only while nothing machine-wide lived under it. The global lesson tier does, so a test calling `allLessons`
|
|
22
|
+
* without pinning the home read whichever lessons the developer happened to have promoted — green on a fresh
|
|
23
|
+
* machine, green in CI, red on mine the moment I promoted five ([/decisions/ad-042.md](/decisions/ad-042.md)).
|
|
24
|
+
*
|
|
25
|
+
* invariant: hermetic by default, opt in by assignment. A test that wants a runtime home sets one; a test that
|
|
26
|
+
* forgets gets an empty directory rather than a person's machine.
|
|
27
|
+
*/
|
|
28
|
+
process.env.TLC_HOME = mkdtempSync(join(tmpdir(), "tlc-test-home-"));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PROJECT_SCOPED_ENV: string[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The names only, with no side effect, so a test can read them without performing the cleanup it is checking.
|
|
3
|
+
*
|
|
4
|
+
* hazard: this list lived in `test-env.mjs` next to the `delete` loop, and the guard test imported it from
|
|
5
|
+
* there. Importing that module runs the loop, so the guard cleaned the very environment it was asserting about
|
|
6
|
+
* and could never fail. Data and effect are separate for that reason.
|
|
7
|
+
*
|
|
8
|
+
* invariant: only variables that name WHICH project. `TLC_HOME` names which runtime, is set deliberately by
|
|
9
|
+
* CI, and is part of what the suite exercises.
|
|
10
|
+
*/
|
|
11
|
+
// hazard: `CURSOR_PROJECT_DIR` was absent while `cursor.inbound.ts` prefers it over the payload's workspace
|
|
12
|
+
// root — the same hole this list exists to close, for the other provider. A suite launched from inside a Cursor
|
|
13
|
+
// hook would have read the real repository exactly as the Claude case did.
|
|
14
|
+
export const PROJECT_SCOPED_ENV = ["CLAUDE_PROJECT_DIR", "CURSOR_PROJECT_DIR", "TLC_PROJECT_DIR"];
|