harnery 0.5.0 → 0.7.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.
Files changed (176) hide show
  1. package/README.md +16 -6
  2. package/dist/commander.d.ts +29 -0
  3. package/dist/commander.d.ts.map +1 -1
  4. package/dist/commander.js +4 -0
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +94 -33
  7. package/dist/commands/browse-ai.js +1 -1
  8. package/dist/commands/browse.d.ts.map +1 -1
  9. package/dist/commands/browse.js +41 -9
  10. package/dist/commands/cookies.js +1 -1
  11. package/dist/commands/decision.d.ts +4 -0
  12. package/dist/commands/decision.d.ts.map +1 -0
  13. package/dist/commands/decision.js +354 -0
  14. package/dist/commands/deinit.d.ts.map +1 -1
  15. package/dist/commands/deinit.js +4 -0
  16. package/dist/commands/devtools.d.ts +4 -0
  17. package/dist/commands/devtools.d.ts.map +1 -0
  18. package/dist/commands/devtools.js +239 -0
  19. package/dist/commands/docs.d.ts.map +1 -1
  20. package/dist/commands/docs.js +74 -2
  21. package/dist/commands/doctor.js +12 -4
  22. package/dist/commands/env.d.ts.map +1 -1
  23. package/dist/commands/env.js +3 -63
  24. package/dist/commands/fetch.js +1 -1
  25. package/dist/commands/init.d.ts +1 -0
  26. package/dist/commands/init.d.ts.map +1 -1
  27. package/dist/commands/init.js +54 -14
  28. package/dist/commands/scratch.js +1 -1
  29. package/dist/commands/tunnel.d.ts.map +1 -1
  30. package/dist/commands/tunnel.js +273 -62
  31. package/dist/commands/web-fetch.js +1 -1
  32. package/dist/core/agents/coord-client.d.ts.map +1 -1
  33. package/dist/core/agents/coord-client.js +32 -8
  34. package/dist/core/agents/events/consume.d.ts +25 -2
  35. package/dist/core/agents/events/consume.d.ts.map +1 -1
  36. package/dist/core/agents/events/consume.js +55 -7
  37. package/dist/core/agents/events/emit.d.ts +2 -1
  38. package/dist/core/agents/events/emit.d.ts.map +1 -1
  39. package/dist/core/agents/events/emit.js +6 -1
  40. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  41. package/dist/core/agents/rules/claim-conflict.js +16 -5
  42. package/dist/core/agents/state/scratch.d.ts +1 -1
  43. package/dist/core/agents/state/scratch.js +2 -2
  44. package/dist/core/config.d.ts +10 -0
  45. package/dist/core/config.d.ts.map +1 -1
  46. package/dist/core/config.js +13 -0
  47. package/dist/core/hooks/cli.js +3 -3
  48. package/dist/core/hooks/effects/index.d.ts +11 -7
  49. package/dist/core/hooks/effects/index.d.ts.map +1 -1
  50. package/dist/core/hooks/effects/index.js +15 -17
  51. package/dist/core/hooks/events/emit.d.ts.map +1 -1
  52. package/dist/core/hooks/events/emit.js +4 -0
  53. package/dist/core/hooks/events/rotate.d.ts +43 -0
  54. package/dist/core/hooks/events/rotate.d.ts.map +1 -0
  55. package/dist/core/hooks/events/rotate.js +142 -0
  56. package/dist/core/hooks/harness/events.d.ts +11 -1
  57. package/dist/core/hooks/harness/events.d.ts.map +1 -1
  58. package/dist/core/hooks/harness/events.js +22 -3
  59. package/dist/core/hooks/harness/wiring.d.ts +8 -0
  60. package/dist/core/hooks/harness/wiring.d.ts.map +1 -1
  61. package/dist/core/hooks/harness/wiring.js +34 -5
  62. package/dist/core/scratch/index.d.ts.map +1 -0
  63. package/dist/{lib → core}/scratch/index.js +2 -2
  64. package/dist/lib/agent-browser/client.js +1 -1
  65. package/dist/lib/browser/client.d.ts +14 -0
  66. package/dist/lib/browser/client.d.ts.map +1 -1
  67. package/dist/lib/browser/client.js +20 -0
  68. package/dist/lib/browser/index.d.ts +1 -0
  69. package/dist/lib/browser/index.d.ts.map +1 -1
  70. package/dist/lib/browser/runts.d.ts +44 -0
  71. package/dist/lib/browser/runts.d.ts.map +1 -0
  72. package/dist/lib/browser/runts.js +193 -0
  73. package/dist/lib/completion/walk.js +1 -1
  74. package/dist/lib/cookies/client.d.ts +1 -1
  75. package/dist/lib/cookies/client.d.ts.map +1 -1
  76. package/dist/lib/cookies/client.js +1 -1
  77. package/dist/lib/decision/index.d.ts +212 -0
  78. package/dist/lib/decision/index.d.ts.map +1 -0
  79. package/dist/lib/decision/index.js +523 -0
  80. package/dist/lib/devtools.d.ts +178 -0
  81. package/dist/lib/devtools.d.ts.map +1 -0
  82. package/dist/lib/devtools.js +1328 -0
  83. package/dist/lib/docs-frontmatter-migrate.d.ts +33 -0
  84. package/dist/lib/docs-frontmatter-migrate.d.ts.map +1 -0
  85. package/dist/lib/docs-frontmatter-migrate.js +364 -0
  86. package/dist/lib/docs-frontmatter.d.ts +33 -0
  87. package/dist/lib/docs-frontmatter.d.ts.map +1 -0
  88. package/dist/lib/docs-frontmatter.js +130 -0
  89. package/dist/lib/docs-index.d.ts +1 -0
  90. package/dist/lib/docs-index.d.ts.map +1 -1
  91. package/dist/lib/docs-index.js +4 -5
  92. package/dist/lib/docs-lint.d.ts +3 -0
  93. package/dist/lib/docs-lint.d.ts.map +1 -1
  94. package/dist/lib/docs-lint.js +67 -12
  95. package/dist/lib/docs-meta.d.ts +14 -0
  96. package/dist/lib/docs-meta.d.ts.map +1 -0
  97. package/dist/lib/docs-meta.js +34 -0
  98. package/dist/lib/docs-sweep.d.ts +12 -0
  99. package/dist/lib/docs-sweep.d.ts.map +1 -1
  100. package/dist/lib/docs-sweep.js +98 -103
  101. package/dist/lib/format.js +2 -2
  102. package/dist/lib/http/index.d.ts +1 -0
  103. package/dist/lib/http/index.d.ts.map +1 -1
  104. package/dist/lib/http/index.js +1 -0
  105. package/dist/lib/http/request.d.ts +77 -0
  106. package/dist/lib/http/request.d.ts.map +1 -0
  107. package/dist/lib/http/request.js +105 -0
  108. package/dist/lib/instructions/apply.d.ts +63 -0
  109. package/dist/lib/instructions/apply.d.ts.map +1 -0
  110. package/dist/lib/instructions/apply.js +255 -0
  111. package/dist/lib/instructions/splice.d.ts +73 -0
  112. package/dist/lib/instructions/splice.d.ts.map +1 -0
  113. package/dist/lib/instructions/splice.js +118 -0
  114. package/dist/lib/instructions/templates.d.ts +45 -0
  115. package/dist/lib/instructions/templates.d.ts.map +1 -0
  116. package/dist/lib/instructions/templates.js +258 -0
  117. package/dist/lib/tunnel/gate.d.ts +1 -0
  118. package/dist/lib/tunnel/gate.d.ts.map +1 -1
  119. package/dist/lib/tunnel/gate.js +15 -10
  120. package/dist/lib/tunnel/state.d.ts +11 -1
  121. package/dist/lib/tunnel/state.d.ts.map +1 -1
  122. package/dist/lib/tunnel/state.js +8 -3
  123. package/package.json +9 -6
  124. package/src/commander.ts +35 -0
  125. package/src/commands/agents.ts +97 -29
  126. package/src/commands/browse-ai.ts +1 -1
  127. package/src/commands/browse.ts +63 -8
  128. package/src/commands/cookies.ts +1 -1
  129. package/src/commands/decision.ts +438 -0
  130. package/src/commands/deinit.ts +5 -0
  131. package/src/commands/devtools.ts +284 -0
  132. package/src/commands/docs.ts +86 -2
  133. package/src/commands/doctor.ts +13 -4
  134. package/src/commands/env.ts +11 -77
  135. package/src/commands/fetch.ts +1 -1
  136. package/src/commands/init.ts +66 -15
  137. package/src/commands/scratch.ts +1 -1
  138. package/src/commands/tunnel.ts +316 -65
  139. package/src/commands/web-fetch.ts +1 -1
  140. package/src/core/agents/coord-client.ts +34 -7
  141. package/src/core/agents/events/consume.ts +65 -7
  142. package/src/core/agents/events/emit.ts +7 -1
  143. package/src/core/agents/rules/claim-conflict.ts +17 -6
  144. package/src/core/agents/state/scratch.ts +2 -2
  145. package/src/core/config.ts +15 -1
  146. package/src/core/hooks/cli.ts +3 -3
  147. package/src/core/hooks/effects/index.ts +23 -16
  148. package/src/core/hooks/events/emit.ts +5 -0
  149. package/src/core/hooks/events/rotate.ts +151 -0
  150. package/src/core/hooks/harness/events.ts +30 -3
  151. package/src/core/hooks/harness/wiring.ts +46 -5
  152. package/src/{lib → core}/scratch/index.ts +2 -2
  153. package/src/lib/agent-browser/client.ts +1 -1
  154. package/src/lib/browser/client.ts +28 -0
  155. package/src/lib/browser/index.ts +4 -0
  156. package/src/lib/browser/runts.ts +218 -0
  157. package/src/lib/completion/walk.ts +1 -1
  158. package/src/lib/cookies/client.ts +2 -2
  159. package/src/lib/decision/index.ts +685 -0
  160. package/src/lib/devtools.ts +1653 -0
  161. package/src/lib/docs-frontmatter-migrate.ts +427 -0
  162. package/src/lib/docs-frontmatter.ts +151 -0
  163. package/src/lib/docs-index.ts +4 -5
  164. package/src/lib/docs-lint.ts +61 -11
  165. package/src/lib/docs-meta.ts +44 -0
  166. package/src/lib/docs-sweep.ts +104 -102
  167. package/src/lib/format.ts +2 -2
  168. package/src/lib/http/index.ts +1 -0
  169. package/src/lib/http/request.ts +154 -0
  170. package/src/lib/instructions/apply.ts +318 -0
  171. package/src/lib/instructions/splice.ts +148 -0
  172. package/src/lib/instructions/templates.ts +295 -0
  173. package/src/lib/tunnel/gate.ts +15 -10
  174. package/src/lib/tunnel/state.ts +19 -4
  175. package/dist/lib/scratch/index.d.ts.map +0 -1
  176. /package/dist/{lib → core}/scratch/index.d.ts +0 -0
