harnesstrim 0.0.6 → 0.1.0
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/dist/cli.mjs
CHANGED
|
@@ -713,6 +713,22 @@ 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
|
+
changed: partial2.changed ?? true,
|
|
728
|
+
beforeTokens: partial2.beforeTokens ?? null,
|
|
729
|
+
afterTokens: partial2.afterTokens ?? null
|
|
730
|
+
};
|
|
731
|
+
}
|
|
716
732
|
function pct(before, after) {
|
|
717
733
|
if (before === 0) return 0;
|
|
718
734
|
return Math.round((1 - after / before) * 1e3) / 10;
|
|
@@ -720,10 +736,30 @@ function pct(before, after) {
|
|
|
720
736
|
function summarize(events) {
|
|
721
737
|
let beforeChars = 0;
|
|
722
738
|
let afterChars = 0;
|
|
739
|
+
let reduced = 0;
|
|
740
|
+
let passThrough = 0;
|
|
741
|
+
let reductionErrors = 0;
|
|
742
|
+
let grewChars = 0;
|
|
723
743
|
const byReducerMap = /* @__PURE__ */ new Map();
|
|
744
|
+
const byHarnessMap = /* @__PURE__ */ new Map();
|
|
724
745
|
for (const e of events) {
|
|
725
746
|
beforeChars += e.beforeChars;
|
|
726
747
|
afterChars += e.afterChars;
|
|
748
|
+
if (e.changed === false) {
|
|
749
|
+
passThrough++;
|
|
750
|
+
} else if (e.afterChars > e.beforeChars) {
|
|
751
|
+
reductionErrors++;
|
|
752
|
+
grewChars += e.afterChars - e.beforeChars;
|
|
753
|
+
} else {
|
|
754
|
+
reduced++;
|
|
755
|
+
}
|
|
756
|
+
const harness = e.harness ?? "unknown";
|
|
757
|
+
const h = byHarnessMap.get(harness) ?? { harness, count: 0, beforeChars: 0, afterChars: 0, savedChars: 0, reductionPct: 0 };
|
|
758
|
+
h.count += 1;
|
|
759
|
+
h.beforeChars += e.beforeChars;
|
|
760
|
+
h.afterChars += e.afterChars;
|
|
761
|
+
h.savedChars += e.beforeChars - e.afterChars;
|
|
762
|
+
byHarnessMap.set(harness, h);
|
|
727
763
|
if (e.reducer === null) continue;
|
|
728
764
|
const b = byReducerMap.get(e.reducer) ?? { reducer: e.reducer, count: 0, beforeChars: 0, afterChars: 0, savedChars: 0 };
|
|
729
765
|
b.count += 1;
|
|
@@ -733,13 +769,20 @@ function summarize(events) {
|
|
|
733
769
|
byReducerMap.set(e.reducer, b);
|
|
734
770
|
}
|
|
735
771
|
const byReducer = [...byReducerMap.values()].sort((a, b) => b.savedChars - a.savedChars);
|
|
772
|
+
const byHarness = [...byHarnessMap.values()].map((h) => ({ ...h, reductionPct: pct(h.beforeChars, h.afterChars) })).sort((a, b) => b.savedChars - a.savedChars);
|
|
736
773
|
return {
|
|
737
774
|
events: events.length,
|
|
738
775
|
beforeChars,
|
|
739
776
|
afterChars,
|
|
740
777
|
savedChars: beforeChars - afterChars,
|
|
741
778
|
reductionPct: pct(beforeChars, afterChars),
|
|
742
|
-
byReducer
|
|
779
|
+
byReducer,
|
|
780
|
+
byHarness,
|
|
781
|
+
reduced,
|
|
782
|
+
passThrough,
|
|
783
|
+
passThroughRate: events.length === 0 ? 0 : Math.round(passThrough / events.length * 1e3) / 10,
|
|
784
|
+
reductionErrors,
|
|
785
|
+
grewChars
|
|
743
786
|
};
|
|
744
787
|
}
|
|
745
788
|
function parseTrimEvents(jsonl) {
|
|
@@ -753,7 +796,7 @@ function parseTrimEvents(jsonl) {
|
|
|
753
796
|
} catch {
|
|
754
797
|
continue;
|
|
755
798
|
}
|
|
756
|
-
if (isTrimEvent(parsed)) out.push(parsed);
|
|
799
|
+
if (isTrimEvent(parsed)) out.push(normalize(parsed));
|
|
757
800
|
}
|
|
758
801
|
return out;
|
|
759
802
|
}
|
|
@@ -762,9 +805,26 @@ function isTrimEvent(value) {
|
|
|
762
805
|
const v = value;
|
|
763
806
|
return typeof v.beforeChars === "number" && typeof v.afterChars === "number" && typeof v.tool === "string" && (typeof v.reducer === "string" || v.reducer === null);
|
|
764
807
|
}
|
|
808
|
+
function normalize(v) {
|
|
809
|
+
return {
|
|
810
|
+
schemaVersion: typeof v.schemaVersion === "number" ? v.schemaVersion : 0,
|
|
811
|
+
eventId: typeof v.eventId === "string" ? v.eventId : "",
|
|
812
|
+
ts: v.ts,
|
|
813
|
+
harness: v.harness,
|
|
814
|
+
tool: v.tool,
|
|
815
|
+
reducer: v.reducer,
|
|
816
|
+
beforeChars: v.beforeChars,
|
|
817
|
+
afterChars: v.afterChars,
|
|
818
|
+
changed: typeof v.changed === "boolean" ? v.changed : true,
|
|
819
|
+
beforeTokens: typeof v.beforeTokens === "number" ? v.beforeTokens : null,
|
|
820
|
+
afterTokens: typeof v.afterTokens === "number" ? v.afterTokens : null
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
var TRIM_EVENT_SCHEMA_VERSION;
|
|
765
824
|
var init_trim_event = __esm({
|
|
766
825
|
"../core/src/metrics/trim-event.ts"() {
|
|
767
826
|
"use strict";
|
|
827
|
+
TRIM_EVENT_SCHEMA_VERSION = 1;
|
|
768
828
|
}
|
|
769
829
|
});
|
|
770
830
|
|
|
@@ -1239,8 +1299,8 @@ var init_parseUtil = __esm({
|
|
|
1239
1299
|
init_errors();
|
|
1240
1300
|
init_en();
|
|
1241
1301
|
makeIssue = (params) => {
|
|
1242
|
-
const { data, path:
|
|
1243
|
-
const fullPath = [...
|
|
1302
|
+
const { data, path: path17, errorMaps, issueData } = params;
|
|
1303
|
+
const fullPath = [...path17, ...issueData.path || []];
|
|
1244
1304
|
const fullIssue = {
|
|
1245
1305
|
...issueData,
|
|
1246
1306
|
path: fullPath
|
|
@@ -1548,11 +1608,11 @@ var init_types2 = __esm({
|
|
|
1548
1608
|
init_parseUtil();
|
|
1549
1609
|
init_util();
|
|
1550
1610
|
ParseInputLazyPath = class {
|
|
1551
|
-
constructor(parent, value,
|
|
1611
|
+
constructor(parent, value, path17, key) {
|
|
1552
1612
|
this._cachedPath = [];
|
|
1553
1613
|
this.parent = parent;
|
|
1554
1614
|
this.data = value;
|
|
1555
|
-
this._path =
|
|
1615
|
+
this._path = path17;
|
|
1556
1616
|
this._key = key;
|
|
1557
1617
|
}
|
|
1558
1618
|
get path() {
|
|
@@ -5133,10 +5193,10 @@ function assignProp(target, prop, value) {
|
|
|
5133
5193
|
configurable: true
|
|
5134
5194
|
});
|
|
5135
5195
|
}
|
|
5136
|
-
function getElementAtPath(obj,
|
|
5137
|
-
if (!
|
|
5196
|
+
function getElementAtPath(obj, path17) {
|
|
5197
|
+
if (!path17)
|
|
5138
5198
|
return obj;
|
|
5139
|
-
return
|
|
5199
|
+
return path17.reduce((acc, key) => acc?.[key], obj);
|
|
5140
5200
|
}
|
|
5141
5201
|
function promiseAllObject(promisesObj) {
|
|
5142
5202
|
const keys = Object.keys(promisesObj);
|
|
@@ -5385,11 +5445,11 @@ function aborted(x, startIndex = 0) {
|
|
|
5385
5445
|
}
|
|
5386
5446
|
return false;
|
|
5387
5447
|
}
|
|
5388
|
-
function prefixIssues(
|
|
5448
|
+
function prefixIssues(path17, issues) {
|
|
5389
5449
|
return issues.map((iss) => {
|
|
5390
5450
|
var _a;
|
|
5391
5451
|
(_a = iss).path ?? (_a.path = []);
|
|
5392
|
-
iss.path.unshift(
|
|
5452
|
+
iss.path.unshift(path17);
|
|
5393
5453
|
return iss;
|
|
5394
5454
|
});
|
|
5395
5455
|
}
|
|
@@ -16225,8 +16285,8 @@ var require_resolve = __commonJS({
|
|
|
16225
16285
|
}
|
|
16226
16286
|
return count;
|
|
16227
16287
|
}
|
|
16228
|
-
function getFullPath(resolver, id = "",
|
|
16229
|
-
if (
|
|
16288
|
+
function getFullPath(resolver, id = "", normalize2) {
|
|
16289
|
+
if (normalize2 !== false)
|
|
16230
16290
|
id = normalizeId(id);
|
|
16231
16291
|
const p = resolver.parse(id);
|
|
16232
16292
|
return _getFullPath(resolver, p);
|
|
@@ -17219,8 +17279,8 @@ var require_utils = __commonJS({
|
|
|
17219
17279
|
}
|
|
17220
17280
|
return ind;
|
|
17221
17281
|
}
|
|
17222
|
-
function removeDotSegments(
|
|
17223
|
-
let input =
|
|
17282
|
+
function removeDotSegments(path17) {
|
|
17283
|
+
let input = path17;
|
|
17224
17284
|
const output = [];
|
|
17225
17285
|
let nextSlash = -1;
|
|
17226
17286
|
let len = 0;
|
|
@@ -17472,8 +17532,8 @@ var require_schemes = __commonJS({
|
|
|
17472
17532
|
wsComponent.secure = void 0;
|
|
17473
17533
|
}
|
|
17474
17534
|
if (wsComponent.resourceName) {
|
|
17475
|
-
const [
|
|
17476
|
-
wsComponent.path =
|
|
17535
|
+
const [path17, query] = wsComponent.resourceName.split("?");
|
|
17536
|
+
wsComponent.path = path17 && path17 !== "/" ? path17 : void 0;
|
|
17477
17537
|
wsComponent.query = query;
|
|
17478
17538
|
wsComponent.resourceName = void 0;
|
|
17479
17539
|
}
|
|
@@ -17622,7 +17682,7 @@ var require_fast_uri = __commonJS({
|
|
|
17622
17682
|
"use strict";
|
|
17623
17683
|
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
17624
17684
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
17625
|
-
function
|
|
17685
|
+
function normalize2(uri, options) {
|
|
17626
17686
|
if (typeof uri === "string") {
|
|
17627
17687
|
uri = /** @type {T} */
|
|
17628
17688
|
normalizeString(uri, options);
|
|
@@ -17889,7 +17949,7 @@ var require_fast_uri = __commonJS({
|
|
|
17889
17949
|
}
|
|
17890
17950
|
var fastUri = {
|
|
17891
17951
|
SCHEMES,
|
|
17892
|
-
normalize,
|
|
17952
|
+
normalize: normalize2,
|
|
17893
17953
|
resolve,
|
|
17894
17954
|
resolveComponent,
|
|
17895
17955
|
equal,
|
|
@@ -20866,12 +20926,12 @@ var require_dist = __commonJS({
|
|
|
20866
20926
|
throw new Error(`Unknown format "${name}"`);
|
|
20867
20927
|
return f;
|
|
20868
20928
|
};
|
|
20869
|
-
function addFormats(ajv, list,
|
|
20929
|
+
function addFormats(ajv, list, fs13, exportName) {
|
|
20870
20930
|
var _a;
|
|
20871
20931
|
var _b;
|
|
20872
20932
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
20873
20933
|
for (const f of list)
|
|
20874
|
-
ajv.addFormat(f,
|
|
20934
|
+
ajv.addFormat(f, fs13[f]);
|
|
20875
20935
|
}
|
|
20876
20936
|
module.exports = exports = formatsPlugin;
|
|
20877
20937
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -22632,34 +22692,48 @@ __export(server_exports, {
|
|
|
22632
22692
|
runReduceTool: () => runReduceTool,
|
|
22633
22693
|
startStdioServer: () => startStdioServer
|
|
22634
22694
|
});
|
|
22635
|
-
import
|
|
22636
|
-
import
|
|
22695
|
+
import fs11 from "node:fs";
|
|
22696
|
+
import path15 from "node:path";
|
|
22637
22697
|
function createFileSink(metricsPath) {
|
|
22638
22698
|
return (event) => {
|
|
22639
22699
|
try {
|
|
22640
|
-
const p =
|
|
22641
|
-
|
|
22642
|
-
|
|
22700
|
+
const p = path15.resolve(metricsPath);
|
|
22701
|
+
fs11.mkdirSync(path15.dirname(p), { recursive: true });
|
|
22702
|
+
fs11.appendFileSync(p, JSON.stringify(event) + "\n");
|
|
22643
22703
|
} catch {
|
|
22644
22704
|
}
|
|
22645
22705
|
};
|
|
22646
22706
|
}
|
|
22647
|
-
function runReduceTool(text, minLength, sink = noopSink) {
|
|
22707
|
+
function runReduceTool(text, minLength, sink = noopSink, trackPassThrough2 = true) {
|
|
22648
22708
|
const result = reduceAuto(text, minLength);
|
|
22709
|
+
const threshold = minLength ?? DEFAULT_MIN_LENGTH;
|
|
22649
22710
|
if (result.changed) {
|
|
22650
|
-
sink(
|
|
22651
|
-
|
|
22652
|
-
|
|
22653
|
-
|
|
22654
|
-
|
|
22655
|
-
|
|
22656
|
-
|
|
22657
|
-
|
|
22711
|
+
sink(
|
|
22712
|
+
makeTrimEvent({
|
|
22713
|
+
harness: "mcp",
|
|
22714
|
+
tool: "reduce",
|
|
22715
|
+
reducer: result.reducer,
|
|
22716
|
+
beforeChars: text.length,
|
|
22717
|
+
afterChars: result.output.length
|
|
22718
|
+
})
|
|
22719
|
+
);
|
|
22720
|
+
} else if (trackPassThrough2 && text.length >= threshold) {
|
|
22721
|
+
sink(
|
|
22722
|
+
makeTrimEvent({
|
|
22723
|
+
harness: "mcp",
|
|
22724
|
+
tool: "reduce",
|
|
22725
|
+
reducer: null,
|
|
22726
|
+
beforeChars: text.length,
|
|
22727
|
+
afterChars: text.length,
|
|
22728
|
+
changed: false
|
|
22729
|
+
})
|
|
22730
|
+
);
|
|
22658
22731
|
}
|
|
22659
22732
|
return { content: [{ type: "text", text: result.output }] };
|
|
22660
22733
|
}
|
|
22661
22734
|
function createServer(options = {}) {
|
|
22662
22735
|
const sink = options.metricsPath ? createFileSink(options.metricsPath) : noopSink;
|
|
22736
|
+
const trackPassThrough2 = options.trackPassThrough !== false;
|
|
22663
22737
|
const server = new McpServer({ name: "harnesstrim", version: "0.0.1" });
|
|
22664
22738
|
server.registerTool(
|
|
22665
22739
|
"reduce",
|
|
@@ -22671,13 +22745,15 @@ function createServer(options = {}) {
|
|
|
22671
22745
|
minLength: external_exports.number().optional().describe("Skip reduction for inputs shorter than this many characters (default 400)")
|
|
22672
22746
|
}
|
|
22673
22747
|
},
|
|
22674
|
-
async ({ text, minLength }) => runReduceTool(text, minLength, sink)
|
|
22748
|
+
async ({ text, minLength }) => runReduceTool(text, minLength, sink, trackPassThrough2)
|
|
22675
22749
|
);
|
|
22676
22750
|
return server;
|
|
22677
22751
|
}
|
|
22678
22752
|
async function startStdioServer(options = {}) {
|
|
22679
22753
|
const metricsPath = options.metricsPath ?? process.env.HARNESSTRIM_TELEMETRY_PATH;
|
|
22680
|
-
const
|
|
22754
|
+
const envTrack = process.env.HARNESSTRIM_TRACK_PASSTHROUGH;
|
|
22755
|
+
const trackPassThrough2 = options.trackPassThrough ?? (envTrack !== void 0 ? envTrack !== "0" && envTrack !== "false" : true);
|
|
22756
|
+
const server = createServer({ metricsPath, trackPassThrough: trackPassThrough2 });
|
|
22681
22757
|
const transport = new StdioServerTransport();
|
|
22682
22758
|
await server.connect(transport);
|
|
22683
22759
|
}
|
|
@@ -22698,8 +22774,8 @@ var init_server3 = __esm({
|
|
|
22698
22774
|
// src/cli.ts
|
|
22699
22775
|
init_src();
|
|
22700
22776
|
import { parseArgs } from "node:util";
|
|
22701
|
-
import
|
|
22702
|
-
import
|
|
22777
|
+
import fs12 from "node:fs";
|
|
22778
|
+
import path16 from "node:path";
|
|
22703
22779
|
import os4 from "node:os";
|
|
22704
22780
|
|
|
22705
22781
|
// src/doctor.ts
|
|
@@ -22930,7 +23006,7 @@ function planOpencodeInstall(input) {
|
|
|
22930
23006
|
const changed = wrapperChanged || pkgChanged || opencodeChanged;
|
|
22931
23007
|
return { wrapperContent, packageJsonContent, opencodeJsonContent, alreadyInstalled, changed };
|
|
22932
23008
|
}
|
|
22933
|
-
function runInstallOpencode(dir, apply, presetName, installDeps = true) {
|
|
23009
|
+
function runInstallOpencode(dir, apply, presetName, installDeps = true, options = {}) {
|
|
22934
23010
|
let preset;
|
|
22935
23011
|
let adapterConfig = { ...DEFAULT_OPENCODE_ADAPTER_CONFIG };
|
|
22936
23012
|
if (presetName) {
|
|
@@ -22938,19 +23014,22 @@ function runInstallOpencode(dir, apply, presetName, installDeps = true) {
|
|
|
22938
23014
|
if (!preset) throw new Error(`Unknown preset: ${presetName}`);
|
|
22939
23015
|
adapterConfig = { ...adapterConfig, ...preset.adapter };
|
|
22940
23016
|
}
|
|
23017
|
+
if (options.mode !== void 0) adapterConfig.mode = options.mode;
|
|
23018
|
+
if (options.minLength !== void 0) adapterConfig.minLength = options.minLength;
|
|
23019
|
+
if (options.tools !== void 0) adapterConfig.toolFilter = options.tools;
|
|
22941
23020
|
const wrapperPath = path2.join(dir, WRAPPER_REL);
|
|
22942
23021
|
const packageJsonPath = path2.join(dir, PKG_REL);
|
|
22943
23022
|
const opencodeJsonPath = path2.join(dir, OPENCODE_JSON);
|
|
22944
|
-
const
|
|
23023
|
+
const readOrNull2 = (p) => {
|
|
22945
23024
|
try {
|
|
22946
23025
|
return fs2.readFileSync(p, "utf8");
|
|
22947
23026
|
} catch {
|
|
22948
23027
|
return null;
|
|
22949
23028
|
}
|
|
22950
23029
|
};
|
|
22951
|
-
const existingWrapper =
|
|
22952
|
-
const existingPackageJson =
|
|
22953
|
-
const existingOpencodeJson =
|
|
23030
|
+
const existingWrapper = readOrNull2(wrapperPath);
|
|
23031
|
+
const existingPackageJson = readOrNull2(packageJsonPath);
|
|
23032
|
+
const existingOpencodeJson = readOrNull2(opencodeJsonPath);
|
|
22954
23033
|
const plan = planOpencodeInstall({
|
|
22955
23034
|
existingWrapper,
|
|
22956
23035
|
existingPackageJson,
|
|
@@ -23007,9 +23086,15 @@ import path3 from "node:path";
|
|
|
23007
23086
|
init_src();
|
|
23008
23087
|
function reduceCodexPayload(rawJson, minLength) {
|
|
23009
23088
|
const extracted = extractToolOutput(rawJson);
|
|
23010
|
-
if (extracted === null) return { response: "{}", event: null };
|
|
23089
|
+
if (extracted === null) return { response: "{}", event: null, attempt: null };
|
|
23011
23090
|
const result = reduceAuto(extracted.output, minLength);
|
|
23012
|
-
if (!result.changed)
|
|
23091
|
+
if (!result.changed) {
|
|
23092
|
+
return {
|
|
23093
|
+
response: "{}",
|
|
23094
|
+
event: null,
|
|
23095
|
+
attempt: extracted.output.length >= (minLength ?? DEFAULT_MIN_LENGTH) ? { tool: extracted.toolName, beforeChars: extracted.output.length } : null
|
|
23096
|
+
};
|
|
23097
|
+
}
|
|
23013
23098
|
const response = JSON.stringify({
|
|
23014
23099
|
decision: "block",
|
|
23015
23100
|
reason: `HarnessTrim reduced ${extracted.toolName} output (${result.reducer}):
|
|
@@ -23023,7 +23108,8 @@ ${result.output}`
|
|
|
23023
23108
|
reducer: result.reducer,
|
|
23024
23109
|
beforeChars: extracted.output.length,
|
|
23025
23110
|
afterChars: result.output.length
|
|
23026
|
-
}
|
|
23111
|
+
},
|
|
23112
|
+
attempt: null
|
|
23027
23113
|
};
|
|
23028
23114
|
}
|
|
23029
23115
|
function extractToolOutput(rawJson) {
|
|
@@ -23133,6 +23219,7 @@ function planCodexHookInstall(input) {
|
|
|
23133
23219
|
};
|
|
23134
23220
|
}
|
|
23135
23221
|
function planCodexInstall(input) {
|
|
23222
|
+
const includeInstructions = input.includeInstructions ?? true;
|
|
23136
23223
|
const skillsDest = path3.join(input.projectDir, ".codex", "skills");
|
|
23137
23224
|
const existing = new Set(input.existingSkillNames);
|
|
23138
23225
|
const skills = input.skillNames.map((name) => ({
|
|
@@ -23142,7 +23229,9 @@ function planCodexInstall(input) {
|
|
|
23142
23229
|
present: existing.has(name)
|
|
23143
23230
|
}));
|
|
23144
23231
|
let instructionsAction;
|
|
23145
|
-
if (
|
|
23232
|
+
if (!includeInstructions) {
|
|
23233
|
+
instructionsAction = "skip";
|
|
23234
|
+
} else if (input.agentsMdContent === null) {
|
|
23146
23235
|
instructionsAction = "create";
|
|
23147
23236
|
} else if (input.agentsMdContent.includes(HARNESSTRIM_MARKER)) {
|
|
23148
23237
|
instructionsAction = "present";
|
|
@@ -23154,7 +23243,8 @@ function planCodexInstall(input) {
|
|
|
23154
23243
|
skills,
|
|
23155
23244
|
instructionsFile: path3.join(input.projectDir, "AGENTS.md"),
|
|
23156
23245
|
instructionsAction,
|
|
23157
|
-
instructionsSnippet: REDUCE_INSTRUCTION_SNIPPET
|
|
23246
|
+
instructionsSnippet: REDUCE_INSTRUCTION_SNIPPET,
|
|
23247
|
+
changed: skills.some((s) => !s.present) || instructionsAction !== "present" && instructionsAction !== "skip"
|
|
23158
23248
|
};
|
|
23159
23249
|
}
|
|
23160
23250
|
|
|
@@ -23219,7 +23309,7 @@ function applyHookPlan(plan, apply) {
|
|
|
23219
23309
|
fs5.writeFileSync(plan.hooksFile, JSON.stringify(plan.nextHooks, null, 2) + "\n");
|
|
23220
23310
|
return true;
|
|
23221
23311
|
}
|
|
23222
|
-
function runInstallCodex(dir, apply, hook = false) {
|
|
23312
|
+
function runInstallCodex(dir, apply, hook = false, options = {}) {
|
|
23223
23313
|
const skillsSourceDir = resolveSkillsSourceDir();
|
|
23224
23314
|
const skillNames = listShippedSkills(skillsSourceDir);
|
|
23225
23315
|
const skillsDest = path6.join(dir, ".codex", "skills");
|
|
@@ -23235,14 +23325,16 @@ function runInstallCodex(dir, apply, hook = false) {
|
|
|
23235
23325
|
skillsSourceDir,
|
|
23236
23326
|
skillNames,
|
|
23237
23327
|
agentsMdContent,
|
|
23238
|
-
existingSkillNames: existingSkillNames(skillsDest)
|
|
23328
|
+
existingSkillNames: existingSkillNames(skillsDest),
|
|
23329
|
+
includeInstructions: options.includeInstructions
|
|
23239
23330
|
});
|
|
23240
23331
|
const hooksPath = path6.join(dir, ".codex", "hooks.json");
|
|
23241
23332
|
const hooksJsonContent = hook ? readHooksJson(hooksPath) : null;
|
|
23242
23333
|
const hookPlan = hook ? planCodexHookInstall({ projectDir: dir, hooksJsonContent, hookCommand: resolveCodexHookCommand() }) : null;
|
|
23243
23334
|
const copied = [];
|
|
23335
|
+
const hookChanged = hookPlan !== null && hookPlan.action !== "present";
|
|
23244
23336
|
let applied = false;
|
|
23245
|
-
if (apply) {
|
|
23337
|
+
if (apply && (plan.changed || hookChanged)) {
|
|
23246
23338
|
for (const skill of plan.skills) {
|
|
23247
23339
|
if (skill.present) continue;
|
|
23248
23340
|
fs5.cpSync(skill.from, skill.to, { recursive: true });
|
|
@@ -23253,7 +23345,7 @@ function runInstallCodex(dir, apply, hook = false) {
|
|
|
23253
23345
|
} else if (plan.instructionsAction === "append") {
|
|
23254
23346
|
fs5.appendFileSync(plan.instructionsFile, "\n\n" + plan.instructionsSnippet + "\n");
|
|
23255
23347
|
}
|
|
23256
|
-
if (hookPlan) applyHookPlan(hookPlan, true);
|
|
23348
|
+
if (hookPlan && hookChanged) applyHookPlan(hookPlan, true);
|
|
23257
23349
|
applied = true;
|
|
23258
23350
|
}
|
|
23259
23351
|
return { plan, hookPlan, applied, copied };
|
|
@@ -23278,9 +23370,15 @@ import path8 from "node:path";
|
|
|
23278
23370
|
init_src();
|
|
23279
23371
|
function reduceClaudePayload(rawJson, minLength) {
|
|
23280
23372
|
const extracted = extractToolOutput2(rawJson);
|
|
23281
|
-
if (extracted === null) return { response: "{}", event: null };
|
|
23373
|
+
if (extracted === null) return { response: "{}", event: null, attempt: null };
|
|
23282
23374
|
const result = reduceAuto(extracted.output, minLength);
|
|
23283
|
-
if (!result.changed)
|
|
23375
|
+
if (!result.changed) {
|
|
23376
|
+
return {
|
|
23377
|
+
response: "{}",
|
|
23378
|
+
event: null,
|
|
23379
|
+
attempt: extracted.output.length >= (minLength ?? DEFAULT_MIN_LENGTH) ? { tool: extracted.toolName, beforeChars: extracted.output.length } : null
|
|
23380
|
+
};
|
|
23381
|
+
}
|
|
23284
23382
|
const response = JSON.stringify({
|
|
23285
23383
|
hookSpecificOutput: {
|
|
23286
23384
|
hookEventName: "PostToolUse",
|
|
@@ -23294,7 +23392,8 @@ function reduceClaudePayload(rawJson, minLength) {
|
|
|
23294
23392
|
reducer: result.reducer,
|
|
23295
23393
|
beforeChars: extracted.output.length,
|
|
23296
23394
|
afterChars: result.output.length
|
|
23297
|
-
}
|
|
23395
|
+
},
|
|
23396
|
+
attempt: null
|
|
23298
23397
|
};
|
|
23299
23398
|
}
|
|
23300
23399
|
function extractToolOutput2(rawJson) {
|
|
@@ -23349,6 +23448,8 @@ function hasHarnessTrimHook2(settings) {
|
|
|
23349
23448
|
);
|
|
23350
23449
|
}
|
|
23351
23450
|
function planClaudeInstall(input) {
|
|
23451
|
+
const includeHook = input.includeHook ?? true;
|
|
23452
|
+
const includeInstructions = input.includeInstructions ?? true;
|
|
23352
23453
|
const skillsDest = path7.join(input.projectDir, ".claude", "skills");
|
|
23353
23454
|
const existing = new Set(input.existingSkillNames);
|
|
23354
23455
|
const skills = input.skillNames.map((name) => ({
|
|
@@ -23370,8 +23471,12 @@ function planClaudeInstall(input) {
|
|
|
23370
23471
|
}
|
|
23371
23472
|
action = hasHarnessTrimHook2(settings) ? "present" : "patch";
|
|
23372
23473
|
}
|
|
23373
|
-
|
|
23374
|
-
|
|
23474
|
+
if (!includeHook) {
|
|
23475
|
+
action = "skip";
|
|
23476
|
+
settings = input.settingsJsonContent === null ? {} : settings;
|
|
23477
|
+
}
|
|
23478
|
+
const nextSettings = action === "present" || action === "skip" ? settings : addHook(settings);
|
|
23479
|
+
const instructionsAction = !includeInstructions ? "skip" : input.claudeMdContent === null ? "create" : input.claudeMdContent.includes(HARNESSTRIM_MARKER2) ? "present" : "append";
|
|
23375
23480
|
return {
|
|
23376
23481
|
skillsDest,
|
|
23377
23482
|
skills,
|
|
@@ -23380,7 +23485,8 @@ function planClaudeInstall(input) {
|
|
|
23380
23485
|
nextSettings,
|
|
23381
23486
|
instructionsFile: path7.join(input.projectDir, "CLAUDE.md"),
|
|
23382
23487
|
instructionsAction,
|
|
23383
|
-
instructionsSnippet: REDUCE_INSTRUCTION_SNIPPET2
|
|
23488
|
+
instructionsSnippet: REDUCE_INSTRUCTION_SNIPPET2,
|
|
23489
|
+
changed: skills.some((s) => !s.present) || action !== "present" && action !== "skip" || instructionsAction !== "present" && instructionsAction !== "skip"
|
|
23384
23490
|
};
|
|
23385
23491
|
}
|
|
23386
23492
|
function addHook(settings) {
|
|
@@ -23394,7 +23500,7 @@ function addHook(settings) {
|
|
|
23394
23500
|
}
|
|
23395
23501
|
|
|
23396
23502
|
// src/install-claude.ts
|
|
23397
|
-
function runInstallClaude(dir, apply) {
|
|
23503
|
+
function runInstallClaude(dir, apply, options = {}) {
|
|
23398
23504
|
const skillsSourceDir = resolveSkillsSourceDir();
|
|
23399
23505
|
const skillNames = listShippedSkills(skillsSourceDir);
|
|
23400
23506
|
const skillsDest = path8.join(dir, ".claude", "skills");
|
|
@@ -23418,17 +23524,19 @@ function runInstallClaude(dir, apply) {
|
|
|
23418
23524
|
skillNames,
|
|
23419
23525
|
settingsJsonContent,
|
|
23420
23526
|
claudeMdContent,
|
|
23421
|
-
existingSkillNames: existingSkillNames(skillsDest)
|
|
23527
|
+
existingSkillNames: existingSkillNames(skillsDest),
|
|
23528
|
+
includeHook: options.includeHook,
|
|
23529
|
+
includeInstructions: options.includeInstructions
|
|
23422
23530
|
});
|
|
23423
23531
|
const copied = [];
|
|
23424
23532
|
let applied = false;
|
|
23425
|
-
if (apply) {
|
|
23533
|
+
if (apply && plan.changed) {
|
|
23426
23534
|
for (const skill of plan.skills) {
|
|
23427
23535
|
if (skill.present) continue;
|
|
23428
23536
|
fs6.cpSync(skill.from, skill.to, { recursive: true });
|
|
23429
23537
|
copied.push(skill.name);
|
|
23430
23538
|
}
|
|
23431
|
-
if (plan.settingsAction !== "present") {
|
|
23539
|
+
if (plan.settingsAction !== "present" && plan.settingsAction !== "skip") {
|
|
23432
23540
|
fs6.mkdirSync(path8.dirname(plan.settingsFile), { recursive: true });
|
|
23433
23541
|
fs6.writeFileSync(plan.settingsFile, JSON.stringify(plan.nextSettings, null, 2) + "\n");
|
|
23434
23542
|
}
|
|
@@ -23499,12 +23607,16 @@ function runInstallPi(installDir, apply) {
|
|
|
23499
23607
|
let applied = false;
|
|
23500
23608
|
if (apply) {
|
|
23501
23609
|
fs7.mkdirSync(dest, { recursive: true });
|
|
23502
|
-
|
|
23503
|
-
|
|
23504
|
-
|
|
23505
|
-
|
|
23610
|
+
const sourceFiles = fs7.readdirSync(extensionSourceDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name);
|
|
23611
|
+
for (const existing of fs7.readdirSync(dest)) {
|
|
23612
|
+
if (existing !== ".installed" && !sourceFiles.includes(existing)) {
|
|
23613
|
+
fs7.rmSync(path10.join(dest, existing), { recursive: true, force: true });
|
|
23506
23614
|
}
|
|
23507
23615
|
}
|
|
23616
|
+
for (const entry of sourceFiles) {
|
|
23617
|
+
fs7.copyFileSync(path10.join(extensionSourceDir, entry), path10.join(dest, entry));
|
|
23618
|
+
copiedFiles.push(entry);
|
|
23619
|
+
}
|
|
23508
23620
|
fs7.writeFileSync(path10.join(dest, ".installed"), markerFileContent());
|
|
23509
23621
|
copiedFiles.push(".installed");
|
|
23510
23622
|
applied = true;
|
|
@@ -23621,7 +23733,7 @@ function loadMetrics(filePath) {
|
|
|
23621
23733
|
// package.json
|
|
23622
23734
|
var package_default = {
|
|
23623
23735
|
name: "harnesstrim",
|
|
23624
|
-
version: "0.0
|
|
23736
|
+
version: "0.1.0",
|
|
23625
23737
|
description: "HarnessTrim CLI: doctor (diagnose token waste), install adapters, run benchmarks.",
|
|
23626
23738
|
license: "MIT",
|
|
23627
23739
|
type: "module",
|
|
@@ -23669,6 +23781,324 @@ function reducePipe(input, minLength) {
|
|
|
23669
23781
|
return { ...result, beforeChars: input.length, afterChars: result.output.length };
|
|
23670
23782
|
}
|
|
23671
23783
|
|
|
23784
|
+
// src/capabilities.ts
|
|
23785
|
+
var CAPABILITIES = {
|
|
23786
|
+
harnesses: {
|
|
23787
|
+
opencode: {
|
|
23788
|
+
adapter: "@harnesstrim/adapter-opencode",
|
|
23789
|
+
surfaces: [
|
|
23790
|
+
"tool.execute.after \u2014 slims noisy tool output in place before it enters context",
|
|
23791
|
+
"experimental.session.compacting \u2014 injects compaction-handoff guidance"
|
|
23792
|
+
],
|
|
23793
|
+
narrowing: [
|
|
23794
|
+
{ flag: "--mode active|dryrun|off", produces: "bake the reduction mode into the generated wrapper (active/dryrun/off)" },
|
|
23795
|
+
{ flag: "--min-length <n>", produces: "leave tool outputs shorter than n chars untouched (overrides preset)" },
|
|
23796
|
+
{ flag: "--tools <name,...>", produces: "confine reduction to a subset of tool families (e.g. bash,read)" },
|
|
23797
|
+
{ flag: "--preset <name>", produces: "bake a policy preset's adapter config into the wrapper" }
|
|
23798
|
+
],
|
|
23799
|
+
writeSet: [
|
|
23800
|
+
".opencode/plugin/harnesstrim.ts",
|
|
23801
|
+
".opencode/package.json",
|
|
23802
|
+
"opencode.json (cleans a stale adapter entry, never adds one)"
|
|
23803
|
+
]
|
|
23804
|
+
},
|
|
23805
|
+
codex: {
|
|
23806
|
+
adapter: "@harnesstrim/adapter-codex",
|
|
23807
|
+
surfaces: [
|
|
23808
|
+
"PostToolUse Bash hook \u2014 deterministic reduction of simple Bash output (optional --hook)",
|
|
23809
|
+
"AGENTS.md reduce-pipe instruction \u2014 model pipes noisy output through `harnesstrim reduce`",
|
|
23810
|
+
"MCP reduce tool \u2014 deterministic, instruction-free reduction (separate `harnesstrim mcp`)"
|
|
23811
|
+
],
|
|
23812
|
+
narrowing: [
|
|
23813
|
+
{ flag: "--no-instructions", produces: "skills only \u2014 no AGENTS.md reduce-pipe instruction" },
|
|
23814
|
+
{ flag: "--hook", produces: "also install the experimental Bash PostToolUse hook" },
|
|
23815
|
+
{ flag: "--global", produces: "install the hook once in ~/.codex (with --hook)" }
|
|
23816
|
+
],
|
|
23817
|
+
writeSet: [".codex/skills/", "AGENTS.md (marker-guarded snippet)", ".codex/hooks.json (with --hook)"]
|
|
23818
|
+
},
|
|
23819
|
+
claude: {
|
|
23820
|
+
adapter: "@harnesstrim/adapter-claude",
|
|
23821
|
+
surfaces: [
|
|
23822
|
+
"PostToolUse Bash hook \u2014 spec-correct updatedToolOutput (not honored by Claude Code 2.1.37\u20132.1.212)",
|
|
23823
|
+
"CLAUDE.md reduce-pipe instruction \u2014 the effective reduction path on current Claude Code"
|
|
23824
|
+
],
|
|
23825
|
+
narrowing: [
|
|
23826
|
+
{ flag: "--no-hook", produces: "skills only \u2014 no PostToolUse hook in .claude/settings.json" },
|
|
23827
|
+
{ flag: "--no-instructions", produces: "skills only \u2014 no CLAUDE.md reduce-pipe instruction" }
|
|
23828
|
+
],
|
|
23829
|
+
writeSet: [".claude/skills/", ".claude/settings.json", "CLAUDE.md (marker-guarded snippet)"]
|
|
23830
|
+
},
|
|
23831
|
+
hermes: {
|
|
23832
|
+
adapter: "@harnesstrim/adapter-hermes",
|
|
23833
|
+
surfaces: ["transform_tool_result \u2014 deterministic reduction before the result enters context"],
|
|
23834
|
+
narrowing: [],
|
|
23835
|
+
writeSet: [".hermes/plugins/harnesstrim/ (incl. .installed marker)"]
|
|
23836
|
+
},
|
|
23837
|
+
pi: {
|
|
23838
|
+
adapter: "@harnesstrim/adapter-pi",
|
|
23839
|
+
surfaces: ["tool_result \u2014 deterministic reduction of text chunks in structured results"],
|
|
23840
|
+
narrowing: [],
|
|
23841
|
+
writeSet: [".pi/extensions/harnesstrim/ or .pi/agent/extensions/harnesstrim/ (incl. .installed marker)"]
|
|
23842
|
+
}
|
|
23843
|
+
}
|
|
23844
|
+
};
|
|
23845
|
+
function getCapabilities(version2) {
|
|
23846
|
+
return { version: version2, ...CAPABILITIES };
|
|
23847
|
+
}
|
|
23848
|
+
|
|
23849
|
+
// src/uninstall.ts
|
|
23850
|
+
import fs10 from "node:fs";
|
|
23851
|
+
import path14 from "node:path";
|
|
23852
|
+
function readOrNull(p) {
|
|
23853
|
+
try {
|
|
23854
|
+
return fs10.readFileSync(p, "utf8");
|
|
23855
|
+
} catch {
|
|
23856
|
+
return null;
|
|
23857
|
+
}
|
|
23858
|
+
}
|
|
23859
|
+
function stripMarkedRegion(content, marker) {
|
|
23860
|
+
const begin = `<!-- ${marker} -->`;
|
|
23861
|
+
const end = "<!-- harnesstrim:end -->";
|
|
23862
|
+
const start = content.indexOf(begin);
|
|
23863
|
+
if (start === -1) return content;
|
|
23864
|
+
const endIdx = content.indexOf(end, start);
|
|
23865
|
+
if (endIdx === -1) return content;
|
|
23866
|
+
const after = content.slice(endIdx + end.length);
|
|
23867
|
+
const before = content.slice(0, start).replace(/\s+$/, "");
|
|
23868
|
+
const next = (before + "\n" + after.replace(/^\s+/, "")).replace(/\n{3,}/g, "\n\n").trim();
|
|
23869
|
+
return next;
|
|
23870
|
+
}
|
|
23871
|
+
function stripHookEntries(settings) {
|
|
23872
|
+
const hooks = settings.hooks;
|
|
23873
|
+
if (!hooks || !Array.isArray(hooks.PostToolUse)) return { next: settings, removed: false };
|
|
23874
|
+
const post = hooks.PostToolUse;
|
|
23875
|
+
const kept = post.filter((entry) => {
|
|
23876
|
+
const hooksArr = entry.hooks;
|
|
23877
|
+
if (!Array.isArray(hooksArr)) return true;
|
|
23878
|
+
return !hooksArr.some((h) => typeof h.command === "string" && h.command.includes("harnesstrim hook"));
|
|
23879
|
+
});
|
|
23880
|
+
if (kept.length === post.length) return { next: settings, removed: false };
|
|
23881
|
+
const nextHooks = { ...hooks };
|
|
23882
|
+
nextHooks.PostToolUse = kept;
|
|
23883
|
+
const next = { ...settings, hooks: nextHooks };
|
|
23884
|
+
return { next, removed: true };
|
|
23885
|
+
}
|
|
23886
|
+
function isSkillDir(pathName) {
|
|
23887
|
+
try {
|
|
23888
|
+
return fs10.statSync(pathName).isDirectory();
|
|
23889
|
+
} catch {
|
|
23890
|
+
return false;
|
|
23891
|
+
}
|
|
23892
|
+
}
|
|
23893
|
+
function markerPresent3(dest) {
|
|
23894
|
+
try {
|
|
23895
|
+
return fs10.statSync(path14.join(dest, ".installed")).isFile();
|
|
23896
|
+
} catch {
|
|
23897
|
+
return false;
|
|
23898
|
+
}
|
|
23899
|
+
}
|
|
23900
|
+
function skillRemovalActions(dir, destRel) {
|
|
23901
|
+
const sourceDir = resolveSkillsSourceDir();
|
|
23902
|
+
const shipped = listShippedSkills(sourceDir);
|
|
23903
|
+
const dest = path14.join(dir, destRel);
|
|
23904
|
+
const actions = [];
|
|
23905
|
+
for (const name of shipped) {
|
|
23906
|
+
const skillPath = path14.join(dest, name);
|
|
23907
|
+
if (isSkillDir(skillPath)) {
|
|
23908
|
+
actions.push({ type: "remove-dir", path: skillPath, note: `skill directory installed by HarnessTrim` });
|
|
23909
|
+
}
|
|
23910
|
+
}
|
|
23911
|
+
if (actions.length > 0 && isSkillDir(dest)) {
|
|
23912
|
+
const remaining = fs10.readdirSync(dest).filter((name) => !shipped.includes(name));
|
|
23913
|
+
if (remaining.length === 0) {
|
|
23914
|
+
actions.push({ type: "remove-dir", path: dest, note: "empty skills directory left by HarnessTrim" });
|
|
23915
|
+
}
|
|
23916
|
+
}
|
|
23917
|
+
return actions;
|
|
23918
|
+
}
|
|
23919
|
+
function planClaudeUninstall(dir) {
|
|
23920
|
+
const actions = [...skillRemovalActions(dir, ".claude/skills")];
|
|
23921
|
+
const settingsPath = path14.join(dir, ".claude", "settings.json");
|
|
23922
|
+
const settingsContent = readOrNull(settingsPath);
|
|
23923
|
+
if (settingsContent !== null) {
|
|
23924
|
+
try {
|
|
23925
|
+
const parsed = JSON.parse(settingsContent);
|
|
23926
|
+
const { next, removed } = stripHookEntries(parsed);
|
|
23927
|
+
if (removed) {
|
|
23928
|
+
actions.push({
|
|
23929
|
+
type: Object.keys(next).length === 0 ? "remove-file" : "write",
|
|
23930
|
+
path: settingsPath,
|
|
23931
|
+
note: "remove the HarnessTrim PostToolUse hook"
|
|
23932
|
+
});
|
|
23933
|
+
}
|
|
23934
|
+
} catch {
|
|
23935
|
+
}
|
|
23936
|
+
}
|
|
23937
|
+
const claudeMdPath = path14.join(dir, "CLAUDE.md");
|
|
23938
|
+
const claudeMd = readOrNull(claudeMdPath);
|
|
23939
|
+
if (claudeMd !== null && claudeMd.includes(HARNESSTRIM_MARKER2)) {
|
|
23940
|
+
const next = stripMarkedRegion(claudeMd, HARNESSTRIM_MARKER2);
|
|
23941
|
+
if (next !== null && next !== claudeMd) {
|
|
23942
|
+
actions.push({
|
|
23943
|
+
type: next.length === 0 ? "remove-file" : "write",
|
|
23944
|
+
path: claudeMdPath,
|
|
23945
|
+
note: "remove the marker-guarded HarnessTrim instruction"
|
|
23946
|
+
});
|
|
23947
|
+
}
|
|
23948
|
+
}
|
|
23949
|
+
return { harness: "claude", dir, changed: actions.length > 0, actions };
|
|
23950
|
+
}
|
|
23951
|
+
function planCodexUninstall(dir) {
|
|
23952
|
+
const actions = [...skillRemovalActions(dir, ".codex/skills")];
|
|
23953
|
+
const agentsPath = path14.join(dir, "AGENTS.md");
|
|
23954
|
+
const agents = readOrNull(agentsPath);
|
|
23955
|
+
if (agents !== null && agents.includes(HARNESSTRIM_MARKER)) {
|
|
23956
|
+
const next = stripMarkedRegion(agents, HARNESSTRIM_MARKER);
|
|
23957
|
+
if (next !== null && next !== agents) {
|
|
23958
|
+
actions.push({
|
|
23959
|
+
type: next.length === 0 ? "remove-file" : "write",
|
|
23960
|
+
path: agentsPath,
|
|
23961
|
+
note: "remove the marker-guarded HarnessTrim instruction"
|
|
23962
|
+
});
|
|
23963
|
+
}
|
|
23964
|
+
}
|
|
23965
|
+
const hooksPath = path14.join(dir, ".codex", "hooks.json");
|
|
23966
|
+
const hooksContent = readOrNull(hooksPath);
|
|
23967
|
+
if (hooksContent !== null) {
|
|
23968
|
+
try {
|
|
23969
|
+
const parsed = JSON.parse(hooksContent);
|
|
23970
|
+
const { next, removed } = stripHookEntries(parsed);
|
|
23971
|
+
if (removed) {
|
|
23972
|
+
actions.push({
|
|
23973
|
+
type: Object.keys(next).length === 0 ? "remove-file" : "write",
|
|
23974
|
+
path: hooksPath,
|
|
23975
|
+
note: "remove the HarnessTrim PostToolUse hook"
|
|
23976
|
+
});
|
|
23977
|
+
}
|
|
23978
|
+
} catch {
|
|
23979
|
+
}
|
|
23980
|
+
}
|
|
23981
|
+
return { harness: "codex", dir, changed: actions.length > 0, actions };
|
|
23982
|
+
}
|
|
23983
|
+
function planOpencodeUninstall(dir) {
|
|
23984
|
+
const actions = [];
|
|
23985
|
+
const wrapperPath = path14.join(dir, ".opencode", "plugin", "harnesstrim.ts");
|
|
23986
|
+
const wrapper = readOrNull(wrapperPath);
|
|
23987
|
+
if (wrapper !== null && wrapper.includes(OPENCODE_PLUGIN_NAME)) {
|
|
23988
|
+
actions.push({ type: "remove-file", path: wrapperPath, note: "HarnessTrim plugin wrapper" });
|
|
23989
|
+
const pluginDir = path14.join(dir, ".opencode", "plugin");
|
|
23990
|
+
if (fs10.existsSync(pluginDir)) {
|
|
23991
|
+
const entries = fs10.readdirSync(pluginDir).filter((name) => name !== "harnesstrim.ts");
|
|
23992
|
+
if (entries.length === 0) {
|
|
23993
|
+
actions.push({ type: "remove-dir", path: pluginDir, note: "empty plugin directory left by HarnessTrim" });
|
|
23994
|
+
}
|
|
23995
|
+
}
|
|
23996
|
+
}
|
|
23997
|
+
const pkgPath = path14.join(dir, ".opencode", "package.json");
|
|
23998
|
+
const pkgContent = readOrNull(pkgPath);
|
|
23999
|
+
if (pkgContent !== null) {
|
|
24000
|
+
try {
|
|
24001
|
+
const parsed = JSON.parse(pkgContent);
|
|
24002
|
+
const deps = parsed.dependencies;
|
|
24003
|
+
if (deps && typeof deps[OPENCODE_PLUGIN_NAME] === "string") {
|
|
24004
|
+
const nextDeps = { ...deps };
|
|
24005
|
+
delete nextDeps[OPENCODE_PLUGIN_NAME];
|
|
24006
|
+
const next = { ...parsed, dependencies: nextDeps };
|
|
24007
|
+
const onlyOurDep = Object.keys(nextDeps).length === 0 && Object.keys(parsed).length <= 1;
|
|
24008
|
+
actions.push({
|
|
24009
|
+
type: onlyOurDep ? "remove-file" : "write",
|
|
24010
|
+
path: pkgPath,
|
|
24011
|
+
note: onlyOurDep ? "remove .opencode/package.json (only declared the adapter)" : "drop the @harnesstrim/adapter-opencode dependency"
|
|
24012
|
+
});
|
|
24013
|
+
}
|
|
24014
|
+
} catch {
|
|
24015
|
+
}
|
|
24016
|
+
}
|
|
24017
|
+
return { harness: "opencode", dir, changed: actions.length > 0, actions };
|
|
24018
|
+
}
|
|
24019
|
+
function planHermesUninstall(dir) {
|
|
24020
|
+
const pluginDest = path14.join(dir, ".hermes", "plugins", "harnesstrim");
|
|
24021
|
+
const actions = [];
|
|
24022
|
+
if (markerPresent3(pluginDest)) {
|
|
24023
|
+
actions.push({ type: "remove-dir", path: pluginDest, note: "Hermes plugin installed by HarnessTrim" });
|
|
24024
|
+
}
|
|
24025
|
+
return { harness: "hermes", dir, changed: actions.length > 0, actions };
|
|
24026
|
+
}
|
|
24027
|
+
function planPiUninstall(dir) {
|
|
24028
|
+
const scope = path14.resolve(dir) === path14.resolve(process.env.HOME ?? "") ? "user" : "project";
|
|
24029
|
+
const dest = scope === "user" ? path14.join(dir, ".pi", "agent", "extensions", "harnesstrim") : path14.join(dir, ".pi", "extensions", "harnesstrim");
|
|
24030
|
+
const actions = [];
|
|
24031
|
+
if (markerPresent3(dest)) {
|
|
24032
|
+
actions.push({ type: "remove-dir", path: dest, note: "Pi extension installed by HarnessTrim" });
|
|
24033
|
+
}
|
|
24034
|
+
return { harness: "pi", dir, changed: actions.length > 0, actions };
|
|
24035
|
+
}
|
|
24036
|
+
function planUninstall(harness, dir) {
|
|
24037
|
+
switch (harness) {
|
|
24038
|
+
case "claude":
|
|
24039
|
+
return planClaudeUninstall(dir);
|
|
24040
|
+
case "codex":
|
|
24041
|
+
return planCodexUninstall(dir);
|
|
24042
|
+
case "opencode":
|
|
24043
|
+
return planOpencodeUninstall(dir);
|
|
24044
|
+
case "hermes":
|
|
24045
|
+
return planHermesUninstall(dir);
|
|
24046
|
+
case "pi":
|
|
24047
|
+
return planPiUninstall(dir);
|
|
24048
|
+
default:
|
|
24049
|
+
throw new Error(`Unknown uninstall target: ${harness}. Supported: opencode, codex, claude, hermes, pi.`);
|
|
24050
|
+
}
|
|
24051
|
+
}
|
|
24052
|
+
function runUninstall(harness, dir, apply) {
|
|
24053
|
+
const plan = planUninstall(harness, dir);
|
|
24054
|
+
if (!apply || !plan.changed) return { ...plan, applied: false };
|
|
24055
|
+
for (const action of plan.actions) {
|
|
24056
|
+
switch (action.type) {
|
|
24057
|
+
case "remove-file":
|
|
24058
|
+
fs10.rmSync(action.path, { force: true });
|
|
24059
|
+
break;
|
|
24060
|
+
case "remove-dir":
|
|
24061
|
+
fs10.rmSync(action.path, { recursive: true, force: true });
|
|
24062
|
+
break;
|
|
24063
|
+
case "write": {
|
|
24064
|
+
if (action.path.endsWith("settings.json") || action.path.endsWith("hooks.json")) {
|
|
24065
|
+
const raw = readOrNull(action.path);
|
|
24066
|
+
if (raw !== null) {
|
|
24067
|
+
try {
|
|
24068
|
+
const parsed = JSON.parse(raw);
|
|
24069
|
+
const { next } = stripHookEntries(parsed);
|
|
24070
|
+
fs10.writeFileSync(action.path, JSON.stringify(next, null, 2) + "\n");
|
|
24071
|
+
} catch {
|
|
24072
|
+
}
|
|
24073
|
+
}
|
|
24074
|
+
} else if (action.path.endsWith(path14.join(".opencode", "package.json"))) {
|
|
24075
|
+
const raw = readOrNull(action.path);
|
|
24076
|
+
if (raw !== null) {
|
|
24077
|
+
try {
|
|
24078
|
+
const parsed = JSON.parse(raw);
|
|
24079
|
+
const deps = { ...parsed.dependencies ?? {} };
|
|
24080
|
+
delete deps[OPENCODE_PLUGIN_NAME];
|
|
24081
|
+
fs10.writeFileSync(action.path, JSON.stringify({ ...parsed, dependencies: deps }, null, 2) + "\n");
|
|
24082
|
+
} catch {
|
|
24083
|
+
}
|
|
24084
|
+
}
|
|
24085
|
+
} else {
|
|
24086
|
+
const marker = action.path.endsWith("CLAUDE.md") ? HARNESSTRIM_MARKER2 : HARNESSTRIM_MARKER;
|
|
24087
|
+
const raw = readOrNull(action.path);
|
|
24088
|
+
if (raw !== null) {
|
|
24089
|
+
const next = stripMarkedRegion(raw, marker);
|
|
24090
|
+
if (next !== null) fs10.writeFileSync(action.path, next + "\n");
|
|
24091
|
+
}
|
|
24092
|
+
}
|
|
24093
|
+
break;
|
|
24094
|
+
}
|
|
24095
|
+
default:
|
|
24096
|
+
break;
|
|
24097
|
+
}
|
|
24098
|
+
}
|
|
24099
|
+
return { ...plan, applied: true };
|
|
24100
|
+
}
|
|
24101
|
+
|
|
23672
24102
|
// src/render.ts
|
|
23673
24103
|
var ICON = { warn: "!", info: "i", ok: "+" };
|
|
23674
24104
|
function renderDoctor(report) {
|
|
@@ -23767,7 +24197,7 @@ function renderCodexInstall(result, apply) {
|
|
|
23767
24197
|
lines.push(` ${s.name.padEnd(18)} ${state}`);
|
|
23768
24198
|
}
|
|
23769
24199
|
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.`;
|
|
24200
|
+
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
24201
|
lines.push(instr);
|
|
23772
24202
|
if (result.hookPlan) {
|
|
23773
24203
|
lines.push("");
|
|
@@ -23816,7 +24246,9 @@ function renderClaudeInstall(result, apply) {
|
|
|
23816
24246
|
lines.push(` ${s.name.padEnd(18)} ${state}`);
|
|
23817
24247
|
}
|
|
23818
24248
|
lines.push("");
|
|
23819
|
-
if (plan.settingsAction === "
|
|
24249
|
+
if (plan.settingsAction === "skip") {
|
|
24250
|
+
lines.push(`${plan.settingsFile}: PostToolUse hook skipped (--no-hook); skills only.`);
|
|
24251
|
+
} else if (plan.settingsAction === "present") {
|
|
23820
24252
|
lines.push(`${plan.settingsFile}: PostToolUse reducer hook already present (no change).`);
|
|
23821
24253
|
} else {
|
|
23822
24254
|
lines.push(
|
|
@@ -23829,7 +24261,9 @@ function renderClaudeInstall(result, apply) {
|
|
|
23829
24261
|
}
|
|
23830
24262
|
}
|
|
23831
24263
|
lines.push("");
|
|
23832
|
-
if (plan.instructionsAction === "
|
|
24264
|
+
if (plan.instructionsAction === "skip") {
|
|
24265
|
+
lines.push(`${plan.instructionsFile}: reduce-pipe instruction skipped (--no-instructions); skills only.`);
|
|
24266
|
+
} else if (plan.instructionsAction === "present") {
|
|
23833
24267
|
lines.push(`${plan.instructionsFile}: reduce-pipe instruction already present (no change).`);
|
|
23834
24268
|
} else {
|
|
23835
24269
|
lines.push(
|
|
@@ -23884,7 +24318,7 @@ function renderPiInstall(result, apply) {
|
|
|
23884
24318
|
lines.push(` Copied: ${result.copiedFiles.join(", ")}`);
|
|
23885
24319
|
} else {
|
|
23886
24320
|
lines.push(` Source: ${plan.extensionSource}`);
|
|
23887
|
-
lines.push(" (
|
|
24321
|
+
lines.push(" (index.ts + .installed marker)");
|
|
23888
24322
|
}
|
|
23889
24323
|
lines.push("");
|
|
23890
24324
|
lines.push("The extension hooks Pi's `tool_result` and needs `harnesstrim` on PATH.");
|
|
@@ -23908,18 +24342,217 @@ Enable it in the adapter (telemetry: true) to record reductions.`;
|
|
|
23908
24342
|
const lines = [
|
|
23909
24343
|
`harnesstrim metrics \u2014 ${result.path}`,
|
|
23910
24344
|
"",
|
|
23911
|
-
`
|
|
23912
|
-
`
|
|
23913
|
-
|
|
23914
|
-
"By reducer:"
|
|
24345
|
+
`Attempts: ${s.events} (${s.reduced} reduced, ${s.passThrough} pass-through, ${s.reductionErrors} error)`,
|
|
24346
|
+
`Pass-through: ${s.passThroughRate}% of attempts unchanged`,
|
|
24347
|
+
`Chars: ${s.beforeChars} -> ${s.afterChars} (saved ${s.savedChars}, -${s.reductionPct}%)`
|
|
23915
24348
|
];
|
|
24349
|
+
if (s.reductionErrors > 0) {
|
|
24350
|
+
lines.push(`Reduction errors: ${s.reductionErrors} attempt(s) GREW the output (+${s.grewChars} chars) \u2014 investigate`);
|
|
24351
|
+
}
|
|
24352
|
+
lines.push("");
|
|
24353
|
+
lines.push("By reducer:");
|
|
23916
24354
|
for (const b of s.byReducer) {
|
|
23917
24355
|
const p = b.beforeChars === 0 ? 0 : Math.round(b.savedChars / b.beforeChars * 1e3) / 10;
|
|
23918
24356
|
lines.push(` ${b.reducer.padEnd(20)} ${b.count}x saved ${b.savedChars} chars (-${p}%)`);
|
|
23919
24357
|
}
|
|
24358
|
+
if (s.byHarness.length > 0) {
|
|
24359
|
+
lines.push("");
|
|
24360
|
+
lines.push("By harness:");
|
|
24361
|
+
for (const h of s.byHarness) {
|
|
24362
|
+
const p = h.beforeChars === 0 ? 0 : Math.round(h.savedChars / h.beforeChars * 1e3) / 10;
|
|
24363
|
+
const sign = p < 0 ? "" : "-";
|
|
24364
|
+
lines.push(` ${h.harness.padEnd(12)} ${h.count}x saved ${h.savedChars} chars (${sign}${Math.abs(p)}%)`);
|
|
24365
|
+
}
|
|
24366
|
+
}
|
|
24367
|
+
return lines.join("\n");
|
|
24368
|
+
}
|
|
24369
|
+
function renderUninstall(result, apply) {
|
|
24370
|
+
const lines = [`${apply ? "Uninstalled" : "Would uninstall"} ${result.harness} integration`, ""];
|
|
24371
|
+
if (!result.changed) {
|
|
24372
|
+
lines.push("Nothing to remove \u2014 no HarnessTrim files found in the install set.");
|
|
24373
|
+
return lines.join("\n");
|
|
24374
|
+
}
|
|
24375
|
+
for (const action of result.actions) {
|
|
24376
|
+
const verb = action.type === "remove-file" || action.type === "remove-dir" ? apply ? "removed" : "remove" : action.type === "write" ? apply ? "updated" : "update" : "clean";
|
|
24377
|
+
lines.push(` ${verb.padEnd(8)} ${action.path}`);
|
|
24378
|
+
if (action.note) lines.push(` (${action.note})`);
|
|
24379
|
+
}
|
|
24380
|
+
if (!apply) {
|
|
24381
|
+
lines.push("");
|
|
24382
|
+
lines.push("Dry run \u2014 nothing written. Re-run with `--apply`.");
|
|
24383
|
+
}
|
|
24384
|
+
lines.push("");
|
|
24385
|
+
lines.push("Only files HarnessTrim wrote are touched; marker-guarded regions and your");
|
|
24386
|
+
lines.push("other settings/hook entries are preserved.");
|
|
23920
24387
|
return lines.join("\n");
|
|
23921
24388
|
}
|
|
23922
24389
|
|
|
24390
|
+
// src/json.ts
|
|
24391
|
+
function doctorJson(report) {
|
|
24392
|
+
return JSON.stringify(report, null, 2);
|
|
24393
|
+
}
|
|
24394
|
+
function metricsJson(result) {
|
|
24395
|
+
return JSON.stringify(result, null, 2);
|
|
24396
|
+
}
|
|
24397
|
+
function opencodeInstallJson(result, apply) {
|
|
24398
|
+
const actions = [];
|
|
24399
|
+
if (result.changed) {
|
|
24400
|
+
actions.push(
|
|
24401
|
+
{
|
|
24402
|
+
type: "write",
|
|
24403
|
+
path: result.wrapperPath,
|
|
24404
|
+
note: "local plugin wrapper with the adapter options"
|
|
24405
|
+
},
|
|
24406
|
+
{
|
|
24407
|
+
type: "write",
|
|
24408
|
+
path: result.packageJsonPath,
|
|
24409
|
+
note: "declares @harnesstrim/adapter-opencode"
|
|
24410
|
+
}
|
|
24411
|
+
);
|
|
24412
|
+
if (result.opencodeJsonContent !== null) {
|
|
24413
|
+
actions.push({
|
|
24414
|
+
type: "clean",
|
|
24415
|
+
path: result.opencodeJsonPath,
|
|
24416
|
+
note: "remove the stale adapter entry (options live in the wrapper now)"
|
|
24417
|
+
});
|
|
24418
|
+
}
|
|
24419
|
+
}
|
|
24420
|
+
return {
|
|
24421
|
+
harness: "opencode",
|
|
24422
|
+
dryRun: !apply,
|
|
24423
|
+
changed: result.changed,
|
|
24424
|
+
alreadyInstalled: result.alreadyInstalled,
|
|
24425
|
+
applied: result.applied,
|
|
24426
|
+
actions,
|
|
24427
|
+
details: {
|
|
24428
|
+
preset: result.preset?.name ?? null,
|
|
24429
|
+
depsInstalled: result.depsInstalled,
|
|
24430
|
+
depsMessage: result.depsMessage ?? null
|
|
24431
|
+
}
|
|
24432
|
+
};
|
|
24433
|
+
}
|
|
24434
|
+
function codexInstallJson(result, apply) {
|
|
24435
|
+
const actions = result.plan.skills.map((s) => ({
|
|
24436
|
+
type: s.present ? "none" : "copy",
|
|
24437
|
+
path: s.to,
|
|
24438
|
+
from: s.from,
|
|
24439
|
+
note: s.present ? "already present" : "copy skill"
|
|
24440
|
+
}));
|
|
24441
|
+
if (result.plan.instructionsAction !== "present" && result.plan.instructionsAction !== "skip") {
|
|
24442
|
+
actions.push({
|
|
24443
|
+
type: result.plan.instructionsAction === "create" ? "write" : "append",
|
|
24444
|
+
path: result.plan.instructionsFile,
|
|
24445
|
+
note: `reduce-pipe instruction (${result.plan.instructionsAction})`
|
|
24446
|
+
});
|
|
24447
|
+
}
|
|
24448
|
+
if (result.hookPlan && result.hookPlan.action !== "present") {
|
|
24449
|
+
actions.push({
|
|
24450
|
+
type: "write",
|
|
24451
|
+
path: result.hookPlan.hooksFile,
|
|
24452
|
+
note: `Bash PostToolUse hook (${result.hookPlan.action})`
|
|
24453
|
+
});
|
|
24454
|
+
}
|
|
24455
|
+
const hookChanged = result.hookPlan !== null && result.hookPlan.action !== "present";
|
|
24456
|
+
return {
|
|
24457
|
+
harness: "codex",
|
|
24458
|
+
dryRun: !apply,
|
|
24459
|
+
changed: result.plan.changed || hookChanged,
|
|
24460
|
+
alreadyInstalled: !result.plan.changed && !hookChanged,
|
|
24461
|
+
applied: result.applied,
|
|
24462
|
+
actions,
|
|
24463
|
+
details: { hook: result.hookPlan ? result.hookPlan.action : "skipped" }
|
|
24464
|
+
};
|
|
24465
|
+
}
|
|
24466
|
+
function claudeInstallJson(result, apply) {
|
|
24467
|
+
const actions = result.plan.skills.map((s) => ({
|
|
24468
|
+
type: s.present ? "none" : "copy",
|
|
24469
|
+
path: s.to,
|
|
24470
|
+
from: s.from,
|
|
24471
|
+
note: s.present ? "already present" : "copy skill"
|
|
24472
|
+
}));
|
|
24473
|
+
if (result.plan.settingsAction !== "present" && result.plan.settingsAction !== "skip") {
|
|
24474
|
+
actions.push({
|
|
24475
|
+
type: result.plan.settingsAction === "create" ? "write" : "write",
|
|
24476
|
+
path: result.plan.settingsFile,
|
|
24477
|
+
note: `PostToolUse Bash hook (${result.plan.settingsAction})`
|
|
24478
|
+
});
|
|
24479
|
+
}
|
|
24480
|
+
if (result.plan.instructionsAction !== "present" && result.plan.instructionsAction !== "skip") {
|
|
24481
|
+
actions.push({
|
|
24482
|
+
type: result.plan.instructionsAction === "create" ? "write" : "append",
|
|
24483
|
+
path: result.plan.instructionsFile,
|
|
24484
|
+
note: `reduce-pipe instruction (${result.plan.instructionsAction})`
|
|
24485
|
+
});
|
|
24486
|
+
}
|
|
24487
|
+
return {
|
|
24488
|
+
harness: "claude",
|
|
24489
|
+
dryRun: !apply,
|
|
24490
|
+
changed: result.plan.changed,
|
|
24491
|
+
alreadyInstalled: !result.plan.changed,
|
|
24492
|
+
applied: result.applied,
|
|
24493
|
+
actions,
|
|
24494
|
+
details: {
|
|
24495
|
+
settingsAction: result.plan.settingsAction,
|
|
24496
|
+
instructionsAction: result.plan.instructionsAction
|
|
24497
|
+
}
|
|
24498
|
+
};
|
|
24499
|
+
}
|
|
24500
|
+
function hermesInstallJson(result, apply) {
|
|
24501
|
+
const actions = [
|
|
24502
|
+
{
|
|
24503
|
+
type: result.plan.alreadyInstalled ? "none" : "copy",
|
|
24504
|
+
path: result.plan.pluginDest,
|
|
24505
|
+
from: result.plan.pluginSource,
|
|
24506
|
+
note: result.plan.alreadyInstalled ? "already installed" : "copy Hermes plugin bundle"
|
|
24507
|
+
}
|
|
24508
|
+
];
|
|
24509
|
+
return {
|
|
24510
|
+
harness: "hermes",
|
|
24511
|
+
dryRun: !apply,
|
|
24512
|
+
changed: !result.plan.alreadyInstalled,
|
|
24513
|
+
alreadyInstalled: result.plan.alreadyInstalled,
|
|
24514
|
+
applied: result.applied,
|
|
24515
|
+
actions,
|
|
24516
|
+
details: { enabled: result.enabled, enableMessage: result.enableMessage ?? null }
|
|
24517
|
+
};
|
|
24518
|
+
}
|
|
24519
|
+
function piInstallJson(result, apply) {
|
|
24520
|
+
const actions = [
|
|
24521
|
+
{
|
|
24522
|
+
type: result.plan.alreadyInstalled ? "none" : "copy",
|
|
24523
|
+
path: result.plan.extensionDest,
|
|
24524
|
+
from: result.plan.extensionSource,
|
|
24525
|
+
note: result.plan.alreadyInstalled ? "already installed" : "copy Pi extension bundle"
|
|
24526
|
+
}
|
|
24527
|
+
];
|
|
24528
|
+
return {
|
|
24529
|
+
harness: "pi",
|
|
24530
|
+
dryRun: !apply,
|
|
24531
|
+
changed: !result.plan.alreadyInstalled,
|
|
24532
|
+
alreadyInstalled: result.plan.alreadyInstalled,
|
|
24533
|
+
applied: result.applied,
|
|
24534
|
+
actions
|
|
24535
|
+
};
|
|
24536
|
+
}
|
|
24537
|
+
function codexGlobalHookJson(result, apply) {
|
|
24538
|
+
const actions = [
|
|
24539
|
+
{
|
|
24540
|
+
type: result.hookPlan.action === "present" ? "none" : "write",
|
|
24541
|
+
path: result.hookPlan.hooksFile,
|
|
24542
|
+
note: `global Bash PostToolUse hook (${result.hookPlan.action})`
|
|
24543
|
+
}
|
|
24544
|
+
];
|
|
24545
|
+
return {
|
|
24546
|
+
harness: "codex",
|
|
24547
|
+
dryRun: !apply,
|
|
24548
|
+
changed: result.hookPlan.action !== "present",
|
|
24549
|
+
alreadyInstalled: result.hookPlan.action === "present",
|
|
24550
|
+
applied: result.applied,
|
|
24551
|
+
actions,
|
|
24552
|
+
details: { scope: "global" }
|
|
24553
|
+
};
|
|
24554
|
+
}
|
|
24555
|
+
|
|
23923
24556
|
// src/cli.ts
|
|
23924
24557
|
var HELP = `harnesstrim \u2014 one token policy for coding harnesses
|
|
23925
24558
|
|
|
@@ -23928,16 +24561,25 @@ Usage:
|
|
|
23928
24561
|
harnesstrim install opencode [dir] Wire the adapter into opencode.json (dry-run)
|
|
23929
24562
|
--apply Actually write the change
|
|
23930
24563
|
--preset <name> Bake a policy preset's adapter config in
|
|
24564
|
+
--mode <m> Override mode: active|dryrun|off
|
|
24565
|
+
--min-length <n> Override the reduction threshold (chars)
|
|
24566
|
+
--tools <list> Confine reduction to a subset of tool families
|
|
23931
24567
|
harnesstrim install codex [dir] Install skills + AGENTS.md reduction guidance (dry-run)
|
|
23932
24568
|
--apply Actually write the change
|
|
23933
24569
|
--hook Also install the experimental Bash PostToolUse hook
|
|
24570
|
+
--no-instructions Skills only (no AGENTS.md reduce-pipe instruction)
|
|
23934
24571
|
--global With --hook, install it once in ~/.codex (no project files)
|
|
23935
24572
|
harnesstrim install claude [dir] Install skills + PostToolUse reducer hook (dry-run)
|
|
23936
24573
|
--apply Actually write the change
|
|
24574
|
+
--no-hook Skills only (no PostToolUse hook)
|
|
24575
|
+
--no-instructions Skills only (no CLAUDE.md reduce-pipe instruction)
|
|
23937
24576
|
harnesstrim install hermes [dir] Install Hermes plugin (dry-run)
|
|
23938
24577
|
--apply Actually write the change
|
|
23939
24578
|
harnesstrim install pi [dir] Install Pi tool_result extension (dry-run)
|
|
23940
24579
|
--apply Actually write the change
|
|
24580
|
+
harnesstrim uninstall <harness> [dir] Remove only what install wrote (dry-run)
|
|
24581
|
+
--apply Actually write the change
|
|
24582
|
+
harnesstrim capabilities Print machine-readable per-harness capabilities (JSON)
|
|
23941
24583
|
harnesstrim hook claude [--metrics <path>]
|
|
23942
24584
|
PostToolUse hook runtime; --metrics records a TrimEvent per reduction
|
|
23943
24585
|
harnesstrim hook codex [--metrics <path>]
|
|
@@ -23953,8 +24595,11 @@ Usage:
|
|
|
23953
24595
|
harnesstrim bench Run the Tier A reducer micro-benchmark
|
|
23954
24596
|
harnesstrim --version Print the installed version
|
|
23955
24597
|
|
|
24598
|
+
Flags:
|
|
24599
|
+
--json Print machine-readable JSON (doctor, metrics, install, uninstall)
|
|
24600
|
+
|
|
23956
24601
|
Notes:
|
|
23957
|
-
- install
|
|
24602
|
+
- install and uninstall are dry-run by default; nothing is written without --apply.
|
|
23958
24603
|
- dir defaults to the current directory; metrics path defaults to ${DEFAULT_METRICS_PATH}.
|
|
23959
24604
|
- reduce reads stdin and writes slimmed output to stdout, e.g. npm test 2>&1 | harnesstrim reduce`;
|
|
23960
24605
|
async function main(argv) {
|
|
@@ -23971,7 +24616,12 @@ async function main(argv) {
|
|
|
23971
24616
|
log: { type: "string" },
|
|
23972
24617
|
metrics: { type: "string" },
|
|
23973
24618
|
hook: { type: "boolean" },
|
|
23974
|
-
global: { type: "boolean" }
|
|
24619
|
+
global: { type: "boolean" },
|
|
24620
|
+
"no-hook": { type: "boolean" },
|
|
24621
|
+
"no-instructions": { type: "boolean" },
|
|
24622
|
+
mode: { type: "string" },
|
|
24623
|
+
tools: { type: "string" },
|
|
24624
|
+
json: { type: "boolean" }
|
|
23975
24625
|
}
|
|
23976
24626
|
});
|
|
23977
24627
|
const [command, ...rest] = positionals;
|
|
@@ -23986,16 +24636,34 @@ async function main(argv) {
|
|
|
23986
24636
|
switch (command) {
|
|
23987
24637
|
case "doctor": {
|
|
23988
24638
|
const dir = rest[0] ?? process.cwd();
|
|
23989
|
-
|
|
24639
|
+
const report = inspect(dir);
|
|
24640
|
+
if (values.json) {
|
|
24641
|
+
console.log(doctorJson(report));
|
|
24642
|
+
} else {
|
|
24643
|
+
console.log(renderDoctor(report));
|
|
24644
|
+
}
|
|
23990
24645
|
return 0;
|
|
23991
24646
|
}
|
|
23992
24647
|
case "install": {
|
|
23993
24648
|
const target = rest[0];
|
|
23994
24649
|
const dir = rest[1] ?? (target === "hermes" ? os4.homedir() : process.cwd());
|
|
23995
24650
|
const apply = values.apply === true;
|
|
24651
|
+
const asJson = values.json === true;
|
|
23996
24652
|
if (target === "opencode") {
|
|
23997
|
-
const
|
|
23998
|
-
|
|
24653
|
+
const mode = parseModeFlag(values.mode);
|
|
24654
|
+
if (mode === void 0 && values.mode !== void 0) {
|
|
24655
|
+
console.error(`Invalid --mode: ${values.mode} (expected active, dryrun, or off).`);
|
|
24656
|
+
return 1;
|
|
24657
|
+
}
|
|
24658
|
+
const minLength = values["min-length"] !== void 0 ? Number(values["min-length"]) : void 0;
|
|
24659
|
+
if (minLength !== void 0 && !Number.isFinite(minLength)) {
|
|
24660
|
+
console.error(`Invalid --min-length: ${values["min-length"]}`);
|
|
24661
|
+
return 1;
|
|
24662
|
+
}
|
|
24663
|
+
const tools = values.tools !== void 0 ? splitTools(values.tools) : void 0;
|
|
24664
|
+
const result = runInstallOpencode(dir, apply, values.preset, true, { mode, minLength, tools });
|
|
24665
|
+
if (asJson) console.log(JSON.stringify(opencodeInstallJson(result, apply), null, 2));
|
|
24666
|
+
else console.log(renderInstall(result, apply));
|
|
23999
24667
|
return 0;
|
|
24000
24668
|
}
|
|
24001
24669
|
if (target === "codex") {
|
|
@@ -24004,27 +24672,63 @@ async function main(argv) {
|
|
|
24004
24672
|
console.error("`harnesstrim install codex --global` requires `--hook`.");
|
|
24005
24673
|
return 1;
|
|
24006
24674
|
}
|
|
24007
|
-
|
|
24675
|
+
const result2 = runInstallCodexGlobalHook(path16.join(os4.homedir(), ".codex"), apply);
|
|
24676
|
+
if (asJson) console.log(JSON.stringify(codexGlobalHookJson(result2, apply), null, 2));
|
|
24677
|
+
else console.log(renderCodexGlobalHookInstall(result2, apply));
|
|
24008
24678
|
return 0;
|
|
24009
24679
|
}
|
|
24010
|
-
|
|
24680
|
+
const result = runInstallCodex(dir, apply, values.hook === true, {
|
|
24681
|
+
includeInstructions: values["no-instructions"] !== true
|
|
24682
|
+
});
|
|
24683
|
+
if (asJson) console.log(JSON.stringify(codexInstallJson(result, apply), null, 2));
|
|
24684
|
+
else console.log(renderCodexInstall(result, apply));
|
|
24011
24685
|
return 0;
|
|
24012
24686
|
}
|
|
24013
24687
|
if (target === "claude") {
|
|
24014
|
-
|
|
24688
|
+
const result = runInstallClaude(dir, apply, {
|
|
24689
|
+
includeHook: values["no-hook"] !== true,
|
|
24690
|
+
includeInstructions: values["no-instructions"] !== true
|
|
24691
|
+
});
|
|
24692
|
+
if (asJson) console.log(JSON.stringify(claudeInstallJson(result, apply), null, 2));
|
|
24693
|
+
else console.log(renderClaudeInstall(result, apply));
|
|
24015
24694
|
return 0;
|
|
24016
24695
|
}
|
|
24017
24696
|
if (target === "hermes") {
|
|
24018
|
-
|
|
24697
|
+
const result = runInstallHermes(dir, apply);
|
|
24698
|
+
if (asJson) console.log(JSON.stringify(hermesInstallJson(result, apply), null, 2));
|
|
24699
|
+
else console.log(renderHermesInstall(result, apply));
|
|
24019
24700
|
return 0;
|
|
24020
24701
|
}
|
|
24021
24702
|
if (target === "pi") {
|
|
24022
|
-
|
|
24703
|
+
const result = runInstallPi(dir, apply);
|
|
24704
|
+
if (asJson) console.log(JSON.stringify(piInstallJson(result, apply), null, 2));
|
|
24705
|
+
else console.log(renderPiInstall(result, apply));
|
|
24023
24706
|
return 0;
|
|
24024
24707
|
}
|
|
24025
24708
|
console.error(`Unknown install target: ${target ?? "(none)"}. Supported: opencode, codex, claude, hermes, pi.`);
|
|
24026
24709
|
return 1;
|
|
24027
24710
|
}
|
|
24711
|
+
case "uninstall": {
|
|
24712
|
+
const target = rest[0];
|
|
24713
|
+
const dir = rest[1] ?? (target === "hermes" ? os4.homedir() : process.cwd());
|
|
24714
|
+
const apply = values.apply === true;
|
|
24715
|
+
try {
|
|
24716
|
+
const result = runUninstall(target, dir, apply);
|
|
24717
|
+
if (values.json) {
|
|
24718
|
+
console.log(JSON.stringify(result, null, 2));
|
|
24719
|
+
} else {
|
|
24720
|
+
console.log(renderUninstall(result, apply));
|
|
24721
|
+
}
|
|
24722
|
+
} catch (err) {
|
|
24723
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
24724
|
+
return 1;
|
|
24725
|
+
}
|
|
24726
|
+
return 0;
|
|
24727
|
+
}
|
|
24728
|
+
case "capabilities": {
|
|
24729
|
+
console.log(JSON.stringify(getCapabilities(package_default.version), null, 2));
|
|
24730
|
+
return 0;
|
|
24731
|
+
}
|
|
24028
24732
|
case "hook": {
|
|
24029
24733
|
const which = rest[0];
|
|
24030
24734
|
if (which !== "claude" && which !== "codex") {
|
|
@@ -24032,22 +24736,45 @@ async function main(argv) {
|
|
|
24032
24736
|
return 1;
|
|
24033
24737
|
}
|
|
24034
24738
|
const input = await readStdin();
|
|
24035
|
-
const { response, event } = which === "claude" ? reduceClaudePayload(input) : reduceCodexPayload(input);
|
|
24739
|
+
const { response, event, attempt } = which === "claude" ? reduceClaudePayload(input) : reduceCodexPayload(input);
|
|
24036
24740
|
process.stdout.write(response);
|
|
24037
|
-
if (values.metrics
|
|
24741
|
+
if (values.metrics) {
|
|
24038
24742
|
try {
|
|
24039
|
-
const p =
|
|
24040
|
-
|
|
24041
|
-
|
|
24042
|
-
|
|
24043
|
-
|
|
24044
|
-
|
|
24743
|
+
const p = path16.resolve(values.metrics);
|
|
24744
|
+
fs12.mkdirSync(path16.dirname(p), { recursive: true });
|
|
24745
|
+
if (event) {
|
|
24746
|
+
fs12.appendFileSync(
|
|
24747
|
+
p,
|
|
24748
|
+
JSON.stringify(
|
|
24749
|
+
makeTrimEvent({
|
|
24750
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24751
|
+
harness: which,
|
|
24752
|
+
...event
|
|
24753
|
+
})
|
|
24754
|
+
) + "\n"
|
|
24755
|
+
);
|
|
24756
|
+
} else if (attempt && trackPassThrough()) {
|
|
24757
|
+
fs12.appendFileSync(
|
|
24758
|
+
p,
|
|
24759
|
+
JSON.stringify(
|
|
24760
|
+
makeTrimEvent({
|
|
24761
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24762
|
+
harness: which,
|
|
24763
|
+
tool: attempt.tool,
|
|
24764
|
+
reducer: null,
|
|
24765
|
+
beforeChars: attempt.beforeChars,
|
|
24766
|
+
afterChars: attempt.beforeChars,
|
|
24767
|
+
changed: false
|
|
24768
|
+
})
|
|
24769
|
+
) + "\n"
|
|
24770
|
+
);
|
|
24771
|
+
}
|
|
24045
24772
|
} catch {
|
|
24046
24773
|
}
|
|
24047
24774
|
}
|
|
24048
24775
|
if (values.log) {
|
|
24049
24776
|
try {
|
|
24050
|
-
|
|
24777
|
+
fs12.appendFileSync(
|
|
24051
24778
|
values.log,
|
|
24052
24779
|
JSON.stringify({ inputChars: input.length, changed: response !== "{}", responseChars: response.length }) + "\n"
|
|
24053
24780
|
);
|
|
@@ -24076,8 +24803,13 @@ async function main(argv) {
|
|
|
24076
24803
|
return 1;
|
|
24077
24804
|
}
|
|
24078
24805
|
case "metrics": {
|
|
24079
|
-
const
|
|
24080
|
-
|
|
24806
|
+
const path17 = rest[0] ?? DEFAULT_METRICS_PATH;
|
|
24807
|
+
const result = loadMetrics(path17);
|
|
24808
|
+
if (values.json) {
|
|
24809
|
+
console.log(metricsJson(result));
|
|
24810
|
+
} else {
|
|
24811
|
+
console.log(renderMetrics(result));
|
|
24812
|
+
}
|
|
24081
24813
|
return 0;
|
|
24082
24814
|
}
|
|
24083
24815
|
case "reduce": {
|
|
@@ -24090,21 +24822,40 @@ async function main(argv) {
|
|
|
24090
24822
|
const input = await readStdin();
|
|
24091
24823
|
const result = reducePipe(input, minLength);
|
|
24092
24824
|
process.stdout.write(result.output);
|
|
24093
|
-
if (values.metrics
|
|
24825
|
+
if (values.metrics) {
|
|
24094
24826
|
try {
|
|
24095
|
-
const p =
|
|
24096
|
-
|
|
24097
|
-
|
|
24098
|
-
|
|
24099
|
-
|
|
24100
|
-
|
|
24101
|
-
|
|
24102
|
-
|
|
24103
|
-
|
|
24104
|
-
|
|
24105
|
-
|
|
24106
|
-
|
|
24107
|
-
|
|
24827
|
+
const p = path16.resolve(values.metrics);
|
|
24828
|
+
fs12.mkdirSync(path16.dirname(p), { recursive: true });
|
|
24829
|
+
if (result.changed) {
|
|
24830
|
+
fs12.appendFileSync(
|
|
24831
|
+
p,
|
|
24832
|
+
JSON.stringify(
|
|
24833
|
+
makeTrimEvent({
|
|
24834
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24835
|
+
harness: "pipe",
|
|
24836
|
+
tool: "reduce",
|
|
24837
|
+
reducer: result.reducer,
|
|
24838
|
+
beforeChars: result.beforeChars,
|
|
24839
|
+
afterChars: result.afterChars
|
|
24840
|
+
})
|
|
24841
|
+
) + "\n"
|
|
24842
|
+
);
|
|
24843
|
+
} else if (trackPassThrough() && input.length >= (minLength ?? DEFAULT_MIN_LENGTH)) {
|
|
24844
|
+
fs12.appendFileSync(
|
|
24845
|
+
p,
|
|
24846
|
+
JSON.stringify(
|
|
24847
|
+
makeTrimEvent({
|
|
24848
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24849
|
+
harness: "pipe",
|
|
24850
|
+
tool: "reduce",
|
|
24851
|
+
reducer: null,
|
|
24852
|
+
beforeChars: input.length,
|
|
24853
|
+
afterChars: input.length,
|
|
24854
|
+
changed: false
|
|
24855
|
+
})
|
|
24856
|
+
) + "\n"
|
|
24857
|
+
);
|
|
24858
|
+
}
|
|
24108
24859
|
} catch {
|
|
24109
24860
|
}
|
|
24110
24861
|
}
|
|
@@ -24144,6 +24895,16 @@ async function main(argv) {
|
|
|
24144
24895
|
return 1;
|
|
24145
24896
|
}
|
|
24146
24897
|
}
|
|
24898
|
+
function parseModeFlag(value) {
|
|
24899
|
+
return value === "active" || value === "dryrun" || value === "off" ? value : void 0;
|
|
24900
|
+
}
|
|
24901
|
+
function splitTools(value) {
|
|
24902
|
+
return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
24903
|
+
}
|
|
24904
|
+
function trackPassThrough() {
|
|
24905
|
+
const v = process.env.HARNESSTRIM_TRACK_PASSTHROUGH;
|
|
24906
|
+
return v === void 0 || v !== "0" && v !== "false";
|
|
24907
|
+
}
|
|
24147
24908
|
main(process.argv.slice(2)).then((code) => {
|
|
24148
24909
|
process.exitCode = code;
|
|
24149
24910
|
}).catch((err) => {
|