harnery 0.9.0 → 0.11.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 (211) hide show
  1. package/README.md +1 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +6 -0
  4. package/dist/commands/agents.d.ts +24 -0
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +244 -13
  7. package/dist/commands/backup.d.ts +5 -4
  8. package/dist/commands/backup.d.ts.map +1 -1
  9. package/dist/commands/backup.js +15 -14
  10. package/dist/commands/browse.d.ts.map +1 -1
  11. package/dist/commands/browse.js +27 -1
  12. package/dist/commands/claude-desktop.d.ts +19 -0
  13. package/dist/commands/claude-desktop.d.ts.map +1 -0
  14. package/dist/commands/claude-desktop.js +168 -0
  15. package/dist/commands/context.d.ts.map +1 -1
  16. package/dist/commands/context.js +149 -1
  17. package/dist/commands/doctor.d.ts.map +1 -1
  18. package/dist/commands/doctor.js +2 -3
  19. package/dist/commands/eml.d.ts +32 -0
  20. package/dist/commands/eml.d.ts.map +1 -1
  21. package/dist/commands/eml.js +16 -3
  22. package/dist/commands/harness.d.ts +11 -0
  23. package/dist/commands/harness.d.ts.map +1 -0
  24. package/dist/commands/harness.js +118 -0
  25. package/dist/commands/presence.d.ts +9 -4
  26. package/dist/commands/presence.d.ts.map +1 -1
  27. package/dist/commands/presence.js +88 -5
  28. package/dist/commands/relay.d.ts +9 -0
  29. package/dist/commands/relay.d.ts.map +1 -0
  30. package/dist/commands/relay.js +143 -0
  31. package/dist/commands/sync.d.ts.map +1 -1
  32. package/dist/commands/sync.js +5 -0
  33. package/dist/commands/workflow.d.ts.map +1 -1
  34. package/dist/commands/workflow.js +53 -17
  35. package/dist/core/agents/canonical-emit.d.ts +15 -0
  36. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  37. package/dist/core/agents/canonical-emit.js +41 -3
  38. package/dist/core/agents/cli.js +4 -2
  39. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  40. package/dist/core/agents/render/prompt-context.js +44 -3
  41. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  42. package/dist/core/agents/render/session-context.js +26 -1
  43. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  44. package/dist/core/agents/rules/commit-conflict.js +4 -37
  45. package/dist/core/agents/session-events.d.ts.map +1 -1
  46. package/dist/core/agents/session-events.js +16 -43
  47. package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
  48. package/dist/core/agents/state/heartbeat-projector.js +11 -2
  49. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  50. package/dist/core/agents/state/heartbeat-writer.js +3 -3
  51. package/dist/core/agents/state/names.d.ts +38 -3
  52. package/dist/core/agents/state/names.d.ts.map +1 -1
  53. package/dist/core/agents/state/names.js +46 -7
  54. package/dist/core/agents/state/pidmap.d.ts +5 -1
  55. package/dist/core/agents/state/pidmap.d.ts.map +1 -1
  56. package/dist/core/agents/state/pidmap.js +58 -2
  57. package/dist/core/agents/state/stale-sweep.d.ts +3 -2
  58. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  59. package/dist/core/agents/state/stale-sweep.js +6 -6
  60. package/dist/core/config.d.ts +68 -11
  61. package/dist/core/config.d.ts.map +1 -1
  62. package/dist/core/config.js +191 -25
  63. package/dist/core/context/index.d.ts +144 -0
  64. package/dist/core/context/index.d.ts.map +1 -0
  65. package/dist/core/context/index.js +380 -0
  66. package/dist/core/harnesses/bench.d.ts +29 -0
  67. package/dist/core/harnesses/bench.d.ts.map +1 -0
  68. package/dist/core/harnesses/bench.js +151 -0
  69. package/dist/core/harnesses/index.d.ts +9 -0
  70. package/dist/core/harnesses/index.d.ts.map +1 -0
  71. package/dist/core/harnesses/index.js +4 -0
  72. package/dist/core/harnesses/profiles.d.ts +62 -0
  73. package/dist/core/harnesses/profiles.d.ts.map +1 -0
  74. package/dist/core/harnesses/profiles.js +115 -0
  75. package/dist/core/harnesses/registry.d.ts +15 -0
  76. package/dist/core/harnesses/registry.d.ts.map +1 -0
  77. package/dist/core/harnesses/registry.js +113 -0
  78. package/dist/core/harnesses/types.d.ts +64 -0
  79. package/dist/core/harnesses/types.d.ts.map +1 -0
  80. package/dist/core/harnesses/types.js +20 -0
  81. package/dist/core/hooks/cli.js +280 -63
  82. package/dist/core/hooks/events/schema.d.ts +38 -1
  83. package/dist/core/hooks/events/schema.d.ts.map +1 -1
  84. package/dist/core/hooks/harness/events.d.ts.map +1 -1
  85. package/dist/core/hooks/harness/events.js +3 -0
  86. package/dist/core/hooks/harness/parse.d.ts +1 -1
  87. package/dist/core/hooks/harness/parse.d.ts.map +1 -1
  88. package/dist/core/hooks/harness/parse.js +4 -0
  89. package/dist/core/presence/blob.d.ts +40 -0
  90. package/dist/core/presence/blob.d.ts.map +1 -0
  91. package/dist/core/presence/blob.js +91 -0
  92. package/dist/core/presence/git.d.ts +68 -0
  93. package/dist/core/presence/git.d.ts.map +1 -0
  94. package/dist/core/presence/git.js +173 -0
  95. package/dist/core/presence/index.d.ts +74 -0
  96. package/dist/core/presence/index.d.ts.map +1 -0
  97. package/dist/core/presence/index.js +224 -0
  98. package/dist/core/presence/relay-client.d.ts +39 -0
  99. package/dist/core/presence/relay-client.d.ts.map +1 -0
  100. package/dist/core/presence/relay-client.js +295 -0
  101. package/dist/core/presence/relay-protocol.d.ts +94 -0
  102. package/dist/core/presence/relay-protocol.d.ts.map +1 -0
  103. package/dist/core/presence/relay-protocol.js +169 -0
  104. package/dist/core/workflow/billing.d.ts +1 -1
  105. package/dist/core/workflow/billing.d.ts.map +1 -1
  106. package/dist/core/workflow/billing.js +7 -1
  107. package/dist/core/workflow/engine.d.ts +5 -0
  108. package/dist/core/workflow/engine.d.ts.map +1 -1
  109. package/dist/core/workflow/engine.js +164 -16
  110. package/dist/core/workflow/harnesses.d.ts +3 -3
  111. package/dist/core/workflow/harnesses.d.ts.map +1 -1
  112. package/dist/core/workflow/harnesses.js +8 -17
  113. package/dist/core/workflow/index.d.ts +5 -0
  114. package/dist/core/workflow/index.d.ts.map +1 -0
  115. package/dist/core/workflow/index.js +3 -0
  116. package/dist/core/workflow/proof.d.ts +42 -0
  117. package/dist/core/workflow/proof.d.ts.map +1 -0
  118. package/dist/core/workflow/proof.js +342 -0
  119. package/dist/core/workflow/spawn-claude.d.ts +4 -1
  120. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  121. package/dist/core/workflow/spawn-claude.js +39 -18
  122. package/dist/core/workflow/spawn-codex.d.ts +4 -1
  123. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  124. package/dist/core/workflow/spawn-codex.js +44 -22
  125. package/dist/core/workflow/spawn-cursor.d.ts +4 -1
  126. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  127. package/dist/core/workflow/spawn-cursor.js +35 -16
  128. package/dist/core/workflow/types.d.ts +156 -2
  129. package/dist/core/workflow/types.d.ts.map +1 -1
  130. package/dist/core/workflow/types.js +1 -1
  131. package/dist/lib/browser/client.d.ts +16 -0
  132. package/dist/lib/browser/client.d.ts.map +1 -1
  133. package/dist/lib/browser/client.js +22 -0
  134. package/dist/lib/browser/index.d.ts +1 -0
  135. package/dist/lib/browser/index.d.ts.map +1 -1
  136. package/dist/lib/browser/index.js +1 -0
  137. package/dist/lib/browser/launch-args.d.ts +21 -0
  138. package/dist/lib/browser/launch-args.d.ts.map +1 -0
  139. package/dist/lib/browser/launch-args.js +32 -0
  140. package/dist/lib/claude-desktop.d.ts +105 -0
  141. package/dist/lib/claude-desktop.d.ts.map +1 -0
  142. package/dist/lib/claude-desktop.js +217 -0
  143. package/dist/lib/docs-lint.d.ts.map +1 -1
  144. package/dist/lib/docs-lint.js +5 -2
  145. package/dist/lib/identities/assume.d.ts +51 -0
  146. package/dist/lib/identities/assume.d.ts.map +1 -0
  147. package/dist/lib/identities/assume.js +274 -0
  148. package/dist/lib/identities/index.d.ts +7 -7
  149. package/dist/lib/identities/index.d.ts.map +1 -1
  150. package/dist/lib/identities/index.js +24 -24
  151. package/dist/lib/instructions/templates.d.ts.map +1 -1
  152. package/dist/lib/instructions/templates.js +15 -2
  153. package/package.json +22 -6
  154. package/schemas/config.schema.json +88 -19
  155. package/src/commander.ts +6 -0
  156. package/src/commands/agents.ts +280 -14
  157. package/src/commands/backup.ts +17 -16
  158. package/src/commands/browse.ts +32 -0
  159. package/src/commands/claude-desktop.ts +215 -0
  160. package/src/commands/context.ts +174 -1
  161. package/src/commands/doctor.ts +2 -3
  162. package/src/commands/eml.ts +22 -4
  163. package/src/commands/harness.ts +147 -0
  164. package/src/commands/presence.ts +111 -5
  165. package/src/commands/relay.ts +160 -0
  166. package/src/commands/sync.ts +4 -0
  167. package/src/commands/workflow.ts +62 -19
  168. package/src/core/agents/canonical-emit.ts +41 -3
  169. package/src/core/agents/cli.ts +4 -2
  170. package/src/core/agents/render/prompt-context.ts +48 -3
  171. package/src/core/agents/render/session-context.ts +27 -1
  172. package/src/core/agents/rules/commit-conflict.ts +4 -33
  173. package/src/core/agents/session-events.ts +15 -39
  174. package/src/core/agents/state/heartbeat-projector.ts +10 -1
  175. package/src/core/agents/state/heartbeat-writer.ts +3 -2
  176. package/src/core/agents/state/names.ts +68 -9
  177. package/src/core/agents/state/pidmap.ts +59 -2
  178. package/src/core/agents/state/stale-sweep.ts +6 -11
  179. package/src/core/config.ts +248 -24
  180. package/src/core/context/index.ts +575 -0
  181. package/src/core/harnesses/bench.ts +214 -0
  182. package/src/core/harnesses/index.ts +31 -0
  183. package/src/core/harnesses/profiles.ts +141 -0
  184. package/src/core/harnesses/registry.ts +140 -0
  185. package/src/core/harnesses/types.ts +90 -0
  186. package/src/core/hooks/cli.ts +331 -62
  187. package/src/core/hooks/events/schema.ts +68 -0
  188. package/src/core/hooks/harness/events.ts +3 -0
  189. package/src/core/hooks/harness/parse.ts +7 -1
  190. package/src/core/presence/blob.ts +125 -0
  191. package/src/core/presence/git.ts +191 -0
  192. package/src/core/presence/index.ts +274 -0
  193. package/src/core/presence/relay-client.ts +322 -0
  194. package/src/core/presence/relay-protocol.ts +246 -0
  195. package/src/core/workflow/billing.ts +8 -2
  196. package/src/core/workflow/engine.ts +182 -16
  197. package/src/core/workflow/harnesses.ts +14 -19
  198. package/src/core/workflow/index.ts +43 -0
  199. package/src/core/workflow/proof.ts +434 -0
  200. package/src/core/workflow/spawn-claude.ts +39 -20
  201. package/src/core/workflow/spawn-codex.ts +47 -28
  202. package/src/core/workflow/spawn-cursor.ts +37 -18
  203. package/src/core/workflow/types.ts +176 -2
  204. package/src/lib/browser/client.ts +37 -0
  205. package/src/lib/browser/index.ts +1 -0
  206. package/src/lib/browser/launch-args.ts +33 -0
  207. package/src/lib/claude-desktop.ts +293 -0
  208. package/src/lib/docs-lint.ts +5 -2
  209. package/src/lib/identities/assume.ts +363 -0
  210. package/src/lib/identities/index.ts +28 -24
  211. package/src/lib/instructions/templates.ts +16 -2
