harnesstrim 0.0.5 → 0.0.6

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HarnessTrim contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // Pi fires `tool_result` after a tool finishes and before the result reaches the model;
4
4
  // handlers chain like middleware and may return a patch ({ content, details, isError }).
5
- // This extension reduces string `content` for noisy output (test runners, git diffs, ...)
5
+ // This extension reduces text chunks in structured tool results (test runners, git diffs, ...)
6
6
  // by shelling out to `harnesstrim reduce`, so it is self-contained (no workspace imports)
7
7
  // and loads from `~/.pi/agent/extensions/` or `<project>/.pi/extensions/`.
8
8
  //
@@ -12,15 +12,19 @@
12
12
  // HARNESSTRIM_MINLENGTH=<chars> (default 400)
13
13
  import { spawnSync } from "node:child_process";
14
14
 
15
+ type TextContent = { type: "text"; text: string };
16
+ type ToolContent = TextContent | { type: string; [key: string]: unknown };
17
+
15
18
  interface ToolResultEvent {
16
- content?: unknown;
19
+ content?: ToolContent[];
17
20
  isError?: boolean;
18
21
  }
19
22
  interface ExtensionAPI {
20
23
  on(event: string, handler: (event: ToolResultEvent, ctx: unknown) => unknown): void;
21
24
  }
22
25
 
23
- const env = globalThis.process?.env ?? {};
26
+ const runtime = globalThis as typeof globalThis & { process?: NodeJS.Process };
27
+ const env = runtime.process?.env ?? {};
24
28
  const MODE = env.HARNESSTRIM_MODE ?? "dryrun";
25
29
  const MIN_LENGTH = Number(env.HARNESSTRIM_MINLENGTH ?? "400") || 400;
26
30
  const MARKER = "[harnesstrim";
