harnesstrim 0.0.1 → 0.0.2
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/package.json +4 -6
- package/src/cli.ts +0 -238
package/package.json
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harnesstrim",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "HarnessTrim CLI: doctor (diagnose token waste), install adapters, run benchmarks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"harnesstrim": "./
|
|
8
|
+
"harnesstrim": "./dist/cli.mjs"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"assets"
|
|
13
13
|
],
|
|
14
14
|
"publishConfig": {
|
|
15
|
-
"access": "public"
|
|
16
|
-
"bin": {
|
|
17
|
-
"harnesstrim": "./dist/cli.mjs"
|
|
18
|
-
}
|
|
15
|
+
"access": "public"
|
|
19
16
|
},
|
|
20
17
|
"devDependencies": {
|
|
21
18
|
"@harnesstrim/adapter-claude": "workspace:*",
|
|
@@ -29,6 +26,7 @@
|
|
|
29
26
|
},
|
|
30
27
|
"scripts": {
|
|
31
28
|
"build": "node build.mjs",
|
|
29
|
+
"prepare": "node build.mjs",
|
|
32
30
|
"prepack": "node build.mjs",
|
|
33
31
|
"test": "node --test \"src/**/*.test.ts\"",
|
|
34
32
|
"typecheck": "tsc -p tsconfig.json"
|
package/src/cli.ts
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { parseArgs } from "node:util";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import os from "node:os";
|
|
6
|
-
import { getPreset, listPresets } from "@harnesstrim/core";
|
|
7
|
-
import { inspect } from "./doctor.ts";
|
|
8
|
-
import { runInstallOpencode } from "./install.ts";
|
|
9
|
-
import { runInstallCodex } from "./install-codex.ts";
|
|
10
|
-
import { runInstallClaude } from "./install-claude.ts";
|
|
11
|
-
import { runInstallPi } from "./install-pi.ts";
|
|
12
|
-
import { runInstallHermes } from "./install-hermes.ts";
|
|
13
|
-
import { reduceClaudePayload } from "@harnesstrim/adapter-claude";
|
|
14
|
-
import { loadMetrics, DEFAULT_METRICS_PATH } from "./metrics.ts";
|
|
15
|
-
import { readStdin, reducePipe } from "./reduce.ts";
|
|
16
|
-
import {
|
|
17
|
-
renderDoctor,
|
|
18
|
-
renderInstall,
|
|
19
|
-
renderCodexInstall,
|
|
20
|
-
renderClaudeInstall,
|
|
21
|
-
renderHermesInstall,
|
|
22
|
-
renderPiInstall,
|
|
23
|
-
renderMetrics,
|
|
24
|
-
renderPresetList,
|
|
25
|
-
renderPresetShow,
|
|
26
|
-
} from "./render.ts";
|
|
27
|
-
|
|
28
|
-
const HELP = `harnesstrim — one token policy for coding harnesses
|
|
29
|
-
|
|
30
|
-
Usage:
|
|
31
|
-
harnesstrim doctor [dir] Diagnose token-waste signals in a project
|
|
32
|
-
harnesstrim install opencode [dir] Wire the adapter into opencode.json (dry-run)
|
|
33
|
-
--apply Actually write the change
|
|
34
|
-
--preset <name> Bake a policy preset's adapter config in
|
|
35
|
-
harnesstrim install codex [dir] Install skills + AGENTS.md reduce-pipe (dry-run)
|
|
36
|
-
--apply Actually write the change
|
|
37
|
-
harnesstrim install claude [dir] Install skills + PostToolUse reducer hook (dry-run)
|
|
38
|
-
--apply Actually write the change
|
|
39
|
-
harnesstrim install hermes [dir] Install Hermes plugin (dry-run)
|
|
40
|
-
--apply Actually write the change
|
|
41
|
-
harnesstrim install pi [dir] Install Pi tool_result extension (dry-run)
|
|
42
|
-
--apply Actually write the change
|
|
43
|
-
harnesstrim hook claude [--metrics <path>]
|
|
44
|
-
PostToolUse hook runtime; --metrics records a TrimEvent per reduction
|
|
45
|
-
harnesstrim preset list List policy presets
|
|
46
|
-
harnesstrim preset show <name> Show a preset in detail
|
|
47
|
-
harnesstrim metrics [path] Summarize adapter telemetry (JSONL)
|
|
48
|
-
harnesstrim reduce [--stats] Slim stdin -> stdout (pipe noisy command output)
|
|
49
|
-
harnesstrim mcp Start the MCP server (stdio) exposing a reduce tool
|
|
50
|
-
harnesstrim bench Run the Tier A reducer micro-benchmark
|
|
51
|
-
harnesstrim --help Show this help
|
|
52
|
-
|
|
53
|
-
Notes:
|
|
54
|
-
- install is dry-run by default; nothing is written without --apply.
|
|
55
|
-
- dir defaults to the current directory; metrics path defaults to ${DEFAULT_METRICS_PATH}.
|
|
56
|
-
- reduce reads stdin and writes slimmed output to stdout, e.g. npm test 2>&1 | harnesstrim reduce`;
|
|
57
|
-
|
|
58
|
-
async function main(argv: string[]): Promise<number> {
|
|
59
|
-
const { values, positionals } = parseArgs({
|
|
60
|
-
args: argv,
|
|
61
|
-
allowPositionals: true,
|
|
62
|
-
options: {
|
|
63
|
-
help: { type: "boolean", short: "h" },
|
|
64
|
-
apply: { type: "boolean" },
|
|
65
|
-
preset: { type: "string" },
|
|
66
|
-
stats: { type: "boolean" },
|
|
67
|
-
"min-length": { type: "string" },
|
|
68
|
-
log: { type: "string" },
|
|
69
|
-
metrics: { type: "string" },
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const [command, ...rest] = positionals;
|
|
74
|
-
|
|
75
|
-
if (values.help || !command) {
|
|
76
|
-
console.log(HELP);
|
|
77
|
-
return 0;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
switch (command) {
|
|
81
|
-
case "doctor": {
|
|
82
|
-
const dir = rest[0] ?? process.cwd();
|
|
83
|
-
console.log(renderDoctor(inspect(dir)));
|
|
84
|
-
return 0;
|
|
85
|
-
}
|
|
86
|
-
case "install": {
|
|
87
|
-
const target = rest[0];
|
|
88
|
-
// Hermes uses the user-level Hermes home by default; other adapters keep
|
|
89
|
-
// their project-directory default. Pass an explicit directory for a
|
|
90
|
-
// project-local or alternate-profile installation.
|
|
91
|
-
const dir = rest[1] ?? (target === "hermes" ? os.homedir() : process.cwd());
|
|
92
|
-
const apply = values.apply === true;
|
|
93
|
-
if (target === "opencode") {
|
|
94
|
-
const result = runInstallOpencode(dir, apply, values.preset);
|
|
95
|
-
console.log(renderInstall(result, apply));
|
|
96
|
-
return 0;
|
|
97
|
-
}
|
|
98
|
-
if (target === "codex") {
|
|
99
|
-
console.log(renderCodexInstall(runInstallCodex(dir, apply), apply));
|
|
100
|
-
return 0;
|
|
101
|
-
}
|
|
102
|
-
if (target === "claude") {
|
|
103
|
-
console.log(renderClaudeInstall(runInstallClaude(dir, apply), apply));
|
|
104
|
-
return 0;
|
|
105
|
-
}
|
|
106
|
-
if (target === "hermes") {
|
|
107
|
-
console.log(renderHermesInstall(runInstallHermes(dir, apply), apply));
|
|
108
|
-
return 0;
|
|
109
|
-
}
|
|
110
|
-
if (target === "pi") {
|
|
111
|
-
console.log(renderPiInstall(runInstallPi(dir, apply), apply));
|
|
112
|
-
return 0;
|
|
113
|
-
}
|
|
114
|
-
console.error(`Unknown install target: ${target ?? "(none)"}. Supported: opencode, codex, claude, hermes, pi.`);
|
|
115
|
-
return 1;
|
|
116
|
-
}
|
|
117
|
-
case "hook": {
|
|
118
|
-
const which = rest[0];
|
|
119
|
-
if (which !== "claude") {
|
|
120
|
-
console.error(`Unknown hook target: ${which ?? "(none)"}. Supported: claude.`);
|
|
121
|
-
return 1;
|
|
122
|
-
}
|
|
123
|
-
const input = await readStdin();
|
|
124
|
-
const { response, event } = reduceClaudePayload(input);
|
|
125
|
-
process.stdout.write(response);
|
|
126
|
-
// --metrics <path>: append a TrimEvent per reduction, read by `harnesstrim metrics`.
|
|
127
|
-
if (values.metrics && event) {
|
|
128
|
-
try {
|
|
129
|
-
const p = path.resolve(values.metrics);
|
|
130
|
-
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
131
|
-
fs.appendFileSync(
|
|
132
|
-
p,
|
|
133
|
-
JSON.stringify({ ts: new Date().toISOString(), harness: "claude", ...event }) + "\n"
|
|
134
|
-
);
|
|
135
|
-
} catch {
|
|
136
|
-
/* telemetry must never break the hook */
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// --log <path>: lightweight debug line (input size, whether it changed).
|
|
140
|
-
if (values.log) {
|
|
141
|
-
try {
|
|
142
|
-
fs.appendFileSync(
|
|
143
|
-
values.log,
|
|
144
|
-
JSON.stringify({ inputChars: input.length, changed: response !== "{}", responseChars: response.length }) + "\n"
|
|
145
|
-
);
|
|
146
|
-
} catch {
|
|
147
|
-
/* logging must never break the hook */
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return 0;
|
|
151
|
-
}
|
|
152
|
-
case "preset": {
|
|
153
|
-
const sub = rest[0];
|
|
154
|
-
if (sub === "list" || sub === undefined) {
|
|
155
|
-
console.log(renderPresetList(listPresets()));
|
|
156
|
-
return 0;
|
|
157
|
-
}
|
|
158
|
-
if (sub === "show") {
|
|
159
|
-
const name = rest[1];
|
|
160
|
-
const preset = name ? getPreset(name) : undefined;
|
|
161
|
-
if (!preset) {
|
|
162
|
-
console.error(`Unknown preset: ${name ?? "(none)"}. Try \`harnesstrim preset list\`.`);
|
|
163
|
-
return 1;
|
|
164
|
-
}
|
|
165
|
-
console.log(renderPresetShow(preset));
|
|
166
|
-
return 0;
|
|
167
|
-
}
|
|
168
|
-
console.error(`Unknown preset subcommand: ${sub}. Use \`list\` or \`show <name>\`.`);
|
|
169
|
-
return 1;
|
|
170
|
-
}
|
|
171
|
-
case "metrics": {
|
|
172
|
-
const path = rest[0] ?? DEFAULT_METRICS_PATH;
|
|
173
|
-
console.log(renderMetrics(loadMetrics(path)));
|
|
174
|
-
return 0;
|
|
175
|
-
}
|
|
176
|
-
case "reduce": {
|
|
177
|
-
const minLenRaw = values["min-length"];
|
|
178
|
-
const minLength = minLenRaw !== undefined ? Number(minLenRaw) : undefined;
|
|
179
|
-
if (minLength !== undefined && !Number.isFinite(minLength)) {
|
|
180
|
-
console.error(`Invalid --min-length: ${minLenRaw}`);
|
|
181
|
-
return 1;
|
|
182
|
-
}
|
|
183
|
-
const input = await readStdin();
|
|
184
|
-
const result = reducePipe(input, minLength);
|
|
185
|
-
process.stdout.write(result.output);
|
|
186
|
-
if (values.stats) {
|
|
187
|
-
const note = result.changed
|
|
188
|
-
? `${result.reducer}: ${result.beforeChars} -> ${result.afterChars} chars`
|
|
189
|
-
: "no reduction (no reducer matched or below min-length)";
|
|
190
|
-
console.error(`[harnesstrim reduce] ${note}`);
|
|
191
|
-
}
|
|
192
|
-
return 0;
|
|
193
|
-
}
|
|
194
|
-
case "mcp": {
|
|
195
|
-
const { startStdioServer } = await import("@harnesstrim/mcp");
|
|
196
|
-
await startStdioServer();
|
|
197
|
-
// startStdioServer resolves once connected; keep the process alive for stdio.
|
|
198
|
-
await new Promise<never>(() => {});
|
|
199
|
-
return 0;
|
|
200
|
-
}
|
|
201
|
-
case "bench": {
|
|
202
|
-
// The Tier A micro-benchmark runs against the repo's fixtures and writes a
|
|
203
|
-
// report back into the repo — it's a monorepo-development tool, kept out of the
|
|
204
|
-
// published bundle (see build.mjs). In a standalone install the import fails
|
|
205
|
-
// (ERR_MODULE_NOT_FOUND); from a stray location the fixtures are missing
|
|
206
|
-
// (ENOENT). Either way, degrade with a clear message instead of a raw stack.
|
|
207
|
-
try {
|
|
208
|
-
const { runBench } = await import("@harnesstrim/benchmarks/run");
|
|
209
|
-
runBench();
|
|
210
|
-
} catch (err) {
|
|
211
|
-
const code = (err as NodeJS.ErrnoException)?.code;
|
|
212
|
-
if (code === "ENOENT" || code === "ERR_MODULE_NOT_FOUND") {
|
|
213
|
-
console.error(
|
|
214
|
-
"harnesstrim bench is a repository-development command (it reads the benchmark\n" +
|
|
215
|
-
"fixtures and writes a report). Run it from a HarnessTrim checkout:\n" +
|
|
216
|
-
" git clone https://github.com/giuliastro/HarnessTrim && pnpm install && pnpm exec harnesstrim bench"
|
|
217
|
-
);
|
|
218
|
-
return 1;
|
|
219
|
-
}
|
|
220
|
-
throw err;
|
|
221
|
-
}
|
|
222
|
-
return 0;
|
|
223
|
-
}
|
|
224
|
-
default:
|
|
225
|
-
console.error(`Unknown command: ${command}\n`);
|
|
226
|
-
console.log(HELP);
|
|
227
|
-
return 1;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
main(process.argv.slice(2))
|
|
232
|
-
.then((code) => {
|
|
233
|
-
process.exitCode = code;
|
|
234
|
-
})
|
|
235
|
-
.catch((err) => {
|
|
236
|
-
console.error(`harnesstrim: ${err instanceof Error ? err.message : String(err)}`);
|
|
237
|
-
process.exitCode = 1;
|
|
238
|
-
});
|