harnesstrim 0.0.5 → 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.
- package/LICENSE +21 -0
- package/assets/adapter-hermes/plugin/__init__.py +5 -0
- package/assets/adapter-pi/extension/{harnesstrim.ts → index.ts} +35 -17
- package/dist/cli.mjs +915 -105
- package/package.json +10 -12
package/dist/cli.mjs
CHANGED
|
@@ -581,6 +581,75 @@ ${input.slice(response.index)}`;
|
|
|
581
581
|
}
|
|
582
582
|
});
|
|
583
583
|
|
|
584
|
+
// ../core/src/reducers/lint-output-slim.ts
|
|
585
|
+
function isLintLine(line) {
|
|
586
|
+
return LINT_LINE_RE.test(line);
|
|
587
|
+
}
|
|
588
|
+
var MARKER_PREFIX7, LINT_LINE_RE, MAX_RULES_IN_MARKER, lintOutputSlim;
|
|
589
|
+
var init_lint_output_slim = __esm({
|
|
590
|
+
"../core/src/reducers/lint-output-slim.ts"() {
|
|
591
|
+
"use strict";
|
|
592
|
+
MARKER_PREFIX7 = "[harnesstrim:lint-output-slim]";
|
|
593
|
+
LINT_LINE_RE = /^[\w.\/\\-]+:\d+:\d+\s+(warning|error)\s+([\w@.\/-]+)/;
|
|
594
|
+
MAX_RULES_IN_MARKER = 8;
|
|
595
|
+
lintOutputSlim = {
|
|
596
|
+
name: "lint-output-slim",
|
|
597
|
+
reduce(input) {
|
|
598
|
+
const lines = input.split(/\r?\n/);
|
|
599
|
+
const out = [];
|
|
600
|
+
let droppedTotal = 0;
|
|
601
|
+
let i = 0;
|
|
602
|
+
while (i < lines.length) {
|
|
603
|
+
const line = lines[i];
|
|
604
|
+
if (!isLintLine(line)) {
|
|
605
|
+
out.push(line);
|
|
606
|
+
i++;
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
const counts = [];
|
|
610
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
611
|
+
let runEnd = i;
|
|
612
|
+
while (runEnd < lines.length && isLintLine(lines[runEnd])) {
|
|
613
|
+
const m = LINT_LINE_RE.exec(lines[runEnd]);
|
|
614
|
+
const severity = m[1] === "error" ? "error" : "warning";
|
|
615
|
+
const rule = m[2];
|
|
616
|
+
const key = `${severity}:${rule}`;
|
|
617
|
+
const existing = byKey.get(key);
|
|
618
|
+
if (existing) {
|
|
619
|
+
existing.count++;
|
|
620
|
+
} else {
|
|
621
|
+
const entry = { severity, rule, count: 1 };
|
|
622
|
+
byKey.set(key, entry);
|
|
623
|
+
counts.push(entry);
|
|
624
|
+
}
|
|
625
|
+
runEnd++;
|
|
626
|
+
}
|
|
627
|
+
const runLength = runEnd - i;
|
|
628
|
+
if (runLength >= 2) {
|
|
629
|
+
const parts = counts.slice(0, MAX_RULES_IN_MARKER).map(
|
|
630
|
+
(c) => `${c.rule} \xD7${c.count}`
|
|
631
|
+
);
|
|
632
|
+
const truncated = counts.length > MAX_RULES_IN_MARKER;
|
|
633
|
+
const suffix = truncated ? `, +${counts.length - MAX_RULES_IN_MARKER} more rule(s)` : "";
|
|
634
|
+
const severities = counts.some((c) => c.severity === "error") && counts.some((c) => c.severity === "warning") ? "error(s) and warning(s)" : counts.some((c) => c.severity === "error") ? "error(s)" : "warning(s)";
|
|
635
|
+
out.push(`${MARKER_PREFIX7} omitted ${runLength} lint line(s) (${severities}: ${parts.join(", ")}${suffix})`);
|
|
636
|
+
droppedTotal += runLength;
|
|
637
|
+
} else {
|
|
638
|
+
for (let j = i; j < runEnd; j++) out.push(lines[j]);
|
|
639
|
+
}
|
|
640
|
+
i = runEnd;
|
|
641
|
+
}
|
|
642
|
+
const output = out.join("\n");
|
|
643
|
+
return {
|
|
644
|
+
output,
|
|
645
|
+
changed: droppedTotal > 0,
|
|
646
|
+
note: droppedTotal > 0 ? `dropped ${droppedTotal} lint noise line(s)` : void 0
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
|
|
584
653
|
// ../core/src/reducers/index.ts
|
|
585
654
|
var init_reducers = __esm({
|
|
586
655
|
"../core/src/reducers/index.ts"() {
|
|
@@ -592,6 +661,7 @@ var init_reducers = __esm({
|
|
|
592
661
|
init_json_output_slim();
|
|
593
662
|
init_file_listing_slim();
|
|
594
663
|
init_cron_output_slim();
|
|
664
|
+
init_lint_output_slim();
|
|
595
665
|
}
|
|
596
666
|
});
|
|
597
667
|
|
|
@@ -600,6 +670,7 @@ function pickReducer(text) {
|
|
|
600
670
|
if (GIT_DIFF_RE.test(text)) return gitDiffSlim;
|
|
601
671
|
if (TEST_OUTPUT_RE.test(text)) return testOutputSlim;
|
|
602
672
|
if (CRON_OUTPUT_RE.test(text) && text.length >= 400) return cronOutputSlim;
|
|
673
|
+
if (LINT_OUTPUT_RE.test(text) && text.length >= 400) return lintOutputSlim;
|
|
603
674
|
if (JSON_RE.test(text) && text.length >= 400) return jsonOutputSlim;
|
|
604
675
|
if (FILE_LISTING_RE.test(text) && text.length >= 400) return fileListingSlim;
|
|
605
676
|
if (LONG_TEXT_RE.test(text) && text.length >= 1e3) return genericTextSlim;
|
|
@@ -619,7 +690,7 @@ function reduceAuto(text, minLength = DEFAULT_MIN_LENGTH) {
|
|
|
619
690
|
}
|
|
620
691
|
return { ...result, reducer: reducer.name };
|
|
621
692
|
}
|
|
622
|
-
var DEFAULT_MIN_LENGTH, GIT_DIFF_RE, TEST_OUTPUT_RE, JSON_RE, FILE_LISTING_RE, CRON_OUTPUT_RE, LONG_TEXT_RE;
|
|
693
|
+
var DEFAULT_MIN_LENGTH, GIT_DIFF_RE, TEST_OUTPUT_RE, JSON_RE, FILE_LISTING_RE, CRON_OUTPUT_RE, LINT_OUTPUT_RE, LONG_TEXT_RE;
|
|
623
694
|
var init_dispatch = __esm({
|
|
624
695
|
"../core/src/dispatch.ts"() {
|
|
625
696
|
"use strict";
|
|
@@ -629,17 +700,34 @@ var init_dispatch = __esm({
|
|
|
629
700
|
init_json_output_slim();
|
|
630
701
|
init_file_listing_slim();
|
|
631
702
|
init_cron_output_slim();
|
|
703
|
+
init_lint_output_slim();
|
|
632
704
|
DEFAULT_MIN_LENGTH = 400;
|
|
633
705
|
GIT_DIFF_RE = /^diff --git /m;
|
|
634
706
|
TEST_OUTPUT_RE = /\b\d+\s+(passed|failed)\b|^(PASS|FAIL)\s|::\w.*\b(PASSED|FAILED)\b|=+\s*(FAILURES|short test summary)/im;
|
|
635
707
|
JSON_RE = /^\s*[\[{]/m;
|
|
636
708
|
FILE_LISTING_RE = /(?:^total\s+\d+|^[\-bcdlsp][\-r][\-w][\-xs\-][\-r][\-w][\-xs\-][\-r][\-w][\-xs\-]|^\.\/(?:\.|[^.\s])|^\s*(?:├──|└──|│\s+)|^[\w.\/\-]+\.[a-zA-Z]{1,4}:\d+\|)/m;
|
|
637
709
|
CRON_OUTPUT_RE = /^# Cron Job:.*\n[\s\S]*^## Prompt\s*$[\s\S]*^## Response\s*$/m;
|
|
710
|
+
LINT_OUTPUT_RE = /^[\w.\/\\-]+:\d+:\d+\s+(?:warning|error)\s+[\w@.\/-]+\s/m;
|
|
638
711
|
LONG_TEXT_RE = /^#{1,4}\s.*\n(?:(?!^#{1,4}\s|^diff --git |^```).*\n){5,}/m;
|
|
639
712
|
}
|
|
640
713
|
});
|
|
641
714
|
|
|
642
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
|
+
}
|
|
643
731
|
function pct(before, after) {
|
|
644
732
|
if (before === 0) return 0;
|
|
645
733
|
return Math.round((1 - after / before) * 1e3) / 10;
|
|
@@ -680,7 +768,7 @@ function parseTrimEvents(jsonl) {
|
|
|
680
768
|
} catch {
|
|
681
769
|
continue;
|
|
682
770
|
}
|
|
683
|
-
if (isTrimEvent(parsed)) out.push(parsed);
|
|
771
|
+
if (isTrimEvent(parsed)) out.push(normalize(parsed));
|
|
684
772
|
}
|
|
685
773
|
return out;
|
|
686
774
|
}
|
|
@@ -689,9 +777,25 @@ function isTrimEvent(value) {
|
|
|
689
777
|
const v = value;
|
|
690
778
|
return typeof v.beforeChars === "number" && typeof v.afterChars === "number" && typeof v.tool === "string" && (typeof v.reducer === "string" || v.reducer === null);
|
|
691
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;
|
|
692
795
|
var init_trim_event = __esm({
|
|
693
796
|
"../core/src/metrics/trim-event.ts"() {
|
|
694
797
|
"use strict";
|
|
798
|
+
TRIM_EVENT_SCHEMA_VERSION = 1;
|
|
695
799
|
}
|
|
696
800
|
});
|
|
697
801
|
|
|
@@ -1166,8 +1270,8 @@ var init_parseUtil = __esm({
|
|
|
1166
1270
|
init_errors();
|
|
1167
1271
|
init_en();
|
|
1168
1272
|
makeIssue = (params) => {
|
|
1169
|
-
const { data, path:
|
|
1170
|
-
const fullPath = [...
|
|
1273
|
+
const { data, path: path17, errorMaps, issueData } = params;
|
|
1274
|
+
const fullPath = [...path17, ...issueData.path || []];
|
|
1171
1275
|
const fullIssue = {
|
|
1172
1276
|
...issueData,
|
|
1173
1277
|
path: fullPath
|
|
@@ -1475,11 +1579,11 @@ var init_types2 = __esm({
|
|
|
1475
1579
|
init_parseUtil();
|
|
1476
1580
|
init_util();
|
|
1477
1581
|
ParseInputLazyPath = class {
|
|
1478
|
-
constructor(parent, value,
|
|
1582
|
+
constructor(parent, value, path17, key) {
|
|
1479
1583
|
this._cachedPath = [];
|
|
1480
1584
|
this.parent = parent;
|
|
1481
1585
|
this.data = value;
|
|
1482
|
-
this._path =
|
|
1586
|
+
this._path = path17;
|
|
1483
1587
|
this._key = key;
|
|
1484
1588
|
}
|
|
1485
1589
|
get path() {
|
|
@@ -5060,10 +5164,10 @@ function assignProp(target, prop, value) {
|
|
|
5060
5164
|
configurable: true
|
|
5061
5165
|
});
|
|
5062
5166
|
}
|
|
5063
|
-
function getElementAtPath(obj,
|
|
5064
|
-
if (!
|
|
5167
|
+
function getElementAtPath(obj, path17) {
|
|
5168
|
+
if (!path17)
|
|
5065
5169
|
return obj;
|
|
5066
|
-
return
|
|
5170
|
+
return path17.reduce((acc, key) => acc?.[key], obj);
|
|
5067
5171
|
}
|
|
5068
5172
|
function promiseAllObject(promisesObj) {
|
|
5069
5173
|
const keys = Object.keys(promisesObj);
|
|
@@ -5312,11 +5416,11 @@ function aborted(x, startIndex = 0) {
|
|
|
5312
5416
|
}
|
|
5313
5417
|
return false;
|
|
5314
5418
|
}
|
|
5315
|
-
function prefixIssues(
|
|
5419
|
+
function prefixIssues(path17, issues) {
|
|
5316
5420
|
return issues.map((iss) => {
|
|
5317
5421
|
var _a;
|
|
5318
5422
|
(_a = iss).path ?? (_a.path = []);
|
|
5319
|
-
iss.path.unshift(
|
|
5423
|
+
iss.path.unshift(path17);
|
|
5320
5424
|
return iss;
|
|
5321
5425
|
});
|
|
5322
5426
|
}
|
|
@@ -16152,8 +16256,8 @@ var require_resolve = __commonJS({
|
|
|
16152
16256
|
}
|
|
16153
16257
|
return count;
|
|
16154
16258
|
}
|
|
16155
|
-
function getFullPath(resolver, id = "",
|
|
16156
|
-
if (
|
|
16259
|
+
function getFullPath(resolver, id = "", normalize2) {
|
|
16260
|
+
if (normalize2 !== false)
|
|
16157
16261
|
id = normalizeId(id);
|
|
16158
16262
|
const p = resolver.parse(id);
|
|
16159
16263
|
return _getFullPath(resolver, p);
|
|
@@ -17146,8 +17250,8 @@ var require_utils = __commonJS({
|
|
|
17146
17250
|
}
|
|
17147
17251
|
return ind;
|
|
17148
17252
|
}
|
|
17149
|
-
function removeDotSegments(
|
|
17150
|
-
let input =
|
|
17253
|
+
function removeDotSegments(path17) {
|
|
17254
|
+
let input = path17;
|
|
17151
17255
|
const output = [];
|
|
17152
17256
|
let nextSlash = -1;
|
|
17153
17257
|
let len = 0;
|
|
@@ -17399,8 +17503,8 @@ var require_schemes = __commonJS({
|
|
|
17399
17503
|
wsComponent.secure = void 0;
|
|
17400
17504
|
}
|
|
17401
17505
|
if (wsComponent.resourceName) {
|
|
17402
|
-
const [
|
|
17403
|
-
wsComponent.path =
|
|
17506
|
+
const [path17, query] = wsComponent.resourceName.split("?");
|
|
17507
|
+
wsComponent.path = path17 && path17 !== "/" ? path17 : void 0;
|
|
17404
17508
|
wsComponent.query = query;
|
|
17405
17509
|
wsComponent.resourceName = void 0;
|
|
17406
17510
|
}
|
|
@@ -17549,7 +17653,7 @@ var require_fast_uri = __commonJS({
|
|
|
17549
17653
|
"use strict";
|
|
17550
17654
|
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
17551
17655
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
17552
|
-
function
|
|
17656
|
+
function normalize2(uri, options) {
|
|
17553
17657
|
if (typeof uri === "string") {
|
|
17554
17658
|
uri = /** @type {T} */
|
|
17555
17659
|
normalizeString(uri, options);
|
|
@@ -17816,7 +17920,7 @@ var require_fast_uri = __commonJS({
|
|
|
17816
17920
|
}
|
|
17817
17921
|
var fastUri = {
|
|
17818
17922
|
SCHEMES,
|
|
17819
|
-
normalize,
|
|
17923
|
+
normalize: normalize2,
|
|
17820
17924
|
resolve,
|
|
17821
17925
|
resolveComponent,
|
|
17822
17926
|
equal,
|
|
@@ -20793,12 +20897,12 @@ var require_dist = __commonJS({
|
|
|
20793
20897
|
throw new Error(`Unknown format "${name}"`);
|
|
20794
20898
|
return f;
|
|
20795
20899
|
};
|
|
20796
|
-
function addFormats(ajv, list,
|
|
20900
|
+
function addFormats(ajv, list, fs13, exportName) {
|
|
20797
20901
|
var _a;
|
|
20798
20902
|
var _b;
|
|
20799
20903
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
20800
20904
|
for (const f of list)
|
|
20801
|
-
ajv.addFormat(f,
|
|
20905
|
+
ajv.addFormat(f, fs13[f]);
|
|
20802
20906
|
}
|
|
20803
20907
|
module.exports = exports = formatsPlugin;
|
|
20804
20908
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -22559,14 +22663,14 @@ __export(server_exports, {
|
|
|
22559
22663
|
runReduceTool: () => runReduceTool,
|
|
22560
22664
|
startStdioServer: () => startStdioServer
|
|
22561
22665
|
});
|
|
22562
|
-
import
|
|
22563
|
-
import
|
|
22666
|
+
import fs11 from "node:fs";
|
|
22667
|
+
import path15 from "node:path";
|
|
22564
22668
|
function createFileSink(metricsPath) {
|
|
22565
22669
|
return (event) => {
|
|
22566
22670
|
try {
|
|
22567
|
-
const p =
|
|
22568
|
-
|
|
22569
|
-
|
|
22671
|
+
const p = path15.resolve(metricsPath);
|
|
22672
|
+
fs11.mkdirSync(path15.dirname(p), { recursive: true });
|
|
22673
|
+
fs11.appendFileSync(p, JSON.stringify(event) + "\n");
|
|
22570
22674
|
} catch {
|
|
22571
22675
|
}
|
|
22572
22676
|
};
|
|
@@ -22574,14 +22678,15 @@ function createFileSink(metricsPath) {
|
|
|
22574
22678
|
function runReduceTool(text, minLength, sink = noopSink) {
|
|
22575
22679
|
const result = reduceAuto(text, minLength);
|
|
22576
22680
|
if (result.changed) {
|
|
22577
|
-
sink(
|
|
22578
|
-
|
|
22579
|
-
|
|
22580
|
-
|
|
22581
|
-
|
|
22582
|
-
|
|
22583
|
-
|
|
22584
|
-
|
|
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
|
+
);
|
|
22585
22690
|
}
|
|
22586
22691
|
return { content: [{ type: "text", text: result.output }] };
|
|
22587
22692
|
}
|
|
@@ -22625,9 +22730,9 @@ var init_server3 = __esm({
|
|
|
22625
22730
|
// src/cli.ts
|
|
22626
22731
|
init_src();
|
|
22627
22732
|
import { parseArgs } from "node:util";
|
|
22628
|
-
import
|
|
22629
|
-
import
|
|
22630
|
-
import
|
|
22733
|
+
import fs12 from "node:fs";
|
|
22734
|
+
import path16 from "node:path";
|
|
22735
|
+
import os4 from "node:os";
|
|
22631
22736
|
|
|
22632
22737
|
// src/doctor.ts
|
|
22633
22738
|
import fs from "node:fs";
|
|
@@ -22857,7 +22962,7 @@ function planOpencodeInstall(input) {
|
|
|
22857
22962
|
const changed = wrapperChanged || pkgChanged || opencodeChanged;
|
|
22858
22963
|
return { wrapperContent, packageJsonContent, opencodeJsonContent, alreadyInstalled, changed };
|
|
22859
22964
|
}
|
|
22860
|
-
function runInstallOpencode(dir, apply, presetName, installDeps = true) {
|
|
22965
|
+
function runInstallOpencode(dir, apply, presetName, installDeps = true, options = {}) {
|
|
22861
22966
|
let preset;
|
|
22862
22967
|
let adapterConfig = { ...DEFAULT_OPENCODE_ADAPTER_CONFIG };
|
|
22863
22968
|
if (presetName) {
|
|
@@ -22865,19 +22970,22 @@ function runInstallOpencode(dir, apply, presetName, installDeps = true) {
|
|
|
22865
22970
|
if (!preset) throw new Error(`Unknown preset: ${presetName}`);
|
|
22866
22971
|
adapterConfig = { ...adapterConfig, ...preset.adapter };
|
|
22867
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;
|
|
22868
22976
|
const wrapperPath = path2.join(dir, WRAPPER_REL);
|
|
22869
22977
|
const packageJsonPath = path2.join(dir, PKG_REL);
|
|
22870
22978
|
const opencodeJsonPath = path2.join(dir, OPENCODE_JSON);
|
|
22871
|
-
const
|
|
22979
|
+
const readOrNull2 = (p) => {
|
|
22872
22980
|
try {
|
|
22873
22981
|
return fs2.readFileSync(p, "utf8");
|
|
22874
22982
|
} catch {
|
|
22875
22983
|
return null;
|
|
22876
22984
|
}
|
|
22877
22985
|
};
|
|
22878
|
-
const existingWrapper =
|
|
22879
|
-
const existingPackageJson =
|
|
22880
|
-
const existingOpencodeJson =
|
|
22986
|
+
const existingWrapper = readOrNull2(wrapperPath);
|
|
22987
|
+
const existingPackageJson = readOrNull2(packageJsonPath);
|
|
22988
|
+
const existingOpencodeJson = readOrNull2(opencodeJsonPath);
|
|
22881
22989
|
const plan = planOpencodeInstall({
|
|
22882
22990
|
existingWrapper,
|
|
22883
22991
|
existingPackageJson,
|
|
@@ -22897,7 +23005,9 @@ function runInstallOpencode(dir, apply, presetName, installDeps = true) {
|
|
|
22897
23005
|
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
22898
23006
|
const res = spawnSync(npm, ["install", "--silent"], {
|
|
22899
23007
|
cwd: path2.dirname(packageJsonPath),
|
|
22900
|
-
encoding: "utf8"
|
|
23008
|
+
encoding: "utf8",
|
|
23009
|
+
input: "",
|
|
23010
|
+
timeout: 12e4
|
|
22901
23011
|
});
|
|
22902
23012
|
if (res.error || res.status !== 0) {
|
|
22903
23013
|
depsInstalled = false;
|
|
@@ -22922,6 +23032,7 @@ function runInstallOpencode(dir, apply, presetName, installDeps = true) {
|
|
|
22922
23032
|
|
|
22923
23033
|
// src/install-codex.ts
|
|
22924
23034
|
import fs5 from "node:fs";
|
|
23035
|
+
import os from "node:os";
|
|
22925
23036
|
import path6 from "node:path";
|
|
22926
23037
|
|
|
22927
23038
|
// ../adapter-codex/src/index.ts
|
|
@@ -22993,9 +23104,31 @@ function hasHarnessTrimHook(document) {
|
|
|
22993
23104
|
const post = hooks?.PostToolUse;
|
|
22994
23105
|
if (!Array.isArray(post)) return false;
|
|
22995
23106
|
return post.some(
|
|
22996
|
-
(entry) => Array.isArray(entry?.hooks) && entry.hooks.some(
|
|
23107
|
+
(entry) => Array.isArray(entry?.hooks) && entry.hooks.some(
|
|
23108
|
+
(hook) => typeof hook?.command === "string" && hook.command.includes("harnesstrim") && hook.command.includes("hook codex")
|
|
23109
|
+
)
|
|
23110
|
+
);
|
|
23111
|
+
}
|
|
23112
|
+
function hasExactHarnessTrimHookCommand(document, command) {
|
|
23113
|
+
const hooks = document.hooks;
|
|
23114
|
+
const post = hooks?.PostToolUse;
|
|
23115
|
+
return Array.isArray(post) && post.some(
|
|
23116
|
+
(entry) => Array.isArray(entry?.hooks) && entry.hooks.some((hook) => hook?.command === command)
|
|
22997
23117
|
);
|
|
22998
23118
|
}
|
|
23119
|
+
function replaceHarnessTrimHookCommand(document, command) {
|
|
23120
|
+
const hooks = document.hooks;
|
|
23121
|
+
const post = hooks?.PostToolUse;
|
|
23122
|
+
if (!Array.isArray(post)) return;
|
|
23123
|
+
for (const entry of post) {
|
|
23124
|
+
if (!Array.isArray(entry?.hooks)) continue;
|
|
23125
|
+
for (const hook of entry.hooks) {
|
|
23126
|
+
if (typeof hook?.command === "string" && hook.command.includes("harnesstrim") && hook.command.includes("hook codex")) {
|
|
23127
|
+
hook.command = command;
|
|
23128
|
+
}
|
|
23129
|
+
}
|
|
23130
|
+
}
|
|
23131
|
+
}
|
|
22999
23132
|
function planCodexHookInstall(input) {
|
|
23000
23133
|
let document = {};
|
|
23001
23134
|
let action;
|
|
@@ -23012,14 +23145,21 @@ function planCodexHookInstall(input) {
|
|
|
23012
23145
|
throw new Error(".codex/hooks.json must contain a JSON object; refusing to overwrite it.");
|
|
23013
23146
|
}
|
|
23014
23147
|
document = parsed;
|
|
23015
|
-
action = hasHarnessTrimHook(document) ? "present" : "patch";
|
|
23148
|
+
action = hasHarnessTrimHook(document) && (!input.hookCommand || hasExactHarnessTrimHookCommand(document, input.hookCommand)) ? "present" : "patch";
|
|
23016
23149
|
}
|
|
23017
23150
|
if (action === "present") {
|
|
23018
23151
|
return { hooksFile: path3.join(input.projectDir, ".codex", "hooks.json"), action, nextHooks: document };
|
|
23019
23152
|
}
|
|
23153
|
+
if (hasHarnessTrimHook(document)) {
|
|
23154
|
+
replaceHarnessTrimHookCommand(document, input.hookCommand ?? CODEX_HOOK_COMMAND);
|
|
23155
|
+
return { hooksFile: path3.join(input.projectDir, ".codex", "hooks.json"), action, nextHooks: document };
|
|
23156
|
+
}
|
|
23020
23157
|
const hooks = { ...document.hooks ?? {} };
|
|
23021
23158
|
const post = Array.isArray(hooks.PostToolUse) ? [...hooks.PostToolUse] : [];
|
|
23022
|
-
post.push({
|
|
23159
|
+
post.push({
|
|
23160
|
+
matcher: CODEX_HOOK_MATCHER,
|
|
23161
|
+
hooks: [{ type: "command", command: input.hookCommand ?? CODEX_HOOK_COMMAND }]
|
|
23162
|
+
});
|
|
23023
23163
|
hooks.PostToolUse = post;
|
|
23024
23164
|
return {
|
|
23025
23165
|
hooksFile: path3.join(input.projectDir, ".codex", "hooks.json"),
|
|
@@ -23028,6 +23168,7 @@ function planCodexHookInstall(input) {
|
|
|
23028
23168
|
};
|
|
23029
23169
|
}
|
|
23030
23170
|
function planCodexInstall(input) {
|
|
23171
|
+
const includeInstructions = input.includeInstructions ?? true;
|
|
23031
23172
|
const skillsDest = path3.join(input.projectDir, ".codex", "skills");
|
|
23032
23173
|
const existing = new Set(input.existingSkillNames);
|
|
23033
23174
|
const skills = input.skillNames.map((name) => ({
|
|
@@ -23037,7 +23178,9 @@ function planCodexInstall(input) {
|
|
|
23037
23178
|
present: existing.has(name)
|
|
23038
23179
|
}));
|
|
23039
23180
|
let instructionsAction;
|
|
23040
|
-
if (
|
|
23181
|
+
if (!includeInstructions) {
|
|
23182
|
+
instructionsAction = "skip";
|
|
23183
|
+
} else if (input.agentsMdContent === null) {
|
|
23041
23184
|
instructionsAction = "create";
|
|
23042
23185
|
} else if (input.agentsMdContent.includes(HARNESSTRIM_MARKER)) {
|
|
23043
23186
|
instructionsAction = "present";
|
|
@@ -23049,7 +23192,8 @@ function planCodexInstall(input) {
|
|
|
23049
23192
|
skills,
|
|
23050
23193
|
instructionsFile: path3.join(input.projectDir, "AGENTS.md"),
|
|
23051
23194
|
instructionsAction,
|
|
23052
|
-
instructionsSnippet: REDUCE_INSTRUCTION_SNIPPET
|
|
23195
|
+
instructionsSnippet: REDUCE_INSTRUCTION_SNIPPET,
|
|
23196
|
+
changed: skills.some((s) => !s.present) || instructionsAction !== "present" && instructionsAction !== "skip"
|
|
23053
23197
|
};
|
|
23054
23198
|
}
|
|
23055
23199
|
|
|
@@ -23094,6 +23238,13 @@ function existingSkillNames(dest) {
|
|
|
23094
23238
|
}
|
|
23095
23239
|
|
|
23096
23240
|
// src/install-codex.ts
|
|
23241
|
+
function resolveCodexHookCommand() {
|
|
23242
|
+
if (process.platform !== "win32") return void 0;
|
|
23243
|
+
const pnpmHome = process.env.PNPM_HOME ?? path6.join(process.env.LOCALAPPDATA ?? path6.join(os.homedir(), "AppData", "Local"), "pnpm");
|
|
23244
|
+
const shim = path6.join(pnpmHome, "harnesstrim.CMD");
|
|
23245
|
+
if (!fs5.existsSync(shim)) return void 0;
|
|
23246
|
+
return `"${shim}" hook codex --metrics .harnesstrim/metrics.jsonl`;
|
|
23247
|
+
}
|
|
23097
23248
|
function readHooksJson(hooksPath) {
|
|
23098
23249
|
try {
|
|
23099
23250
|
return fs5.readFileSync(hooksPath, "utf8");
|
|
@@ -23107,7 +23258,7 @@ function applyHookPlan(plan, apply) {
|
|
|
23107
23258
|
fs5.writeFileSync(plan.hooksFile, JSON.stringify(plan.nextHooks, null, 2) + "\n");
|
|
23108
23259
|
return true;
|
|
23109
23260
|
}
|
|
23110
|
-
function runInstallCodex(dir, apply, hook = false) {
|
|
23261
|
+
function runInstallCodex(dir, apply, hook = false, options = {}) {
|
|
23111
23262
|
const skillsSourceDir = resolveSkillsSourceDir();
|
|
23112
23263
|
const skillNames = listShippedSkills(skillsSourceDir);
|
|
23113
23264
|
const skillsDest = path6.join(dir, ".codex", "skills");
|
|
@@ -23123,14 +23274,16 @@ function runInstallCodex(dir, apply, hook = false) {
|
|
|
23123
23274
|
skillsSourceDir,
|
|
23124
23275
|
skillNames,
|
|
23125
23276
|
agentsMdContent,
|
|
23126
|
-
existingSkillNames: existingSkillNames(skillsDest)
|
|
23277
|
+
existingSkillNames: existingSkillNames(skillsDest),
|
|
23278
|
+
includeInstructions: options.includeInstructions
|
|
23127
23279
|
});
|
|
23128
23280
|
const hooksPath = path6.join(dir, ".codex", "hooks.json");
|
|
23129
23281
|
const hooksJsonContent = hook ? readHooksJson(hooksPath) : null;
|
|
23130
|
-
const hookPlan = hook ? planCodexHookInstall({ projectDir: dir, hooksJsonContent }) : null;
|
|
23282
|
+
const hookPlan = hook ? planCodexHookInstall({ projectDir: dir, hooksJsonContent, hookCommand: resolveCodexHookCommand() }) : null;
|
|
23131
23283
|
const copied = [];
|
|
23284
|
+
const hookChanged = hookPlan !== null && hookPlan.action !== "present";
|
|
23132
23285
|
let applied = false;
|
|
23133
|
-
if (apply) {
|
|
23286
|
+
if (apply && (plan.changed || hookChanged)) {
|
|
23134
23287
|
for (const skill of plan.skills) {
|
|
23135
23288
|
if (skill.present) continue;
|
|
23136
23289
|
fs5.cpSync(skill.from, skill.to, { recursive: true });
|
|
@@ -23141,7 +23294,7 @@ function runInstallCodex(dir, apply, hook = false) {
|
|
|
23141
23294
|
} else if (plan.instructionsAction === "append") {
|
|
23142
23295
|
fs5.appendFileSync(plan.instructionsFile, "\n\n" + plan.instructionsSnippet + "\n");
|
|
23143
23296
|
}
|
|
23144
|
-
if (hookPlan) applyHookPlan(hookPlan, true);
|
|
23297
|
+
if (hookPlan && hookChanged) applyHookPlan(hookPlan, true);
|
|
23145
23298
|
applied = true;
|
|
23146
23299
|
}
|
|
23147
23300
|
return { plan, hookPlan, applied, copied };
|
|
@@ -23152,7 +23305,8 @@ function runInstallCodexGlobalHook(codexHome, apply) {
|
|
|
23152
23305
|
// The planner expects the directory that contains .codex; for a user-level config
|
|
23153
23306
|
// the Codex home is itself that directory, so add its parent and use a normal path.
|
|
23154
23307
|
projectDir: path6.dirname(codexHome),
|
|
23155
|
-
hooksJsonContent: readHooksJson(hooksPath)
|
|
23308
|
+
hooksJsonContent: readHooksJson(hooksPath),
|
|
23309
|
+
hookCommand: resolveCodexHookCommand()
|
|
23156
23310
|
});
|
|
23157
23311
|
return { hookPlan, applied: applyHookPlan(hookPlan, apply) };
|
|
23158
23312
|
}
|
|
@@ -23236,6 +23390,8 @@ function hasHarnessTrimHook2(settings) {
|
|
|
23236
23390
|
);
|
|
23237
23391
|
}
|
|
23238
23392
|
function planClaudeInstall(input) {
|
|
23393
|
+
const includeHook = input.includeHook ?? true;
|
|
23394
|
+
const includeInstructions = input.includeInstructions ?? true;
|
|
23239
23395
|
const skillsDest = path7.join(input.projectDir, ".claude", "skills");
|
|
23240
23396
|
const existing = new Set(input.existingSkillNames);
|
|
23241
23397
|
const skills = input.skillNames.map((name) => ({
|
|
@@ -23257,8 +23413,12 @@ function planClaudeInstall(input) {
|
|
|
23257
23413
|
}
|
|
23258
23414
|
action = hasHarnessTrimHook2(settings) ? "present" : "patch";
|
|
23259
23415
|
}
|
|
23260
|
-
|
|
23261
|
-
|
|
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";
|
|
23262
23422
|
return {
|
|
23263
23423
|
skillsDest,
|
|
23264
23424
|
skills,
|
|
@@ -23267,7 +23427,8 @@ function planClaudeInstall(input) {
|
|
|
23267
23427
|
nextSettings,
|
|
23268
23428
|
instructionsFile: path7.join(input.projectDir, "CLAUDE.md"),
|
|
23269
23429
|
instructionsAction,
|
|
23270
|
-
instructionsSnippet: REDUCE_INSTRUCTION_SNIPPET2
|
|
23430
|
+
instructionsSnippet: REDUCE_INSTRUCTION_SNIPPET2,
|
|
23431
|
+
changed: skills.some((s) => !s.present) || action !== "present" && action !== "skip" || instructionsAction !== "present" && instructionsAction !== "skip"
|
|
23271
23432
|
};
|
|
23272
23433
|
}
|
|
23273
23434
|
function addHook(settings) {
|
|
@@ -23281,7 +23442,7 @@ function addHook(settings) {
|
|
|
23281
23442
|
}
|
|
23282
23443
|
|
|
23283
23444
|
// src/install-claude.ts
|
|
23284
|
-
function runInstallClaude(dir, apply) {
|
|
23445
|
+
function runInstallClaude(dir, apply, options = {}) {
|
|
23285
23446
|
const skillsSourceDir = resolveSkillsSourceDir();
|
|
23286
23447
|
const skillNames = listShippedSkills(skillsSourceDir);
|
|
23287
23448
|
const skillsDest = path8.join(dir, ".claude", "skills");
|
|
@@ -23305,17 +23466,19 @@ function runInstallClaude(dir, apply) {
|
|
|
23305
23466
|
skillNames,
|
|
23306
23467
|
settingsJsonContent,
|
|
23307
23468
|
claudeMdContent,
|
|
23308
|
-
existingSkillNames: existingSkillNames(skillsDest)
|
|
23469
|
+
existingSkillNames: existingSkillNames(skillsDest),
|
|
23470
|
+
includeHook: options.includeHook,
|
|
23471
|
+
includeInstructions: options.includeInstructions
|
|
23309
23472
|
});
|
|
23310
23473
|
const copied = [];
|
|
23311
23474
|
let applied = false;
|
|
23312
|
-
if (apply) {
|
|
23475
|
+
if (apply && plan.changed) {
|
|
23313
23476
|
for (const skill of plan.skills) {
|
|
23314
23477
|
if (skill.present) continue;
|
|
23315
23478
|
fs6.cpSync(skill.from, skill.to, { recursive: true });
|
|
23316
23479
|
copied.push(skill.name);
|
|
23317
23480
|
}
|
|
23318
|
-
if (plan.settingsAction !== "present") {
|
|
23481
|
+
if (plan.settingsAction !== "present" && plan.settingsAction !== "skip") {
|
|
23319
23482
|
fs6.mkdirSync(path8.dirname(plan.settingsFile), { recursive: true });
|
|
23320
23483
|
fs6.writeFileSync(plan.settingsFile, JSON.stringify(plan.nextSettings, null, 2) + "\n");
|
|
23321
23484
|
}
|
|
@@ -23332,12 +23495,13 @@ function runInstallClaude(dir, apply) {
|
|
|
23332
23495
|
// src/install-pi.ts
|
|
23333
23496
|
import fs7 from "node:fs";
|
|
23334
23497
|
import path10 from "node:path";
|
|
23498
|
+
import os2 from "node:os";
|
|
23335
23499
|
|
|
23336
23500
|
// ../adapter-pi/src/index.ts
|
|
23337
23501
|
import path9 from "node:path";
|
|
23338
23502
|
var PI_EXTENSION_NAME = "harnesstrim";
|
|
23339
23503
|
function planPiInstall(input) {
|
|
23340
|
-
const extensionDest = path9.join(input.installDir, ".pi", "extensions", PI_EXTENSION_NAME);
|
|
23504
|
+
const extensionDest = input.scope === "user" ? path9.join(input.installDir, ".pi", "agent", "extensions", PI_EXTENSION_NAME) : path9.join(input.installDir, ".pi", "extensions", PI_EXTENSION_NAME);
|
|
23341
23505
|
return {
|
|
23342
23506
|
extensionDest,
|
|
23343
23507
|
extensionSource: input.extensionSourceDir,
|
|
@@ -23372,23 +23536,29 @@ function markerPresent(dest) {
|
|
|
23372
23536
|
}
|
|
23373
23537
|
function runInstallPi(installDir, apply) {
|
|
23374
23538
|
const extensionSourceDir = resolvePiExtensionSourceDir();
|
|
23375
|
-
const
|
|
23539
|
+
const scope = path10.resolve(installDir) === path10.resolve(os2.homedir()) ? "user" : "project";
|
|
23540
|
+
const dest = scope === "user" ? path10.join(installDir, ".pi", "agent", "extensions", "harnesstrim") : path10.join(installDir, ".pi", "extensions", "harnesstrim");
|
|
23376
23541
|
const plan = planPiInstall({
|
|
23377
23542
|
installDir,
|
|
23378
23543
|
extensionSourceDir,
|
|
23379
23544
|
extensionDirExists: dirExists(dest),
|
|
23380
|
-
markerPresent: markerPresent(dest)
|
|
23545
|
+
markerPresent: markerPresent(dest),
|
|
23546
|
+
scope
|
|
23381
23547
|
});
|
|
23382
23548
|
const copiedFiles = [];
|
|
23383
23549
|
let applied = false;
|
|
23384
|
-
if (apply
|
|
23550
|
+
if (apply) {
|
|
23385
23551
|
fs7.mkdirSync(dest, { recursive: true });
|
|
23386
|
-
|
|
23387
|
-
|
|
23388
|
-
|
|
23389
|
-
|
|
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 });
|
|
23390
23556
|
}
|
|
23391
23557
|
}
|
|
23558
|
+
for (const entry of sourceFiles) {
|
|
23559
|
+
fs7.copyFileSync(path10.join(extensionSourceDir, entry), path10.join(dest, entry));
|
|
23560
|
+
copiedFiles.push(entry);
|
|
23561
|
+
}
|
|
23392
23562
|
fs7.writeFileSync(path10.join(dest, ".installed"), markerFileContent());
|
|
23393
23563
|
copiedFiles.push(".installed");
|
|
23394
23564
|
applied = true;
|
|
@@ -23485,8 +23655,8 @@ function runInstallHermes(installDir, apply) {
|
|
|
23485
23655
|
init_src();
|
|
23486
23656
|
import fs9 from "node:fs";
|
|
23487
23657
|
import path13 from "node:path";
|
|
23488
|
-
import
|
|
23489
|
-
var HERMES_METRICS_PATH = path13.join(
|
|
23658
|
+
import os3 from "node:os";
|
|
23659
|
+
var HERMES_METRICS_PATH = path13.join(os3.homedir(), ".hermes", "harnesstrim-metrics.jsonl");
|
|
23490
23660
|
var LOCAL_METRICS_PATH = ".harnesstrim/metrics.jsonl";
|
|
23491
23661
|
var DEFAULT_METRICS_PATH = fs9.existsSync(HERMES_METRICS_PATH) ? HERMES_METRICS_PATH : LOCAL_METRICS_PATH;
|
|
23492
23662
|
function loadMetrics(filePath) {
|
|
@@ -23502,9 +23672,46 @@ function loadMetrics(filePath) {
|
|
|
23502
23672
|
return { path: filePath, found: true, summary: summarize(parseTrimEvents(raw)) };
|
|
23503
23673
|
}
|
|
23504
23674
|
|
|
23675
|
+
// package.json
|
|
23676
|
+
var package_default = {
|
|
23677
|
+
name: "harnesstrim",
|
|
23678
|
+
version: "0.0.7",
|
|
23679
|
+
description: "HarnessTrim CLI: doctor (diagnose token waste), install adapters, run benchmarks.",
|
|
23680
|
+
license: "MIT",
|
|
23681
|
+
type: "module",
|
|
23682
|
+
bin: {
|
|
23683
|
+
harnesstrim: "./dist/cli.mjs"
|
|
23684
|
+
},
|
|
23685
|
+
files: [
|
|
23686
|
+
"dist",
|
|
23687
|
+
"assets"
|
|
23688
|
+
],
|
|
23689
|
+
publishConfig: {
|
|
23690
|
+
access: "public"
|
|
23691
|
+
},
|
|
23692
|
+
devDependencies: {
|
|
23693
|
+
"@harnesstrim/adapter-claude": "workspace:*",
|
|
23694
|
+
"@harnesstrim/adapter-codex": "workspace:*",
|
|
23695
|
+
"@harnesstrim/adapter-hermes": "workspace:*",
|
|
23696
|
+
"@harnesstrim/adapter-pi": "workspace:*",
|
|
23697
|
+
"@harnesstrim/benchmarks": "workspace:*",
|
|
23698
|
+
"@harnesstrim/core": "workspace:*",
|
|
23699
|
+
"@harnesstrim/mcp": "workspace:*",
|
|
23700
|
+
esbuild: "^0.25.0"
|
|
23701
|
+
},
|
|
23702
|
+
scripts: {
|
|
23703
|
+
build: "node build.mjs",
|
|
23704
|
+
prepare: "node build.mjs",
|
|
23705
|
+
prepack: "node build.mjs",
|
|
23706
|
+
test: 'node --test "src/**/*.test.ts"',
|
|
23707
|
+
typecheck: "tsc -p tsconfig.json"
|
|
23708
|
+
}
|
|
23709
|
+
};
|
|
23710
|
+
|
|
23505
23711
|
// src/reduce.ts
|
|
23506
23712
|
init_src();
|
|
23507
23713
|
async function readStdin() {
|
|
23714
|
+
if (process.stdin.isTTY) return "";
|
|
23508
23715
|
const chunks = [];
|
|
23509
23716
|
for await (const chunk of process.stdin) {
|
|
23510
23717
|
chunks.push(chunk);
|
|
@@ -23516,6 +23723,324 @@ function reducePipe(input, minLength) {
|
|
|
23516
23723
|
return { ...result, beforeChars: input.length, afterChars: result.output.length };
|
|
23517
23724
|
}
|
|
23518
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
|
+
|
|
23519
24044
|
// src/render.ts
|
|
23520
24045
|
var ICON = { warn: "!", info: "i", ok: "+" };
|
|
23521
24046
|
function renderDoctor(report) {
|
|
@@ -23614,7 +24139,7 @@ function renderCodexInstall(result, apply) {
|
|
|
23614
24139
|
lines.push(` ${s.name.padEnd(18)} ${state}`);
|
|
23615
24140
|
}
|
|
23616
24141
|
lines.push("");
|
|
23617
|
-
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.`;
|
|
23618
24143
|
lines.push(instr);
|
|
23619
24144
|
if (result.hookPlan) {
|
|
23620
24145
|
lines.push("");
|
|
@@ -23663,7 +24188,9 @@ function renderClaudeInstall(result, apply) {
|
|
|
23663
24188
|
lines.push(` ${s.name.padEnd(18)} ${state}`);
|
|
23664
24189
|
}
|
|
23665
24190
|
lines.push("");
|
|
23666
|
-
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") {
|
|
23667
24194
|
lines.push(`${plan.settingsFile}: PostToolUse reducer hook already present (no change).`);
|
|
23668
24195
|
} else {
|
|
23669
24196
|
lines.push(
|
|
@@ -23676,7 +24203,9 @@ function renderClaudeInstall(result, apply) {
|
|
|
23676
24203
|
}
|
|
23677
24204
|
}
|
|
23678
24205
|
lines.push("");
|
|
23679
|
-
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") {
|
|
23680
24209
|
lines.push(`${plan.instructionsFile}: reduce-pipe instruction already present (no change).`);
|
|
23681
24210
|
} else {
|
|
23682
24211
|
lines.push(
|
|
@@ -23731,7 +24260,7 @@ function renderPiInstall(result, apply) {
|
|
|
23731
24260
|
lines.push(` Copied: ${result.copiedFiles.join(", ")}`);
|
|
23732
24261
|
} else {
|
|
23733
24262
|
lines.push(` Source: ${plan.extensionSource}`);
|
|
23734
|
-
lines.push(" (
|
|
24263
|
+
lines.push(" (index.ts + .installed marker)");
|
|
23735
24264
|
}
|
|
23736
24265
|
lines.push("");
|
|
23737
24266
|
lines.push("The extension hooks Pi's `tool_result` and needs `harnesstrim` on PATH.");
|
|
@@ -23766,6 +24295,192 @@ Enable it in the adapter (telemetry: true) to record reductions.`;
|
|
|
23766
24295
|
}
|
|
23767
24296
|
return lines.join("\n");
|
|
23768
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
|
+
}
|
|
23769
24484
|
|
|
23770
24485
|
// src/cli.ts
|
|
23771
24486
|
var HELP = `harnesstrim \u2014 one token policy for coding harnesses
|
|
@@ -23775,16 +24490,25 @@ Usage:
|
|
|
23775
24490
|
harnesstrim install opencode [dir] Wire the adapter into opencode.json (dry-run)
|
|
23776
24491
|
--apply Actually write the change
|
|
23777
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
|
|
23778
24496
|
harnesstrim install codex [dir] Install skills + AGENTS.md reduction guidance (dry-run)
|
|
23779
24497
|
--apply Actually write the change
|
|
23780
24498
|
--hook Also install the experimental Bash PostToolUse hook
|
|
24499
|
+
--no-instructions Skills only (no AGENTS.md reduce-pipe instruction)
|
|
23781
24500
|
--global With --hook, install it once in ~/.codex (no project files)
|
|
23782
24501
|
harnesstrim install claude [dir] Install skills + PostToolUse reducer hook (dry-run)
|
|
23783
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)
|
|
23784
24505
|
harnesstrim install hermes [dir] Install Hermes plugin (dry-run)
|
|
23785
24506
|
--apply Actually write the change
|
|
23786
24507
|
harnesstrim install pi [dir] Install Pi tool_result extension (dry-run)
|
|
23787
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)
|
|
23788
24512
|
harnesstrim hook claude [--metrics <path>]
|
|
23789
24513
|
PostToolUse hook runtime; --metrics records a TrimEvent per reduction
|
|
23790
24514
|
harnesstrim hook codex [--metrics <path>]
|
|
@@ -23798,10 +24522,13 @@ Usage:
|
|
|
23798
24522
|
harnesstrim mcp [--metrics <path>] Start the MCP server (stdio) exposing a reduce tool;
|
|
23799
24523
|
--metrics records a TrimEvent per reduction
|
|
23800
24524
|
harnesstrim bench Run the Tier A reducer micro-benchmark
|
|
23801
|
-
harnesstrim --
|
|
24525
|
+
harnesstrim --version Print the installed version
|
|
24526
|
+
|
|
24527
|
+
Flags:
|
|
24528
|
+
--json Print machine-readable JSON (doctor, metrics, install, uninstall)
|
|
23802
24529
|
|
|
23803
24530
|
Notes:
|
|
23804
|
-
- install
|
|
24531
|
+
- install and uninstall are dry-run by default; nothing is written without --apply.
|
|
23805
24532
|
- dir defaults to the current directory; metrics path defaults to ${DEFAULT_METRICS_PATH}.
|
|
23806
24533
|
- reduce reads stdin and writes slimmed output to stdout, e.g. npm test 2>&1 | harnesstrim reduce`;
|
|
23807
24534
|
async function main(argv) {
|
|
@@ -23810,6 +24537,7 @@ async function main(argv) {
|
|
|
23810
24537
|
allowPositionals: true,
|
|
23811
24538
|
options: {
|
|
23812
24539
|
help: { type: "boolean", short: "h" },
|
|
24540
|
+
version: { type: "boolean", short: "v" },
|
|
23813
24541
|
apply: { type: "boolean" },
|
|
23814
24542
|
preset: { type: "string" },
|
|
23815
24543
|
stats: { type: "boolean" },
|
|
@@ -23817,10 +24545,19 @@ async function main(argv) {
|
|
|
23817
24545
|
log: { type: "string" },
|
|
23818
24546
|
metrics: { type: "string" },
|
|
23819
24547
|
hook: { type: "boolean" },
|
|
23820
|
-
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" }
|
|
23821
24554
|
}
|
|
23822
24555
|
});
|
|
23823
24556
|
const [command, ...rest] = positionals;
|
|
24557
|
+
if (values.version) {
|
|
24558
|
+
console.log(package_default.version);
|
|
24559
|
+
return 0;
|
|
24560
|
+
}
|
|
23824
24561
|
if (values.help || !command) {
|
|
23825
24562
|
console.log(HELP);
|
|
23826
24563
|
return 0;
|
|
@@ -23828,16 +24565,34 @@ async function main(argv) {
|
|
|
23828
24565
|
switch (command) {
|
|
23829
24566
|
case "doctor": {
|
|
23830
24567
|
const dir = rest[0] ?? process.cwd();
|
|
23831
|
-
|
|
24568
|
+
const report = inspect(dir);
|
|
24569
|
+
if (values.json) {
|
|
24570
|
+
console.log(doctorJson(report));
|
|
24571
|
+
} else {
|
|
24572
|
+
console.log(renderDoctor(report));
|
|
24573
|
+
}
|
|
23832
24574
|
return 0;
|
|
23833
24575
|
}
|
|
23834
24576
|
case "install": {
|
|
23835
24577
|
const target = rest[0];
|
|
23836
|
-
const dir = rest[1] ?? (target === "hermes" ?
|
|
24578
|
+
const dir = rest[1] ?? (target === "hermes" ? os4.homedir() : process.cwd());
|
|
23837
24579
|
const apply = values.apply === true;
|
|
24580
|
+
const asJson = values.json === true;
|
|
23838
24581
|
if (target === "opencode") {
|
|
23839
|
-
const
|
|
23840
|
-
|
|
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));
|
|
23841
24596
|
return 0;
|
|
23842
24597
|
}
|
|
23843
24598
|
if (target === "codex") {
|
|
@@ -23846,27 +24601,63 @@ async function main(argv) {
|
|
|
23846
24601
|
console.error("`harnesstrim install codex --global` requires `--hook`.");
|
|
23847
24602
|
return 1;
|
|
23848
24603
|
}
|
|
23849
|
-
|
|
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));
|
|
23850
24607
|
return 0;
|
|
23851
24608
|
}
|
|
23852
|
-
|
|
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));
|
|
23853
24614
|
return 0;
|
|
23854
24615
|
}
|
|
23855
24616
|
if (target === "claude") {
|
|
23856
|
-
|
|
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));
|
|
23857
24623
|
return 0;
|
|
23858
24624
|
}
|
|
23859
24625
|
if (target === "hermes") {
|
|
23860
|
-
|
|
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));
|
|
23861
24629
|
return 0;
|
|
23862
24630
|
}
|
|
23863
24631
|
if (target === "pi") {
|
|
23864
|
-
|
|
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));
|
|
23865
24635
|
return 0;
|
|
23866
24636
|
}
|
|
23867
24637
|
console.error(`Unknown install target: ${target ?? "(none)"}. Supported: opencode, codex, claude, hermes, pi.`);
|
|
23868
24638
|
return 1;
|
|
23869
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
|
+
}
|
|
23870
24661
|
case "hook": {
|
|
23871
24662
|
const which = rest[0];
|
|
23872
24663
|
if (which !== "claude" && which !== "codex") {
|
|
@@ -23878,18 +24669,24 @@ async function main(argv) {
|
|
|
23878
24669
|
process.stdout.write(response);
|
|
23879
24670
|
if (values.metrics && event) {
|
|
23880
24671
|
try {
|
|
23881
|
-
const p =
|
|
23882
|
-
|
|
23883
|
-
|
|
24672
|
+
const p = path16.resolve(values.metrics);
|
|
24673
|
+
fs12.mkdirSync(path16.dirname(p), { recursive: true });
|
|
24674
|
+
fs12.appendFileSync(
|
|
23884
24675
|
p,
|
|
23885
|
-
JSON.stringify(
|
|
24676
|
+
JSON.stringify(
|
|
24677
|
+
makeTrimEvent({
|
|
24678
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24679
|
+
harness: which,
|
|
24680
|
+
...event
|
|
24681
|
+
})
|
|
24682
|
+
) + "\n"
|
|
23886
24683
|
);
|
|
23887
24684
|
} catch {
|
|
23888
24685
|
}
|
|
23889
24686
|
}
|
|
23890
24687
|
if (values.log) {
|
|
23891
24688
|
try {
|
|
23892
|
-
|
|
24689
|
+
fs12.appendFileSync(
|
|
23893
24690
|
values.log,
|
|
23894
24691
|
JSON.stringify({ inputChars: input.length, changed: response !== "{}", responseChars: response.length }) + "\n"
|
|
23895
24692
|
);
|
|
@@ -23918,8 +24715,13 @@ async function main(argv) {
|
|
|
23918
24715
|
return 1;
|
|
23919
24716
|
}
|
|
23920
24717
|
case "metrics": {
|
|
23921
|
-
const
|
|
23922
|
-
|
|
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
|
+
}
|
|
23923
24725
|
return 0;
|
|
23924
24726
|
}
|
|
23925
24727
|
case "reduce": {
|
|
@@ -23934,18 +24736,20 @@ async function main(argv) {
|
|
|
23934
24736
|
process.stdout.write(result.output);
|
|
23935
24737
|
if (values.metrics && result.changed) {
|
|
23936
24738
|
try {
|
|
23937
|
-
const p =
|
|
23938
|
-
|
|
23939
|
-
|
|
24739
|
+
const p = path16.resolve(values.metrics);
|
|
24740
|
+
fs12.mkdirSync(path16.dirname(p), { recursive: true });
|
|
24741
|
+
fs12.appendFileSync(
|
|
23940
24742
|
p,
|
|
23941
|
-
JSON.stringify(
|
|
23942
|
-
|
|
23943
|
-
|
|
23944
|
-
|
|
23945
|
-
|
|
23946
|
-
|
|
23947
|
-
|
|
23948
|
-
|
|
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"
|
|
23949
24753
|
);
|
|
23950
24754
|
} catch {
|
|
23951
24755
|
}
|
|
@@ -23986,6 +24790,12 @@ async function main(argv) {
|
|
|
23986
24790
|
return 1;
|
|
23987
24791
|
}
|
|
23988
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
|
+
}
|
|
23989
24799
|
main(process.argv.slice(2)).then((code) => {
|
|
23990
24800
|
process.exitCode = code;
|
|
23991
24801
|
}).catch((err) => {
|