package/README.md CHANGED
@@ -7,23 +7,24 @@
7
7
 
8
8
  # Harnery
9
9
 
10
- > Multi-agent coordination + harness adapters + portable CLI utilities for Claude Code / Cursor / Codex.
10
+ > Multi-agent coordination for AI coding agents — Claude Code, Cursor, and Codex.
11
11
 
12
12
  [![CI](https://github.com/ryanjkelly/harnery/actions/workflows/ci.yml/badge.svg)](https://github.com/ryanjkelly/harnery/actions/workflows/ci.yml)
13
13
  [![npm version](https://img.shields.io/npm/v/harnery.svg)](https://www.npmjs.com/package/harnery)
14
14
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
15
15
 
16
- > ⚠️ **Pre-1.0.** API surface is still settling. Pin a specific minor version (`harnery@^0.1.0`) and read the [CHANGELOG](CHANGELOG.md) before each upgrade.
16
+ > ⚠️ **Pre-1.0.** API surface is still settling. Pin a specific minor version (`harnery@^0.7.0`) and read the [CHANGELOG](CHANGELOG.md) before each upgrade.
17
17
 
18
18
  ## What it is
19
19
 
20
- `harnery` is a utility layer extracted from years of building agent tooling across a multi-project monorepo. It bundles:
20
+ `harnery` keeps multiple AI coding agents from stepping on each other in a shared checkout. It was extracted from years of running several Claude Code / Cursor / Codex sessions at once against the same monorepo:
21
21
 
22
- - **Multi-agent coordination:** per-agent heartbeats in `.harnery/active/`, claim-time and commit-time guards, the canonical event stream, harness adapters for Claude Code / Cursor / Codex.
23
- - **Portable CLI utilities:** `tokens`, `eml`, `env`, `grep`, `docs`, `repo`, `wip`, `share`, `browse`, `fetch`, `read`, and more. Cross-platform and dependency-light, with sensible defaults out of the box.
24
- - **Standalone web UI:** `harn web up` boots a local Next.js dashboard for the coord layer, councils, and per-project state. Ships with the git clone, not the npm package (see [Install](#install)).
22
+ - **Multi-agent coordination:** per-agent heartbeats in `.harnery/active/`, claim-time and commit-time guards, the canonical event stream, councils, a decision docket, per-agent scratchpads, and harness adapters for Claude Code / Cursor / Codex.
23
+ - **Standalone web UI:** `harn web up` boots a local Next.js dashboard for the coord layer, councils, per-project state, and a local coding-agent status view (Claude Code / Codex / Cursor usage, backed by `harn devtools`). Ships with the git clone, not the npm package (see [Install](#install)).
25
24
  - **Backup + sync:** `harn backup` snapshots `.harnery/` via [restic](https://restic.net/); `harn sync` keeps a curated subset live across machines via [rclone](https://rclone.org/) (Google Drive or any rclone remote).
26
25
 
26
+ The CLI also ships batteries: portable utility commands (`tokens`, `eml`, `env`, `grep`, `docs`, `browse`, `fetch`, `read`, `devtools`, and more) plus the library toolkit they're built from. Useful, cross-platform, dependency-light — and deliberately not the headline. Coordination is why harnery exists; the toolkit exists because the CLI needed it (see [Public surface tiers](#public-surface-tiers)).
27
+
27
28
  ## Install
28
29
 
29
30
  ```bash
@@ -80,6 +81,15 @@ await program.parseAsync(process.argv);
80
81
 
81
82
  `mycli agents status` then resolves to **the same code** as `harn agents status`, loaded as a library. See [examples/extending-with-commander.ts](examples/extending-with-commander.ts) for the full pattern.
82
83
 
84
+ ### Public surface tiers
85
+
86
+ The exports map draws the line between what harnery *is* and what it *ships with*:
87
+
88
+ - **Product tier** — `harnery`, `harnery/commander`, `harnery/core/*`: the coordination layer and CLI composition. This is the API to build against, and the reason to install harnery.
89
+ - **Toolkit tier** — every `harnery/lib/*` subpath (`http`, `cookies`, `format`, `readability`, `browser`, `machine`, …): the supporting utilities harnery's own CLI is built from, exposed for embedding hosts that want to lean on them. Supported, but secondary: it can evolve faster than the product tier, and it isn't a reason to adopt harnery on its own.
90
+
91
+ The boundary is enforced, not aspirational: CI verifies that no `harnery/lib/*` export imports the coordination core, directly or transitively (`scripts/check-layering.ts`), so pulling a toolkit module never drags in coordination state. Details: [Embedding + surface tiers](https://harnery.com/concepts/embedding/).
92
+
83
93
  ## Documentation
84
94
 
85
95
  Full docs at **[harnery.com](https://harnery.com)**:
@@ -42,6 +42,18 @@ export interface HarneryContextOpts {
42
42
  */
43
43
  skipCommands?: readonly string[];
44
44
  }
45
+ /** One row in a `harn env` section report. */
46
+ export interface EnvCheck {
47
+ label: string;
48
+ value: string;
49
+ status?: "ok" | "missing" | "warn" | "info";
50
+ }
51
+ /**
52
+ * A `harn env` section: an async probe returning its rows. Hosts register extra
53
+ * sections (e.g. cloud-provider connectivity) via `context.envSections`; harnery
54
+ * core ships only the generic sections (runtimes, docker, git).
55
+ */
56
+ export type EnvSection = () => Promise<EnvCheck[]>;
45
57
  export interface HarneryProgramContext {
46
58
  /** Project name (e.g., "my-monorepo"). Used in user-facing log lines + telemetry tags. */
47
59
  projectName?: string;
@@ -59,6 +71,13 @@ export interface HarneryProgramContext {
59
71
  * submodule row.
60
72
  */
61
73
  submodules?: readonly string[];
74
+ /**
75
+ * Extra `harn env` sections keyed by name (e.g. `{ gcp, bq }`). Merged into
76
+ * the built-in generic sections (runtimes, docker, git), so a host can expose
77
+ * `harn env <name>` for its own environment probes without harnery core
78
+ * carrying provider-specific checks. harn standalone ships none.
79
+ */
80
+ envSections?: Record<string, EnvSection>;
62
81
  /**
63
82
  * Optional callback that returns extra HTTP headers to attach to outbound
64
83
  * `fetch` calls based on the target URL. Useful for consumers that need
@@ -91,6 +110,16 @@ export interface HarneryProgramContext {
91
110
  * exclusions (`.claude/`, `.harnery/`, `.codex/`, `.cursor/`).
92
111
  */
93
112
  extraDocsExcludedPrefixes?: readonly string[];
113
+ /**
114
+ * Filenames permitted at the host project's `docs/` root (parent repo only).
115
+ * When set, `harn docs lint` flags any other `.md`/`.json` file sitting
116
+ * loose at `docs/` root (rule `docs-root-file`) — topic docs belong in
117
+ * `docs/<topic>/` subdirs. Names are matched exactly (basename). When
118
+ * omitted or empty, the rule is a no-op, so standalone `harn` and consumers
119
+ * that don't opt in are unaffected. Submodule `docs/` roots are never
120
+ * checked (their entry tiers differ from the parent's).
121
+ */
122
+ docsRootAllowlist?: readonly string[];
94
123
  /**
95
124
  * Default Host header for `tunnel up` when `--vhost` is omitted: a literal
96
125
  * host, or a resolver evaluated at start time (e.g. read a dev stack's
@@ -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;AA+BpC,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;;;;;;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,CA2C3E"}
1
+ {"version":3,"file":"commander.d.ts","sourceRoot":"","sources":["../src/commander.ts"],"names":[],"mappings":"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"}
package/dist/commander.js CHANGED
@@ -26,7 +26,9 @@ 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";
31
+ import { registerDevtoolsCommand } from "./commands/devtools.js";
30
32
  import { registerDocsCommand } from "./commands/docs.js";
31
33
  import { registerDoctorCommand } from "./commands/doctor.js";
32
34
  import { registerEditBatchCommand } from "./commands/edit-batch.js";
@@ -110,6 +112,8 @@ export function createHarneryProgram(opts = {}) {
110
112
  registerCompletionCommand(program, emit, opts.context);
111
113
  registerContextCommand(program, emit, opts.context);
112
114
  registerScratchCommand(program, emit);
115
+ registerDecisionCommand(program, emit);
116
+ registerDevtoolsCommand(program, emit);
113
117
  registerTunnelCommand(program, emit, opts.context);
114
118
  registerDocsCommand(program, emit, opts.context);
115
119
  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;AAgFnD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,GAAG,IAAI,CA6OpF"}
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;AAwFnD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,GAAG,IAAI,CA4OpF"}
@@ -16,12 +16,17 @@ 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 { emitCanonical, monorepoRoot, normalizeHarness, readHeartbeat, resolveOwner, resolveOwnerWithSource, } from "../core/agents/index.js";
19
+ import { readStreamTailBounded } from "../core/agents/events/consume.js";
20
+ import { emitCanonical, monorepoRoot, normalizeHarness, readHeartbeat, resolveOwner, resolveOwnerBySessionEnv, 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";
27
+ import { appendEntry, resolveOwnerByName } from "../core/scratch/index.js";
22
28
  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
29
  import { displayName as displayAgentName, ensureIdentity, listIdentities, lookupById as lookupIdentityById, lookupByName as lookupIdentityByName, } from "../lib/identities/index.js";
24
- import { appendEntry, resolveOwnerByName } from "../lib/scratch/index.js";
25
30
  const FRESHNESS_SECS = 600; // 10-minute heartbeat-freshness window.
26
31
  const SUBAGENT_NOTE = "Bash identity is process-level in v1; if you're running inside a subagent, " +
27
32
  "this resolves to the parent group's name, not the subagent's. A subagent-aware " +
@@ -75,8 +80,7 @@ export function registerAgentsCommand(program, emitParam) {
75
80
  });
76
81
  cmd
77
82
  .command("show <name>")
78
- .description("Deep-dive on one peer agent: registry state (files held, last tool, task) " +
79
- "plus claude-sessions history (latest title, recent prompts, recent tools, tool-usage tallies). " +
83
+ .description("Deep-dive on one peer agent: registry state (files held, last tool, task, turn summary). " +
80
84
  "Disambiguates name → instance_id via prefix match.")
81
85
  .option("--json", "JSON envelope output")
82
86
  .action(async (name, opts) => {
@@ -430,6 +434,7 @@ function runWhoami(opts) {
430
434
  });
431
435
  process.exit(1);
432
436
  }
437
+ ensureCursorSession(root);
433
438
  const resolved = resolveOwnerWithSource();
434
439
  const myOwner = resolved.owner;
435
440
  if (!myOwner) {
@@ -868,6 +873,49 @@ function formatLocalShort(iso) {
868
873
  timeZone: "America/Chicago",
869
874
  }).format(d);
870
875
  }
876
+ function cursorEnvSessionId() {
877
+ const raw = process.env.CURSOR_SESSION_ID?.trim() || process.env.CURSOR_CONVERSATION_ID?.trim();
878
+ if (!raw)
879
+ return null;
880
+ return raw.startsWith("bc-") && raw.length > 3 ? raw.slice(3) : raw;
881
+ }
882
+ function shouldBootstrapCursorSession() {
883
+ return process.env.CURSOR_AGENT === "1" && cursorEnvSessionId() !== null;
884
+ }
885
+ function ensureCursorSession(root) {
886
+ if (!shouldBootstrapCursorSession())
887
+ return;
888
+ if (resolveOwnerBySessionEnv(root))
889
+ return;
890
+ const sessionId = cursorEnvSessionId();
891
+ if (!sessionId)
892
+ return;
893
+ const agentHook = resolve(root, "harnery", "bin", "agent-hook");
894
+ if (!existsSync(agentHook))
895
+ return;
896
+ const payload = JSON.stringify({
897
+ conversation_id: sessionId,
898
+ session_id: sessionId,
899
+ hook_event_name: "sessionStart",
900
+ workspace_roots: [root],
901
+ cwd: root,
902
+ composer_mode: "agent",
903
+ is_background_agent: false,
904
+ });
905
+ spawnSync("bash", [agentHook, "session-start", "--harness", "cursor"], {
906
+ input: payload,
907
+ cwd: root,
908
+ encoding: "utf8",
909
+ timeout: 3000,
910
+ env: {
911
+ ...process.env,
912
+ HARNERY_AGENT_COORD_PLATFORM: "cursor",
913
+ HARNERY_COORD_ROOT_OVERRIDE: root,
914
+ CURSOR_SESSION_ID: sessionId,
915
+ CURSOR_CONVERSATION_ID: sessionId,
916
+ },
917
+ });
918
+ }
871
919
  function runReleaseClaim(path) {
872
920
  const root = monorepoRoot();
873
921
  if (!root) {
@@ -915,6 +963,8 @@ function runSetTask(task, opts) {
915
963
  // Identity: prefer explicit --session-id (the ppid-walk-free escape hatch,
916
964
  // mirrors `status`), fall back to the ppid walk. Cursor shell tool calls
917
965
  // don't descend from a pid-map-registered anchor, so the walk can miss there.
966
+ if (!opts?.sessionId)
967
+ ensureCursorSession(root);
918
968
  const myOwner = opts?.sessionId ?? resolveOwner();
919
969
  if (!myOwner) {
920
970
  emit.error({
@@ -961,6 +1011,8 @@ function runStatus(opts) {
961
1011
  }
962
1012
  // Identity resolution: prefer explicit --session-id (hook-friendly), fall
963
1013
  // back to ppid walk for interactive shell usage.
1014
+ if (!opts.sessionId)
1015
+ ensureCursorSession(root);
964
1016
  const myOwner = opts.sessionId ?? resolveOwner();
965
1017
  if (!myOwner) {
966
1018
  emit.error({
@@ -1717,8 +1769,12 @@ function readStreamStats(root) {
1717
1769
  let backlog = 0;
1718
1770
  let seenCursor = cursor === null; // no cursor → everything is "backlog"
1719
1771
  try {
1720
- const raw = readFileSync(streamPath, "utf8");
1721
- for (const line of raw.split("\n")) {
1772
+ // Bounded tail read: the ledger grows without bound and a whole-file
1773
+ // readFileSync throws past V8's ~512MB string limit. lines/backlog are then
1774
+ // scoped to the scanned window; `bytes` (full size, via statSync above) still
1775
+ // reflects the true stream size for the health rollup.
1776
+ const { text } = readStreamTailBounded(streamPath, STREAM_SCAN_CAP_BYTES);
1777
+ for (const line of text.split("\n")) {
1722
1778
  if (!line.trim())
1723
1779
  continue;
1724
1780
  lines++;
@@ -1835,27 +1891,32 @@ function runTrace(name, opts) {
1835
1891
  const sinceMs = opts.since ? Date.now() - (parseWindowSecs(opts.since) ?? 0) * 1000 : 0;
1836
1892
  const limit = Math.max(1, Number.parseInt(opts.limit, 10) || 200);
1837
1893
  const candidateIds = targetId.includes("\x00") ? targetId.split("\x00") : [targetId];
1838
- // Scan the full stream once, bucket events by instance_id for the candidates.
1894
+ // Scan the stream tail once, bucket events by instance_id for the candidates.
1895
+ // Bounded read: the ledger grows without bound and a whole-file readFileSync
1896
+ // throws past V8's ~512MB string limit. A trace of an agent whose events all
1897
+ // predate the window is reported as truncated rather than crashing.
1839
1898
  const streamPath = resolve(root, ".harnery", "events.ndjson");
1840
1899
  const byId = new Map();
1841
- if (existsSync(streamPath)) {
1842
- for (const line of readFileSync(streamPath, "utf8").split("\n")) {
1843
- if (!line)
1900
+ const { text: streamText, truncated: streamTruncated } = readStreamTailBounded(streamPath, STREAM_SCAN_CAP_BYTES);
1901
+ for (const line of streamText.split("\n")) {
1902
+ if (!line)
1903
+ continue;
1904
+ try {
1905
+ const ev = JSON.parse(line);
1906
+ if (!ev.instance_id || !candidateIds.includes(ev.instance_id))
1844
1907
  continue;
1845
- try {
1846
- const ev = JSON.parse(line);
1847
- if (!ev.instance_id || !candidateIds.includes(ev.instance_id))
1848
- continue;
1849
- if (sinceMs && Date.parse(ev.ts) < sinceMs)
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
- }
1908
+ if (sinceMs && Date.parse(ev.ts) < sinceMs)
1909
+ continue;
1910
+ const arr = byId.get(ev.instance_id) ?? [];
1911
+ arr.push(ev);
1912
+ byId.set(ev.instance_id, arr);
1858
1913
  }
1914
+ catch {
1915
+ /* skip */
1916
+ }
1917
+ }
1918
+ if (streamTruncated) {
1919
+ 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
1920
  }
1860
1921
  // If the name mapped to multiple instances, trace the one with the latest event.
1861
1922
  let resolvedId = candidateIds[0];
@@ -2208,7 +2269,7 @@ function renderHealthBox(report) {
2208
2269
  ];
2209
2270
  const localTime = formatLocalTime(new Date(report.generated_at));
2210
2271
  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 + bp-session-teed contexts
2272
+ 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
2273
  if (report.anomalies.length > 0) {
2213
2274
  process.stdout.write("\n"); // lint-ok-emission: same chat-paste path
2214
2275
  for (const a of report.anomalies) {
@@ -2245,7 +2306,7 @@ function runHarnessProbe(id, opts) {
2245
2306
  // and `resolveOwner` here, so the probe reports exactly what the live hot
2246
2307
  // path resolves.
2247
2308
  const anchorTokens = new Set(["claude", "claude-code", "cursor", "codex"]);
2248
- const override = process.env.BP_AGENT_COORD_TEST_ANCHOR_PID;
2309
+ const override = process.env.HARNERY_AGENT_COORD_TEST_ANCHOR_PID;
2249
2310
  let anchorPid = override && Number(override) > 0 ? override : "";
2250
2311
  const chainParts = [];
2251
2312
  let walkPid = process.pid;
@@ -2348,12 +2409,12 @@ function runHarnessProbe(id, opts) {
2348
2409
  * dispatcher in an isolated sandbox.
2349
2410
  *
2350
2411
  * Sandbox isolation strategy:
2351
- * - mkdtempSync(tmpdir(), "bp-harness-probe-") creates a non-git tmp dir.
2412
+ * - mkdtempSync(tmpdir(), "harn-harness-probe-") creates a non-git tmp dir.
2352
2413
  * - The dispatcher's coord-root resolution falls back to
2353
- * `BP_COORD_ROOT_OVERRIDE` when git rev-parse fails. We set it to the sandbox.
2414
+ * `HARNERY_COORD_ROOT_OVERRIDE` when git rev-parse fails. We set it to the sandbox.
2354
2415
  * - We rewrite the payload's `cwd` field (Cursor cds to it) to the sandbox,
2355
2416
  * so real `.harnery/` never gets touched.
2356
- * - We set `BP_AGENT_COORD_OFF=0` explicitly so any user-side off-switch in
2417
+ * - We set `HARNERY_AGENT_COORD_OFF=0` explicitly so any user-side off-switch in
2357
2418
  * the environment doesn't mask adapter crashes.
2358
2419
  *
2359
2420
  * Sample shape: probe-meta wrapped (`_probe_meta.event` + `.payload`) OR bare
@@ -2420,7 +2481,7 @@ function replayHarnessSamples(harness, root, relativeSampleDir, filter) {
2420
2481
  StopFailure: "stop-failure",
2421
2482
  };
2422
2483
  const harnessFlag = harness === "claude_code" ? "claude-code" : harness;
2423
- const sandbox = mkdtempSync(join(tmpdir(), "bp-harness-probe-"));
2484
+ const sandbox = mkdtempSync(join(tmpdir(), "harn-harness-probe-"));
2424
2485
  const results = [];
2425
2486
  try {
2426
2487
  for (const file of fixtures) {
@@ -2464,10 +2525,10 @@ function replayHarnessSamples(harness, root, relativeSampleDir, filter) {
2464
2525
  timeout: 10_000,
2465
2526
  env: {
2466
2527
  ...process.env,
2467
- BP_COORD_ROOT_OVERRIDE: sandbox,
2468
- BP_AGENT_COORD_HARNESS: harness,
2469
- BP_AGENT_COORD_PLATFORM: harness,
2470
- BP_AGENT_COORD_OFF: "0",
2528
+ HARNERY_COORD_ROOT_OVERRIDE: sandbox,
2529
+ HARNERY_AGENT_COORD_HARNESS: harness,
2530
+ HARNERY_AGENT_COORD_PLATFORM: harness,
2531
+ HARNERY_AGENT_COORD_OFF: "0",
2471
2532
  },
2472
2533
  });
2473
2534
  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: "bp-browse-ai" });
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;AA0G1E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CAwJN"}
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"}
@@ -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: "bp-browse" });
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.checkVisibleNoAnnotate !== true) {
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.checkWidthNoAnnotate !== true;
182
- const annotateOverflow = overflow && opts.checkOverflowNoAnnotate !== true;
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.checkVisibleNoAnnotate !== true) {
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) {
@@ -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: "bp-cookies",
15
+ source: "harn-cookies",
16
16
  });
17
17
  }
18
18
  // Module-scoped emit assigned by registerCookiesCommand; the module-level
@@ -0,0 +1,4 @@
1
+ import type { Command } from "commander";
2
+ import type { EmitContext } from "../commander.js";
3
+ export declare function registerDecisionCommand(program: Command, emitParam: EmitContext): void;
4
+ //# sourceMappingURL=decision.d.ts.map
@@ -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"}