@@ -2,7 +2,7 @@ import { mkdirSync, statSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, isAbsolute, resolve } from "node:path";
4
4
  import { resolveBinName } from "../core/config.js";
5
- import { Browser, captureDevOverlay, } from "../lib/browser/index.js";
5
+ import { Browser, captureDevOverlay, wslHeadedLaunchArgs, } from "../lib/browser/index.js";
6
6
  import { diffAgainstBaseline, saveBaseline, } from "../lib/browser/visual-diff.js";
7
7
  import { CookieJar } from "../lib/cookies/index.js";
8
8
  /**
@@ -62,6 +62,11 @@ export function registerBrowseCommand(program, emitParam, context) {
62
62
  .option("--viewport <preset|WxH>", "Viewport: mobile (390x844), tablet (820x1180), desktop (1280x800), hd (1920x1080), or explicit '1920x1080'", "desktop")
63
63
  .option("--login", "Headed mode for one-time auth flow (cookies persist in profile)")
64
64
  .option("--headed", "Headed mode for one-off (no auth-flow framing)")
65
+ .option("--browser-arg <flag>", "Extra Chromium launch flag, passed straight to the browser (repeatable). " +
66
+ "e.g. --browser-arg --disable-gpu. Also settable machine-wide via the " +
67
+ "HARNERY_BROWSER_ARGS env var (whitespace-separated). Under WSL, headed " +
68
+ "launches auto-add --disable-gpu to fix blank-window paint; opt out with " +
69
+ "HARNERY_BROWSER_NO_WSL_DEFAULTS=1.", (value, prev = []) => [...prev, value], [])
65
70
  .option("--no-cookies", "Skip cookie-jar attach and persist")
66
71
  .option("--store <path>", `Cookie store path (default ${DEFAULT_STORE})`)
67
72
  .option("--profile <dir>", `Persistent Chromium profile dir (default ${DEFAULT_PROFILE})`)
@@ -137,6 +142,7 @@ async function runBrowse(url, opts, context) {
137
142
  waitUntil: opts.waitUntil,
138
143
  recordHarPath: opts.networkHar ? resolve(opts.networkHar) : undefined,
139
144
  extraHeaders: context?.extraHeaders,
145
+ launchArgs: resolveLaunchArgs(opts, Boolean(headed)),
140
146
  });
141
147
  // Print mode: --snapshot / --html / --json all suppress file writes.
142
148
  const printMode = opts.snapshot || opts.html || opts.json;
@@ -598,6 +604,26 @@ function summarizeDiagnostics(diag) {
598
604
  failedRequests: diag.failedRequests,
599
605
  };
600
606
  }
607
+ /**
608
+ * Merge Chromium launch flags from three sources, in order, de-duped:
609
+ * 1. WSLg headed default (`--disable-gpu`) — auto-applied for headed launches
610
+ * under WSL so the window actually paints. Opt out with
611
+ * HARNERY_BROWSER_NO_WSL_DEFAULTS=1.
612
+ * 2. HARNERY_BROWSER_ARGS env (whitespace-separated) — a machine-wide default.
613
+ * 3. --browser-arg flags on this invocation (repeatable).
614
+ */
615
+ function resolveLaunchArgs(opts, headed) {
616
+ const args = [];
617
+ if (headed && !process.env.HARNERY_BROWSER_NO_WSL_DEFAULTS) {
618
+ args.push(...wslHeadedLaunchArgs());
619
+ }
620
+ const envArgs = process.env.HARNERY_BROWSER_ARGS?.trim();
621
+ if (envArgs)
622
+ args.push(...envArgs.split(/\s+/));
623
+ if (opts.browserArg && opts.browserArg.length > 0)
624
+ args.push(...opts.browserArg);
625
+ return [...new Set(args)];
626
+ }
601
627
  function parseViewport(spec) {
602
628
  const preset = VIEWPORT_PRESETS[spec.toLowerCase()];
603
629
  if (preset)
@@ -0,0 +1,19 @@
1
+ import type { Command } from "commander";
2
+ import type { EmitContext } from "../commander.js";
3
+ /**
4
+ * `claude-desktop`: make Claude desktop-app sessions survive account
5
+ * switches.
6
+ *
7
+ * The desktop app scopes its Claude Code session sidebar per signed-in
8
+ * account (plain JSON entry files under
9
+ * `<dataDir>/claude-code-sessions/<account-uuid>/<env-id>/`), while the
10
+ * transcripts live account-agnostically under `~/.claude/projects/`. A user
11
+ * who hops accounts (e.g. when one hits its usage limit) loses sidebar
12
+ * access to every session the other account created — even though the data
13
+ * is all still on disk. `mirror` copies the entry files across account
14
+ * directories so each account's sidebar lists the union; restarting the
15
+ * desktop app picks the copies up, and opening one resumes with full
16
+ * history.
17
+ */
18
+ export declare function registerClaudeDesktopCommand(program: Command, emit: EmitContext): void;
19
+ //# sourceMappingURL=claude-desktop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-desktop.d.ts","sourceRoot":"","sources":["../../src/commands/claude-desktop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAUnD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAgKtF"}
@@ -0,0 +1,168 @@
1
+ import { applyMirror, findDesktopDataDirs, listAccounts, planMirror, readCliAccount, } from "../lib/claude-desktop.js";
2
+ /**
3
+ * `claude-desktop`: make Claude desktop-app sessions survive account
4
+ * switches.
5
+ *
6
+ * The desktop app scopes its Claude Code session sidebar per signed-in
7
+ * account (plain JSON entry files under
8
+ * `<dataDir>/claude-code-sessions/<account-uuid>/<env-id>/`), while the
9
+ * transcripts live account-agnostically under `~/.claude/projects/`. A user
10
+ * who hops accounts (e.g. when one hits its usage limit) loses sidebar
11
+ * access to every session the other account created — even though the data
12
+ * is all still on disk. `mirror` copies the entry files across account
13
+ * directories so each account's sidebar lists the union; restarting the
14
+ * desktop app picks the copies up, and opening one resumes with full
15
+ * history.
16
+ */
17
+ export function registerClaudeDesktopCommand(program, emit) {
18
+ const cmd = program
19
+ .command("claude-desktop")
20
+ .description("Claude desktop-app session index: list accounts/sessions, mirror sessions across accounts")
21
+ .action(() => {
22
+ const dirs = requireDataDirs(emit, undefined);
23
+ for (const dir of dirs) {
24
+ emit.data({
25
+ data_dir: dir,
26
+ accounts: listAccounts(dir).map((a) => accountSummary(a)),
27
+ cli_account: readCliAccount(),
28
+ });
29
+ }
30
+ });
31
+ cmd
32
+ .command("accounts")
33
+ .description("List account directories + session counts (labels the CLI's account when known)")
34
+ .option("--data-dir <path>", "Explicit desktop-app data directory")
35
+ .action((opts) => {
36
+ const dirs = requireDataDirs(emit, opts.dataDir);
37
+ const cli = readCliAccount();
38
+ emit.data(dirs.map((dir) => ({
39
+ data_dir: dir,
40
+ accounts: listAccounts(dir).map((a) => ({
41
+ ...accountSummary(a),
42
+ is_cli_account: cli !== null && a.accountUuid === cli.accountUuid,
43
+ cli_email: cli !== null && a.accountUuid === cli.accountUuid ? cli.email : null,
44
+ })),
45
+ })));
46
+ });
47
+ cmd
48
+ .command("sessions")
49
+ .description("List session entries across every account (newest activity first)")
50
+ .option("--data-dir <path>", "Explicit desktop-app data directory")
51
+ .option("--account <uuid-prefix>", "Only this account (uuid prefix match)")
52
+ .option("--archived", "Include archived entries")
53
+ .action((opts) => {
54
+ const dirs = requireDataDirs(emit, opts.dataDir);
55
+ const rows = [];
56
+ for (const dir of dirs) {
57
+ for (const account of listAccounts(dir)) {
58
+ if (opts.account && !account.accountUuid.startsWith(opts.account))
59
+ continue;
60
+ for (const e of account.entries) {
61
+ if (e.isArchived && !opts.archived)
62
+ continue;
63
+ rows.push({
64
+ account_uuid: account.accountUuid,
65
+ cli_session_id: e.cliSessionId,
66
+ title: e.title,
67
+ cwd: e.cwd,
68
+ model: e.model,
69
+ archived: e.isArchived,
70
+ last_activity_at: e.lastActivityAt,
71
+ });
72
+ }
73
+ }
74
+ }
75
+ rows.sort((a, b) => (b.last_activity_at ?? 0) - (a.last_activity_at ?? 0));
76
+ emit.data(rows);
77
+ });
78
+ cmd
79
+ .command("mirror")
80
+ .description("Copy session entries across accounts so each sidebar lists the union. " +
81
+ "Dry-run by default; --yes applies. Restart the desktop app afterward to pick up copies.")
82
+ .option("--data-dir <path>", "Explicit desktop-app data directory")
83
+ .option("--session <id-or-title>", "Only sessions matching this cliSessionId or title substring (repeatable)", collect, [])
84
+ .option("--all", "Mirror every (non-archived) session")
85
+ .option("--to <uuid-prefix>", "Only copy INTO accounts matching this prefix (repeatable)", collect, [])
86
+ .option("--from <uuid-prefix>", "Only copy FROM accounts matching this prefix (repeatable)", collect, [])
87
+ .option("--include-archived", "Also mirror archived entries")
88
+ .option("--yes", "Apply the plan (default is dry-run)")
89
+ .action((opts) => {
90
+ if (!opts.all && opts.session.length === 0) {
91
+ emit.error({
92
+ code: "no_selection",
93
+ message: "pass --session <id-or-title> (repeatable) or --all",
94
+ });
95
+ process.exit(2);
96
+ }
97
+ const dirs = requireDataDirs(emit, opts.dataDir);
98
+ for (const dir of dirs) {
99
+ const accounts = listAccounts(dir);
100
+ if (accounts.length < 2) {
101
+ emit.error({
102
+ code: "single_account",
103
+ message: `only ${accounts.length} account dir(s) under ${dir} — nothing to mirror across`,
104
+ });
105
+ process.exit(2);
106
+ }
107
+ const plan = planMirror(accounts, {
108
+ to: opts.to,
109
+ from: opts.from,
110
+ sessions: opts.all ? undefined : opts.session,
111
+ includeArchived: opts.includeArchived,
112
+ });
113
+ const planned = plan.actions.map((a) => ({
114
+ title: a.entry.title,
115
+ cli_session_id: a.entry.cliSessionId,
116
+ to_account: a.targetAccountUuid,
117
+ to_file: a.to,
118
+ }));
119
+ if (!opts.yes) {
120
+ emit.data({
121
+ data_dir: dir,
122
+ dry_run: true,
123
+ planned,
124
+ skipped_existing: plan.skippedExisting,
125
+ skipped_archived: plan.skippedArchived,
126
+ hint: plan.actions.length > 0 ? "re-run with --yes to apply" : "nothing to copy",
127
+ });
128
+ continue;
129
+ }
130
+ const { copied } = applyMirror(plan);
131
+ emit.data({
132
+ data_dir: dir,
133
+ dry_run: false,
134
+ copied,
135
+ planned,
136
+ skipped_existing: plan.skippedExisting,
137
+ skipped_archived: plan.skippedArchived,
138
+ hint: copied > 0
139
+ ? "fully quit the Claude desktop app (tray icon, not the X) and relaunch to see the sessions"
140
+ : "nothing to copy",
141
+ });
142
+ }
143
+ });
144
+ }
145
+ function collect(value, previous) {
146
+ return previous.concat([value]);
147
+ }
148
+ function accountSummary(a) {
149
+ return {
150
+ account_uuid: a.accountUuid,
151
+ sessions: a.entries.length,
152
+ archived: a.entries.filter((e) => e.isArchived).length,
153
+ latest_title: a.entries[0]?.title ?? null,
154
+ latest_activity_at: a.entries[0]?.lastActivityAt ?? null,
155
+ };
156
+ }
157
+ function requireDataDirs(emit, explicit) {
158
+ const dirs = findDesktopDataDirs(explicit);
159
+ if (dirs.length === 0) {
160
+ emit.error({
161
+ code: "not_found",
162
+ message: "no Claude desktop-app data directory with a claude-code-sessions/ index found " +
163
+ "(is the desktop app installed? pass --data-dir or set HARNERY_CLAUDE_DESKTOP_DIR)",
164
+ });
165
+ process.exit(2);
166
+ }
167
+ return dirs;
168
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/commands/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAS1E;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CAiDN"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/commands/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAiB1E;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CAqKN"}
@@ -1,3 +1,5 @@
1
+ import { monorepoRoot, readHeartbeat, resolveOwner } from "../core/agents/index.js";
2
+ import { checkpointContext, readContextState, readLatestCapsule, } from "../core/context/index.js";
1
3
  import { buildContext, DEFAULT_SECTIONS, OPT_IN_SECTIONS, } from "../lib/context/index.js";
2
4
  /**
3
5
  * `harn context`: one-shot orientation snapshot.
@@ -7,7 +9,7 @@ import { buildContext, DEFAULT_SECTIONS, OPT_IN_SECTIONS, } from "../lib/context
7
9
  * dispatching subagents. Fail-open per section.
8
10
  */
9
11
  export function registerContextCommand(program, emit, context) {
10
- program
12
+ const command = program
11
13
  .command("context")
12
14
  .description(`One-shot orientation snapshot: self, repo, submodules, peers, recent commits. Default sections: ${DEFAULT_SECTIONS.join(" / ")}. Opt-in: ${OPT_IN_SECTIONS.join(" / ")}.`)
13
15
  .option("--section <name>", `Limit to specific sections (comma-list or repeated; valid: ${[...DEFAULT_SECTIONS, ...OPT_IN_SECTIONS].join(", ")})`, collectSections, [])
@@ -37,6 +39,152 @@ export function registerContextCommand(program, emit, context) {
37
39
  process.exit(1);
38
40
  }
39
41
  });