@@ -44,19 +48,28 @@ function reduceViaCli(text: string): string | null {
44
48
  export default function harnesstrim(pi: ExtensionAPI): void {
45
49
  if (MODE === "off") return;
46
50
  pi.on("tool_result", async (event) => {
47
- const content = event.content;
48
- if (typeof content !== "string" || content.length < MIN_LENGTH) return;
49
- if (content.includes(MARKER)) return; // already reduced — avoid double work
50
-
51
- const reduced = reduceViaCli(content);
52
- if (!reduced || reduced.length >= content.length) return;
53
-
54
- if (MODE === "dryrun") {
55
- globalThis.process?.stderr?.write(
56
- `[harnesstrim] dryrun tool_result: ${content.length} -> ${reduced.length} chars\n`
57
- );
58
- return; // dryrun: observe only
59
- }
60
- return { content: reduced }; // active: patch the tool result the model sees
51
+ if (!Array.isArray(event.content)) return;
52
+
53
+ let changed = false;
54
+ const content = event.content.map((chunk) => {
55
+ if (chunk.type !== "text" || typeof chunk.text !== "string") return chunk;
56
+ const text = chunk.text;
57
+ if (text.length < MIN_LENGTH || text.includes(MARKER)) return chunk;
58
+
59
+ const reduced = reduceViaCli(text);
60
+ if (!reduced || reduced.length >= text.length) return chunk;
61
+
62
+ if (MODE === "dryrun") {
63
+ runtime.process?.stderr?.write(
64
+ `[harnesstrim] dryrun tool_result: ${text.length} -> ${reduced.length} chars\n`
65
+ );
66
+ return chunk;
67
+ }
68
+
69
+ changed = true;
70
+ return { ...chunk, text: reduced };
71
+ });
72
+
73
+ return changed ? { content } : undefined;
61
74
  });
62
75
  }
package/dist/cli.mjs CHANGED
@@ -581,6 +581,75 @@ ${input.slice(response.index)}`;
581
581
  }
582
582
  });
583
583
 
584
+ // ../core/src/reducers/lint-output-slim.ts
585
+ function isLintLine(line) {
586
+ return LINT_LINE_RE.test(line);
587
+ }
588
+ var MARKER_PREFIX7, LINT_LINE_RE, MAX_RULES_IN_MARKER, lintOutputSlim;
589
+ var init_lint_output_slim = __esm({
590
+ "../core/src/reducers/lint-output-slim.ts"() {
591
+ "use strict";
592
+ MARKER_PREFIX7 = "[harnesstrim:lint-output-slim]";
593
+ LINT_LINE_RE = /^[\w.\/\\-]+:\d+:\d+\s+(warning|error)\s+([\w@.\/-]+)/;
594
+ MAX_RULES_IN_MARKER = 8;
595
+ lintOutputSlim = {
596
+ name: "lint-output-slim",
597
+ reduce(input) {
598
+ const lines = input.split(/\r?\n/);
599
+ const out = [];
600
+ let droppedTotal = 0;
601
+ let i = 0;
602
+ while (i < lines.length) {
603
+ const line = lines[i];
604
+ if (!isLintLine(line)) {
605
+ out.push(line);
606
+ i++;
607
+ continue;
608
+ }
609
+ const counts = [];
610
+ const byKey = /* @__PURE__ */ new Map();
611
+ let runEnd = i;
612
+ while (runEnd < lines.length && isLintLine(lines[runEnd])) {
613
+ const m = LINT_LINE_RE.exec(lines[runEnd]);
614
+ const severity = m[1] === "error" ? "error" : "warning";
615
+ const rule = m[2];
616
+ const key = `${severity}:${rule}`;
617
+ const existing = byKey.get(key);
618
+ if (existing) {
619
+ existing.count++;
620
+ } else {
621
+ const entry = { severity, rule, count: 1 };
622
+ byKey.set(key, entry);
623
+ counts.push(entry);
624
+ }
625
+ runEnd++;
626
+ }
627
+ const runLength = runEnd - i;
628
+ if (runLength >= 2) {
629
+ const parts = counts.slice(0, MAX_RULES_IN_MARKER).map(
630
+ (c) => `${c.rule} \xD7${c.count}`
631
+ );
632
+ const truncated = counts.length > MAX_RULES_IN_MARKER;
633
+ const suffix = truncated ? `, +${counts.length - MAX_RULES_IN_MARKER} more rule(s)` : "";
634
+ const severities = counts.some((c) => c.severity === "error") && counts.some((c) => c.severity === "warning") ? "error(s) and warning(s)" : counts.some((c) => c.severity === "error") ? "error(s)" : "warning(s)";
635
+ out.push(`${MARKER_PREFIX7} omitted ${runLength} lint line(s) (${severities}: ${parts.join(", ")}${suffix})`);
636
+ droppedTotal += runLength;
637
+ } else {
638
+ for (let j = i; j < runEnd; j++) out.push(lines[j]);
639
+ }
640
+ i = runEnd;
641
+ }
642
+ const output = out.join("\n");
643
+ return {
644
+ output,
645
+ changed: droppedTotal > 0,
646
+ note: droppedTotal > 0 ? `dropped ${droppedTotal} lint noise line(s)` : void 0
647
+ };
648
+ }
649
+ };
650
+ }
651
+ });
652
+
584
653
  // ../core/src/reducers/index.ts
585
654
  var init_reducers = __esm({
586
655
  "../core/src/reducers/index.ts"() {
@@ -592,6 +661,7 @@ var init_reducers = __esm({
592
661
  init_json_output_slim();
593
662
  init_file_listing_slim();
594
663
  init_cron_output_slim();
664
+ init_lint_output_slim();
595
665
  }
596
666
  });
597
667
 
@@ -600,6 +670,7 @@ function pickReducer(text) {
600
670
  if (GIT_DIFF_RE.test(text)) return gitDiffSlim;
601
671
  if (TEST_OUTPUT_RE.test(text)) return testOutputSlim;
602
672
  if (CRON_OUTPUT_RE.test(text) && text.length >= 400) return cronOutputSlim;
673
+ if (LINT_OUTPUT_RE.test(text) && text.length >= 400) return lintOutputSlim;
603
674
  if (JSON_RE.test(text) && text.length >= 400) return jsonOutputSlim;
604
675
  if (FILE_LISTING_RE.test(text) && text.length >= 400) return fileListingSlim;
605
676
  if (LONG_TEXT_RE.test(text) && text.length >= 1e3) return genericTextSlim;
@@ -619,7 +690,7 @@ function reduceAuto(text, minLength = DEFAULT_MIN_LENGTH) {
619
690
  }
620
691
  return { ...result, reducer: reducer.name };
621
692
  }
622
- var DEFAULT_MIN_LENGTH, GIT_DIFF_RE, TEST_OUTPUT_RE, JSON_RE, FILE_LISTING_RE, CRON_OUTPUT_RE, LONG_TEXT_RE;
693
+ var DEFAULT_MIN_LENGTH, GIT_DIFF_RE, TEST_OUTPUT_RE, JSON_RE, FILE_LISTING_RE, CRON_OUTPUT_RE, LINT_OUTPUT_RE, LONG_TEXT_RE;
623
694
  var init_dispatch = __esm({
624
695
  "../core/src/dispatch.ts"() {
625
696
  "use strict";
@@ -629,12 +700,14 @@ var init_dispatch = __esm({
629
700
  init_json_output_slim();
630
701
  init_file_listing_slim();
631
702
  init_cron_output_slim();
703
+ init_lint_output_slim();
632
704
  DEFAULT_MIN_LENGTH = 400;
633
705
  GIT_DIFF_RE = /^diff --git /m;
634
706
  TEST_OUTPUT_RE = /\b\d+\s+(passed|failed)\b|^(PASS|FAIL)\s|::\w.*\b(PASSED|FAILED)\b|=+\s*(FAILURES|short test summary)/im;
635
707
  JSON_RE = /^\s*[\[{]/m;
636
708
  FILE_LISTING_RE = /(?:^total\s+\d+|^[\-bcdlsp][\-r][\-w][\-xs\-][\-r][\-w][\-xs\-][\-r][\-w][\-xs\-]|^\.\/(?:\.|[^.\s])|^\s*(?:├──|└──|│\s+)|^[\w.\/\-]+\.[a-zA-Z]{1,4}:\d+\|)/m;
637
709
  CRON_OUTPUT_RE = /^# Cron Job:.*\n[\s\S]*^## Prompt\s*$[\s\S]*^## Response\s*$/m;
710
+ LINT_OUTPUT_RE = /^[\w.\/\\-]+:\d+:\d+\s+(?:warning|error)\s+[\w@.\/-]+\s/m;
638
711
  LONG_TEXT_RE = /^#{1,4}\s.*\n(?:(?!^#{1,4}\s|^diff --git |^```).*\n){5,}/m;
