harnesstrim 0.0.6 → 0.0.7
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.
|
@@ -225,14 +225,19 @@ def _write_metric(tool: str, reducer: str | None, before: int, after: int) -> No
|
|
|
225
225
|
directory lazily and swallows any error — telemetry must never crash the plugin.
|
|
226
226
|
"""
|
|
227
227
|
import datetime as _dt
|
|
228
|
+
import uuid as _uuid
|
|
228
229
|
|
|
229
230
|
event = {
|
|
231
|
+
"schemaVersion": 1,
|
|
232
|
+
"eventId": str(_uuid.uuid4()),
|
|
230
233
|
"ts": _dt.datetime.now(_dt.timezone.utc).isoformat(),
|
|
231
234
|
"harness": "hermes",
|
|
232
235
|
"tool": tool,
|
|
233
236
|
"reducer": reducer,
|
|
234
237
|
"beforeChars": before,
|
|
235
238
|
"afterChars": after,
|
|
239
|
+
"beforeTokens": None,
|
|
240
|
+
"afterTokens": None,
|
|
236
241
|
}
|
|
237
242
|
try:
|
|
238
243
|
METRICS_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -29,6 +29,11 @@ const MODE = env.HARNESSTRIM_MODE ?? "dryrun";
|
|
|
29
29
|
const MIN_LENGTH = Number(env.HARNESSTRIM_MINLENGTH ?? "400") || 400;
|
|
30
30
|
const MARKER = "[harnesstrim";
|
|
31
31
|
|
|
32
|
+
/** True when a text chunk should not be reduced: too short, or already reduced. */
|
|
33
|
+
export function shouldSkip(text: string, minLength: number): boolean {
|
|
34
|
+
return text.length < minLength || text.includes(MARKER);
|
|
35
|
+
}
|
|
36
|
+
|
|
32
37
|
function reduceViaCli(text: string): string | null {
|
|
33
38
|
try {
|
|
34
39
|
const r = spawnSync("harnesstrim", ["reduce", "--min-length", String(MIN_LENGTH)], {
|
|
@@ -54,7 +59,7 @@ export default function harnesstrim(pi: ExtensionAPI): void {
|
|
|
54
59
|
const content = event.content.map((chunk) => {
|
|
55
60
|
if (chunk.type !== "text" || typeof chunk.text !== "string") return chunk;
|
|
56
61
|
const text = chunk.text;
|
|
57
|
-
if (text
|
|
62
|
+
if (shouldSkip(text, MIN_LENGTH)) return chunk;
|
|
58
63
|
|
|
59
64
|
const reduced = reduceViaCli(text);
|
|
60
65
|
if (!reduced || reduced.length >= text.length) return chunk;
|
package/dist/cli.mjs
CHANGED
|
@@ -713,6 +713,21 @@ var init_dispatch = __esm({
|
|
|
713
713
|
});
|
|
714
714
|
|
|
715
715
|
// ../core/src/metrics/trim-event.ts
|
|
716
|
+
import { randomUUID } from "node:crypto";
|
|
717
|
+
function makeTrimEvent(partial2) {
|
|
718
|
+
return {
|
|
719
|
+
schemaVersion: TRIM_EVENT_SCHEMA_VERSION,
|
|
720
|
+
eventId: randomUUID(),
|
|
721
|
+
ts: partial2.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
722
|
+
harness: partial2.harness,
|
|
723
|
+
tool: partial2.tool,
|
|
724
|
+
reducer: partial2.reducer,
|
|
725
|
+
beforeChars: partial2.beforeChars,
|
|
726
|
+
afterChars: partial2.afterChars,
|
|
727
|
+
beforeTokens: partial2.beforeTokens ?? null,
|
|
728
|
+
afterTokens: partial2.afterTokens ?? null
|
|
729
|
+
};
|
|
730
|
+
}
|
|
716
731
|
function pct(before, after) {
|
|
717
732
|
if (before === 0) return 0;
|
|
718
733
|
return Math.round((1 - after / before) * 1e3) / 10;
|
|
@@ -753,7 +768,7 @@ function parseTrimEvents(jsonl) {
|
|
|
753
768
|
} catch {
|
|
754
769
|
continue;
|
|
755
770
|
}
|
|
756
|
-
if (isTrimEvent(parsed)) out.push(parsed);
|
|
771
|
+
if (isTrimEvent(parsed)) out.push(normalize(parsed));
|
|
757
772
|
}
|
|
758
773
|
return out;
|
|
759
774
|
}
|
|
@@ -762,9 +777,25 @@ function isTrimEvent(value) {
|
|
|
762
777
|
const v = value;
|
|
763
778
|
return typeof v.beforeChars === "number" && typeof v.afterChars === "number" && typeof v.tool === "string" && (typeof v.reducer === "string" || v.reducer === null);
|
|
764
779
|
}
|
|
780
|
+
function normalize(v) {
|
|
781
|
+
return {
|
|
782
|
+
schemaVersion: typeof v.schemaVersion === "number" ? v.schemaVersion : 0,
|
|
783
|
+
eventId: typeof v.eventId === "string" ? v.eventId : "",
|
|
784
|
+
ts: v.ts,
|
|
785
|
+
harness: v.harness,
|
|
786
|
+
tool: v.tool,
|
|
787
|
+
reducer: v.reducer,
|
|
788
|
+
beforeChars: v.beforeChars,
|
|
789
|
+
afterChars: v.afterChars,
|
|
790
|
+
beforeTokens: typeof v.beforeTokens === "number" ? v.beforeTokens : null,
|
|
791
|
+
afterTokens: typeof v.afterTokens === "number" ? v.afterTokens : null
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
var TRIM_EVENT_SCHEMA_VERSION;
|
|
765
795
|
var init_trim_event = __esm({
|
|
766
796
|
"../core/src/metrics/trim-event.ts"() {
|
|
767
797
|
"use strict";
|
|
798
|
+
TRIM_EVENT_SCHEMA_VERSION = 1;
|
|
768
799
|
}
|
|
769
800
|
});
|
|
770
801
|
|
|
@@ -1239,8 +1270,8 @@ var init_parseUtil = __esm({
|
|
|
1239
1270
|
init_errors();
|
|
1240
1271
|
init_en();
|
|
1241
1272
|
makeIssue = (params) => {
|
|
1242
|
-
const { data, path:
|
|
1243
|
-
const fullPath = [...
|
|
1273
|
+
const { data, path: path17, errorMaps, issueData } = params;
|
|
1274
|
+
const fullPath = [...path17, ...issueData.path || []];
|
|
1244
1275
|
const fullIssue = {
|
|
1245
1276
|
...issueData,
|
|
1246
1277
|
path: fullPath
|
|
@@ -1548,11 +1579,11 @@ var init_types2 = __esm({
|
|
|
1548
1579
|
init_parseUtil();
|
|
1549
1580
|
init_util();
|
|
1550
1581
|
ParseInputLazyPath = class {
|
|
1551
|
-
constructor(parent, value,
|
|
1582
|
+
constructor(parent, value, path17, key) {
|
|
1552
1583
|
this._cachedPath = [];
|
|
1553
1584
|
this.parent = parent;
|
|
1554
1585
|
this.data = value;
|
|
1555
|
-
this._path =
|
|
1586
|
+
this._path = path17;
|
|
1556
1587
|
this._key = key;
|
|
1557
1588
|
}
|
|
1558
1589
|
get path() {
|
|
@@ -5133,10 +5164,10 @@ function assignProp(target, prop, value) {
|
|
|
5133
5164
|
configurable: true
|
|
5134
5165
|
});
|
|
5135
5166
|
}
|
|
5136
|
-
function getElementAtPath(obj,
|
|
5137
|
-
if (!
|
|
5167
|
+
function getElementAtPath(obj, path17) {
|
|
5168
|
+
if (!path17)
|
|
5138
5169
|
return obj;
|
|
5139
|
-
return
|
|
5170
|
+
return path17.reduce((acc, key) => acc?.[key], obj);
|
|
5140
5171
|
}
|
|
5141
5172
|
function promiseAllObject(promisesObj) {
|
|
5142
5173
|
const keys = Object.keys(promisesObj);
|
|
@@ -5385,11 +5416,11 @@ function aborted(x, startIndex = 0) {
|
|
|
5385
5416
|
}
|
|
5386
5417
|
return false;
|
|
5387
5418
|
}
|
|
5388
|
-
function prefixIssues(
|
|
5419
|
+
function prefixIssues(path17, issues) {
|
|
5389
5420
|
return issues.map((iss) => {
|
|
5390
5421
|
var _a;
|
|
5391
5422
|
(_a = iss).path ?? (_a.path = []);
|
|
5392
|
-
iss.path.unshift(
|
|
5423
|
+
iss.path.unshift(path17);
|
|
5393
5424
|
return iss;
|
|
5394
5425
|
});
|
|
5395
5426
|
}
|
|
@@ -16225,8 +16256,8 @@ var require_resolve = __commonJS({
|
|
|
16225
16256
|
}
|
|
16226
16257
|
return count;
|
|
16227
16258
|
}
|
|
16228
|
-
function getFullPath(resolver, id = "",
|
|
16229
|
-
if (
|
|
16259
|
+
function getFullPath(resolver, id = "", normalize2) {
|
|
16260
|
+
if (normalize2 !== false)
|
|
16230
16261
|
id = normalizeId(id);
|
|
16231
16262
|
const p = resolver.parse(id);
|
|
16232
16263
|
return _getFullPath(resolver, p);
|
|
@@ -17219,8 +17250,8 @@ var require_utils = __commonJS({
|
|
|
17219
17250
|
}
|
|
17220
17251
|
return ind;
|
|
17221
17252
|
}
|
|
17222
|
-
function removeDotSegments(
|
|
17223
|
-
let input =
|
|
17253
|
+
function removeDotSegments(path17) {
|
|
17254
|
+
let input = path17;
|
|
17224
17255
|
const output = [];
|
|
17225
17256
|
let nextSlash = -1;
|
|
17226
17257
|
let len = 0;
|
|
@@ -17472,8 +17503,8 @@ var require_schemes = __commonJS({
|
|
|
17472
17503
|
wsComponent.secure = void 0;
|
|
17473
17504
|
}
|
|
17474
17505
|
if (wsComponent.resourceName) {
|
|
17475
|
-
const [
|
|
17476
|
-
wsComponent.path =
|
|
17506
|
+
const [path17, query] = wsComponent.resourceName.split("?");
|
|
17507
|
+
wsComponent.path = path17 && path17 !== "/" ? path17 : void 0;
|
|
17477
17508
|
wsComponent.query = query;
|
|
17478
17509
|
wsComponent.resourceName = void 0;
|
|
17479
17510
|
}
|
|
@@ -17622,7 +17653,7 @@ var require_fast_uri = __commonJS({
|
|
|
17622
17653
|
"use strict";
|
|
17623
17654
|
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
17624
17655
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
17625
|
-
function
|
|
17656
|
+
function normalize2(uri, options) {
|
|
17626
17657
|
if (typeof uri === "string") {
|
|
17627
17658
|
uri = /** @type {T} */
|
|
17628
17659
|
normalizeString(uri, options);
|
|
@@ -17889,7 +17920,7 @@ var require_fast_uri = __commonJS({
|
|
|
17889
17920
|
}
|
|
17890
17921
|
var fastUri = {
|
|
17891
17922
|
SCHEMES,
|
|
17892
|
-
normalize,
|
|
17923
|
+
normalize: normalize2,
|
|
17893
17924
|
resolve,
|
|
17894
17925
|
resolveComponent,
|
|
17895
17926
|
equal,
|
|
@@ -20866,12 +20897,12 @@ var require_dist = __commonJS({
|
|
|
20866
20897
|
throw new Error(`Unknown format "${name}"`);
|
|
20867
20898
|
return f;
|
|
20868
20899
|
};
|
|
20869
|
-
function addFormats(ajv, list,
|
|
20900
|
+
function addFormats(ajv, list, fs13, exportName) {
|
|
20870
20901
|
var _a;
|
|
20871
20902
|
var _b;
|
|
20872
20903
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
20873
20904
|
for (const f of list)
|
|
20874
|
-
ajv.addFormat(f,
|
|
20905
|
+
ajv.addFormat(f, fs13[f]);
|
|
20875
20906
|
}
|
|
20876
20907
|
module.exports = exports = formatsPlugin;
|
|
20877
20908
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -22632,14 +22663,14 @@ __export(server_exports, {
|
|
|
22632
22663
|
runReduceTool: () => runReduceTool,
|
|
22633
22664
|
startStdioServer: () => startStdioServer
|
|
22634
22665
|
});
|
|
22635
|
-
import
|
|
22636
|
-
import
|
|
22666
|
+
import fs11 from "node:fs";
|
|
22667
|
+
import path15 from "node:path";
|
|
22637
22668
|
function createFileSink(metricsPath) {
|
|
22638
22669
|
return (event) => {
|
|
22639
22670
|
try {
|
|
22640
|
-
const p =
|
|
22641
|
-
|
|
22642
|
-
|
|
22671
|
+
const p = path15.resolve(metricsPath);
|
|
22672
|
+
fs11.mkdirSync(path15.dirname(p), { recursive: true });
|
|
22673
|
+
fs11.appendFileSync(p, JSON.stringify(event) + "\n");
|
|
22643
22674
|
} catch {
|
|
22644
22675
|
}
|
|
22645
22676
|
};
|
|
@@ -22647,14 +22678,15 @@ function createFileSink(metricsPath) {
|
|
|
22647
22678
|
function runReduceTool(text, minLength, sink = noopSink) {
|
|
22648
22679
|
const result = reduceAuto(text, minLength);
|
|
22649
22680
|
if (result.changed) {
|
|
22650
|
-
sink(
|
|
22651
|
-
|
|
22652
|
-
|
|
22653
|
-
|
|
22654
|
-
|
|
22655
|
-
|
|
22656
|
-
|
|
22657
|
-
|
|
22681
|
+
sink(
|
|
22682
|
+
makeTrimEvent({
|
|
22683
|
+
harness: "mcp",
|
|
22684
|
+
tool: "reduce",
|
|
22685
|
+
reducer: result.reducer,
|
|
22686
|
+
beforeChars: text.length,
|
|
22687
|
+
afterChars: result.output.length
|
|
22688
|
+
})
|
|
22689
|
+
);
|
|
22658
22690
|
}
|
|
22659
22691
|
return { content: [{ type: "text", text: result.output }] };
|
|
22660
22692
|
}
|
|
@@ -22698,8 +22730,8 @@ var init_server3 = __esm({
|
|
|
22698
22730
|
// src/cli.ts
|
|
22699
22731
|
init_src();
|
|
22700
22732
|
import { parseArgs } from "node:util";
|
|
22701
|
-
import
|
|
22702
|
-
import
|
|
22733
|
+
import fs12 from "node:fs";
|
|
22734
|
+
import path16 from "node:path";
|
|
22703
22735
|
import os4 from "node:os";
|
|
22704
22736
|
|
|
22705
22737
|
// src/doctor.ts
|
|
@@ -22930,7 +22962,7 @@ function planOpencodeInstall(input) {
|
|
|
22930
22962
|
const changed = wrapperChanged || pkgChanged || opencodeChanged;
|
|
22931
22963
|
return { wrapperContent, packageJsonContent, opencodeJsonContent, alreadyInstalled, changed };
|
|
22932
22964
|
}
|
|
22933
|
-
function runInstallOpencode(dir, apply, presetName, installDeps = true) {
|
|
22965
|
+
function runInstallOpencode(dir, apply, presetName, installDeps = true, options = {}) {
|
|
22934
22966
|
let preset;
|
|
22935
22967
|
let adapterConfig = { ...DEFAULT_OPENCODE_ADAPTER_CONFIG };
|
|
22936
22968
|
if (presetName) {
|
|
@@ -22938,19 +22970,22 @@ function runInstallOpencode(dir, apply, presetName, installDeps = true) {
|
|
|
22938
22970
|
if (!preset) throw new Error(`Unknown preset: ${presetName}`);
|
|
22939
22971
|
adapterConfig = { ...adapterConfig, ...preset.adapter };
|
|
22940
22972
|
}
|
|
22973
|
+
if (options.mode !== void 0) adapterConfig.mode = options.mode;
|
|
22974
|
+
if (options.minLength !== void 0) adapterConfig.minLength = options.minLength;
|
|
22975
|
+
if (options.tools !== void 0) adapterConfig.toolFilter = options.tools;
|
|
22941
22976
|
const wrapperPath = path2.join(dir, WRAPPER_REL);
|
|
22942
22977
|
const packageJsonPath = path2.join(dir, PKG_REL);
|
|
22943
22978
|
const opencodeJsonPath = path2.join(dir, OPENCODE_JSON);
|
|
22944
|
-
const
|
|
22979
|
+
const readOrNull2 = (p) => {
|
|
22945
22980
|
try {
|
|
22946
22981
|
return fs2.readFileSync(p, "utf8");
|
|
22947
22982
|
} catch {
|
|
22948
22983
|
return null;
|
|
22949
22984
|
}
|
|
22950
22985
|
};
|
|
22951
|
-
const existingWrapper =
|
|
22952
|
-
const existingPackageJson =
|
|
22953
|
-
const existingOpencodeJson =
|
|
22986
|
+
const existingWrapper = readOrNull2(wrapperPath);
|
|
22987
|
+
const existingPackageJson = readOrNull2(packageJsonPath);
|
|
22988
|
+
const existingOpencodeJson = readOrNull2(opencodeJsonPath);
|
|
22954
22989
|
const plan = planOpencodeInstall({
|
|
22955
22990
|
existingWrapper,
|
|
22956
22991
|
existingPackageJson,
|
|
@@ -23133,6 +23168,7 @@ function planCodexHookInstall(input) {
|
|
|
23133
23168
|
};
|
|
23134
23169
|
}
|
|
23135
23170
|
function planCodexInstall(input) {
|
|
23171
|
+
const includeInstructions = input.includeInstructions ?? true;
|
|
23136
23172
|
const skillsDest = path3.join(input.projectDir, ".codex", "skills");
|
|
23137
23173
|
const existing = new Set(input.existingSkillNames);
|
|
23138
23174
|
const skills = input.skillNames.map((name) => ({
|
|
@@ -23142,7 +23178,9 @@ function planCodexInstall(input) {
|
|
|
23142
23178
|
present: existing.has(name)
|
|
23143
23179
|
}));
|
|
23144
23180
|
let instructionsAction;
|
|
23145
|
-
if (
|
|
23181
|
+
if (!includeInstructions) {
|
|
23182
|
+
instructionsAction = "skip";
|
|
23183
|
+
} else if (input.agentsMdContent === null) {
|
|
23146
23184
|
instructionsAction = "create";
|
|
23147
23185
|
} else if (input.agentsMdContent.includes(HARNESSTRIM_MARKER)) {
|
|
23148
23186
|
instructionsAction = "present";
|
|
@@ -23154,7 +23192,8 @@ function planCodexInstall(input) {
|
|
|
23154
23192
|
skills,
|
|
23155
23193
|
instructionsFile: path3.join(input.projectDir, "AGENTS.md"),
|
|
23156
23194
|
instructionsAction,
|
|
23157
|
-
instructionsSnippet: REDUCE_INSTRUCTION_SNIPPET
|
|
23195
|
+
instructionsSnippet: REDUCE_INSTRUCTION_SNIPPET,
|
|
23196
|
+
changed: skills.some((s) => !s.present) || instructionsAction !== "present" && instructionsAction !== "skip"
|
|
23158
23197
|
};
|
|
23159
23198
|
}
|
|
23160
23199
|
|
|
@@ -23219,7 +23258,7 @@ function applyHookPlan(plan, apply) {
|
|
|
23219
23258
|
fs5.writeFileSync(plan.hooksFile, JSON.stringify(plan.nextHooks, null, 2) + "\n");
|
|
23220
23259
|
return true;
|
|
23221
23260
|
}
|
|
23222
|
-
function runInstallCodex(dir, apply, hook = false) {
|
|
23261
|
+
function runInstallCodex(dir, apply, hook = false, options = {}) {
|
|
23223
23262
|
const skillsSourceDir = resolveSkillsSourceDir();
|
|
23224
23263
|
const skillNames = listShippedSkills(skillsSourceDir);
|
|
23225
23264
|
const skillsDest = path6.join(dir, ".codex", "skills");
|
|
@@ -23235,14 +23274,16 @@ function runInstallCodex(dir, apply, hook = false) {
|
|
|
23235
23274
|
skillsSourceDir,
|
|
23236
23275
|
skillNames,
|
|
23237
23276
|
agentsMdContent,
|
|
23238
|
-
existingSkillNames: existingSkillNames(skillsDest)
|
|
23277
|
+
existingSkillNames: existingSkillNames(skillsDest),
|
|
23278
|
+
includeInstructions: options.includeInstructions
|
|
23239
23279
|
});
|
|
23240
23280
|
const hooksPath = path6.join(dir, ".codex", "hooks.json");
|
|
23241
23281
|
const hooksJsonContent = hook ? readHooksJson(hooksPath) : null;
|
|
23242
23282
|
const hookPlan = hook ? planCodexHookInstall({ projectDir: dir, hooksJsonContent, hookCommand: resolveCodexHookCommand() }) : null;
|
|
23243
23283
|
const copied = [];
|
|
23284
|
+
const hookChanged = hookPlan !== null && hookPlan.action !== "present";
|
|
23244
23285
|
let applied = false;
|
|
23245
|
-
if (apply) {
|
|
23286
|
+
if (apply && (plan.changed || hookChanged)) {
|
|
23246
23287
|
for (const skill of plan.skills) {
|
|
23247
23288
|
if (skill.present) continue;
|
|
23248
23289
|
fs5.cpSync(skill.from, skill.to, { recursive: true });
|
|
@@ -23253,7 +23294,7 @@ function runInstallCodex(dir, apply, hook = false) {
|
|
|
23253
23294
|
} else if (plan.instructionsAction === "append") {
|
|
23254
23295
|
fs5.appendFileSync(plan.instructionsFile, "\n\n" + plan.instructionsSnippet + "\n");
|
|
23255
23296
|
}
|
|
23256
|
-
if (hookPlan) applyHookPlan(hookPlan, true);
|
|
23297
|
+
if (hookPlan && hookChanged) applyHookPlan(hookPlan, true);
|
|
23257
23298
|
applied = true;
|
|
23258
23299
|
}
|
|
23259
23300
|
return { plan, hookPlan, applied, copied };
|
|
@@ -23349,6 +23390,8 @@ function hasHarnessTrimHook2(settings) {
|
|
|
23349
23390
|
);
|
|
23350
23391
|
}
|
|
23351
23392
|
function planClaudeInstall(input) {
|
|
23393
|
+
const includeHook = input.includeHook ?? true;
|
|
23394
|
+
const includeInstructions = input.includeInstructions ?? true;
|
|
23352
23395
|
const skillsDest = path7.join(input.projectDir, ".claude", "skills");
|
|
23353
23396
|
const existing = new Set(input.existingSkillNames);
|
|
23354
23397
|
const skills = input.skillNames.map((name) => ({
|
|
@@ -23370,8 +23413,12 @@ function planClaudeInstall(input) {
|
|
|
23370
23413
|
}
|
|
23371
23414
|
action = hasHarnessTrimHook2(settings) ? "present" : "patch";
|
|
23372
23415
|
}
|
|
23373
|
-
|
|
23374
|
-
|
|
23416
|
+
if (!includeHook) {
|
|
23417
|
+
action = "skip";
|
|
23418
|
+
settings = input.settingsJsonContent === null ? {} : settings;
|
|
23419
|
+
}
|
|
23420
|
+
const nextSettings = action === "present" || action === "skip" ? settings : addHook(settings);
|
|
23421
|
+
const instructionsAction = !includeInstructions ? "skip" : input.claudeMdContent === null ? "create" : input.claudeMdContent.includes(HARNESSTRIM_MARKER2) ? "present" : "append";
|
|
23375
23422
|
return {
|
|
23376
23423
|
skillsDest,
|
|
23377
23424
|
skills,
|
|
@@ -23380,7 +23427,8 @@ function planClaudeInstall(input) {
|
|
|
23380
23427
|
nextSettings,
|
|
23381
23428
|
instructionsFile: path7.join(input.projectDir, "CLAUDE.md"),
|
|
23382
23429
|
instructionsAction,
|
|
23383
|
-
instructionsSnippet: REDUCE_INSTRUCTION_SNIPPET2
|
|
23430
|
+
instructionsSnippet: REDUCE_INSTRUCTION_SNIPPET2,
|
|
23431
|
+
changed: skills.some((s) => !s.present) || action !== "present" && action !== "skip" || instructionsAction !== "present" && instructionsAction !== "skip"
|
|
23384
23432
|
};
|
|
23385
23433
|
}
|
|
23386
23434
|
function addHook(settings) {
|
|
@@ -23394,7 +23442,7 @@ function addHook(settings) {
|
|
|
23394
23442
|
}
|
|
23395
23443
|
|
|
23396
23444
|
// src/install-claude.ts
|
|
23397
|
-
function runInstallClaude(dir, apply) {
|
|
23445
|
+
function runInstallClaude(dir, apply, options = {}) {
|
|
23398
23446
|
const skillsSourceDir = resolveSkillsSourceDir();
|
|
23399
23447
|
const skillNames = listShippedSkills(skillsSourceDir);
|
|
23400
23448
|
const skillsDest = path8.join(dir, ".claude", "skills");
|
|
@@ -23418,17 +23466,19 @@ function runInstallClaude(dir, apply) {
|
|
|
23418
23466
|
skillNames,
|
|
23419
23467
|
settingsJsonContent,
|
|
23420
23468
|
claudeMdContent,
|
|
23421
|
-
existingSkillNames: existingSkillNames(skillsDest)
|
|
23469
|
+
existingSkillNames: existingSkillNames(skillsDest),
|
|
23470
|
+
includeHook: options.includeHook,
|
|
23471
|
+
includeInstructions: options.includeInstructions
|
|
23422
23472
|
});
|
|
23423
23473
|
const copied = [];
|
|
23424
23474
|
let applied = false;
|
|
23425
|
-
if (apply) {
|
|
23475
|
+
if (apply && plan.changed) {
|
|
23426
23476
|
for (const skill of plan.skills) {
|
|
23427
23477
|
if (skill.present) continue;
|
|
23428
23478
|
fs6.cpSync(skill.from, skill.to, { recursive: true });
|
|
23429
23479
|
copied.push(skill.name);
|
|
23430
23480
|
}
|
|
23431
|
-
if (plan.settingsAction !== "present") {
|
|
23481
|
+
if (plan.settingsAction !== "present" && plan.settingsAction !== "skip") {
|
|
23432
23482
|
fs6.mkdirSync(path8.dirname(plan.settingsFile), { recursive: true });
|
|
23433
23483
|
fs6.writeFileSync(plan.settingsFile, JSON.stringify(plan.nextSettings, null, 2) + "\n");
|
|
23434
23484
|
}
|
|
@@ -23499,12 +23549,16 @@ function runInstallPi(installDir, apply) {
|
|
|
23499
23549
|
let applied = false;
|
|
23500
23550
|
if (apply) {
|
|
23501
23551
|
fs7.mkdirSync(dest, { recursive: true });
|
|
23502
|
-
|
|
23503
|
-
|
|
23504
|
-
|
|
23505
|
-
|
|
23552
|
+
const sourceFiles = fs7.readdirSync(extensionSourceDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name);
|
|
23553
|
+
for (const existing of fs7.readdirSync(dest)) {
|
|
23554
|
+
if (existing !== ".installed" && !sourceFiles.includes(existing)) {
|
|
23555
|
+
fs7.rmSync(path10.join(dest, existing), { recursive: true, force: true });
|
|
23506
23556
|
}
|
|
23507
23557
|
}
|
|
23558
|
+
for (const entry of sourceFiles) {
|
|
23559
|
+
fs7.copyFileSync(path10.join(extensionSourceDir, entry), path10.join(dest, entry));
|
|
23560
|
+
copiedFiles.push(entry);
|
|
23561
|
+
}
|
|
23508
23562
|
fs7.writeFileSync(path10.join(dest, ".installed"), markerFileContent());
|
|
23509
23563
|
copiedFiles.push(".installed");
|
|
23510
23564
|
applied = true;
|
|
@@ -23621,7 +23675,7 @@ function loadMetrics(filePath) {
|
|
|
23621
23675
|
// package.json
|
|
23622
23676
|
var package_default = {
|
|
23623
23677
|
name: "harnesstrim",
|
|
23624
|
-
version: "0.0.
|
|
23678
|
+
version: "0.0.7",
|
|
23625
23679
|
description: "HarnessTrim CLI: doctor (diagnose token waste), install adapters, run benchmarks.",
|
|
23626
23680
|
license: "MIT",
|
|
23627
23681
|
type: "module",
|
|
@@ -23669,6 +23723,324 @@ function reducePipe(input, minLength) {
|
|
|
23669
23723
|
return { ...result, beforeChars: input.length, afterChars: result.output.length };
|
|
23670
23724
|
}
|
|
23671
23725
|
|
|
23726
|
+
// src/capabilities.ts
|
|
23727
|
+
var CAPABILITIES = {
|
|
23728
|
+
harnesses: {
|
|
23729
|
+
opencode: {
|
|
23730
|
+
adapter: "@harnesstrim/adapter-opencode",
|
|
23731
|
+
surfaces: [
|
|
23732
|
+
"tool.execute.after \u2014 slims noisy tool output in place before it enters context",
|
|
23733
|
+
"experimental.session.compacting \u2014 injects compaction-handoff guidance"
|
|
23734
|
+
],
|
|
23735
|
+
narrowing: [
|
|
23736
|
+
{ flag: "--mode active|dryrun|off", produces: "bake the reduction mode into the generated wrapper (active/dryrun/off)" },
|
|
23737
|
+
{ flag: "--min-length <n>", produces: "leave tool outputs shorter than n chars untouched (overrides preset)" },
|
|
23738
|
+
{ flag: "--tools <name,...>", produces: "confine reduction to a subset of tool families (e.g. bash,read)" },
|
|
23739
|
+
{ flag: "--preset <name>", produces: "bake a policy preset's adapter config into the wrapper" }
|
|
23740
|
+
],
|
|
23741
|
+
writeSet: [
|
|
23742
|
+
".opencode/plugin/harnesstrim.ts",
|
|
23743
|
+
".opencode/package.json",
|
|
23744
|
+
"opencode.json (cleans a stale adapter entry, never adds one)"
|
|
23745
|
+
]
|
|
23746
|
+
},
|
|
23747
|
+
codex: {
|
|
23748
|
+
adapter: "@harnesstrim/adapter-codex",
|
|
23749
|
+
surfaces: [
|
|
23750
|
+
"PostToolUse Bash hook \u2014 deterministic reduction of simple Bash output (optional --hook)",
|
|
23751
|
+
"AGENTS.md reduce-pipe instruction \u2014 model pipes noisy output through `harnesstrim reduce`",
|
|
23752
|
+
"MCP reduce tool \u2014 deterministic, instruction-free reduction (separate `harnesstrim mcp`)"
|
|
23753
|
+
],
|
|
23754
|
+
narrowing: [
|
|
23755
|
+
{ flag: "--no-instructions", produces: "skills only \u2014 no AGENTS.md reduce-pipe instruction" },
|
|
23756
|
+
{ flag: "--hook", produces: "also install the experimental Bash PostToolUse hook" },
|
|
23757
|
+
{ flag: "--global", produces: "install the hook once in ~/.codex (with --hook)" }
|
|
23758
|
+
],
|
|
23759
|
+
writeSet: [".codex/skills/", "AGENTS.md (marker-guarded snippet)", ".codex/hooks.json (with --hook)"]
|
|
23760
|
+
},
|
|
23761
|
+
claude: {
|
|
23762
|
+
adapter: "@harnesstrim/adapter-claude",
|
|
23763
|
+
surfaces: [
|
|
23764
|
+
"PostToolUse Bash hook \u2014 spec-correct updatedToolOutput (not honored by Claude Code 2.1.37\u20132.1.212)",
|
|
23765
|
+
"CLAUDE.md reduce-pipe instruction \u2014 the effective reduction path on current Claude Code"
|
|
23766
|
+
],
|
|
23767
|
+
narrowing: [
|
|
23768
|
+
{ flag: "--no-hook", produces: "skills only \u2014 no PostToolUse hook in .claude/settings.json" },
|
|
23769
|
+
{ flag: "--no-instructions", produces: "skills only \u2014 no CLAUDE.md reduce-pipe instruction" }
|
|
23770
|
+
],
|
|
23771
|
+
writeSet: [".claude/skills/", ".claude/settings.json", "CLAUDE.md (marker-guarded snippet)"]
|
|
23772
|
+
},
|
|
23773
|
+
hermes: {
|
|
23774
|
+
adapter: "@harnesstrim/adapter-hermes",
|
|
23775
|
+
surfaces: ["transform_tool_result \u2014 deterministic reduction before the result enters context"],
|
|
23776
|
+
narrowing: [],
|
|
23777
|
+
writeSet: [".hermes/plugins/harnesstrim/ (incl. .installed marker)"]
|
|
23778
|
+
},
|
|
23779
|
+
pi: {
|
|
23780
|
+
adapter: "@harnesstrim/adapter-pi",
|
|
23781
|
+
surfaces: ["tool_result \u2014 deterministic reduction of text chunks in structured results"],
|
|
23782
|
+
narrowing: [],
|
|
23783
|
+
writeSet: [".pi/extensions/harnesstrim/ or .pi/agent/extensions/harnesstrim/ (incl. .installed marker)"]
|
|
23784
|
+
}
|
|
23785
|
+
}
|
|
23786
|
+
};
|
|
23787
|
+
function getCapabilities(version2) {
|
|
23788
|
+
return { version: version2, ...CAPABILITIES };
|
|
23789
|
+
}
|
|
23790
|
+
|
|
23791
|
+
// src/uninstall.ts
|
|
23792
|
+
import fs10 from "node:fs";
|
|
23793
|
+
import path14 from "node:path";
|
|
23794
|
+
function readOrNull(p) {
|
|
23795
|
+
try {
|
|
23796
|
+
return fs10.readFileSync(p, "utf8");
|
|
23797
|
+
} catch {
|
|
23798
|
+
return null;
|
|
23799
|
+
}
|
|
23800
|
+
}
|
|
23801
|
+
function stripMarkedRegion(content, marker) {
|
|
23802
|
+
const begin = `<!-- ${marker} -->`;
|
|
23803
|
+
const end = "<!-- harnesstrim:end -->";
|
|
23804
|
+
const start = content.indexOf(begin);
|
|
23805
|
+
if (start === -1) return content;
|
|
23806
|
+
const endIdx = content.indexOf(end, start);
|
|
23807
|
+
if (endIdx === -1) return content;
|
|
23808
|
+
const after = content.slice(endIdx + end.length);
|
|
23809
|
+
const before = content.slice(0, start).replace(/\s+$/, "");
|
|
23810
|
+
const next = (before + "\n" + after.replace(/^\s+/, "")).replace(/\n{3,}/g, "\n\n").trim();
|
|
23811
|
+
return next;
|
|
23812
|
+
}
|
|
23813
|
+
function stripHookEntries(settings) {
|
|
23814
|
+
const hooks = settings.hooks;
|
|
23815
|
+
if (!hooks || !Array.isArray(hooks.PostToolUse)) return { next: settings, removed: false };
|
|
23816
|
+
const post = hooks.PostToolUse;
|
|
23817
|
+
const kept = post.filter((entry) => {
|
|
23818
|
+
const hooksArr = entry.hooks;
|
|
23819
|
+
if (!Array.isArray(hooksArr)) return true;
|
|
23820
|
+
return !hooksArr.some((h) => typeof h.command === "string" && h.command.includes("harnesstrim hook"));
|
|
23821
|
+
});
|
|
23822
|
+
if (kept.length === post.length) return { next: settings, removed: false };
|
|
23823
|
+
const nextHooks = { ...hooks };
|
|
23824
|
+
nextHooks.PostToolUse = kept;
|
|
23825
|
+
const next = { ...settings, hooks: nextHooks };
|
|
23826
|
+
return { next, removed: true };
|
|
23827
|
+
}
|
|
23828
|
+
function isSkillDir(pathName) {
|
|
23829
|
+
try {
|
|
23830
|
+
return fs10.statSync(pathName).isDirectory();
|
|
23831
|
+
} catch {
|
|
23832
|
+
return false;
|
|
23833
|
+
}
|
|
23834
|
+
}
|
|
23835
|
+
function markerPresent3(dest) {
|
|
23836
|
+
try {
|
|
23837
|
+
return fs10.statSync(path14.join(dest, ".installed")).isFile();
|
|
23838
|
+
} catch {
|
|
23839
|
+
return false;
|
|
23840
|
+
}
|
|
23841
|
+
}
|
|
23842
|
+
function skillRemovalActions(dir, destRel) {
|
|
23843
|
+
const sourceDir = resolveSkillsSourceDir();
|
|
23844
|
+
const shipped = listShippedSkills(sourceDir);
|
|
23845
|
+
const dest = path14.join(dir, destRel);
|
|
23846
|
+
const actions = [];
|
|
23847
|
+
for (const name of shipped) {
|
|
23848
|
+
const skillPath = path14.join(dest, name);
|
|
23849
|
+
if (isSkillDir(skillPath)) {
|
|
23850
|
+
actions.push({ type: "remove-dir", path: skillPath, note: `skill directory installed by HarnessTrim` });
|
|
23851
|
+
}
|
|
23852
|
+
}
|
|
23853
|
+
if (actions.length > 0 && isSkillDir(dest)) {
|
|
23854
|
+
const remaining = fs10.readdirSync(dest).filter((name) => !shipped.includes(name));
|
|
23855
|
+
if (remaining.length === 0) {
|
|
23856
|
+
actions.push({ type: "remove-dir", path: dest, note: "empty skills directory left by HarnessTrim" });
|
|
23857
|
+
}
|
|
23858
|
+
}
|
|
23859
|
+
return actions;
|
|
23860
|
+
}
|
|
23861
|
+
function planClaudeUninstall(dir) {
|
|
23862
|
+
const actions = [...skillRemovalActions(dir, ".claude/skills")];
|
|
23863
|
+
const settingsPath = path14.join(dir, ".claude", "settings.json");
|
|
23864
|
+
const settingsContent = readOrNull(settingsPath);
|
|
23865
|
+
if (settingsContent !== null) {
|
|
23866
|
+
try {
|
|
23867
|
+
const parsed = JSON.parse(settingsContent);
|
|
23868
|
+
const { next, removed } = stripHookEntries(parsed);
|
|
23869
|
+
if (removed) {
|
|
23870
|
+
actions.push({
|
|
23871
|
+
type: Object.keys(next).length === 0 ? "remove-file" : "write",
|
|
23872
|
+
path: settingsPath,
|
|
23873
|
+
note: "remove the HarnessTrim PostToolUse hook"
|
|
23874
|
+
});
|
|
23875
|
+
}
|
|
23876
|
+
} catch {
|
|
23877
|
+
}
|
|
23878
|
+
}
|
|
23879
|
+
const claudeMdPath = path14.join(dir, "CLAUDE.md");
|
|
23880
|
+
const claudeMd = readOrNull(claudeMdPath);
|
|
23881
|
+
if (claudeMd !== null && claudeMd.includes(HARNESSTRIM_MARKER2)) {
|
|
23882
|
+
const next = stripMarkedRegion(claudeMd, HARNESSTRIM_MARKER2);
|
|
23883
|
+
if (next !== null && next !== claudeMd) {
|
|
23884
|
+
actions.push({
|
|
23885
|
+
type: next.length === 0 ? "remove-file" : "write",
|
|
23886
|
+
path: claudeMdPath,
|
|
23887
|
+
note: "remove the marker-guarded HarnessTrim instruction"
|
|
23888
|
+
});
|
|
23889
|
+
}
|
|
23890
|
+
}
|
|
23891
|
+
return { harness: "claude", dir, changed: actions.length > 0, actions };
|
|
23892
|
+
}
|
|
23893
|
+
function planCodexUninstall(dir) {
|
|
23894
|
+
const actions = [...skillRemovalActions(dir, ".codex/skills")];
|
|
23895
|
+
const agentsPath = path14.join(dir, "AGENTS.md");
|
|
23896
|
+
const agents = readOrNull(agentsPath);
|
|
23897
|
+
if (agents !== null && agents.includes(HARNESSTRIM_MARKER)) {
|
|
23898
|
+
const next = stripMarkedRegion(agents, HARNESSTRIM_MARKER);
|
|
23899
|
+
if (next !== null && next !== agents) {
|
|
23900
|
+
actions.push({
|
|
23901
|
+
type: next.length === 0 ? "remove-file" : "write",
|
|
23902
|
+
path: agentsPath,
|
|
23903
|
+
note: "remove the marker-guarded HarnessTrim instruction"
|
|
23904
|
+
});
|
|
23905
|
+
}
|
|
23906
|
+
}
|
|
23907
|
+
const hooksPath = path14.join(dir, ".codex", "hooks.json");
|
|
23908
|
+
const hooksContent = readOrNull(hooksPath);
|
|
23909
|
+
if (hooksContent !== null) {
|
|
23910
|
+
try {
|
|
23911
|
+
const parsed = JSON.parse(hooksContent);
|
|
23912
|
+
const { next, removed } = stripHookEntries(parsed);
|
|
23913
|
+
if (removed) {
|
|
23914
|
+
actions.push({
|
|
23915
|
+
type: Object.keys(next).length === 0 ? "remove-file" : "write",
|
|
23916
|
+
path: hooksPath,
|
|
23917
|
+
note: "remove the HarnessTrim PostToolUse hook"
|
|
23918
|
+
});
|
|
23919
|
+
}
|
|
23920
|
+
} catch {
|
|
23921
|
+
}
|
|
23922
|
+
}
|
|
23923
|
+
return { harness: "codex", dir, changed: actions.length > 0, actions };
|
|
23924
|
+
}
|
|
23925
|
+
function planOpencodeUninstall(dir) {
|
|
23926
|
+
const actions = [];
|
|
23927
|
+
const wrapperPath = path14.join(dir, ".opencode", "plugin", "harnesstrim.ts");
|
|
23928
|
+
const wrapper = readOrNull(wrapperPath);
|
|
23929
|
+
if (wrapper !== null && wrapper.includes(OPENCODE_PLUGIN_NAME)) {
|
|
23930
|
+
actions.push({ type: "remove-file", path: wrapperPath, note: "HarnessTrim plugin wrapper" });
|
|
23931
|
+
const pluginDir = path14.join(dir, ".opencode", "plugin");
|
|
23932
|
+
if (fs10.existsSync(pluginDir)) {
|
|
23933
|
+
const entries = fs10.readdirSync(pluginDir).filter((name) => name !== "harnesstrim.ts");
|
|
23934
|
+
if (entries.length === 0) {
|
|
23935
|
+
actions.push({ type: "remove-dir", path: pluginDir, note: "empty plugin directory left by HarnessTrim" });
|
|
23936
|
+
}
|
|
23937
|
+
}
|
|
23938
|
+
}
|
|
23939
|
+
const pkgPath = path14.join(dir, ".opencode", "package.json");
|
|
23940
|
+
const pkgContent = readOrNull(pkgPath);
|
|
23941
|
+
if (pkgContent !== null) {
|
|
23942
|
+
try {
|
|
23943
|
+
const parsed = JSON.parse(pkgContent);
|
|
23944
|
+
const deps = parsed.dependencies;
|
|
23945
|
+
if (deps && typeof deps[OPENCODE_PLUGIN_NAME] === "string") {
|
|
23946
|
+
const nextDeps = { ...deps };
|
|
23947
|
+
delete nextDeps[OPENCODE_PLUGIN_NAME];
|
|
23948
|
+
const next = { ...parsed, dependencies: nextDeps };
|
|
23949
|
+
const onlyOurDep = Object.keys(nextDeps).length === 0 && Object.keys(parsed).length <= 1;
|
|
23950
|
+
actions.push({
|
|
23951
|
+
type: onlyOurDep ? "remove-file" : "write",
|
|
23952
|
+
path: pkgPath,
|
|
23953
|
+
note: onlyOurDep ? "remove .opencode/package.json (only declared the adapter)" : "drop the @harnesstrim/adapter-opencode dependency"
|
|
23954
|
+
});
|
|
23955
|
+
}
|
|
23956
|
+
} catch {
|
|
23957
|
+
}
|
|
23958
|
+
}
|
|
23959
|
+
return { harness: "opencode", dir, changed: actions.length > 0, actions };
|
|
23960
|
+
}
|
|
23961
|
+
function planHermesUninstall(dir) {
|
|
23962
|
+
const pluginDest = path14.join(dir, ".hermes", "plugins", "harnesstrim");
|
|
23963
|
+
const actions = [];
|
|
23964
|
+
if (markerPresent3(pluginDest)) {
|
|
23965
|
+
actions.push({ type: "remove-dir", path: pluginDest, note: "Hermes plugin installed by HarnessTrim" });
|
|
23966
|
+
}
|
|
23967
|
+
return { harness: "hermes", dir, changed: actions.length > 0, actions };
|
|
23968
|
+
}
|
|
23969
|
+
function planPiUninstall(dir) {
|
|
23970
|
+
const scope = path14.resolve(dir) === path14.resolve(process.env.HOME ?? "") ? "user" : "project";
|
|
23971
|
+
const dest = scope === "user" ? path14.join(dir, ".pi", "agent", "extensions", "harnesstrim") : path14.join(dir, ".pi", "extensions", "harnesstrim");
|
|
23972
|
+
const actions = [];
|
|
23973
|
+
if (markerPresent3(dest)) {
|
|
23974
|
+
actions.push({ type: "remove-dir", path: dest, note: "Pi extension installed by HarnessTrim" });
|
|
23975
|
+
}
|
|
23976
|
+
return { harness: "pi", dir, changed: actions.length > 0, actions };
|
|
23977
|
+
}
|
|
23978
|
+
function planUninstall(harness, dir) {
|
|
23979
|
+
switch (harness) {
|
|
23980
|
+
case "claude":
|
|
23981
|
+
return planClaudeUninstall(dir);
|
|
23982
|
+
case "codex":
|
|
23983
|
+
return planCodexUninstall(dir);
|
|
23984
|
+
case "opencode":
|
|
23985
|
+
return planOpencodeUninstall(dir);
|
|
23986
|
+
case "hermes":
|
|
23987
|
+
return planHermesUninstall(dir);
|
|
23988
|
+
case "pi":
|
|
23989
|
+
return planPiUninstall(dir);
|
|
23990
|
+
default:
|
|
23991
|
+
throw new Error(`Unknown uninstall target: ${harness}. Supported: opencode, codex, claude, hermes, pi.`);
|
|
23992
|
+
}
|
|
23993
|
+
}
|
|
23994
|
+
function runUninstall(harness, dir, apply) {
|
|
23995
|
+
const plan = planUninstall(harness, dir);
|
|
23996
|
+
if (!apply || !plan.changed) return { ...plan, applied: false };
|
|
23997
|
+
for (const action of plan.actions) {
|
|
23998
|
+
switch (action.type) {
|
|
23999
|
+
case "remove-file":
|
|
24000
|
+
fs10.rmSync(action.path, { force: true });
|
|
24001
|
+
break;
|
|
24002
|
+
case "remove-dir":
|
|
24003
|
+
fs10.rmSync(action.path, { recursive: true, force: true });
|
|
24004
|
+
break;
|
|
24005
|
+
case "write": {
|
|
24006
|
+
if (action.path.endsWith("settings.json") || action.path.endsWith("hooks.json")) {
|
|
24007
|
+
const raw = readOrNull(action.path);
|
|
24008
|
+
if (raw !== null) {
|
|
24009
|
+
try {
|
|
24010
|
+
const parsed = JSON.parse(raw);
|
|
24011
|
+
const { next } = stripHookEntries(parsed);
|
|
24012
|
+
fs10.writeFileSync(action.path, JSON.stringify(next, null, 2) + "\n");
|
|
24013
|
+
} catch {
|
|
24014
|
+
}
|
|
24015
|
+
}
|
|
24016
|
+
} else if (action.path.endsWith(path14.join(".opencode", "package.json"))) {
|
|
24017
|
+
const raw = readOrNull(action.path);
|
|
24018
|
+
if (raw !== null) {
|
|
24019
|
+
try {
|
|
24020
|
+
const parsed = JSON.parse(raw);
|
|
24021
|
+
const deps = { ...parsed.dependencies ?? {} };
|
|
24022
|
+
delete deps[OPENCODE_PLUGIN_NAME];
|
|
24023
|
+
fs10.writeFileSync(action.path, JSON.stringify({ ...parsed, dependencies: deps }, null, 2) + "\n");
|
|
24024
|
+
} catch {
|
|
24025
|
+
}
|
|
24026
|
+
}
|
|
24027
|
+
} else {
|
|
24028
|
+
const marker = action.path.endsWith("CLAUDE.md") ? HARNESSTRIM_MARKER2 : HARNESSTRIM_MARKER;
|
|
24029
|
+
const raw = readOrNull(action.path);
|
|
24030
|
+
if (raw !== null) {
|
|
24031
|
+
const next = stripMarkedRegion(raw, marker);
|
|
24032
|
+
if (next !== null) fs10.writeFileSync(action.path, next + "\n");
|
|
24033
|
+
}
|
|
24034
|
+
}
|
|
24035
|
+
break;
|
|
24036
|
+
}
|
|
24037
|
+
default:
|
|
24038
|
+
break;
|
|
24039
|
+
}
|
|
24040
|
+
}
|
|
24041
|
+
return { ...plan, applied: true };
|
|
24042
|
+
}
|
|
24043
|
+
|
|
23672
24044
|
// src/render.ts
|
|
23673
24045
|
var ICON = { warn: "!", info: "i", ok: "+" };
|
|
23674
24046
|
function renderDoctor(report) {
|
|
@@ -23767,7 +24139,7 @@ function renderCodexInstall(result, apply) {
|
|
|
23767
24139
|
lines.push(` ${s.name.padEnd(18)} ${state}`);
|
|
23768
24140
|
}
|
|
23769
24141
|
lines.push("");
|
|
23770
|
-
const instr = plan.instructionsAction === "present" ? "AGENTS.md already contains the HarnessTrim instruction (no change)." : plan.instructionsAction === "create" ? `AGENTS.md ${apply ? "created" : "would be created"} with the reduce-pipe instruction.` : `Reduce-pipe instruction ${apply ? "appended" : "would be appended"} to AGENTS.md.`;
|
|
24142
|
+
const instr = plan.instructionsAction === "skip" ? "AGENTS.md instruction skipped (--no-instructions); skills only." : plan.instructionsAction === "present" ? "AGENTS.md already contains the HarnessTrim instruction (no change)." : plan.instructionsAction === "create" ? `AGENTS.md ${apply ? "created" : "would be created"} with the reduce-pipe instruction.` : `Reduce-pipe instruction ${apply ? "appended" : "would be appended"} to AGENTS.md.`;
|
|
23771
24143
|
lines.push(instr);
|
|
23772
24144
|
if (result.hookPlan) {
|
|
23773
24145
|
lines.push("");
|
|
@@ -23816,7 +24188,9 @@ function renderClaudeInstall(result, apply) {
|
|
|
23816
24188
|
lines.push(` ${s.name.padEnd(18)} ${state}`);
|
|
23817
24189
|
}
|
|
23818
24190
|
lines.push("");
|
|
23819
|
-
if (plan.settingsAction === "
|
|
24191
|
+
if (plan.settingsAction === "skip") {
|
|
24192
|
+
lines.push(`${plan.settingsFile}: PostToolUse hook skipped (--no-hook); skills only.`);
|
|
24193
|
+
} else if (plan.settingsAction === "present") {
|
|
23820
24194
|
lines.push(`${plan.settingsFile}: PostToolUse reducer hook already present (no change).`);
|
|
23821
24195
|
} else {
|
|
23822
24196
|
lines.push(
|
|
@@ -23829,7 +24203,9 @@ function renderClaudeInstall(result, apply) {
|
|
|
23829
24203
|
}
|
|
23830
24204
|
}
|
|
23831
24205
|
lines.push("");
|
|
23832
|
-
if (plan.instructionsAction === "
|
|
24206
|
+
if (plan.instructionsAction === "skip") {
|
|
24207
|
+
lines.push(`${plan.instructionsFile}: reduce-pipe instruction skipped (--no-instructions); skills only.`);
|
|
24208
|
+
} else if (plan.instructionsAction === "present") {
|
|
23833
24209
|
lines.push(`${plan.instructionsFile}: reduce-pipe instruction already present (no change).`);
|
|
23834
24210
|
} else {
|
|
23835
24211
|
lines.push(
|
|
@@ -23884,7 +24260,7 @@ function renderPiInstall(result, apply) {
|
|
|
23884
24260
|
lines.push(` Copied: ${result.copiedFiles.join(", ")}`);
|
|
23885
24261
|
} else {
|
|
23886
24262
|
lines.push(` Source: ${plan.extensionSource}`);
|
|
23887
|
-
lines.push(" (
|
|
24263
|
+
lines.push(" (index.ts + .installed marker)");
|
|
23888
24264
|
}
|
|
23889
24265
|
lines.push("");
|
|
23890
24266
|
lines.push("The extension hooks Pi's `tool_result` and needs `harnesstrim` on PATH.");
|
|
@@ -23919,6 +24295,192 @@ Enable it in the adapter (telemetry: true) to record reductions.`;
|
|
|
23919
24295
|
}
|
|
23920
24296
|
return lines.join("\n");
|
|
23921
24297
|
}
|
|
24298
|
+
function renderUninstall(result, apply) {
|
|
24299
|
+
const lines = [`${apply ? "Uninstalled" : "Would uninstall"} ${result.harness} integration`, ""];
|
|
24300
|
+
if (!result.changed) {
|
|
24301
|
+
lines.push("Nothing to remove \u2014 no HarnessTrim files found in the install set.");
|
|
24302
|
+
return lines.join("\n");
|
|
24303
|
+
}
|
|
24304
|
+
for (const action of result.actions) {
|
|
24305
|
+
const verb = action.type === "remove-file" || action.type === "remove-dir" ? apply ? "removed" : "remove" : action.type === "write" ? apply ? "updated" : "update" : "clean";
|
|
24306
|
+
lines.push(` ${verb.padEnd(8)} ${action.path}`);
|
|
24307
|
+
if (action.note) lines.push(` (${action.note})`);
|
|
24308
|
+
}
|
|
24309
|
+
if (!apply) {
|
|
24310
|
+
lines.push("");
|
|
24311
|
+
lines.push("Dry run \u2014 nothing written. Re-run with `--apply`.");
|
|
24312
|
+
}
|
|
24313
|
+
lines.push("");
|
|
24314
|
+
lines.push("Only files HarnessTrim wrote are touched; marker-guarded regions and your");
|
|
24315
|
+
lines.push("other settings/hook entries are preserved.");
|
|
24316
|
+
return lines.join("\n");
|
|
24317
|
+
}
|
|
24318
|
+
|
|
24319
|
+
// src/json.ts
|
|
24320
|
+
function doctorJson(report) {
|
|
24321
|
+
return JSON.stringify(report, null, 2);
|
|
24322
|
+
}
|
|
24323
|
+
function metricsJson(result) {
|
|
24324
|
+
return JSON.stringify(result, null, 2);
|
|
24325
|
+
}
|
|
24326
|
+
function opencodeInstallJson(result, apply) {
|
|
24327
|
+
const actions = [];
|
|
24328
|
+
if (result.changed) {
|
|
24329
|
+
actions.push(
|
|
24330
|
+
{
|
|
24331
|
+
type: "write",
|
|
24332
|
+
path: result.wrapperPath,
|
|
24333
|
+
note: "local plugin wrapper with the adapter options"
|
|
24334
|
+
},
|
|
24335
|
+
{
|
|
24336
|
+
type: "write",
|
|
24337
|
+
path: result.packageJsonPath,
|
|
24338
|
+
note: "declares @harnesstrim/adapter-opencode"
|
|
24339
|
+
}
|
|
24340
|
+
);
|
|
24341
|
+
if (result.opencodeJsonContent !== null) {
|
|
24342
|
+
actions.push({
|
|
24343
|
+
type: "clean",
|
|
24344
|
+
path: result.opencodeJsonPath,
|
|
24345
|
+
note: "remove the stale adapter entry (options live in the wrapper now)"
|
|
24346
|
+
});
|
|
24347
|
+
}
|
|
24348
|
+
}
|
|
24349
|
+
return {
|
|
24350
|
+
harness: "opencode",
|
|
24351
|
+
dryRun: !apply,
|
|
24352
|
+
changed: result.changed,
|
|
24353
|
+
alreadyInstalled: result.alreadyInstalled,
|
|
24354
|
+
applied: result.applied,
|
|
24355
|
+
actions,
|
|
24356
|
+
details: {
|
|
24357
|
+
preset: result.preset?.name ?? null,
|
|
24358
|
+
depsInstalled: result.depsInstalled,
|
|
24359
|
+
depsMessage: result.depsMessage ?? null
|
|
24360
|
+
}
|
|
24361
|
+
};
|
|
24362
|
+
}
|
|
24363
|
+
function codexInstallJson(result, apply) {
|
|
24364
|
+
const actions = result.plan.skills.map((s) => ({
|
|
24365
|
+
type: s.present ? "none" : "copy",
|
|
24366
|
+
path: s.to,
|
|
24367
|
+
from: s.from,
|
|
24368
|
+
note: s.present ? "already present" : "copy skill"
|
|
24369
|
+
}));
|
|
24370
|
+
if (result.plan.instructionsAction !== "present" && result.plan.instructionsAction !== "skip") {
|
|
24371
|
+
actions.push({
|
|
24372
|
+
type: result.plan.instructionsAction === "create" ? "write" : "append",
|
|
24373
|
+
path: result.plan.instructionsFile,
|
|
24374
|
+
note: `reduce-pipe instruction (${result.plan.instructionsAction})`
|
|
24375
|
+
});
|
|
24376
|
+
}
|
|
24377
|
+
if (result.hookPlan && result.hookPlan.action !== "present") {
|
|
24378
|
+
actions.push({
|
|
24379
|
+
type: "write",
|
|
24380
|
+
path: result.hookPlan.hooksFile,
|
|
24381
|
+
note: `Bash PostToolUse hook (${result.hookPlan.action})`
|
|
24382
|
+
});
|
|
24383
|
+
}
|
|
24384
|
+
const hookChanged = result.hookPlan !== null && result.hookPlan.action !== "present";
|
|
24385
|
+
return {
|
|
24386
|
+
harness: "codex",
|
|
24387
|
+
dryRun: !apply,
|
|
24388
|
+
changed: result.plan.changed || hookChanged,
|
|
24389
|
+
alreadyInstalled: !result.plan.changed && !hookChanged,
|
|
24390
|
+
applied: result.applied,
|
|
24391
|
+
actions,
|
|
24392
|
+
details: { hook: result.hookPlan ? result.hookPlan.action : "skipped" }
|
|
24393
|
+
};
|
|
24394
|
+
}
|
|
24395
|
+
function claudeInstallJson(result, apply) {
|
|
24396
|
+
const actions = result.plan.skills.map((s) => ({
|
|
24397
|
+
type: s.present ? "none" : "copy",
|
|
24398
|
+
path: s.to,
|
|
24399
|
+
from: s.from,
|
|
24400
|
+
note: s.present ? "already present" : "copy skill"
|
|
24401
|
+
}));
|
|
24402
|
+
if (result.plan.settingsAction !== "present" && result.plan.settingsAction !== "skip") {
|
|
24403
|
+
actions.push({
|
|
24404
|
+
type: result.plan.settingsAction === "create" ? "write" : "write",
|
|
24405
|
+
path: result.plan.settingsFile,
|
|
24406
|
+
note: `PostToolUse Bash hook (${result.plan.settingsAction})`
|
|
24407
|
+
});
|
|
24408
|
+
}
|
|
24409
|
+
if (result.plan.instructionsAction !== "present" && result.plan.instructionsAction !== "skip") {
|
|
24410
|
+
actions.push({
|
|
24411
|
+
type: result.plan.instructionsAction === "create" ? "write" : "append",
|
|
24412
|
+
path: result.plan.instructionsFile,
|
|
24413
|
+
note: `reduce-pipe instruction (${result.plan.instructionsAction})`
|
|
24414
|
+
});
|
|
24415
|
+
}
|
|
24416
|
+
return {
|
|
24417
|
+
harness: "claude",
|
|
24418
|
+
dryRun: !apply,
|
|
24419
|
+
changed: result.plan.changed,
|
|
24420
|
+
alreadyInstalled: !result.plan.changed,
|
|
24421
|
+
applied: result.applied,
|
|
24422
|
+
actions,
|
|
24423
|
+
details: {
|
|
24424
|
+
settingsAction: result.plan.settingsAction,
|
|
24425
|
+
instructionsAction: result.plan.instructionsAction
|
|
24426
|
+
}
|
|
24427
|
+
};
|
|
24428
|
+
}
|
|
24429
|
+
function hermesInstallJson(result, apply) {
|
|
24430
|
+
const actions = [
|
|
24431
|
+
{
|
|
24432
|
+
type: result.plan.alreadyInstalled ? "none" : "copy",
|
|
24433
|
+
path: result.plan.pluginDest,
|
|
24434
|
+
from: result.plan.pluginSource,
|
|
24435
|
+
note: result.plan.alreadyInstalled ? "already installed" : "copy Hermes plugin bundle"
|
|
24436
|
+
}
|
|
24437
|
+
];
|
|
24438
|
+
return {
|
|
24439
|
+
harness: "hermes",
|
|
24440
|
+
dryRun: !apply,
|
|
24441
|
+
changed: !result.plan.alreadyInstalled,
|
|
24442
|
+
alreadyInstalled: result.plan.alreadyInstalled,
|
|
24443
|
+
applied: result.applied,
|
|
24444
|
+
actions,
|
|
24445
|
+
details: { enabled: result.enabled, enableMessage: result.enableMessage ?? null }
|
|
24446
|
+
};
|
|
24447
|
+
}
|
|
24448
|
+
function piInstallJson(result, apply) {
|
|
24449
|
+
const actions = [
|
|
24450
|
+
{
|
|
24451
|
+
type: result.plan.alreadyInstalled ? "none" : "copy",
|
|
24452
|
+
path: result.plan.extensionDest,
|
|
24453
|
+
from: result.plan.extensionSource,
|
|
24454
|
+
note: result.plan.alreadyInstalled ? "already installed" : "copy Pi extension bundle"
|
|
24455
|
+
}
|
|
24456
|
+
];
|
|
24457
|
+
return {
|
|
24458
|
+
harness: "pi",
|
|
24459
|
+
dryRun: !apply,
|
|
24460
|
+
changed: !result.plan.alreadyInstalled,
|
|
24461
|
+
alreadyInstalled: result.plan.alreadyInstalled,
|
|
24462
|
+
applied: result.applied,
|
|
24463
|
+
actions
|
|
24464
|
+
};
|
|
24465
|
+
}
|
|
24466
|
+
function codexGlobalHookJson(result, apply) {
|
|
24467
|
+
const actions = [
|
|
24468
|
+
{
|
|
24469
|
+
type: result.hookPlan.action === "present" ? "none" : "write",
|
|
24470
|
+
path: result.hookPlan.hooksFile,
|
|
24471
|
+
note: `global Bash PostToolUse hook (${result.hookPlan.action})`
|
|
24472
|
+
}
|
|
24473
|
+
];
|
|
24474
|
+
return {
|
|
24475
|
+
harness: "codex",
|
|
24476
|
+
dryRun: !apply,
|
|
24477
|
+
changed: result.hookPlan.action !== "present",
|
|
24478
|
+
alreadyInstalled: result.hookPlan.action === "present",
|
|
24479
|
+
applied: result.applied,
|
|
24480
|
+
actions,
|
|
24481
|
+
details: { scope: "global" }
|
|
24482
|
+
};
|
|
24483
|
+
}
|
|
23922
24484
|
|
|
23923
24485
|
// src/cli.ts
|
|
23924
24486
|
var HELP = `harnesstrim \u2014 one token policy for coding harnesses
|
|
@@ -23928,16 +24490,25 @@ Usage:
|
|
|
23928
24490
|
harnesstrim install opencode [dir] Wire the adapter into opencode.json (dry-run)
|
|
23929
24491
|
--apply Actually write the change
|
|
23930
24492
|
--preset <name> Bake a policy preset's adapter config in
|
|
24493
|
+
--mode <m> Override mode: active|dryrun|off
|
|
24494
|
+
--min-length <n> Override the reduction threshold (chars)
|
|
24495
|
+
--tools <list> Confine reduction to a subset of tool families
|
|
23931
24496
|
harnesstrim install codex [dir] Install skills + AGENTS.md reduction guidance (dry-run)
|
|
23932
24497
|
--apply Actually write the change
|
|
23933
24498
|
--hook Also install the experimental Bash PostToolUse hook
|
|
24499
|
+
--no-instructions Skills only (no AGENTS.md reduce-pipe instruction)
|
|
23934
24500
|
--global With --hook, install it once in ~/.codex (no project files)
|
|
23935
24501
|
harnesstrim install claude [dir] Install skills + PostToolUse reducer hook (dry-run)
|
|
23936
24502
|
--apply Actually write the change
|
|
24503
|
+
--no-hook Skills only (no PostToolUse hook)
|
|
24504
|
+
--no-instructions Skills only (no CLAUDE.md reduce-pipe instruction)
|
|
23937
24505
|
harnesstrim install hermes [dir] Install Hermes plugin (dry-run)
|
|
23938
24506
|
--apply Actually write the change
|
|
23939
24507
|
harnesstrim install pi [dir] Install Pi tool_result extension (dry-run)
|
|
23940
24508
|
--apply Actually write the change
|
|
24509
|
+
harnesstrim uninstall <harness> [dir] Remove only what install wrote (dry-run)
|
|
24510
|
+
--apply Actually write the change
|
|
24511
|
+
harnesstrim capabilities Print machine-readable per-harness capabilities (JSON)
|
|
23941
24512
|
harnesstrim hook claude [--metrics <path>]
|
|
23942
24513
|
PostToolUse hook runtime; --metrics records a TrimEvent per reduction
|
|
23943
24514
|
harnesstrim hook codex [--metrics <path>]
|
|
@@ -23953,8 +24524,11 @@ Usage:
|
|
|
23953
24524
|
harnesstrim bench Run the Tier A reducer micro-benchmark
|
|
23954
24525
|
harnesstrim --version Print the installed version
|
|
23955
24526
|
|
|
24527
|
+
Flags:
|
|
24528
|
+
--json Print machine-readable JSON (doctor, metrics, install, uninstall)
|
|
24529
|
+
|
|
23956
24530
|
Notes:
|
|
23957
|
-
- install
|
|
24531
|
+
- install and uninstall are dry-run by default; nothing is written without --apply.
|
|
23958
24532
|
- dir defaults to the current directory; metrics path defaults to ${DEFAULT_METRICS_PATH}.
|
|
23959
24533
|
- reduce reads stdin and writes slimmed output to stdout, e.g. npm test 2>&1 | harnesstrim reduce`;
|
|
23960
24534
|
async function main(argv) {
|
|
@@ -23971,7 +24545,12 @@ async function main(argv) {
|
|
|
23971
24545
|
log: { type: "string" },
|
|
23972
24546
|
metrics: { type: "string" },
|
|
23973
24547
|
hook: { type: "boolean" },
|
|
23974
|
-
global: { type: "boolean" }
|
|
24548
|
+
global: { type: "boolean" },
|
|
24549
|
+
"no-hook": { type: "boolean" },
|
|
24550
|
+
"no-instructions": { type: "boolean" },
|
|
24551
|
+
mode: { type: "string" },
|
|
24552
|
+
tools: { type: "string" },
|
|
24553
|
+
json: { type: "boolean" }
|
|
23975
24554
|
}
|
|
23976
24555
|
});
|
|
23977
24556
|
const [command, ...rest] = positionals;
|
|
@@ -23986,16 +24565,34 @@ async function main(argv) {
|
|
|
23986
24565
|
switch (command) {
|
|
23987
24566
|
case "doctor": {
|
|
23988
24567
|
const dir = rest[0] ?? process.cwd();
|
|
23989
|
-
|
|
24568
|
+
const report = inspect(dir);
|
|
24569
|
+
if (values.json) {
|
|
24570
|
+
console.log(doctorJson(report));
|
|
24571
|
+
} else {
|
|
24572
|
+
console.log(renderDoctor(report));
|
|
24573
|
+
}
|
|
23990
24574
|
return 0;
|
|
23991
24575
|
}
|
|
23992
24576
|
case "install": {
|
|
23993
24577
|
const target = rest[0];
|
|
23994
24578
|
const dir = rest[1] ?? (target === "hermes" ? os4.homedir() : process.cwd());
|
|
23995
24579
|
const apply = values.apply === true;
|
|
24580
|
+
const asJson = values.json === true;
|
|
23996
24581
|
if (target === "opencode") {
|
|
23997
|
-
const
|
|
23998
|
-
|
|
24582
|
+
const mode = parseModeFlag(values.mode);
|
|
24583
|
+
if (mode === void 0 && values.mode !== void 0) {
|
|
24584
|
+
console.error(`Invalid --mode: ${values.mode} (expected active, dryrun, or off).`);
|
|
24585
|
+
return 1;
|
|
24586
|
+
}
|
|
24587
|
+
const minLength = values["min-length"] !== void 0 ? Number(values["min-length"]) : void 0;
|
|
24588
|
+
if (minLength !== void 0 && !Number.isFinite(minLength)) {
|
|
24589
|
+
console.error(`Invalid --min-length: ${values["min-length"]}`);
|
|
24590
|
+
return 1;
|
|
24591
|
+
}
|
|
24592
|
+
const tools = values.tools !== void 0 ? splitTools(values.tools) : void 0;
|
|
24593
|
+
const result = runInstallOpencode(dir, apply, values.preset, true, { mode, minLength, tools });
|
|
24594
|
+
if (asJson) console.log(JSON.stringify(opencodeInstallJson(result, apply), null, 2));
|
|
24595
|
+
else console.log(renderInstall(result, apply));
|
|
23999
24596
|
return 0;
|
|
24000
24597
|
}
|
|
24001
24598
|
if (target === "codex") {
|
|
@@ -24004,27 +24601,63 @@ async function main(argv) {
|
|
|
24004
24601
|
console.error("`harnesstrim install codex --global` requires `--hook`.");
|
|
24005
24602
|
return 1;
|
|
24006
24603
|
}
|
|
24007
|
-
|
|
24604
|
+
const result2 = runInstallCodexGlobalHook(path16.join(os4.homedir(), ".codex"), apply);
|
|
24605
|
+
if (asJson) console.log(JSON.stringify(codexGlobalHookJson(result2, apply), null, 2));
|
|
24606
|
+
else console.log(renderCodexGlobalHookInstall(result2, apply));
|
|
24008
24607
|
return 0;
|
|
24009
24608
|
}
|
|
24010
|
-
|
|
24609
|
+
const result = runInstallCodex(dir, apply, values.hook === true, {
|
|
24610
|
+
includeInstructions: values["no-instructions"] !== true
|
|
24611
|
+
});
|
|
24612
|
+
if (asJson) console.log(JSON.stringify(codexInstallJson(result, apply), null, 2));
|
|
24613
|
+
else console.log(renderCodexInstall(result, apply));
|
|
24011
24614
|
return 0;
|
|
24012
24615
|
}
|
|
24013
24616
|
if (target === "claude") {
|
|
24014
|
-
|
|
24617
|
+
const result = runInstallClaude(dir, apply, {
|
|
24618
|
+
includeHook: values["no-hook"] !== true,
|
|
24619
|
+
includeInstructions: values["no-instructions"] !== true
|
|
24620
|
+
});
|
|
24621
|
+
if (asJson) console.log(JSON.stringify(claudeInstallJson(result, apply), null, 2));
|
|
24622
|
+
else console.log(renderClaudeInstall(result, apply));
|
|
24015
24623
|
return 0;
|
|
24016
24624
|
}
|
|
24017
24625
|
if (target === "hermes") {
|
|
24018
|
-
|
|
24626
|
+
const result = runInstallHermes(dir, apply);
|
|
24627
|
+
if (asJson) console.log(JSON.stringify(hermesInstallJson(result, apply), null, 2));
|
|
24628
|
+
else console.log(renderHermesInstall(result, apply));
|
|
24019
24629
|
return 0;
|
|
24020
24630
|
}
|
|
24021
24631
|
if (target === "pi") {
|
|
24022
|
-
|
|
24632
|
+
const result = runInstallPi(dir, apply);
|
|
24633
|
+
if (asJson) console.log(JSON.stringify(piInstallJson(result, apply), null, 2));
|
|
24634
|
+
else console.log(renderPiInstall(result, apply));
|
|
24023
24635
|
return 0;
|
|
24024
24636
|
}
|
|
24025
24637
|
console.error(`Unknown install target: ${target ?? "(none)"}. Supported: opencode, codex, claude, hermes, pi.`);
|
|
24026
24638
|
return 1;
|
|
24027
24639
|
}
|
|
24640
|
+
case "uninstall": {
|
|
24641
|
+
const target = rest[0];
|
|
24642
|
+
const dir = rest[1] ?? (target === "hermes" ? os4.homedir() : process.cwd());
|
|
24643
|
+
const apply = values.apply === true;
|
|
24644
|
+
try {
|
|
24645
|
+
const result = runUninstall(target, dir, apply);
|
|
24646
|
+
if (values.json) {
|
|
24647
|
+
console.log(JSON.stringify(result, null, 2));
|
|
24648
|
+
} else {
|
|
24649
|
+
console.log(renderUninstall(result, apply));
|
|
24650
|
+
}
|
|
24651
|
+
} catch (err) {
|
|
24652
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
24653
|
+
return 1;
|
|
24654
|
+
}
|
|
24655
|
+
return 0;
|
|
24656
|
+
}
|
|
24657
|
+
case "capabilities": {
|
|
24658
|
+
console.log(JSON.stringify(getCapabilities(package_default.version), null, 2));
|
|
24659
|
+
return 0;
|
|
24660
|
+
}
|
|
24028
24661
|
case "hook": {
|
|
24029
24662
|
const which = rest[0];
|
|
24030
24663
|
if (which !== "claude" && which !== "codex") {
|
|
@@ -24036,18 +24669,24 @@ async function main(argv) {
|
|
|
24036
24669
|
process.stdout.write(response);
|
|
24037
24670
|
if (values.metrics && event) {
|
|
24038
24671
|
try {
|
|
24039
|
-
const p =
|
|
24040
|
-
|
|
24041
|
-
|
|
24672
|
+
const p = path16.resolve(values.metrics);
|
|
24673
|
+
fs12.mkdirSync(path16.dirname(p), { recursive: true });
|
|
24674
|
+
fs12.appendFileSync(
|
|
24042
24675
|
p,
|
|
24043
|
-
JSON.stringify(
|
|
24676
|
+
JSON.stringify(
|
|
24677
|
+
makeTrimEvent({
|
|
24678
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24679
|
+
harness: which,
|
|
24680
|
+
...event
|
|
24681
|
+
})
|
|
24682
|
+
) + "\n"
|
|
24044
24683
|
);
|
|
24045
24684
|
} catch {
|
|
24046
24685
|
}
|
|
24047
24686
|
}
|
|
24048
24687
|
if (values.log) {
|
|
24049
24688
|
try {
|
|
24050
|
-
|
|
24689
|
+
fs12.appendFileSync(
|
|
24051
24690
|
values.log,
|
|
24052
24691
|
JSON.stringify({ inputChars: input.length, changed: response !== "{}", responseChars: response.length }) + "\n"
|
|
24053
24692
|
);
|
|
@@ -24076,8 +24715,13 @@ async function main(argv) {
|
|
|
24076
24715
|
return 1;
|
|
24077
24716
|
}
|
|
24078
24717
|
case "metrics": {
|
|
24079
|
-
const
|
|
24080
|
-
|
|
24718
|
+
const path17 = rest[0] ?? DEFAULT_METRICS_PATH;
|
|
24719
|
+
const result = loadMetrics(path17);
|
|
24720
|
+
if (values.json) {
|
|
24721
|
+
console.log(metricsJson(result));
|
|
24722
|
+
} else {
|
|
24723
|
+
console.log(renderMetrics(result));
|
|
24724
|
+
}
|
|
24081
24725
|
return 0;
|
|
24082
24726
|
}
|
|
24083
24727
|
case "reduce": {
|
|
@@ -24092,18 +24736,20 @@ async function main(argv) {
|
|
|
24092
24736
|
process.stdout.write(result.output);
|
|
24093
24737
|
if (values.metrics && result.changed) {
|
|
24094
24738
|
try {
|
|
24095
|
-
const p =
|
|
24096
|
-
|
|
24097
|
-
|
|
24739
|
+
const p = path16.resolve(values.metrics);
|
|
24740
|
+
fs12.mkdirSync(path16.dirname(p), { recursive: true });
|
|
24741
|
+
fs12.appendFileSync(
|
|
24098
24742
|
p,
|
|
24099
|
-
JSON.stringify(
|
|
24100
|
-
|
|
24101
|
-
|
|
24102
|
-
|
|
24103
|
-
|
|
24104
|
-
|
|
24105
|
-
|
|
24106
|
-
|
|
24743
|
+
JSON.stringify(
|
|
24744
|
+
makeTrimEvent({
|
|
24745
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24746
|
+
harness: "pipe",
|
|
24747
|
+
tool: "reduce",
|
|
24748
|
+
reducer: result.reducer,
|
|
24749
|
+
beforeChars: result.beforeChars,
|
|
24750
|
+
afterChars: result.afterChars
|
|
24751
|
+
})
|
|
24752
|
+
) + "\n"
|
|
24107
24753
|
);
|
|
24108
24754
|
} catch {
|
|
24109
24755
|
}
|
|
@@ -24144,6 +24790,12 @@ async function main(argv) {
|
|
|
24144
24790
|
return 1;
|
|
24145
24791
|
}
|
|
24146
24792
|
}
|
|
24793
|
+
function parseModeFlag(value) {
|
|
24794
|
+
return value === "active" || value === "dryrun" || value === "off" ? value : void 0;
|
|
24795
|
+
}
|
|
24796
|
+
function splitTools(value) {
|
|
24797
|
+
return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
24798
|
+
}
|
|
24147
24799
|
main(process.argv.slice(2)).then((code) => {
|
|
24148
24800
|
process.exitCode = code;
|
|
24149
24801
|
}).catch((err) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harnesstrim",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "HarnessTrim CLI: doctor (diagnose token waste), install adapters, run benchmarks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
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
19
|
"@harnesstrim/adapter-codex": "0.0.1",
|
|
20
|
+
"@harnesstrim/adapter-claude": "0.0.1",
|
|
21
|
+
"@harnesstrim/adapter-pi": "0.0.1",
|
|
23
22
|
"@harnesstrim/benchmarks": "0.0.1",
|
|
24
23
|
"@harnesstrim/mcp": "0.0.1",
|
|
25
|
-
"@harnesstrim/adapter-
|
|
24
|
+
"@harnesstrim/adapter-hermes": "0.0.1",
|
|
25
|
+
"@harnesstrim/core": "0.0.2"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "node build.mjs",
|