harnery 0.4.0 → 0.6.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/commander.d.ts +10 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +2 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +43 -29
- package/dist/commands/browse-ai.js +1 -1
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +41 -9
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/completion.js +48 -10
- package/dist/commands/cookies.js +1 -1
- package/dist/commands/decision.d.ts +4 -0
- package/dist/commands/decision.d.ts.map +1 -0
- package/dist/commands/decision.js +354 -0
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +5 -1
- package/dist/commands/fetch.js +1 -1
- package/dist/core/agents/events/consume.d.ts +25 -2
- package/dist/core/agents/events/consume.d.ts.map +1 -1
- package/dist/core/agents/events/consume.js +55 -7
- package/dist/core/agents/events/emit.d.ts +2 -1
- package/dist/core/agents/events/emit.d.ts.map +1 -1
- package/dist/core/agents/events/emit.js +2 -1
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/claim-conflict.js +10 -2
- package/dist/core/agents/state/scratch.d.ts +1 -1
- package/dist/core/agents/state/scratch.js +2 -2
- package/dist/core/hooks/cli.js +4 -10
- package/dist/core/hooks/effects/index.d.ts.map +1 -1
- package/dist/core/hooks/effects/index.js +2 -1
- package/dist/core/hooks/guard-path.d.ts +29 -0
- package/dist/core/hooks/guard-path.d.ts.map +1 -0
- package/dist/core/hooks/guard-path.js +38 -0
- package/dist/lib/agent-browser/client.js +1 -1
- package/dist/lib/browser/client.d.ts +14 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +20 -0
- package/dist/lib/browser/index.d.ts +1 -0
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/runts.d.ts +44 -0
- package/dist/lib/browser/runts.d.ts.map +1 -0
- package/dist/lib/browser/runts.js +193 -0
- package/dist/lib/completion/bash.d.ts +15 -0
- package/dist/lib/completion/bash.d.ts.map +1 -1
- package/dist/lib/completion/bash.js +35 -0
- package/dist/lib/completion/fish.d.ts +10 -0
- package/dist/lib/completion/fish.d.ts.map +1 -1
- package/dist/lib/completion/fish.js +21 -0
- package/dist/lib/completion/index.d.ts +4 -3
- package/dist/lib/completion/index.d.ts.map +1 -1
- package/dist/lib/completion/index.js +4 -3
- package/dist/lib/completion/resolve.d.ts +52 -0
- package/dist/lib/completion/resolve.d.ts.map +1 -0
- package/dist/lib/completion/resolve.js +171 -0
- package/dist/lib/completion/walk.js +1 -1
- package/dist/lib/completion/zsh.d.ts +8 -0
- package/dist/lib/completion/zsh.d.ts.map +1 -1
- package/dist/lib/completion/zsh.js +33 -0
- package/dist/lib/cookies/client.d.ts +1 -1
- package/dist/lib/cookies/client.d.ts.map +1 -1
- package/dist/lib/cookies/client.js +1 -1
- package/dist/lib/decision/index.d.ts +212 -0
- package/dist/lib/decision/index.d.ts.map +1 -0
- package/dist/lib/decision/index.js +523 -0
- package/dist/lib/docs-lint.d.ts +1 -0
- package/dist/lib/docs-lint.d.ts.map +1 -1
- package/dist/lib/docs-lint.js +55 -0
- package/dist/lib/tunnel/gate.js +1 -1
- package/package.json +3 -1
- package/src/commander.ts +12 -0
- package/src/commands/agents.ts +47 -26
- package/src/commands/browse-ai.ts +1 -1
- package/src/commands/browse.ts +63 -8
- package/src/commands/completion.ts +62 -9
- package/src/commands/cookies.ts +1 -1
- package/src/commands/decision.ts +438 -0
- package/src/commands/docs.ts +5 -1
- package/src/commands/fetch.ts +1 -1
- package/src/core/agents/events/consume.ts +65 -7
- package/src/core/agents/events/emit.ts +2 -1
- package/src/core/agents/rules/claim-conflict.ts +10 -2
- package/src/core/agents/state/scratch.ts +2 -2
- package/src/core/config.ts +1 -1
- package/src/core/hooks/cli.ts +4 -8
- package/src/core/hooks/effects/index.ts +2 -1
- package/src/core/hooks/guard-path.ts +34 -0
- package/src/lib/agent-browser/client.ts +1 -1
- package/src/lib/browser/client.ts +28 -0
- package/src/lib/browser/index.ts +4 -0
- package/src/lib/browser/runts.ts +218 -0
- package/src/lib/completion/bash.ts +36 -0
- package/src/lib/completion/fish.ts +22 -0
- package/src/lib/completion/index.ts +12 -3
- package/src/lib/completion/resolve.ts +210 -0
- package/src/lib/completion/walk.ts +1 -1
- package/src/lib/completion/zsh.ts +34 -0
- package/src/lib/cookies/client.ts +2 -2
- package/src/lib/decision/index.ts +685 -0
- package/src/lib/docs-lint.ts +49 -0
- package/src/lib/tunnel/gate.ts +1 -1
package/dist/commander.d.ts
CHANGED
|
@@ -91,6 +91,16 @@ export interface HarneryProgramContext {
|
|
|
91
91
|
* exclusions (`.claude/`, `.harnery/`, `.codex/`, `.cursor/`).
|
|
92
92
|
*/
|
|
93
93
|
extraDocsExcludedPrefixes?: readonly string[];
|
|
94
|
+
/**
|
|
95
|
+
* Filenames permitted at the host project's `docs/` root (parent repo only).
|
|
96
|
+
* When set, `harn docs lint` flags any other `.md`/`.json` file sitting
|
|
97
|
+
* loose at `docs/` root (rule `docs-root-file`) — topic docs belong in
|
|
98
|
+
* `docs/<topic>/` subdirs. Names are matched exactly (basename). When
|
|
99
|
+
* omitted or empty, the rule is a no-op, so standalone `harn` and consumers
|
|
100
|
+
* that don't opt in are unaffected. Submodule `docs/` roots are never
|
|
101
|
+
* checked (their entry tiers differ from the parent's).
|
|
102
|
+
*/
|
|
103
|
+
docsRootAllowlist?: readonly string[];
|
|
94
104
|
/**
|
|
95
105
|
* Default Host header for `tunnel up` when `--vhost` is omitted: a literal
|
|
96
106
|
* host, or a resolver evaluated at start time (e.g. read a dev stack's
|
package/dist/commander.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commander.d.ts","sourceRoot":"","sources":["../src/commander.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"commander.d.ts","sourceRoot":"","sources":["../src/commander.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,qBAAqB,CAAC;IAEhC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IAEnB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB;IACpC,0FAA0F;IAC1F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvD;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,MAAM,GAAG,SAAS,CAAC;IACzB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvE;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACjE;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,IAAI,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACxC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC3D,KAAK,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,GAAG,OAAO,GAAG,IAAI,CAAC;IACrF,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IACpE,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,eAAO,MAAM,WAAW,EAAE,WAkCzB,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,kBAAuB,GAAG,OAAO,CA4C3E"}
|
package/dist/commander.js
CHANGED
|
@@ -26,6 +26,7 @@ import { registerCompletionCommand } from "./commands/completion.js";
|
|
|
26
26
|
import { registerConfigGetCommand } from "./commands/config-get.js";
|
|
27
27
|
import { registerContextCommand } from "./commands/context.js";
|
|
28
28
|
import { registerCookiesCommand } from "./commands/cookies.js";
|
|
29
|
+
import { registerDecisionCommand } from "./commands/decision.js";
|
|
29
30
|
import { registerDeinitCommand } from "./commands/deinit.js";
|
|
30
31
|
import { registerDocsCommand } from "./commands/docs.js";
|
|
31
32
|
import { registerDoctorCommand } from "./commands/doctor.js";
|
|
@@ -110,6 +111,7 @@ export function createHarneryProgram(opts = {}) {
|
|
|
110
111
|
registerCompletionCommand(program, emit, opts.context);
|
|
111
112
|
registerContextCommand(program, emit, opts.context);
|
|
112
113
|
registerScratchCommand(program, emit);
|
|
114
|
+
registerDecisionCommand(program, emit);
|
|
113
115
|
registerTunnelCommand(program, emit, opts.context);
|
|
114
116
|
registerDocsCommand(program, emit, opts.context);
|
|
115
117
|
registerAgentsCommand(program, emit);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAuFnD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,GAAG,IAAI,CA6OpF"}
|
package/dist/commands/agents.js
CHANGED
|
@@ -16,8 +16,13 @@ import { spawnSync } from "node:child_process";
|
|
|
16
16
|
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
17
17
|
import { homedir, tmpdir } from "node:os";
|
|
18
18
|
import { basename, join, resolve } from "node:path";
|
|
19
|
+
import { readStreamTailBounded } from "../core/agents/events/consume.js";
|
|
19
20
|
import { emitCanonical, monorepoRoot, normalizeHarness, readHeartbeat, resolveOwner, resolveOwnerWithSource, } from "../core/agents/index.js";
|
|
20
21
|
import { resolveBinName } from "../core/config.js";
|
|
22
|
+
/** Cap for CLI scans of the unbounded event ledger (`trace` / `health`). Well
|
|
23
|
+
* under V8's ~512MB max string length so a `readFileSync` of the whole file can
|
|
24
|
+
* never throw; covers ample recent history for a diagnostic scan. */
|
|
25
|
+
const STREAM_SCAN_CAP_BYTES = 128 * 1024 * 1024; // 128 MiB
|
|
21
26
|
import { parsePsChainLine } from "../core/hooks/resolve/anchor.js";
|
|
22
27
|
import { buildCouncilId, buildInviteMarkdown, COUNCIL_SCHEMA_VERSION, contributorsInRound, councilBodyDir, councilsArchiveDir, deleteArchivedCouncil, effectiveSteward, findManifestByPartialId, listKnownAgents, listManifests, moveFromArchive, moveToArchive, normalizeAgentName, pendingCouncilsForMember, readArchivedManifest, readManifest, readRoundPrompts, roundDir, setCouncilSteward, writeContribution, writeManifest, writePrompt, } from "../lib/council/index.js";
|
|
23
28
|
import { displayName as displayAgentName, ensureIdentity, listIdentities, lookupById as lookupIdentityById, lookupByName as lookupIdentityByName, } from "../lib/identities/index.js";
|
|
@@ -1717,8 +1722,12 @@ function readStreamStats(root) {
|
|
|
1717
1722
|
let backlog = 0;
|
|
1718
1723
|
let seenCursor = cursor === null; // no cursor → everything is "backlog"
|
|
1719
1724
|
try {
|
|
1720
|
-
|
|
1721
|
-
|
|
1725
|
+
// Bounded tail read: the ledger grows without bound and a whole-file
|
|
1726
|
+
// readFileSync throws past V8's ~512MB string limit. lines/backlog are then
|
|
1727
|
+
// scoped to the scanned window; `bytes` (full size, via statSync above) still
|
|
1728
|
+
// reflects the true stream size for the health rollup.
|
|
1729
|
+
const { text } = readStreamTailBounded(streamPath, STREAM_SCAN_CAP_BYTES);
|
|
1730
|
+
for (const line of text.split("\n")) {
|
|
1722
1731
|
if (!line.trim())
|
|
1723
1732
|
continue;
|
|
1724
1733
|
lines++;
|
|
@@ -1835,27 +1844,32 @@ function runTrace(name, opts) {
|
|
|
1835
1844
|
const sinceMs = opts.since ? Date.now() - (parseWindowSecs(opts.since) ?? 0) * 1000 : 0;
|
|
1836
1845
|
const limit = Math.max(1, Number.parseInt(opts.limit, 10) || 200);
|
|
1837
1846
|
const candidateIds = targetId.includes("\x00") ? targetId.split("\x00") : [targetId];
|
|
1838
|
-
// Scan the
|
|
1847
|
+
// Scan the stream tail once, bucket events by instance_id for the candidates.
|
|
1848
|
+
// Bounded read: the ledger grows without bound and a whole-file readFileSync
|
|
1849
|
+
// throws past V8's ~512MB string limit. A trace of an agent whose events all
|
|
1850
|
+
// predate the window is reported as truncated rather than crashing.
|
|
1839
1851
|
const streamPath = resolve(root, ".harnery", "events.ndjson");
|
|
1840
1852
|
const byId = new Map();
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1853
|
+
const { text: streamText, truncated: streamTruncated } = readStreamTailBounded(streamPath, STREAM_SCAN_CAP_BYTES);
|
|
1854
|
+
for (const line of streamText.split("\n")) {
|
|
1855
|
+
if (!line)
|
|
1856
|
+
continue;
|
|
1857
|
+
try {
|
|
1858
|
+
const ev = JSON.parse(line);
|
|
1859
|
+
if (!ev.instance_id || !candidateIds.includes(ev.instance_id))
|
|
1844
1860
|
continue;
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
continue;
|
|
1851
|
-
const arr = byId.get(ev.instance_id) ?? [];
|
|
1852
|
-
arr.push(ev);
|
|
1853
|
-
byId.set(ev.instance_id, arr);
|
|
1854
|
-
}
|
|
1855
|
-
catch {
|
|
1856
|
-
/* skip */
|
|
1857
|
-
}
|
|
1861
|
+
if (sinceMs && Date.parse(ev.ts) < sinceMs)
|
|
1862
|
+
continue;
|
|
1863
|
+
const arr = byId.get(ev.instance_id) ?? [];
|
|
1864
|
+
arr.push(ev);
|
|
1865
|
+
byId.set(ev.instance_id, arr);
|
|
1858
1866
|
}
|
|
1867
|
+
catch {
|
|
1868
|
+
/* skip */
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
if (streamTruncated) {
|
|
1872
|
+
process.stderr.write(`note: event ledger exceeds ${Math.round(STREAM_SCAN_CAP_BYTES / 1024 / 1024)}MB; traced only the most recent window (older events omitted)\n`);
|
|
1859
1873
|
}
|
|
1860
1874
|
// If the name mapped to multiple instances, trace the one with the latest event.
|
|
1861
1875
|
let resolvedId = candidateIds[0];
|
|
@@ -2208,7 +2222,7 @@ function renderHealthBox(report) {
|
|
|
2208
2222
|
];
|
|
2209
2223
|
const localTime = formatLocalTime(new Date(report.generated_at));
|
|
2210
2224
|
const title = `Coord Health (${localTime})`;
|
|
2211
|
-
process.stdout.write(`${formatBox(title, rows)}\n`); // lint-ok-emission: chat-paste path; mirrors runStatus's direct write so the box surfaces in both TTY +
|
|
2225
|
+
process.stdout.write(`${formatBox(title, rows)}\n`); // lint-ok-emission: chat-paste path; mirrors runStatus's direct write so the box surfaces in both TTY + harn-session-teed contexts
|
|
2212
2226
|
if (report.anomalies.length > 0) {
|
|
2213
2227
|
process.stdout.write("\n"); // lint-ok-emission: same chat-paste path
|
|
2214
2228
|
for (const a of report.anomalies) {
|
|
@@ -2245,7 +2259,7 @@ function runHarnessProbe(id, opts) {
|
|
|
2245
2259
|
// and `resolveOwner` here, so the probe reports exactly what the live hot
|
|
2246
2260
|
// path resolves.
|
|
2247
2261
|
const anchorTokens = new Set(["claude", "claude-code", "cursor", "codex"]);
|
|
2248
|
-
const override = process.env.
|
|
2262
|
+
const override = process.env.HARNERY_AGENT_COORD_TEST_ANCHOR_PID;
|
|
2249
2263
|
let anchorPid = override && Number(override) > 0 ? override : "";
|
|
2250
2264
|
const chainParts = [];
|
|
2251
2265
|
let walkPid = process.pid;
|
|
@@ -2348,12 +2362,12 @@ function runHarnessProbe(id, opts) {
|
|
|
2348
2362
|
* dispatcher in an isolated sandbox.
|
|
2349
2363
|
*
|
|
2350
2364
|
* Sandbox isolation strategy:
|
|
2351
|
-
* - mkdtempSync(tmpdir(), "
|
|
2365
|
+
* - mkdtempSync(tmpdir(), "harn-harness-probe-") creates a non-git tmp dir.
|
|
2352
2366
|
* - The dispatcher's coord-root resolution falls back to
|
|
2353
|
-
* `
|
|
2367
|
+
* `HARNERY_COORD_ROOT_OVERRIDE` when git rev-parse fails. We set it to the sandbox.
|
|
2354
2368
|
* - We rewrite the payload's `cwd` field (Cursor cds to it) to the sandbox,
|
|
2355
2369
|
* so real `.harnery/` never gets touched.
|
|
2356
|
-
* - We set `
|
|
2370
|
+
* - We set `HARNERY_AGENT_COORD_OFF=0` explicitly so any user-side off-switch in
|
|
2357
2371
|
* the environment doesn't mask adapter crashes.
|
|
2358
2372
|
*
|
|
2359
2373
|
* Sample shape: probe-meta wrapped (`_probe_meta.event` + `.payload`) OR bare
|
|
@@ -2420,7 +2434,7 @@ function replayHarnessSamples(harness, root, relativeSampleDir, filter) {
|
|
|
2420
2434
|
StopFailure: "stop-failure",
|
|
2421
2435
|
};
|
|
2422
2436
|
const harnessFlag = harness === "claude_code" ? "claude-code" : harness;
|
|
2423
|
-
const sandbox = mkdtempSync(join(tmpdir(), "
|
|
2437
|
+
const sandbox = mkdtempSync(join(tmpdir(), "harn-harness-probe-"));
|
|
2424
2438
|
const results = [];
|
|
2425
2439
|
try {
|
|
2426
2440
|
for (const file of fixtures) {
|
|
@@ -2464,10 +2478,10 @@ function replayHarnessSamples(harness, root, relativeSampleDir, filter) {
|
|
|
2464
2478
|
timeout: 10_000,
|
|
2465
2479
|
env: {
|
|
2466
2480
|
...process.env,
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2481
|
+
HARNERY_COORD_ROOT_OVERRIDE: sandbox,
|
|
2482
|
+
HARNERY_AGENT_COORD_HARNESS: harness,
|
|
2483
|
+
HARNERY_AGENT_COORD_PLATFORM: harness,
|
|
2484
|
+
HARNERY_AGENT_COORD_OFF: "0",
|
|
2471
2485
|
},
|
|
2472
2486
|
});
|
|
2473
2487
|
const exit = dispatch.status ?? -1;
|
|
@@ -62,7 +62,7 @@ export function registerBrowseAiCommand(program, emitParam) {
|
|
|
62
62
|
function runBrowseAi(url, opts) {
|
|
63
63
|
const jar = opts.cookies === false
|
|
64
64
|
? null
|
|
65
|
-
: new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "
|
|
65
|
+
: new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "harn-browse-ai" });
|
|
66
66
|
const ab = new AgentBrowser({
|
|
67
67
|
jar,
|
|
68
68
|
timeoutMs: Number.parseInt(opts.timeout, 10),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browse.d.ts","sourceRoot":"","sources":["../../src/commands/browse.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"browse.d.ts","sourceRoot":"","sources":["../../src/commands/browse.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAiH1E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CA0KN"}
|
package/dist/commands/browse.js
CHANGED
|
@@ -92,6 +92,14 @@ export function registerBrowseCommand(program, emitParam, context) {
|
|
|
92
92
|
"Catches the class of mobile-layout bug where a nav/table overflows the viewport edge.")
|
|
93
93
|
.option("--check-overflow-fail", "Exit non-zero if --check-overflow detects horizontal overflow.")
|
|
94
94
|
.option("--no-check-overflow-annotate", "Skip drawing overflow annotations on the screenshot (JSON still emitted).")
|
|
95
|
+
.option("--check-runts [selector]", "Scan text blocks for runts (a single word alone on a block's last visual line) " +
|
|
96
|
+
"by counting words on the last line via per-word Range rects — width thresholds " +
|
|
97
|
+
"miss narrow-column runts. Optional selector scopes the sweep (default: whole body). " +
|
|
98
|
+
"Atomic tokens (URLs, emails, phone numbers) are excluded. Reports hits in the JSON " +
|
|
99
|
+
"envelope under `runts` and annotates them on the screenshot.")
|
|
100
|
+
.option("--check-runts-min-chars <n>", "Minimum block text length to scan (default 40; smaller labels can't meaningfully wrap).", "40")
|
|
101
|
+
.option("--check-runts-fail", "Exit non-zero if any runt is detected.")
|
|
102
|
+
.option("--no-check-runts-annotate", "Skip drawing runt boxes on the screenshot (JSON still emitted).")
|
|
95
103
|
.option("--baseline <name>", "Save the captured screenshot as a named baseline at " +
|
|
96
104
|
"~/.cache/harnery/visual-baselines/<name>.png. Use --diff <name> later to " +
|
|
97
105
|
"compare future captures against it (visual-regression check).")
|
|
@@ -117,7 +125,7 @@ async function runBrowse(url, opts, context) {
|
|
|
117
125
|
// Commander: `--no-cookies` turns `opts.cookies` into `false`. Default is `true`.
|
|
118
126
|
const jar = opts.cookies === false
|
|
119
127
|
? null
|
|
120
|
-
: new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "
|
|
128
|
+
: new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "harn-browse" });
|
|
121
129
|
const headed = opts.login || opts.headed;
|
|
122
130
|
const viewport = parseViewport(opts.viewport ?? "desktop");
|
|
123
131
|
const browser = new Browser({
|
|
@@ -165,7 +173,7 @@ async function runBrowse(url, opts, context) {
|
|
|
165
173
|
visibility = await browser.checkVisibility(opts.checkVisible, {
|
|
166
174
|
sampleGrid: Number.parseInt(opts.checkVisibleSampleGrid ?? "3", 10),
|
|
167
175
|
});
|
|
168
|
-
if (opts.
|
|
176
|
+
if (opts.checkVisibleAnnotate !== false) {
|
|
169
177
|
await browser.annotateVisibility(visibility);
|
|
170
178
|
}
|
|
171
179
|
}
|
|
@@ -177,9 +185,16 @@ async function runBrowse(url, opts, context) {
|
|
|
177
185
|
if (opts.checkOverflow) {
|
|
178
186
|
overflow = await browser.checkOverflow();
|
|
179
187
|
}
|
|
188
|
+
let runts;
|
|
189
|
+
if (opts.checkRunts) {
|
|
190
|
+
runts = await browser.checkRunts({
|
|
191
|
+
scope: typeof opts.checkRunts === "string" ? opts.checkRunts : null,
|
|
192
|
+
minChars: Number.parseInt(opts.checkRuntsMinChars ?? "40", 10),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
180
195
|
const widthThreshold = Number.parseFloat(opts.checkWidthThreshold ?? "0.9");
|
|
181
|
-
const annotateWidth = widths && opts.
|
|
182
|
-
const annotateOverflow = overflow && opts.
|
|
196
|
+
const annotateWidth = widths && opts.checkWidthAnnotate !== false;
|
|
197
|
+
const annotateOverflow = overflow && opts.checkOverflowAnnotate !== false;
|
|
183
198
|
if (annotateWidth || annotateOverflow) {
|
|
184
199
|
await browser.annotateLayout({
|
|
185
200
|
widths: annotateWidth ? widths : [],
|
|
@@ -187,6 +202,10 @@ async function runBrowse(url, opts, context) {
|
|
|
187
202
|
widthThreshold,
|
|
188
203
|
});
|
|
189
204
|
}
|
|
205
|
+
const annotateRunts = runts && runts.runts.length > 0 && opts.checkRuntsAnnotate !== false;
|
|
206
|
+
if (annotateRunts) {
|
|
207
|
+
await browser.annotateRunts(runts);
|
|
208
|
+
}
|
|
190
209
|
if (opts.login) {
|
|
191
210
|
await new Promise((res) => {
|
|
192
211
|
emit.log("[--login] Headed Chromium is open. Walk through your auth flow now. Press Enter here to close + persist cookies into the profile.", "info");
|
|
@@ -203,17 +222,20 @@ async function runBrowse(url, opts, context) {
|
|
|
203
222
|
if (opts.baseline || opts.diff) {
|
|
204
223
|
throw new Error("--baseline / --diff require trio mode (screenshot file). Remove --snapshot/--html/--json or capture the screenshot first.");
|
|
205
224
|
}
|
|
206
|
-
await runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, devOverlay, batchResult);
|
|
225
|
+
await runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, devOverlay, batchResult);
|
|
207
226
|
}
|
|
208
227
|
else {
|
|
209
|
-
await runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, devOverlay, batchResult);
|
|
228
|
+
await runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, devOverlay, batchResult);
|
|
210
229
|
}
|
|
211
|
-
if (visibility && opts.
|
|
230
|
+
if (visibility && opts.checkVisibleAnnotate !== false) {
|
|
212
231
|
await browser.clearVisibilityAnnotations();
|
|
213
232
|
}
|
|
214
233
|
if (annotateWidth || annotateOverflow) {
|
|
215
234
|
await browser.clearLayoutAnnotations();
|
|
216
235
|
}
|
|
236
|
+
if (annotateRunts) {
|
|
237
|
+
await browser.clearRuntsAnnotations();
|
|
238
|
+
}
|
|
217
239
|
if (opts.checkVisibleFail && visibility) {
|
|
218
240
|
const threshold = Number.parseFloat(opts.checkVisibleThreshold ?? "0.9");
|
|
219
241
|
const failed = visibility.filter((r) => !r.found || !r.cssVisible || r.visibleRatio < threshold);
|
|
@@ -249,6 +271,12 @@ async function runBrowse(url, opts, context) {
|
|
|
249
271
|
process.exitCode = 2;
|
|
250
272
|
}
|
|
251
273
|
}
|
|
274
|
+
if (opts.checkRuntsFail && runts && runts.runts.length > 0) {
|
|
275
|
+
for (const hit of runts.runts) {
|
|
276
|
+
emit.log(`check-runts FAIL ${hit.block}: last line is a lone "${hit.word}" ("…${hit.snippet.slice(-40)}")`, "warn");
|
|
277
|
+
}
|
|
278
|
+
process.exitCode = 2;
|
|
279
|
+
}
|
|
252
280
|
if (opts.checkOverflowFail && overflow?.hasHorizontalOverflow) {
|
|
253
281
|
const culprit = overflow.widerThanViewport[0] ?? overflow.rightOverflow[0] ?? null;
|
|
254
282
|
const detail = culprit
|
|
@@ -356,7 +384,7 @@ function splitBatchSteps(input) {
|
|
|
356
384
|
// ---------------------------------------------------------------------------
|
|
357
385
|
// Print mode (--snapshot / --html / --json): stdout, no file writes
|
|
358
386
|
// ---------------------------------------------------------------------------
|
|
359
|
-
async function runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, devOverlay, batchResult) {
|
|
387
|
+
async function runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, devOverlay, batchResult) {
|
|
360
388
|
let body = null;
|
|
361
389
|
if (opts.html) {
|
|
362
390
|
body = await browser.htmlContent(opts.selector);
|
|
@@ -385,6 +413,8 @@ async function runPrintMode(browser, navResult, opts, evalResult, visibility, wi
|
|
|
385
413
|
result.width = widths;
|
|
386
414
|
if (overflow)
|
|
387
415
|
result.overflow = overflow;
|
|
416
|
+
if (runts)
|
|
417
|
+
result.runts = runts;
|
|
388
418
|
if (devOverlay)
|
|
389
419
|
result.devOverlay = devOverlay;
|
|
390
420
|
if (batchResult && batchResult.clipboardReads.length > 0) {
|
|
@@ -408,7 +438,7 @@ async function runPrintMode(browser, navResult, opts, evalResult, visibility, wi
|
|
|
408
438
|
// ---------------------------------------------------------------------------
|
|
409
439
|
// Trio mode (default): write <prefix>.{png,html,json}
|
|
410
440
|
// ---------------------------------------------------------------------------
|
|
411
|
-
async function runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, devOverlay, batchResult) {
|
|
441
|
+
async function runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, devOverlay, batchResult) {
|
|
412
442
|
const prefix = resolveOutPrefix(opts.out);
|
|
413
443
|
mkdirSync(dirname(prefix), { recursive: true });
|
|
414
444
|
const written = [];
|
|
@@ -449,6 +479,8 @@ async function runTrioMode(browser, navResult, opts, evalResult, visibility, wid
|
|
|
449
479
|
envelope.width = widths;
|
|
450
480
|
if (overflow)
|
|
451
481
|
envelope.overflow = overflow;
|
|
482
|
+
if (runts)
|
|
483
|
+
envelope.runts = runts;
|
|
452
484
|
if (devOverlay)
|
|
453
485
|
envelope.devOverlay = devOverlay;
|
|
454
486
|
if (batchResult && batchResult.clipboardReads.length > 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"completion.d.ts","sourceRoot":"","sources":["../../src/commands/completion.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"completion.d.ts","sourceRoot":"","sources":["../../src/commands/completion.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAkB1E;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,WAAW,EAClB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CAqGN"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
|
-
import { generateBash, generateFish, generateZsh, walkProgram, } from "../lib/completion/index.js";
|
|
3
|
+
import { encodeResult, generateBash, generateBashDynamic, generateFish, generateFishDynamic, generateZsh, generateZshDynamic, resolveCompletions, walkProgram, } from "../lib/completion/index.js";
|
|
4
4
|
const noopProviderRunner = async () => [];
|
|
5
5
|
const noopLookup = () => undefined;
|
|
6
6
|
/**
|
|
@@ -22,25 +22,35 @@ export function registerCompletionCommand(program, _emit, context) {
|
|
|
22
22
|
const root = program
|
|
23
23
|
.command("completion")
|
|
24
24
|
.description("Shell tab-completion. Emit a script per shell or install to the standard location.");
|
|
25
|
+
const dynamicHint = "Emit a thin shim that calls the binary at tab-time (never goes stale; install once)";
|
|
25
26
|
root
|
|
26
27
|
.command("bash")
|
|
27
28
|
.description("Emit bash completion script to stdout")
|
|
28
|
-
.
|
|
29
|
-
|
|
29
|
+
.option("--dynamic", dynamicHint)
|
|
30
|
+
.action((opts) => {
|
|
31
|
+
const out = opts.dynamic
|
|
32
|
+
? generateBashDynamic(program.name())
|
|
33
|
+
: generateBash(walkProgram(program, lookup), program.name());
|
|
30
34
|
process.stdout.write(out); // raw bytes: shell completion scripts must be unframed (consumer evals stdout).
|
|
31
35
|
});
|
|
32
36
|
root
|
|
33
37
|
.command("zsh")
|
|
34
38
|
.description("Emit zsh completion script to stdout")
|
|
35
|
-
.
|
|
36
|
-
|
|
39
|
+
.option("--dynamic", dynamicHint)
|
|
40
|
+
.action((opts) => {
|
|
41
|
+
const out = opts.dynamic
|
|
42
|
+
? generateZshDynamic(program.name())
|
|
43
|
+
: generateZsh(walkProgram(program, lookup), program.name());
|
|
37
44
|
process.stdout.write(out); // raw bytes: shell completion scripts must be unframed (consumer evals stdout).
|
|
38
45
|
});
|
|
39
46
|
root
|
|
40
47
|
.command("fish")
|
|
41
48
|
.description("Emit fish completion script to stdout")
|
|
42
|
-
.
|
|
43
|
-
|
|
49
|
+
.option("--dynamic", dynamicHint)
|
|
50
|
+
.action((opts) => {
|
|
51
|
+
const out = opts.dynamic
|
|
52
|
+
? generateFishDynamic(program.name())
|
|
53
|
+
: generateFish(walkProgram(program, lookup), program.name());
|
|
44
54
|
process.stdout.write(out); // raw bytes: shell completion scripts must be unframed (consumer evals stdout).
|
|
45
55
|
});
|
|
46
56
|
root
|
|
@@ -49,6 +59,7 @@ export function registerCompletionCommand(program, _emit, context) {
|
|
|
49
59
|
.option("--shell <name>", "bash | zsh | fish (default: auto-detect from $SHELL)")
|
|
50
60
|
.option("--path <file>", "Override destination path")
|
|
51
61
|
.option("--print-path", "Print the destination path and exit (no write)")
|
|
62
|
+
.option("--dynamic", `${dynamicHint} (recommended)`)
|
|
52
63
|
.action(async (opts) => {
|
|
53
64
|
await installCompletion(program, opts, lookup);
|
|
54
65
|
});
|
|
@@ -72,6 +83,26 @@ export function registerCompletionCommand(program, _emit, context) {
|
|
|
72
83
|
});
|
|
73
84
|
// Keep TS happy that the variable is used.
|
|
74
85
|
void hidden;
|
|
86
|
+
// Hidden internal entry for DYNAMIC completion: the thin shim passes the live
|
|
87
|
+
// command line (cursor index + all words after `--`) and we compute the full
|
|
88
|
+
// candidate set from the live command tree. `--` stops option parsing so
|
|
89
|
+
// words like `-h` reach the variadic instead of being read as our flags.
|
|
90
|
+
const hiddenLine = program
|
|
91
|
+
.command("__complete-line <cword> [words...]", { hidden: true })
|
|
92
|
+
.description("Internal: full-line completion callback for the dynamic shell shim")
|
|
93
|
+
.allowUnknownOption(true)
|
|
94
|
+
.allowExcessArguments(true)
|
|
95
|
+
.action(async (cword, words) => {
|
|
96
|
+
try {
|
|
97
|
+
const result = await resolveCompletions(program, words ?? [], Number.parseInt(cword, 10) || 0, lookup, runProvider);
|
|
98
|
+
process.stdout.write(encodeResult(result)); // lint-ok-emission: shell callback; the encoded candidate/directive stream is the contract with the shim.
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Never break the user's tab: emit just the file-fallback directive.
|
|
102
|
+
process.stdout.write("\x1f:1\n"); // lint-ok-emission: shell callback fallback directive.
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
void hiddenLine;
|
|
75
106
|
}
|
|
76
107
|
async function installCompletion(program, opts, lookup) {
|
|
77
108
|
const shell = opts.shell ?? detectShell();
|
|
@@ -84,16 +115,23 @@ async function installCompletion(program, opts, lookup) {
|
|
|
84
115
|
process.stdout.write(`${destination}\n`); // lint-ok-emission: --print-path is meant to be piped (e.g., dest=$(harn completion install --print-path)).
|
|
85
116
|
return;
|
|
86
117
|
}
|
|
118
|
+
const name = program.name();
|
|
87
119
|
let content;
|
|
88
120
|
switch (shell) {
|
|
89
121
|
case "bash":
|
|
90
|
-
content =
|
|
122
|
+
content = opts.dynamic
|
|
123
|
+
? generateBashDynamic(name)
|
|
124
|
+
: generateBash(walkProgram(program, lookup), name);
|
|
91
125
|
break;
|
|
92
126
|
case "zsh":
|
|
93
|
-
content =
|
|
127
|
+
content = opts.dynamic
|
|
128
|
+
? generateZshDynamic(name)
|
|
129
|
+
: generateZsh(walkProgram(program, lookup), name);
|
|
94
130
|
break;
|
|
95
131
|
case "fish":
|
|
96
|
-
content =
|
|
132
|
+
content = opts.dynamic
|
|
133
|
+
? generateFishDynamic(name)
|
|
134
|
+
: generateFish(walkProgram(program, lookup), name);
|
|
97
135
|
break;
|
|
98
136
|
default:
|
|
99
137
|
process.stderr.write(`Unknown shell: ${shell}\n`); // lint-ok-emission: install-time error, see above.
|
package/dist/commands/cookies.js
CHANGED
|
@@ -12,7 +12,7 @@ const DEFAULT_STORE = resolve(homedir(), ".cache", "harnery", "cookies.json");
|
|
|
12
12
|
function jarFrom(opts) {
|
|
13
13
|
return new CookieJar({
|
|
14
14
|
path: opts.store ?? DEFAULT_STORE,
|
|
15
|
-
source: "
|
|
15
|
+
source: "harn-cookies",
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
// Module-scoped emit assigned by registerCookiesCommand; the module-level
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../../src/commands/decision.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA6CnD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,GAAG,IAAI,CAgStF"}
|