42
+ command
43
+ .command("status")
44
+ .description("Show context-continuity phase, generation, and latest telemetry")
45
+ .option("--session <id>", "Session id (defaults to the current agent heartbeat)")
46
+ .option("--instance <id>", "Agent instance id (defaults to the current owner)")
47
+ .option("--json", "Structured JSON output")
48
+ .action((opts) => {
49
+ try {
50
+ const json = opts.json || Boolean(command.opts().json);
51
+ const identity = resolveContinuityIdentity(context, opts);
52
+ const state = readContextState(identity.coordRoot, identity.sessionId);
53
+ if (json) {
54
+ emit.config({ format: "json" });
55
+ emit.data({ state, identity });
56
+ return;
57
+ }
58
+ if (!state) {
59
+ emit.text(`No context continuity state for session ${identity.sessionId}.\n`);
60
+ return;
61
+ }
62
+ const sample = state.latest_context;
63
+ const usage = sample
64
+ ? `${sample.used_tokens ?? "?"}/${sample.window_tokens ?? "?"} tokens${sample.used_percent === undefined ? "" : ` (${sample.used_percent}%)`}`
65
+ : "not reported by harness";
66
+ emit.text(`${[
67
+ `Session: ${state.session_id}`,
68
+ `Phase: ${state.phase}`,
69
+ `Generation: ${state.generation}`,
70
+ `Telemetry: ${usage}`,
71
+ `Capsule: ${state.latest_capsule ?? "none"}`,
72
+ state.degraded_reason ? `Degraded: ${state.degraded_reason}` : "",
73
+ ]
74
+ .filter(Boolean)
75
+ .join("\n")}\n`);
76
+ }
77
+ catch (err) {
78
+ emitCommandError(emit, "context_status_failed", err);
79
+ }
80
+ });
81
+ command
82
+ .command("checkpoint")
83
+ .description("Create a durable context-continuity capsule for the current work")
84
+ .option("--session <id>", "Session id (defaults to the current agent heartbeat)")
85
+ .option("--instance <id>", "Agent instance id (defaults to the current owner)")
86
+ .option("--harness <id>", "claude-code, codex, or cursor (inferred from heartbeat)")
87
+ .option("--reason <reason>", "manual, pressure, pre_compact, or session_end", "manual")
88
+ .option("--note <text>", "Short continuation note for the recovered agent")
89
+ .option("--json", "Structured JSON output")
90
+ .action((opts) => {
91
+ try {
92
+ const json = opts.json || Boolean(command.opts().json);
93
+ const reason = parseCheckpointReason(opts.reason);
94
+ const identity = resolveContinuityIdentity(context, opts);
95
+ if (!identity.harness) {
96
+ throw new Error("could not infer the harness; pass --harness claude-code|codex|cursor");
97
+ }
98
+ const result = checkpointContext(identity.coordRoot, {
99
+ sessionId: identity.sessionId,
100
+ instanceId: identity.instanceId,
101
+ harness: identity.harness,
102
+ cwd: context?.repoRoot ?? process.cwd(),
103
+ reason,
104
+ continuationNote: opts.note,
105
+ });
106
+ const output = {
107
+ capsule_id: result.capsule.capsule_id,
108
+ generation: result.capsule.generation,
109
+ path: result.state.latest_capsule,
110
+ phase: result.state.phase,
111
+ reused: result.reused,
112
+ };
113
+ if (json) {
114
+ emit.config({ format: "json" });
115
+ emit.data(output);
116
+ return;
117
+ }
118
+ emit.text(`Checkpointed context generation ${output.generation}: ${output.path ?? result.path}\n`);
119
+ }
120
+ catch (err) {
121
+ emitCommandError(emit, "context_checkpoint_failed", err);
122
+ }
123
+ });
124
+ command
125
+ .command("show")
126
+ .description("Show the latest durable context-continuity capsule")
127
+ .option("--session <id>", "Session id (defaults to the current agent heartbeat)")
128
+ .option("--instance <id>", "Agent instance id (defaults to the current owner)")
129
+ .option("--json", "Structured JSON output")
130
+ .action((opts) => {
131
+ try {
132
+ const json = opts.json || Boolean(command.opts().json);
133
+ const identity = resolveContinuityIdentity(context, opts);
134
+ const capsule = readLatestCapsule(identity.coordRoot, identity.sessionId);
135
+ if (!capsule) {
136
+ throw new Error(`no context capsule exists for session ${identity.sessionId}`);
137
+ }
138
+ if (json)
139
+ emit.config({ format: "json" });
140
+ emit.data(capsule);
141
+ }
142
+ catch (err) {
143
+ emitCommandError(emit, "context_show_failed", err);
144
+ }
145
+ });
146
+ }
147
+ function resolveContinuityIdentity(context, opts) {
148
+ const coordRoot = context?.resolveCoordRoot?.() ?? context?.repoRoot ?? monorepoRoot();
149
+ if (!coordRoot)
150
+ throw new Error("could not resolve a project containing .harnery/");
151
+ const instanceId = opts.instance ?? resolveOwner();
152
+ if (!instanceId) {
153
+ throw new Error("could not resolve the current agent; pass --instance and --session");
154
+ }
155
+ const heartbeat = readHeartbeat(instanceId);
156
+ const sessionId = opts.session ?? heartbeat?.session_id ?? instanceId;
157
+ const harness = opts.harness
158
+ ? parseHarness(opts.harness)
159
+ : harnessFromPlatform(heartbeat?.platform);
160
+ return { coordRoot, instanceId, sessionId, harness };
161
+ }
162
+ function harnessFromPlatform(platform) {
163
+ if (platform === "claude_code" || platform === "claude-code")
164
+ return "claude-code";
165
+ if (platform === "cursor")
166
+ return "cursor";
167
+ if (platform === "codex")
168
+ return "codex";
169
+ return null;
170
+ }
171
+ function parseHarness(value) {
172
+ if (value === "claude-code" || value === "codex" || value === "cursor")
173
+ return value;
174
+ throw new Error(`invalid harness "${value}"; expected claude-code, codex, or cursor`);
175
+ }
176
+ function parseCheckpointReason(value) {
177
+ if (value === "manual" ||
178
+ value === "pressure" ||
179
+ value === "pre_compact" ||
180
+ value === "session_end") {
181
+ return value;
182
+ }
183
+ throw new Error(`invalid reason "${value}"; expected manual, pressure, pre_compact, or session_end`);
184
+ }
185
+ function emitCommandError(emit, code, err) {
186
+ emit.error({ code, message: err instanceof Error ? err.message : String(err) });
187
+ emit.setExitCode(1);
40
188
  }
