harnery 0.9.0 → 0.10.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 (201) 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 +8 -15
  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.map +1 -1
  108. package/dist/core/workflow/engine.js +11 -2
  109. package/dist/core/workflow/harnesses.d.ts +3 -3
  110. package/dist/core/workflow/harnesses.d.ts.map +1 -1
  111. package/dist/core/workflow/harnesses.js +8 -17
  112. package/dist/core/workflow/spawn-claude.d.ts +4 -1
  113. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  114. package/dist/core/workflow/spawn-claude.js +39 -18
  115. package/dist/core/workflow/spawn-codex.d.ts +4 -1
  116. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  117. package/dist/core/workflow/spawn-codex.js +44 -22
  118. package/dist/core/workflow/spawn-cursor.d.ts +4 -1
  119. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  120. package/dist/core/workflow/spawn-cursor.js +35 -16
  121. package/dist/core/workflow/types.d.ts +9 -2
  122. package/dist/core/workflow/types.d.ts.map +1 -1
  123. package/dist/lib/browser/client.d.ts +16 -0
  124. package/dist/lib/browser/client.d.ts.map +1 -1
  125. package/dist/lib/browser/client.js +22 -0
  126. package/dist/lib/browser/index.d.ts +1 -0
  127. package/dist/lib/browser/index.d.ts.map +1 -1
  128. package/dist/lib/browser/index.js +1 -0
  129. package/dist/lib/browser/launch-args.d.ts +21 -0
  130. package/dist/lib/browser/launch-args.d.ts.map +1 -0
  131. package/dist/lib/browser/launch-args.js +32 -0
  132. package/dist/lib/claude-desktop.d.ts +105 -0
  133. package/dist/lib/claude-desktop.d.ts.map +1 -0
  134. package/dist/lib/claude-desktop.js +217 -0
  135. package/dist/lib/docs-lint.d.ts.map +1 -1
  136. package/dist/lib/docs-lint.js +5 -2
  137. package/dist/lib/identities/assume.d.ts +51 -0
  138. package/dist/lib/identities/assume.d.ts.map +1 -0
  139. package/dist/lib/identities/assume.js +274 -0
  140. package/dist/lib/identities/index.d.ts +7 -7
  141. package/dist/lib/identities/index.d.ts.map +1 -1
  142. package/dist/lib/identities/index.js +24 -24
  143. package/dist/lib/instructions/templates.d.ts.map +1 -1
  144. package/dist/lib/instructions/templates.js +15 -2
  145. package/package.json +12 -1
  146. package/schemas/config.schema.json +88 -19
  147. package/src/commander.ts +6 -0
  148. package/src/commands/agents.ts +280 -14
  149. package/src/commands/backup.ts +17 -16
  150. package/src/commands/browse.ts +32 -0
  151. package/src/commands/claude-desktop.ts +215 -0
  152. package/src/commands/context.ts +174 -1
  153. package/src/commands/doctor.ts +2 -3
  154. package/src/commands/eml.ts +22 -4
  155. package/src/commands/harness.ts +147 -0
  156. package/src/commands/presence.ts +111 -5
  157. package/src/commands/relay.ts +160 -0
  158. package/src/commands/sync.ts +4 -0
  159. package/src/commands/workflow.ts +9 -18
  160. package/src/core/agents/canonical-emit.ts +41 -3
  161. package/src/core/agents/cli.ts +4 -2
  162. package/src/core/agents/render/prompt-context.ts +48 -3
  163. package/src/core/agents/render/session-context.ts +27 -1
  164. package/src/core/agents/rules/commit-conflict.ts +4 -33
  165. package/src/core/agents/session-events.ts +15 -39
  166. package/src/core/agents/state/heartbeat-projector.ts +10 -1
  167. package/src/core/agents/state/heartbeat-writer.ts +3 -2
  168. package/src/core/agents/state/names.ts +68 -9
  169. package/src/core/agents/state/pidmap.ts +59 -2
  170. package/src/core/agents/state/stale-sweep.ts +6 -11
  171. package/src/core/config.ts +248 -24
  172. package/src/core/context/index.ts +575 -0
  173. package/src/core/harnesses/bench.ts +214 -0
  174. package/src/core/harnesses/index.ts +31 -0
  175. package/src/core/harnesses/profiles.ts +141 -0
  176. package/src/core/harnesses/registry.ts +140 -0
  177. package/src/core/harnesses/types.ts +90 -0
  178. package/src/core/hooks/cli.ts +331 -62
  179. package/src/core/hooks/events/schema.ts +68 -0
  180. package/src/core/hooks/harness/events.ts +3 -0
  181. package/src/core/hooks/harness/parse.ts +7 -1
  182. package/src/core/presence/blob.ts +125 -0
  183. package/src/core/presence/git.ts +191 -0
  184. package/src/core/presence/index.ts +274 -0
  185. package/src/core/presence/relay-client.ts +322 -0
  186. package/src/core/presence/relay-protocol.ts +246 -0
  187. package/src/core/workflow/billing.ts +8 -2
  188. package/src/core/workflow/engine.ts +11 -2
  189. package/src/core/workflow/harnesses.ts +14 -19
  190. package/src/core/workflow/spawn-claude.ts +39 -20
  191. package/src/core/workflow/spawn-codex.ts +47 -28
  192. package/src/core/workflow/spawn-cursor.ts +37 -18
  193. package/src/core/workflow/types.ts +9 -2
  194. package/src/lib/browser/client.ts +37 -0
  195. package/src/lib/browser/index.ts +1 -0
  196. package/src/lib/browser/launch-args.ts +33 -0
  197. package/src/lib/claude-desktop.ts +293 -0
  198. package/src/lib/docs-lint.ts +5 -2
  199. package/src/lib/identities/assume.ts +363 -0
  200. package/src/lib/identities/index.ts +28 -24
  201. package/src/lib/instructions/templates.ts +16 -2
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Claude desktop app session-index access.
3
+ *
4
+ * The Claude desktop app keeps each signed-in account's Claude Code session
5
+ * sidebar as plain JSON files:
6
+ *
7
+ * <dataDir>/claude-code-sessions/<account-uuid>/<env-id>/local_<uuid>.json
8
+ *
9
+ * One file per sidebar entry: { sessionId, cliSessionId, cwd, title, ... }.
10
+ * Nothing inside the file binds it to an account — the scoping is purely
11
+ * which <account-uuid> directory it sits in, and the transcripts themselves
12
+ * live in the (account-agnostic) `~/.claude/projects/` tree of the runtime
13
+ * environment. So when a user signs out of account A and into account B,
14
+ * their sessions "disappear" from the sidebar even though every byte is
15
+ * still on disk. Copying an entry file into another account's directory
16
+ * makes the session visible (and resumable) there.
17
+ *
18
+ * These helpers locate the desktop app's data directory (including the
19
+ * Windows one from inside WSL, where the app runs on the Windows side but
20
+ * the CLI runs in the distro), enumerate the per-account entries, and
21
+ * plan/apply cross-account mirrors. The file format is the desktop app's
22
+ * private state, not a public API — everything here treats it read-mostly
23
+ * and copies files verbatim rather than rewriting them.
24
+ */
25
+ export interface DesktopSessionEntry {
26
+ /** Absolute path of the local_*.json entry file. */
27
+ file: string;
28
+ /** Account UUID directory the entry sits under (= sidebar scoping). */
29
+ accountUuid: string;
30
+ /** Environment directory between account and entry (VM/env identity). */
31
+ envId: string;
32
+ /** The entry's own id (usually "local_<uuid>", mirrors the filename). */
33
+ sessionId: string | null;
34
+ /** The Claude Code session id — matches the JSONL transcript filename. */
35
+ cliSessionId: string | null;
36
+ cwd: string | null;
37
+ title: string | null;
38
+ model: string | null;
39
+ isArchived: boolean;
40
+ createdAt: number | null;
41
+ lastActivityAt: number | null;
42
+ }
43
+ export interface DesktopAccount {
44
+ accountUuid: string;
45
+ path: string;
46
+ entries: DesktopSessionEntry[];
47
+ }
48
+ export interface MirrorAction {
49
+ from: string;
50
+ to: string;
51
+ entry: DesktopSessionEntry;
52
+ targetAccountUuid: string;
53
+ }
54
+ export interface MirrorPlan {
55
+ actions: MirrorAction[];
56
+ /** Entries skipped because the target already lists that cliSessionId. */
57
+ skippedExisting: number;
58
+ /** Entries skipped because they are archived (opt in via includeArchived). */
59
+ skippedArchived: number;
60
+ }
61
+ /**
62
+ * Candidate desktop-app data directories for this machine, most specific
63
+ * first. Only directories that exist AND contain a claude-code-sessions/
64
+ * subdirectory are returned. Precedence: explicit arg, then
65
+ * HARNERY_CLAUDE_DESKTOP_DIR, then platform defaults (on WSL that means
66
+ * scanning every /mnt/c/Users/<user>/AppData/Roaming/Claude).
67
+ */
68
+ export declare function findDesktopDataDirs(explicit?: string): string[];
69
+ /** Enumerate every account directory + its session entries under one data dir. */
70
+ export declare function listAccounts(dataDir: string): DesktopAccount[];
71
+ /**
72
+ * The account the runtime environment's Claude Code CLI is signed into,
73
+ * read from ~/.claude.json (best-effort; null when unavailable). Lets the
74
+ * command label an otherwise-opaque account UUID with an email address.
75
+ */
76
+ export declare function readCliAccount(): {
77
+ accountUuid: string;
78
+ email: string | null;
79
+ } | null;
80
+ export interface MirrorOptions {
81
+ /** Restrict targets to accounts whose uuid starts with one of these. */
82
+ to?: string[];
83
+ /** Restrict sources to accounts whose uuid starts with one of these. */
84
+ from?: string[];
85
+ /**
86
+ * Selectors: each matches a cliSessionId / sessionId exactly, or a title
87
+ * case-insensitively as a substring. Empty/undefined = every entry.
88
+ */
89
+ sessions?: string[];
90
+ includeArchived?: boolean;
91
+ }
92
+ export declare function entryMatchesSelector(entry: DesktopSessionEntry, selector: string): boolean;
93
+ /**
94
+ * Plan the copies that would make each target account list the selected
95
+ * sessions. Union semantics: every entry a source account has and a target
96
+ * account lacks (keyed by cliSessionId, falling back to filename) becomes a
97
+ * copy action. Idempotent by construction — planning again after applying
98
+ * yields zero actions.
99
+ */
100
+ export declare function planMirror(accounts: DesktopAccount[], opts?: MirrorOptions): MirrorPlan;
101
+ /** Apply a plan: verbatim file copies (never rewrites entry contents). */
102
+ export declare function applyMirror(plan: MirrorPlan): {
103
+ copied: number;
104
+ };
105
+ //# sourceMappingURL=claude-desktop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-desktop.d.ts","sourceRoot":"","sources":["../../src/lib/claude-desktop.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,WAAW,mBAAmB;IAClC,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,0EAA0E;IAC1E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,mBAAmB,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,mBAAmB,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,0EAA0E;IAC1E,eAAe,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,eAAe,EAAE,MAAM,CAAC;CACzB;AAaD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAgC/D;AA4CD,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAyB9D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAerF;AAED,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG1F;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,cAAc,EAAE,EAAE,IAAI,GAAE,aAAkB,GAAG,UAAU,CA2C3F;AAED,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAQhE"}
@@ -0,0 +1,217 @@
1
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { basename, dirname, join } from "node:path";
4
+ const SESSIONS_DIRNAME = "claude-code-sessions";
5
+ function isWsl() {
6
+ if (process.env.WSL_DISTRO_NAME)
7
+ return true;
8
+ try {
9
+ return readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft");
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ /**
16
+ * Candidate desktop-app data directories for this machine, most specific
17
+ * first. Only directories that exist AND contain a claude-code-sessions/
18
+ * subdirectory are returned. Precedence: explicit arg, then
19
+ * HARNERY_CLAUDE_DESKTOP_DIR, then platform defaults (on WSL that means
20
+ * scanning every /mnt/c/Users/<user>/AppData/Roaming/Claude).
21
+ */
22
+ export function findDesktopDataDirs(explicit) {
23
+ const candidates = [];
24
+ if (explicit)
25
+ candidates.push(explicit);
26
+ else if (process.env.HARNERY_CLAUDE_DESKTOP_DIR) {
27
+ candidates.push(process.env.HARNERY_CLAUDE_DESKTOP_DIR);
28
+ }
29
+ else {
30
+ const home = homedir();
31
+ if (process.platform === "darwin") {
32
+ candidates.push(join(home, "Library", "Application Support", "Claude"));
33
+ }
34
+ else if (process.platform === "win32") {
35
+ if (process.env.APPDATA)
36
+ candidates.push(join(process.env.APPDATA, "Claude"));
37
+ candidates.push(join(home, "AppData", "Roaming", "Claude"));
38
+ }
39
+ else {
40
+ candidates.push(join(home, ".config", "Claude"));
41
+ if (isWsl()) {
42
+ for (const usersRoot of ["/mnt/c/Users"]) {
43
+ if (!existsSync(usersRoot))
44
+ continue;
45
+ for (const user of safeReaddir(usersRoot)) {
46
+ candidates.push(join(usersRoot, user, "AppData", "Roaming", "Claude"));
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ const seen = new Set();
53
+ const out = [];
54
+ for (const dir of candidates) {
55
+ if (seen.has(dir))
56
+ continue;
57
+ seen.add(dir);
58
+ if (existsSync(join(dir, SESSIONS_DIRNAME)))
59
+ out.push(dir);
60
+ }
61
+ return out;
62
+ }
63
+ function safeReaddir(dir) {
64
+ try {
65
+ return readdirSync(dir);
66
+ }
67
+ catch {
68
+ return [];
69
+ }
70
+ }
71
+ function isDir(p) {
72
+ try {
73
+ return statSync(p).isDirectory();
74
+ }
75
+ catch {
76
+ return false;
77
+ }
78
+ }
79
+ function readEntry(file, accountUuid, envId) {
80
+ let raw;
81
+ try {
82
+ raw = JSON.parse(readFileSync(file, "utf8"));
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ if (typeof raw !== "object" || raw === null)
88
+ return null;
89
+ const d = raw;
90
+ const str = (k) => (typeof d[k] === "string" ? d[k] : null);
91
+ const num = (k) => (typeof d[k] === "number" ? d[k] : null);
92
+ return {
93
+ file,
94
+ accountUuid,
95
+ envId,
96
+ sessionId: str("sessionId"),
97
+ cliSessionId: str("cliSessionId"),
98
+ cwd: str("cwd"),
99
+ title: str("title"),
100
+ model: str("model"),
101
+ isArchived: d.isArchived === true,
102
+ createdAt: num("createdAt"),
103
+ lastActivityAt: num("lastActivityAt"),
104
+ };
105
+ }
106
+ /** Enumerate every account directory + its session entries under one data dir. */
107
+ export function listAccounts(dataDir) {
108
+ const root = join(dataDir, SESSIONS_DIRNAME);
109
+ const accounts = [];
110
+ for (const accountUuid of safeReaddir(root)) {
111
+ const accountPath = join(root, accountUuid);
112
+ if (!isDir(accountPath))
113
+ continue;
114
+ const entries = [];
115
+ for (const envId of safeReaddir(accountPath)) {
116
+ const envPath = join(accountPath, envId);
117
+ if (!isDir(envPath))
118
+ continue;
119
+ for (const f of safeReaddir(envPath)) {
120
+ if (!f.endsWith(".json"))
121
+ continue;
122
+ const entry = readEntry(join(envPath, f), accountUuid, envId);
123
+ if (entry)
124
+ entries.push(entry);
125
+ }
126
+ }
127
+ entries.sort((a, b) => (b.lastActivityAt ?? 0) - (a.lastActivityAt ?? 0));
128
+ accounts.push({ accountUuid, path: accountPath, entries });
129
+ }
130
+ accounts.sort((a, b) => (b.entries[0]?.lastActivityAt ?? 0) - (a.entries[0]?.lastActivityAt ?? 0) ||
131
+ a.accountUuid.localeCompare(b.accountUuid));
132
+ return accounts;
133
+ }
134
+ /**
135
+ * The account the runtime environment's Claude Code CLI is signed into,
136
+ * read from ~/.claude.json (best-effort; null when unavailable). Lets the
137
+ * command label an otherwise-opaque account UUID with an email address.
138
+ */
139
+ export function readCliAccount() {
140
+ try {
141
+ const raw = JSON.parse(readFileSync(join(homedir(), ".claude.json"), "utf8"));
142
+ const oa = raw.oauthAccount;
143
+ if (!oa || typeof oa.accountUuid !== "string")
144
+ return null;
145
+ return {
146
+ accountUuid: oa.accountUuid,
147
+ email: typeof oa.emailAddress === "string" ? oa.emailAddress : null,
148
+ };
149
+ }
150
+ catch {
151
+ return null;
152
+ }
153
+ }
154
+ export function entryMatchesSelector(entry, selector) {
155
+ if (entry.cliSessionId === selector || entry.sessionId === selector)
156
+ return true;
157
+ return entry.title !== null && entry.title.toLowerCase().includes(selector.toLowerCase());
158
+ }
159
+ /**
160
+ * Plan the copies that would make each target account list the selected
161
+ * sessions. Union semantics: every entry a source account has and a target
162
+ * account lacks (keyed by cliSessionId, falling back to filename) becomes a
163
+ * copy action. Idempotent by construction — planning again after applying
164
+ * yields zero actions.
165
+ */
166
+ export function planMirror(accounts, opts = {}) {
167
+ const matchPrefix = (uuid, prefixes) => !prefixes || prefixes.length === 0 || prefixes.some((p) => uuid.startsWith(p));
168
+ const targets = accounts.filter((a) => matchPrefix(a.accountUuid, opts.to));
169
+ const sources = accounts.filter((a) => matchPrefix(a.accountUuid, opts.from));
170
+ const actions = [];
171
+ let skippedExisting = 0;
172
+ let skippedArchived = 0;
173
+ for (const target of targets) {
174
+ const have = new Set();
175
+ for (const e of target.entries) {
176
+ if (e.cliSessionId)
177
+ have.add(e.cliSessionId);
178
+ have.add(basename(e.file));
179
+ }
180
+ for (const source of sources) {
181
+ if (source.accountUuid === target.accountUuid)
182
+ continue;
183
+ for (const entry of source.entries) {
184
+ if (opts.sessions?.length && !opts.sessions.some((s) => entryMatchesSelector(entry, s))) {
185
+ continue;
186
+ }
187
+ const key = entry.cliSessionId ?? basename(entry.file);
188
+ if (have.has(key) || have.has(basename(entry.file))) {
189
+ skippedExisting++;
190
+ continue;
191
+ }
192
+ if (entry.isArchived && !opts.includeArchived) {
193
+ skippedArchived++;
194
+ continue;
195
+ }
196
+ have.add(key);
197
+ actions.push({
198
+ from: entry.file,
199
+ to: join(target.path, entry.envId, basename(entry.file)),
200
+ entry,
201
+ targetAccountUuid: target.accountUuid,
202
+ });
203
+ }
204
+ }
205
+ }
206
+ return { actions, skippedExisting, skippedArchived };
207
+ }
208
+ /** Apply a plan: verbatim file copies (never rewrites entry contents). */
209
+ export function applyMirror(plan) {
210
+ let copied = 0;
211
+ for (const action of plan.actions) {
212
+ mkdirSync(dirname(action.to), { recursive: true });
213
+ copyFileSync(action.from, action.to);
214
+ copied++;
215
+ }
216
+ return { copied };
217
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"docs-lint.d.ts","sourceRoot":"","sources":["../../src/lib/docs-lint.ts"],"names":[],"mappings":"AAcA,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,qBAAqB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC,GAAG,IAAI,CAKP;AAaD;;;;;;GAMG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3C,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAuHD,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMrD;AAsQD,wBAAsB,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAqBlE"}
1
+ {"version":3,"file":"docs-lint.d.ts","sourceRoot":"","sources":["../../src/lib/docs-lint.ts"],"names":[],"mappings":"AAcA,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,qBAAqB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC,GAAG,IAAI,CAKP;AAaD;;;;;;GAMG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3C,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA0HD,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMrD;AAsQD,wBAAsB,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAqBlE"}
@@ -27,8 +27,10 @@ import { basename, join } from "node:path";
27
27
  /** Files allowed at a submodule root level. Includes:
28
28
  * - In-repo conventions: README.md, CLAUDE.md, LLM-BRIEFING.md, AGENTS.md
29
29
  * - GitHub OSS conventions: CHANGELOG.md, CONTRIBUTING.md, CODE_OF_CONDUCT.md,
30
- * SECURITY.md, SUPPORT.md, AUTHORS.md, MAINTAINERS.md, PULL_REQUEST_TEMPLATE.md
31
- * (these are recognized by the GitHub UI, and renaming them breaks the integration)
30
+ * LICENSE.md, SECURITY.md, SUPPORT.md, AUTHORS.md, MAINTAINERS.md,
31
+ * PULL_REQUEST_TEMPLATE.md (these are recognized by the GitHub UI, and
32
+ * renaming them breaks the integration; LICENSE.md also appears inside
33
+ * vendored upstream trees, where renaming would damage provenance)
32
34
  */
33
35
  const ROOT_FILE_ALLOWLIST = new Set([
34
36
  "README.md",
@@ -39,6 +41,7 @@ const ROOT_FILE_ALLOWLIST = new Set([
39
41
  "CHANGELOG.md",
40
42
  "CONTRIBUTING.md",
41
43
  "CODE_OF_CONDUCT.md",
44
+ "LICENSE.md",
42
45
  "SECURITY.md",
43
46
  "SUPPORT.md",
44
47
  "AUTHORS.md",
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Bind a live session to a durable agent persona.
3
+ *
4
+ * The operation is serialized because "is this name live?" and "claim it"
5
+ * must be one critical section. Authoritative state is written twice on
6
+ * purpose: `.name-history` heals a missing heartbeat, while the canonical
7
+ * `identity.assumed` event makes replay and derived readers converge.
8
+ *
9
+ * Local collision policy: a fresh heartbeat alone is not enough to block.
10
+ * If the namesake has no live pid-map process (crashed session, healed
11
+ * zombie, or abandoned harness), reclaim that heartbeat and continue.
12
+ * Refuse only when another process is still alive, or when cached remote
13
+ * presence reports the name.
14
+ */
15
+ export type IdentityAssumeErrorCode = "identity_busy" | "identity_in_use" | "identity_not_found" | "invalid_identity" | "no_heartbeat" | "not_session" | "projection_failed";
16
+ export declare class IdentityAssumeError extends Error {
17
+ readonly code: IdentityAssumeErrorCode;
18
+ constructor(code: IdentityAssumeErrorCode, message: string);
19
+ }
20
+ export interface IdentityConflict {
21
+ instance_id: string;
22
+ name: string;
23
+ scope: "local" | "remote";
24
+ machine?: string;
25
+ }
26
+ export interface IdentityAssumeResult {
27
+ changed: boolean;
28
+ instance_id: string;
29
+ session_id: string;
30
+ previous_name: string | null;
31
+ previous_agent_id: string | null;
32
+ name: string;
33
+ agent_id: string;
34
+ identity_created: boolean;
35
+ event_id: string | null;
36
+ /** Prior local holder swept because it had no live process. */
37
+ reclaimed_instance_id: string | null;
38
+ }
39
+ /** Best-effort collision read across local heartbeats and cached remote
40
+ * presence. No network call occurs; absent presence degrades to local-only. */
41
+ export declare function findIdentityConflict(coordRoot: string, instanceId: string, name: string, nowMs?: number): IdentityConflict | null;
42
+ /**
43
+ * Drop a local namesake whose harness process is gone. Fresh heartbeats can
44
+ * linger after a crash (or after heartbeat heal without a live pid-map), and
45
+ * `identity assume` is the supported takeover path — operators should not
46
+ * need a separate kill/sweep step.
47
+ */
48
+ export declare function reclaimAbandonedLocalConflict(coordRoot: string, conflict: IdentityConflict): boolean;
49
+ /** Assume `target` for one live main session. Safe to retry. */
50
+ export declare function assumeIdentity(coordRoot: string, instanceId: string, target: string): IdentityAssumeResult;
51
+ //# sourceMappingURL=assume.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assume.d.ts","sourceRoot":"","sources":["../../../src/lib/identities/assume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA2BH,MAAM,MAAM,uBAAuB,GAC/B,eAAe,GACf,iBAAiB,GACjB,oBAAoB,GACpB,kBAAkB,GAClB,cAAc,GACd,aAAa,GACb,mBAAmB,CAAC;AAExB,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,IAAI,EAAE,uBAAuB;gBAA7B,IAAI,EAAE,uBAAuB,EAC7C,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,+DAA+D;IAC/D,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAOD;+EAC+E;AAC/E,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,SAAa,GACjB,gBAAgB,GAAG,IAAI,CAiCzB;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CA2CT;AAyED,gEAAgE;AAChE,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,oBAAoB,CAyGtB"}