639
712
  }
640
713
  });
@@ -22627,7 +22700,7 @@ init_src();
22627
22700
  import { parseArgs } from "node:util";
22628
22701
  import fs11 from "node:fs";
22629
22702
  import path15 from "node:path";
22630
- import os2 from "node:os";
22703
+ import os4 from "node:os";
22631
22704
 
22632
22705
  // src/doctor.ts
22633
22706
  import fs from "node:fs";
@@ -22897,7 +22970,9 @@ function runInstallOpencode(dir, apply, presetName, installDeps = true) {
22897
22970
  const npm = process.platform === "win32" ? "npm.cmd" : "npm";
22898
22971
  const res = spawnSync(npm, ["install", "--silent"], {
22899
22972
  cwd: path2.dirname(packageJsonPath),
22900
- encoding: "utf8"
22973
+ encoding: "utf8",
22974
+ input: "",
22975
+ timeout: 12e4
22901
22976
  });
22902
22977
  if (res.error || res.status !== 0) {
22903
22978
  depsInstalled = false;
@@ -22922,6 +22997,7 @@ function runInstallOpencode(dir, apply, presetName, installDeps = true) {
22922
22997
 
22923
22998
  // src/install-codex.ts
22924
22999
  import fs5 from "node:fs";
23000
+ import os from "node:os";
22925
23001
  import path6 from "node:path";
22926
23002
 
22927
23003
  // ../adapter-codex/src/index.ts
@@ -22993,9 +23069,31 @@ function hasHarnessTrimHook(document) {
22993
23069
  const post = hooks?.PostToolUse;
22994
23070
  if (!Array.isArray(post)) return false;
22995
23071
  return post.some(
22996
- (entry) => Array.isArray(entry?.hooks) && entry.hooks.some((hook) => typeof hook?.command === "string" && hook.command.includes("harnesstrim hook codex"))
23072
+ (entry) => Array.isArray(entry?.hooks) && entry.hooks.some(
23073
+ (hook) => typeof hook?.command === "string" && hook.command.includes("harnesstrim") && hook.command.includes("hook codex")
23074
+ )
22997
23075
  );
22998
23076
  }
23077
+ function hasExactHarnessTrimHookCommand(document, command) {
23078
+ const hooks = document.hooks;
23079
+ const post = hooks?.PostToolUse;
23080
+ return Array.isArray(post) && post.some(
23081
+ (entry) => Array.isArray(entry?.hooks) && entry.hooks.some((hook) => hook?.command === command)
23082
+ );
23083
+ }
23084
+ function replaceHarnessTrimHookCommand(document, command) {
23085
+ const hooks = document.hooks;
23086
+ const post = hooks?.PostToolUse;
23087
+ if (!Array.isArray(post)) return;
23088
+ for (const entry of post) {
23089
+ if (!Array.isArray(entry?.hooks)) continue;
23090
+ for (const hook of entry.hooks) {
23091
+ if (typeof hook?.command === "string" && hook.command.includes("harnesstrim") && hook.command.includes("hook codex")) {
23092
+ hook.command = command;
23093
+ }
23094
+ }
23095
+ }
23096
+ }
22999
23097
  function planCodexHookInstall(input) {
23000
23098
  let document = {};
23001
23099
  let action;
@@ -23012,14 +23110,21 @@ function planCodexHookInstall(input) {
23012
23110
  throw new Error(".codex/hooks.json must contain a JSON object; refusing to overwrite it.");
23013
23111
  }
23014
23112
  document = parsed;
23015
- action = hasHarnessTrimHook(document) ? "present" : "patch";
23113
+ action = hasHarnessTrimHook(document) && (!input.hookCommand || hasExactHarnessTrimHookCommand(document, input.hookCommand)) ? "present" : "patch";
23016
23114
  }
23017
23115
  if (action === "present") {
23018
23116
  return { hooksFile: path3.join(input.projectDir, ".codex", "hooks.json"), action, nextHooks: document };
23019
23117
  }
23118
+ if (hasHarnessTrimHook(document)) {
23119
+ replaceHarnessTrimHookCommand(document, input.hookCommand ?? CODEX_HOOK_COMMAND);
23120
+ return { hooksFile: path3.join(input.projectDir, ".codex", "hooks.json"), action, nextHooks: document };
23121
+ }
23020
23122
  const hooks = { ...document.hooks ?? {} };
23021
23123
  const post = Array.isArray(hooks.PostToolUse) ? [...hooks.PostToolUse] : [];
23022
- post.push({ matcher: CODEX_HOOK_MATCHER, hooks: [{ type: "command", command: CODEX_HOOK_COMMAND }] });
23124
+ post.push({
23125
+ matcher: CODEX_HOOK_MATCHER,
23126
+ hooks: [{ type: "command", command: input.hookCommand ?? CODEX_HOOK_COMMAND }]
23127
+ });
23023
23128
  hooks.PostToolUse = post;
23024
23129
  return {
23025
23130
  hooksFile: path3.join(input.projectDir, ".codex", "hooks.json"),
@@ -23094,6 +23199,13 @@ function existingSkillNames(dest) {
23094
23199
  }
23095
23200
 
23096
23201
  // src/install-codex.ts
23202
+ function resolveCodexHookCommand() {
23203
+ if (process.platform !== "win32") return void 0;
23204
+ const pnpmHome = process.env.PNPM_HOME ?? path6.join(process.env.LOCALAPPDATA ?? path6.join(os.homedir(), "AppData", "Local"), "pnpm");
23205
+ const shim = path6.join(pnpmHome, "harnesstrim.CMD");
23206
+ if (!fs5.existsSync(shim)) return void 0;
23207
+ return `"${shim}" hook codex --metrics .harnesstrim/metrics.jsonl`;
23208
+ }
23097
23209
  function readHooksJson(hooksPath) {
23098
23210
  try {
23099
23211
  return fs5.readFileSync(hooksPath, "utf8");
@@ -23127,7 +23239,7 @@ function runInstallCodex(dir, apply, hook = false) {
23127
23239
  });
23128
23240
  const hooksPath = path6.join(dir, ".codex", "hooks.json");
23129
23241
  const hooksJsonContent = hook ? readHooksJson(hooksPath) : null;
23130
- const hookPlan = hook ? planCodexHookInstall({ projectDir: dir, hooksJsonContent }) : null;
23242
+ const hookPlan = hook ? planCodexHookInstall({ projectDir: dir, hooksJsonContent, hookCommand: resolveCodexHookCommand() }) : null;
23131
23243
  const copied = [];
23132
23244
  let applied = false;
23133
23245
  if (apply) {
@@ -23152,7 +23264,8 @@ function runInstallCodexGlobalHook(codexHome, apply) {
23152
23264
  // The planner expects the directory that contains .codex; for a user-level config
23153
23265
  // the Codex home is itself that directory, so add its parent and use a normal path.
23154
23266
  projectDir: path6.dirname(codexHome),
23155
- hooksJsonContent: readHooksJson(hooksPath)
23267
+ hooksJsonContent: readHooksJson(hooksPath),
23268
+ hookCommand: resolveCodexHookCommand()
23156
23269
  });
23157
23270
  return { hookPlan, applied: applyHookPlan(hookPlan, apply) };
23158
23271
  }
@@ -23332,12 +23445,13 @@ function runInstallClaude(dir, apply) {
23332
23445
  // src/install-pi.ts
23333
23446
  import fs7 from "node:fs";
23334
23447
  import path10 from "node:path";
23448
+ import os2 from "node:os";
23335
23449
 
23336
23450
  // ../adapter-pi/src/index.ts
23337
23451
  import path9 from "node:path";
23338
23452
  var PI_EXTENSION_NAME = "harnesstrim";
23339
23453
  function planPiInstall(input) {
23340
- const extensionDest = path9.join(input.installDir, ".pi", "extensions", PI_EXTENSION_NAME);
23454
+ const extensionDest = input.scope === "user" ? path9.join(input.installDir, ".pi", "agent", "extensions", PI_EXTENSION_NAME) : path9.join(input.installDir, ".pi", "extensions", PI_EXTENSION_NAME);
23341
23455
  return {
23342
23456
  extensionDest,
23343
23457
  extensionSource: input.extensionSourceDir,
@@ -23372,16 +23486,18 @@ function markerPresent(dest) {
23372
23486
  }
23373
23487
  function runInstallPi(installDir, apply) {
23374
23488
  const extensionSourceDir = resolvePiExtensionSourceDir();
23375
- const dest = path10.join(installDir, ".pi", "extensions", "harnesstrim");
23489
+ const scope = path10.resolve(installDir) === path10.resolve(os2.homedir()) ? "user" : "project";
23490
+ const dest = scope === "user" ? path10.join(installDir, ".pi", "agent", "extensions", "harnesstrim") : path10.join(installDir, ".pi", "extensions", "harnesstrim");
23376
23491
  const plan = planPiInstall({
23377
23492
  installDir,
23378
23493
  extensionSourceDir,
23379
23494
  extensionDirExists: dirExists(dest),
23380
- markerPresent: markerPresent(dest)
23495
+ markerPresent: markerPresent(dest),
23496
+ scope
23381
23497
  });
23382
23498
  const copiedFiles = [];
23383
23499
  let applied = false;
23384
- if (apply && !plan.alreadyInstalled) {
23500
+ if (apply) {
23385
23501
  fs7.mkdirSync(dest, { recursive: true });
23386
23502
  for (const entry of fs7.readdirSync(extensionSourceDir, { withFileTypes: true })) {
23387
23503
  if (!entry.isDirectory()) {
@@ -23485,8 +23601,8 @@ function runInstallHermes(installDir, apply) {
23485
23601
  init_src();
23486
23602
  import fs9 from "node:fs";
23487
23603
  import path13 from "node:path";
23488
- import os from "node:os";
23489
- var HERMES_METRICS_PATH = path13.join(os.homedir(), ".hermes", "harnesstrim-metrics.jsonl");
23604
+ import os3 from "node:os";
23605
+ var HERMES_METRICS_PATH = path13.join(os3.homedir(), ".hermes", "harnesstrim-metrics.jsonl");
23490
23606
  var LOCAL_METRICS_PATH = ".harnesstrim/metrics.jsonl";
23491
23607
  var DEFAULT_METRICS_PATH = fs9.existsSync(HERMES_METRICS_PATH) ? HERMES_METRICS_PATH : LOCAL_METRICS_PATH;
23492
23608
  function loadMetrics(filePath) {
@@ -23502,9 +23618,46 @@ function loadMetrics(filePath) {
23502
23618
  return { path: filePath, found: true, summary: summarize(parseTrimEvents(raw)) };
23503
23619
  }
23504
23620
 
23621
+ // package.json
23622
+ var package_default = {
23623
+ name: "harnesstrim",
23624
+ version: "0.0.6",
23625
+ description: "HarnessTrim CLI: doctor (diagnose token waste), install adapters, run benchmarks.",
23626
+ license: "MIT",
23627
+ type: "module",
23628
+ bin: {
23629
+ harnesstrim: "./dist/cli.mjs"
23630
+ },
23631
+ files: [
23632
+ "dist",
23633
+ "assets"
23634
+ ],
23635
+ publishConfig: {
23636
+ access: "public"
23637
+ },
23638
+ devDependencies: {
23639
+ "@harnesstrim/adapter-claude": "workspace:*",
23640
+ "@harnesstrim/adapter-codex": "workspace:*",
23641
+ "@harnesstrim/adapter-hermes": "workspace:*",
23642
+ "@harnesstrim/adapter-pi": "workspace:*",
23643
+ "@harnesstrim/benchmarks": "workspace:*",
23644
+ "@harnesstrim/core": "workspace:*",
23645
+ "@harnesstrim/mcp": "workspace:*",
23646
+ esbuild: "^0.25.0"
23647
+ },
23648
+ scripts: {
23649
+ build: "node build.mjs",
23650
+ prepare: "node build.mjs",
23651
+ prepack: "node build.mjs",
23652
+ test: 'node --test "src/**/*.test.ts"',
23653
+ typecheck: "tsc -p tsconfig.json"
23654
+ }
23655
+ };
23656
+
23505
23657
  // src/reduce.ts
23506
23658
  init_src();
23507
23659
  async function readStdin() {
23660
+ if (process.stdin.isTTY) return "";
23508
23661
  const chunks = [];
23509
23662
  for await (const chunk of process.stdin) {
23510
23663
  chunks.push(chunk);
@@ -23798,7 +23951,7 @@ Usage:
23798
23951
  harnesstrim mcp [--metrics <path>] Start the MCP server (stdio) exposing a reduce tool;
23799
23952
  --metrics records a TrimEvent per reduction
23800
23953
  harnesstrim bench Run the Tier A reducer micro-benchmark
23801
- harnesstrim --help Show this help
23954
+ harnesstrim --version Print the installed version
23802
23955
 
23803
23956
  Notes:
23804
23957
  - install is dry-run by default; nothing is written without --apply.
@@ -23810,6 +23963,7 @@ async function main(argv) {
23810
23963
  allowPositionals: true,
23811
23964
  options: {
23812
23965
  help: { type: "boolean", short: "h" },
23966
+ version: { type: "boolean", short: "v" },
23813
23967
  apply: { type: "boolean" },
23814
23968
  preset: { type: "string" },
23815
23969
  stats: { type: "boolean" },
@@ -23821,6 +23975,10 @@ async function main(argv) {
23821
23975
  }
23822
23976
  });
23823
23977
  const [command, ...rest] = positionals;
23978
+ if (values.version) {
23979
+ console.log(package_default.version);
23980
+ return 0;
23981
+ }
23824
23982
  if (values.help || !command) {
23825
23983
  console.log(HELP);
23826
23984
  return 0;
@@ -23833,7 +23991,7 @@ async function main(argv) {
23833
23991
  }
23834
23992
  case "install": {
23835
23993
  const target = rest[0];
23836
- const dir = rest[1] ?? (target === "hermes" ? os2.homedir() : process.cwd());
23994
+ const dir = rest[1] ?? (target === "hermes" ? os4.homedir() : process.cwd());
23837
23995
  const apply = values.apply === true;
23838
23996
  if (target === "opencode") {
23839
23997
  const result = runInstallOpencode(dir, apply, values.preset);
@@ -23846,7 +24004,7 @@ async function main(argv) {
23846
24004
  console.error("`harnesstrim install codex --global` requires `--hook`.");
23847
24005
  return 1;
23848
24006
  }
23849
- console.log(renderCodexGlobalHookInstall(runInstallCodexGlobalHook(path15.join(os2.homedir(), ".codex"), apply), apply));
24007
+ console.log(renderCodexGlobalHookInstall(runInstallCodexGlobalHook(path15.join(os4.homedir(), ".codex"), apply), apply));
23850
24008
  return 0;
23851
24009
  }
23852
24010
  console.log(renderCodexInstall(runInstallCodex(dir, apply, values.hook === true), apply));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harnesstrim",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "HarnessTrim CLI: doctor (diagnose token waste), install adapters, run benchmarks.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -15,20 +15,18 @@
15
15
  "access": "public"
16
16
  },
17
17
  "devDependencies": {
18
- "@harnesstrim/adapter-claude": "workspace:*",
19
- "@harnesstrim/adapter-codex": "workspace:*",
20
- "@harnesstrim/adapter-hermes": "workspace:*",
21
- "@harnesstrim/adapter-pi": "workspace:*",
22
- "@harnesstrim/benchmarks": "workspace:*",
23
- "@harnesstrim/core": "workspace:*",
24
- "@harnesstrim/mcp": "workspace:*",
25
- "esbuild": "^0.25.0"
18
+ "esbuild": "^0.25.0",
19
+ "@harnesstrim/adapter-claude": "0.0.1",
20
+ "@harnesstrim/adapter-hermes": "0.0.1",
21
+ "@harnesstrim/core": "0.0.2",
22
+ "@harnesstrim/adapter-codex": "0.0.1",
23
+ "@harnesstrim/benchmarks": "0.0.1",
24
+ "@harnesstrim/mcp": "0.0.1",
25
+ "@harnesstrim/adapter-pi": "0.0.1"
26
26
  },
27
27
  "scripts": {
28
28
  "build": "node build.mjs",
29
- "prepare": "node build.mjs",
30
- "prepack": "node build.mjs",
31
29
  "test": "node --test \"src/**/*.test.ts\"",
32
30
  "typecheck": "tsc -p tsconfig.json"
33
31
  }
34
- }
32
+ }