41
189
  function collectSections(value, prev) {
42
190
  const all = [...DEFAULT_SECTIONS, ...OPT_IN_SECTIONS];
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAYnD,KAAK,QAAQ,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,UAAU,KAAK;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAOD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAmD/E;AAED,wBAAgB,SAAS,IAAI,KAAK,EAAE,CAgBnC"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAanD,KAAK,QAAQ,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,UAAU,KAAK;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAOD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAmD/E;AAED,wBAAgB,SAAS,IAAI,KAAK,EAAE,CAcnC"}
@@ -15,6 +15,7 @@ import { existsSync } from "node:fs";
15
15
  import os from "node:os";
16
16
  import path from "node:path";
17
17
  import { resolveBinName, ripgrepAutoInstall } from "../core/config.js";
18
+ import { BUILTIN_HARNESS_IDS } from "../core/harnesses/index.js";
18
19
  import { loadHarnessWiring } from "../core/hooks/harness/wiring.js";
19
20
  import { probeBilling } from "../core/workflow/billing.js";
20
21
  import { HARNESS_BINARIES, HARNESS_INSTALL_HINTS, HARNESS_LOGIN_HINTS, } from "../core/workflow/harnesses.js";
@@ -71,9 +72,7 @@ export function runChecks() {
71
72
  checkRipgrep(),
72
73
  checkHarneryDir(),
73
74
  checkHarnessHooks(),
74
- checkWorkflowHarness("claude-code"),
75
- checkWorkflowHarness("codex"),
76
- checkWorkflowHarness("cursor"),
75
+ ...BUILTIN_HARNESS_IDS.map(checkWorkflowHarness),
77
76
  checkRestic(),
78
77
  checkRclone(),
79
78
  checkPlaywright(),
@@ -1,4 +1,36 @@
1
1
  import type { Command } from "commander";
2
+ import { type ParsedMail } from "mailparser";
2
3
  import type { EmitContext } from "../commander.js";
4
+ /**
5
+ * `eml` - Parse Gmail .eml thread exports into clean chronological markdown.
6
+ *
7
+ * Gmail exports a thread as a single .eml where older messages are nested
8
+ * inside `<div class="gmail_quote">` blocks with attribution lines in
9
+ * `<div class="gmail_attr">`. This command reconstructs the full thread
10
+ * in chronological order. Falls back to plain-text quote parsing (`> `
11
+ * prefixes + "On ... wrote:") when HTML is unavailable. Uses the injected
12
+ * EmitContext so composed and standalone consumers share one code path.
13
+ */
14
+ interface ThreadMessage {
15
+ from: string;
16
+ date: Date | null;
17
+ body: string;
18
+ }
3
19
  export declare function registerEmlCommand(program: Command, emit: EmitContext): void;
20
+ export interface EmlOpts {
21
+ output?: string;
22
+ format: string;
23
+ headers?: boolean;
24
+ attachments?: boolean;
25
+ }
26
+ /**
27
+ * Extract individual messages from the parsed email.
28
+ * Strategy: use HTML body (Gmail quote structure) first, fall back to plain text.
29
+ */
30
+ export declare function extractThread(parsed: ParsedMail): ThreadMessage[];
31
+ /**
32
+ * Render the thread as markdown.
33
+ */
34
+ export declare function renderMarkdown(parsed: ParsedMail, messages: ThreadMessage[], opts: EmlOpts): string;
35
+ export {};
4
36
  //# sourceMappingURL=eml.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"eml.d.ts","sourceRoot":"","sources":["../../src/commands/eml.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAmBnD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAkB5E"}
1
+ {"version":3,"file":"eml.d.ts","sourceRoot":"","sources":["../../src/commands/eml.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,KAAK,UAAU,EAAgB,MAAM,YAAY,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD;;;;;;;;;GASG;AAEH,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAkB5E;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAuCD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,aAAa,EAAE,CAYjE;AA4VD;;GAEG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,aAAa,EAAE,EACzB,IAAI,EAAE,OAAO,GACZ,MAAM,CAyDR"}
@@ -7,7 +7,7 @@ export function registerEmlCommand(program, emit) {
7
7
  .argument("<file>", "Path to the .eml file")
8
8
  .option("-o, --output <path>", "Write output to a file instead of stdout")
9
9
  .option("--format <type>", "Output format: markdown, json", "markdown")
10
- .option("--headers", "Include full email headers per message")
10
+ .option("--headers", "Include the source message's full email headers")
11
11
  .option("--attachments", "List attachment filenames and sizes")
12
12
  .action(async (file, opts) => {
13
13
  try {
@@ -58,7 +58,7 @@ async function handleEml(file, opts, emit) {
58
58
  * Extract individual messages from the parsed email.
59
59
  * Strategy: use HTML body (Gmail quote structure) first, fall back to plain text.
60
60
  */
61
- function extractThread(parsed) {
61
+ export function extractThread(parsed) {
62
62
  if (parsed.html && typeof parsed.html === "string") {
63
63
  const messages = extractFromHtml(parsed.html, parsed);
64
64
  if (messages.length > 0)
@@ -369,7 +369,7 @@ function formatAddress(addr) {
369
369
  /**
370
370
  * Render the thread as markdown.
371
371
  */
372
- function renderMarkdown(parsed, messages, opts) {
372
+ export function renderMarkdown(parsed, messages, opts) {
373
373
  const subject = parsed.subject ?? "Untitled Thread";
374
374
  const participants = new Map();
375
375
  for (const m of messages) {
@@ -384,6 +384,19 @@ function renderMarkdown(parsed, messages, opts) {
384
384
  lines.push(`**Participants:** ${[...participants.keys()].join(", ")}`);
385
385
  lines.push(`**Date range:** ${minDate} to ${maxDate}`);
386
386
  lines.push(`**Messages:** ${messages.length}`);
387
+ if (opts.headers && parsed.headerLines?.length) {
388
+ // A Gmail thread export is a SINGLE .eml (older messages are quoted HTML,
389
+ // not separate MIME parts), so there is one real header set — the source
390
+ // message's. Render it verbatim.
391
+ lines.push("");
392
+ lines.push("**Source headers:**");
393
+ lines.push("");
394
+ lines.push("```");
395
+ for (const h of parsed.headerLines) {
396
+ lines.push(h.line);
397
+ }
398
+ lines.push("```");
399
+ }
387
400
  if (opts.attachments && parsed.attachments?.length) {
388
401
  lines.push("");
389
402
  lines.push("**Attachments:**");
@@ -0,0 +1,11 @@
1
+ import type { Command } from "commander";
2
+ import type { EmitContext } from "../commander.js";
3
+ import { type BenchResult, type HarnessBenchReport } from "../core/harnesses/index.js";
4
+ import type { HarnessProfile } from "../core/harnesses/types.js";
5
+ export declare function registerHarnessCommand(program: Command, emit: EmitContext): void;
6
+ export declare function renderProfileTable(profiles: readonly HarnessProfile[]): string;
7
+ export declare function renderProfile(profile: HarnessProfile): string;
8
+ export declare function renderBenchReport(report: HarnessBenchReport): string;
9
+ /** Kept exported so command tests can assert row semantics without model calls. */
10
+ export type { BenchResult };
11
+ //# sourceMappingURL=harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../src/commands/harness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,kBAAkB,EAExB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAYjE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA6DhF;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAU9E;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAkB7D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAcpE;AAgBD,mFAAmF;AACnF,YAAY,EAAE,WAAW,EAAE,CAAC"}