even-keel 0.0.2 → 0.2.0-rc.2

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 (537) hide show
  1. package/.claude-plugin/marketplace.json +52 -0
  2. package/.env.example +66 -0
  3. package/CHANGELOG.md +315 -0
  4. package/LICENSE +21 -0
  5. package/README.md +199 -3
  6. package/dist/src/account/apps.js +100 -0
  7. package/dist/src/account/credit.js +117 -0
  8. package/dist/src/account/current.js +13 -0
  9. package/dist/src/account/daemon.js +65 -0
  10. package/dist/src/account/globals.js +25 -0
  11. package/dist/src/account/header-levels.js +22 -0
  12. package/dist/src/account/homes.js +332 -0
  13. package/dist/src/account/keychain.js +134 -0
  14. package/dist/src/account/keycheck.js +67 -0
  15. package/dist/src/account/ledger.js +92 -0
  16. package/dist/src/account/model.js +234 -0
  17. package/dist/src/account/network.js +51 -0
  18. package/dist/src/account/organizations.js +17 -0
  19. package/dist/src/account/service-defaults.js +17 -0
  20. package/dist/src/account/share.js +275 -0
  21. package/dist/src/account/transport.js +29 -0
  22. package/dist/src/account/vscode.js +84 -0
  23. package/dist/src/account/watcher.js +451 -0
  24. package/dist/src/agent/adapters.js +10 -0
  25. package/dist/src/agent/cli-state.js +1 -0
  26. package/dist/src/agent/contract.js +7 -0
  27. package/dist/src/agent/copies.js +38 -0
  28. package/dist/src/agent/copy.js +42 -0
  29. package/dist/src/agent/download.js +39 -0
  30. package/dist/src/agent/env.js +23 -0
  31. package/dist/src/agent/models.js +90 -0
  32. package/dist/src/agent/npm.js +55 -0
  33. package/dist/src/agent/oneturn.js +48 -0
  34. package/dist/src/agent/pinned.js +79 -0
  35. package/dist/src/agent/process.js +76 -0
  36. package/dist/src/agent/projectRules.js +54 -0
  37. package/dist/src/agent/providers.js +39 -0
  38. package/dist/src/agent/versions.js +89 -0
  39. package/dist/src/agent/windows.js +39 -0
  40. package/dist/src/ansi.js +6 -0
  41. package/dist/src/autostart.js +80 -0
  42. package/dist/src/chain/answers.js +26 -0
  43. package/dist/src/chain/archive.js +25 -0
  44. package/dist/src/chain/carry.js +97 -0
  45. package/dist/src/chain/cleanup.js +134 -0
  46. package/dist/src/chain/dispatcher.js +800 -0
  47. package/dist/src/chain/fix.js +55 -0
  48. package/dist/src/chain/forward.js +195 -0
  49. package/dist/src/chain/handover.js +350 -0
  50. package/dist/src/chain/launches.js +193 -0
  51. package/dist/src/chain/limitNotice.js +30 -0
  52. package/dist/src/chain/lists.js +174 -0
  53. package/dist/src/chain/log.js +23 -0
  54. package/dist/src/chain/maincheck.js +449 -0
  55. package/dist/src/chain/mainci.js +116 -0
  56. package/dist/src/chain/mainfix.js +180 -0
  57. package/dist/src/chain/mainwait.js +73 -0
  58. package/dist/src/chain/milestones.js +67 -0
  59. package/dist/src/chain/model.js +6 -0
  60. package/dist/src/chain/moves.js +100 -0
  61. package/dist/src/chain/order.js +266 -0
  62. package/dist/src/chain/overage.js +44 -0
  63. package/dist/src/chain/pauses.js +81 -0
  64. package/dist/src/chain/places.js +42 -0
  65. package/dist/src/chain/prompts.js +199 -0
  66. package/dist/src/chain/relay.js +153 -0
  67. package/dist/src/chain/restartGuard.js +17 -0
  68. package/dist/src/chain/reviews.js +316 -0
  69. package/dist/src/chain/rules.js +59 -0
  70. package/dist/src/chain/shelved.js +33 -0
  71. package/dist/src/chain/skippedown.js +20 -0
  72. package/dist/src/chain/snapshots.js +37 -0
  73. package/dist/src/chain/start.js +52 -0
  74. package/dist/src/chain/store.js +146 -0
  75. package/dist/src/chain/unchanged.js +71 -0
  76. package/dist/src/changelog/diff.js +118 -0
  77. package/dist/src/changelog/draft.js +59 -0
  78. package/dist/src/changelog/drafts.js +121 -0
  79. package/dist/src/changelog/file.js +146 -0
  80. package/dist/src/changelog/store.js +75 -0
  81. package/dist/src/ci/adapters.js +65 -0
  82. package/dist/src/ci/forgejo-profile.js +86 -0
  83. package/dist/src/ci/forgejo.js +62 -0
  84. package/dist/src/ci/github.js +53 -0
  85. package/dist/src/ci/gitlab.js +62 -0
  86. package/dist/src/ci/host.js +14 -0
  87. package/dist/src/ci/http.js +71 -0
  88. package/dist/src/ci/model.js +41 -0
  89. package/dist/src/ci/protection-audit.js +49 -0
  90. package/dist/src/ci/protection-model.js +1 -0
  91. package/dist/src/ci/protection.js +75 -0
  92. package/dist/src/ci/remote.js +105 -0
  93. package/dist/src/ci/report.js +114 -0
  94. package/dist/src/ci/shared.js +74 -0
  95. package/dist/src/ci/verdict.js +61 -0
  96. package/dist/src/ci/watcher.js +188 -0
  97. package/dist/src/ci/watches.js +116 -0
  98. package/dist/src/ci/wiring.js +26 -0
  99. package/dist/src/claude/adapter.js +174 -0
  100. package/dist/src/claude/calls.js +88 -0
  101. package/dist/src/claude/cli.js +451 -0
  102. package/dist/src/claude/context.js +33 -0
  103. package/dist/src/claude/instructions.js +12 -0
  104. package/dist/src/claude/leftovers.js +32 -0
  105. package/dist/src/claude/name.js +2 -0
  106. package/dist/src/claude/release.js +69 -0
  107. package/dist/src/claude/sdk/executor.js +214 -0
  108. package/dist/src/claude/sdk/host.js +422 -0
  109. package/dist/src/claude/sdk/pipe.js +134 -0
  110. package/dist/src/claude/sdk/registry.js +88 -0
  111. package/dist/src/claude/sdk/release.js +26 -0
  112. package/dist/src/claude/sdk/types.js +1 -0
  113. package/dist/src/claude/tree.js +103 -0
  114. package/dist/src/claude/trust.js +12 -0
  115. package/dist/src/cli-args.js +36 -0
  116. package/dist/src/cli-check/lease.js +35 -0
  117. package/dist/src/cli-check/run.js +381 -0
  118. package/dist/src/cli-check/scheduler.js +134 -0
  119. package/dist/src/cli-check/support.js +105 -0
  120. package/dist/src/cli.js +196 -0
  121. package/dist/src/codex/adapter.js +112 -0
  122. package/dist/src/codex/appserver.js +68 -0
  123. package/dist/src/codex/cli.js +360 -0
  124. package/dist/src/codex/mcp.js +8 -0
  125. package/dist/src/codex/name.js +2 -0
  126. package/dist/src/codex/plugins.js +137 -0
  127. package/dist/src/codex/process.js +81 -0
  128. package/dist/src/codex/protection.js +32 -0
  129. package/dist/src/codex/release.js +33 -0
  130. package/dist/src/codex/rollout.js +478 -0
  131. package/dist/src/codex/rolloutImages.js +49 -0
  132. package/dist/src/codex/rules.js +141 -0
  133. package/dist/src/codex/tail.js +53 -0
  134. package/dist/src/codex/turns.js +140 -0
  135. package/dist/src/codex/usage.js +88 -0
  136. package/dist/src/codex/version.js +2 -0
  137. package/dist/src/config-form.js +359 -0
  138. package/dist/src/config-store.js +184 -0
  139. package/dist/src/config.js +524 -0
  140. package/dist/src/copilot/adapter.js +121 -0
  141. package/dist/src/copilot/cli.js +298 -0
  142. package/dist/src/copilot/errors.js +50 -0
  143. package/dist/src/copilot/home.js +84 -0
  144. package/dist/src/copilot/machine.js +41 -0
  145. package/dist/src/copilot/name.js +2 -0
  146. package/dist/src/copilot/release.js +30 -0
  147. package/dist/src/copilot/tail.js +57 -0
  148. package/dist/src/copilot/transcript.js +385 -0
  149. package/dist/src/diagnostics/start-watch.js +77 -0
  150. package/dist/src/env-file.js +91 -0
  151. package/dist/src/events.js +150 -0
  152. package/dist/src/git/changelog.js +38 -0
  153. package/dist/src/git/changes.js +10 -0
  154. package/dist/src/git/merged.js +36 -0
  155. package/dist/src/git/run.js +25 -0
  156. package/dist/src/git/setup.js +100 -0
  157. package/dist/src/git/slot.js +99 -0
  158. package/dist/src/git/sync.js +72 -0
  159. package/dist/src/git/types.js +1 -0
  160. package/dist/src/git/work.js +119 -0
  161. package/dist/src/git/worktree.js +216 -0
  162. package/dist/src/github/adapter.js +30 -0
  163. package/dist/src/github/model.js +104 -0
  164. package/dist/src/github/profile.js +50 -0
  165. package/dist/src/github/source.js +438 -0
  166. package/dist/src/github/trust.js +25 -0
  167. package/dist/src/github/uploads.js +36 -0
  168. package/dist/src/gitlab/adapter.js +51 -0
  169. package/dist/src/gitlab/api.js +71 -0
  170. package/dist/src/gitlab/batch.js +114 -0
  171. package/dist/src/gitlab/hierarchy.js +59 -0
  172. package/dist/src/gitlab/model.js +56 -0
  173. package/dist/src/gitlab/profile.js +25 -0
  174. package/dist/src/gitlab/source.js +246 -0
  175. package/dist/src/gitlab/uploads.js +65 -0
  176. package/dist/src/guard.js +141 -0
  177. package/dist/src/huly/adapter.js +34 -0
  178. package/dist/src/huly/client.js +158 -0
  179. package/dist/src/huly/credentials.js +27 -0
  180. package/dist/src/huly/ids.js +16 -0
  181. package/dist/src/huly/labels.js +32 -0
  182. package/dist/src/huly/model.js +191 -0
  183. package/dist/src/huly/profile.js +73 -0
  184. package/dist/src/huly/quiet.js +17 -0
  185. package/dist/src/huly/source.js +327 -0
  186. package/dist/src/intake/github.js +73 -0
  187. package/dist/src/intake/intake.js +113 -0
  188. package/dist/src/intake/model.js +72 -0
  189. package/dist/src/jira/adapter.js +35 -0
  190. package/dist/src/jira/adf.js +253 -0
  191. package/dist/src/jira/api.js +96 -0
  192. package/dist/src/jira/mcp.js +134 -0
  193. package/dist/src/jira/model.js +111 -0
  194. package/dist/src/jira/profile.js +17 -0
  195. package/dist/src/jira/source.js +320 -0
  196. package/dist/src/jsonc.js +15 -0
  197. package/dist/src/language.js +4 -0
  198. package/dist/src/limits/chatgpt.js +144 -0
  199. package/dist/src/limits/copilot.js +117 -0
  200. package/dist/src/limits/model.js +183 -0
  201. package/dist/src/limits/overage-session.js +9 -0
  202. package/dist/src/limits/overage.js +74 -0
  203. package/dist/src/limits/recovery.js +23 -0
  204. package/dist/src/limits/reset.js +81 -0
  205. package/dist/src/limits/retry.js +45 -0
  206. package/dist/src/limits/saved.js +17 -0
  207. package/dist/src/limits/spending.js +15 -0
  208. package/dist/src/limits/usage.js +53 -0
  209. package/dist/src/limits/watcher.js +269 -0
  210. package/dist/src/linear/adapter.js +50 -0
  211. package/dist/src/linear/model.js +89 -0
  212. package/dist/src/linear/profile.js +45 -0
  213. package/dist/src/linear/source.js +423 -0
  214. package/dist/src/locations.js +53 -0
  215. package/dist/src/main.js +76 -0
  216. package/dist/src/notice/en.js +489 -0
  217. package/dist/src/notice/notice.js +93 -0
  218. package/dist/src/opencode/adapter.js +121 -0
  219. package/dist/src/opencode/cli.js +433 -0
  220. package/dist/src/opencode/db.js +103 -0
  221. package/dist/src/opencode/home.js +85 -0
  222. package/dist/src/opencode/log.js +87 -0
  223. package/dist/src/opencode/models.js +51 -0
  224. package/dist/src/opencode/name.js +2 -0
  225. package/dist/src/opencode/protection-plugin.js +27 -0
  226. package/dist/src/opencode/release.js +29 -0
  227. package/dist/src/opencode/support.js +14 -0
  228. package/dist/src/opencode/tail.js +42 -0
  229. package/dist/src/opencode/transcript.js +331 -0
  230. package/dist/src/opencode/usage.js +41 -0
  231. package/dist/src/packages/check.js +146 -0
  232. package/dist/src/packages/gateway.js +496 -0
  233. package/dist/src/packages/keys.js +78 -0
  234. package/dist/src/packages/launch.js +99 -0
  235. package/dist/src/packages/model.js +61 -0
  236. package/dist/src/packages/npm.js +115 -0
  237. package/dist/src/packages/plugins.js +162 -0
  238. package/dist/src/packages/service.js +615 -0
  239. package/dist/src/packages/settings.js +25 -0
  240. package/dist/src/packages/skills.js +132 -0
  241. package/dist/src/packages/store.js +39 -0
  242. package/dist/src/packages/version.js +26 -0
  243. package/dist/src/patch.js +1 -0
  244. package/dist/src/paths.js +83 -0
  245. package/dist/src/profiles/control.js +32 -0
  246. package/dist/src/profiles/mcp.js +29 -0
  247. package/dist/src/profiles/model.js +97 -0
  248. package/dist/src/profiles/oauth-error.js +21 -0
  249. package/dist/src/profiles/oauth-protocol.js +106 -0
  250. package/dist/src/profiles/oauth-response.js +12 -0
  251. package/dist/src/profiles/oauth.js +228 -0
  252. package/dist/src/profiles/request.js +20 -0
  253. package/dist/src/profiles/routes.js +31 -0
  254. package/dist/src/profiles/rules.js +54 -0
  255. package/dist/src/profiles/service.js +471 -0
  256. package/dist/src/profiles/store.js +125 -0
  257. package/dist/src/projects/add.js +11 -0
  258. package/dist/src/projects/check.js +174 -0
  259. package/dist/src/projects/colors.js +16 -0
  260. package/dist/src/projects/folder.js +30 -0
  261. package/dist/src/projects/remotes.js +109 -0
  262. package/dist/src/projects/removal.js +14 -0
  263. package/dist/src/projects/standards.js +31 -0
  264. package/dist/src/review/events.js +27 -0
  265. package/dist/src/review/feedback.js +64 -0
  266. package/dist/src/review/forgejo.js +110 -0
  267. package/dist/src/review/github.js +69 -0
  268. package/dist/src/review/gitlab.js +118 -0
  269. package/dist/src/review/model.js +25 -0
  270. package/dist/src/review/open.js +141 -0
  271. package/dist/src/review/refusals.js +20 -0
  272. package/dist/src/review/wiring.js +93 -0
  273. package/dist/src/secrets/catalog.js +34 -0
  274. package/dist/src/secrets/service.js +168 -0
  275. package/dist/src/server-guard.js +51 -0
  276. package/dist/src/server-request.js +360 -0
  277. package/dist/src/server.js +576 -0
  278. package/dist/src/service/accounts.js +584 -0
  279. package/dist/src/service/chain.js +642 -0
  280. package/dist/src/service/cli-check.js +58 -0
  281. package/dist/src/service/hosting.js +508 -0
  282. package/dist/src/service/lifecycle.js +59 -0
  283. package/dist/src/service/stats.js +72 -0
  284. package/dist/src/service/trackers.js +239 -0
  285. package/dist/src/service.js +699 -0
  286. package/dist/src/sessions/activity.js +32 -0
  287. package/dist/src/sessions/agents.js +217 -0
  288. package/dist/src/sessions/approvals.js +105 -0
  289. package/dist/src/sessions/ask.js +234 -0
  290. package/dist/src/sessions/cache.js +14 -0
  291. package/dist/src/sessions/chat.js +60 -0
  292. package/dist/src/sessions/commandPrompt.js +31 -0
  293. package/dist/src/sessions/commands.js +29 -0
  294. package/dist/src/sessions/entry.js +104 -0
  295. package/dist/src/sessions/history.js +88 -0
  296. package/dist/src/sessions/holds.js +63 -0
  297. package/dist/src/sessions/inbox.js +101 -0
  298. package/dist/src/sessions/keepwarm.js +110 -0
  299. package/dist/src/sessions/launch.js +32 -0
  300. package/dist/src/sessions/lines.js +90 -0
  301. package/dist/src/sessions/lingering.js +26 -0
  302. package/dist/src/sessions/mail.js +36 -0
  303. package/dist/src/sessions/mailRelay.js +88 -0
  304. package/dist/src/sessions/mailbox.js +136 -0
  305. package/dist/src/sessions/model-prompts.js +16 -0
  306. package/dist/src/sessions/monitor.js +215 -0
  307. package/dist/src/sessions/note.js +30 -0
  308. package/dist/src/sessions/occupants.js +15 -0
  309. package/dist/src/sessions/owners.js +59 -0
  310. package/dist/src/sessions/paths.js +24 -0
  311. package/dist/src/sessions/permission.js +58 -0
  312. package/dist/src/sessions/place.js +37 -0
  313. package/dist/src/sessions/prompt.js +16 -0
  314. package/dist/src/sessions/protection.js +88 -0
  315. package/dist/src/sessions/remote.js +41 -0
  316. package/dist/src/sessions/setup.js +57 -0
  317. package/dist/src/sessions/share.js +6 -0
  318. package/dist/src/sessions/shareFile.js +32 -0
  319. package/dist/src/sessions/silence.js +25 -0
  320. package/dist/src/sessions/tail.js +79 -0
  321. package/dist/src/sessions/title.js +67 -0
  322. package/dist/src/sessions/transcript.js +477 -0
  323. package/dist/src/sessions/transient.js +4 -0
  324. package/dist/src/sessions/watch.js +78 -0
  325. package/dist/src/settings/changes.js +15 -0
  326. package/dist/src/settings/defaults.js +25 -0
  327. package/dist/src/settings/file.js +197 -0
  328. package/dist/src/settings/import.js +93 -0
  329. package/dist/src/settings/registry.js +422 -0
  330. package/dist/src/settings/resolve.js +80 -0
  331. package/dist/src/settings/store.js +102 -0
  332. package/dist/src/setup.js +7 -0
  333. package/dist/src/state.js +1 -0
  334. package/dist/src/stats/base-sql.js +58 -0
  335. package/dist/src/stats/break-labels.js +53 -0
  336. package/dist/src/stats/calibration.js +43 -0
  337. package/dist/src/stats/checkpoint.js +66 -0
  338. package/dist/src/stats/connect.js +49 -0
  339. package/dist/src/stats/diff.js +88 -0
  340. package/dist/src/stats/mode.js +25 -0
  341. package/dist/src/stats/model.js +108 -0
  342. package/dist/src/stats/reader.js +563 -0
  343. package/dist/src/stats/rule-imports.js +106 -0
  344. package/dist/src/stats/rules-reader.js +257 -0
  345. package/dist/src/stats/rules.js +324 -0
  346. package/dist/src/stats/steps-reader.js +108 -0
  347. package/dist/src/stats/steps.js +159 -0
  348. package/dist/src/stats/tally-schema.js +13 -0
  349. package/dist/src/stats/tally.js +139 -0
  350. package/dist/src/stats/task-checkpoint.js +9 -0
  351. package/dist/src/storage/atomic.js +41 -0
  352. package/dist/src/storage/claim.js +139 -0
  353. package/dist/src/storage/copy.js +69 -0
  354. package/dist/src/storage/database.js +405 -0
  355. package/dist/src/storage/json-folder.js +47 -0
  356. package/dist/src/storage/memory.js +15 -0
  357. package/dist/src/storage/migrations.js +373 -0
  358. package/dist/src/storage/owner.js +114 -0
  359. package/dist/src/storage/package-settings.js +33 -0
  360. package/dist/src/storage/projects.js +23 -0
  361. package/dist/src/storage/schema.js +21 -0
  362. package/dist/src/storage/service-state.js +9 -0
  363. package/dist/src/storage/state-files.js +222 -0
  364. package/dist/src/tools/model.js +1 -0
  365. package/dist/src/tools/panel.js +197 -0
  366. package/dist/src/tools/received.js +114 -0
  367. package/dist/src/tools/sessionSettings.js +15 -0
  368. package/dist/src/tools/settings.js +109 -0
  369. package/dist/src/tracker/adapters.js +28 -0
  370. package/dist/src/tracker/card.js +32 -0
  371. package/dist/src/tracker/closer.js +76 -0
  372. package/dist/src/tracker/closes.js +45 -0
  373. package/dist/src/tracker/comments.js +24 -0
  374. package/dist/src/tracker/control.js +138 -0
  375. package/dist/src/tracker/diagnostics.js +38 -0
  376. package/dist/src/tracker/health.js +1 -0
  377. package/dist/src/tracker/labels.js +25 -0
  378. package/dist/src/tracker/link.js +125 -0
  379. package/dist/src/tracker/model.js +82 -0
  380. package/dist/src/tracker/move-check.js +14 -0
  381. package/dist/src/tracker/mover.js +67 -0
  382. package/dist/src/tracker/parents.js +32 -0
  383. package/dist/src/tracker/poller.js +60 -0
  384. package/dist/src/tracker/questions.js +127 -0
  385. package/dist/src/tracker/readers.js +95 -0
  386. package/dist/src/tracker/source.js +66 -0
  387. package/dist/src/tracker/start.js +256 -0
  388. package/dist/src/tracker/statuses.js +22 -0
  389. package/dist/src/tracker/trackers.js +148 -0
  390. package/dist/src/tracker/vocabulary.js +33 -0
  391. package/dist/src/updates/checkout.js +16 -0
  392. package/dist/src/updates/control.js +139 -0
  393. package/dist/src/updates/launch.js +40 -0
  394. package/dist/src/updates/model.js +105 -0
  395. package/dist/src/updates/prefix.js +19 -0
  396. package/dist/src/updates/release.js +100 -0
  397. package/dist/src/updates/run.js +64 -0
  398. package/dist/src/updates/runner.js +183 -0
  399. package/dist/src/updates/settle.js +18 -0
  400. package/dist/src/updates/steps.js +63 -0
  401. package/dist/src/updates/updater.js +205 -0
  402. package/dist/src/updates/watcher.js +86 -0
  403. package/dist/tally/src/classify.js +144 -0
  404. package/dist/tally/src/closed-tasks.js +25 -0
  405. package/dist/tally/src/collect-worker.js +17 -0
  406. package/dist/tally/src/collect.js +212 -0
  407. package/dist/tally/src/compact.js +33 -0
  408. package/dist/tally/src/config.js +150 -0
  409. package/dist/tally/src/copilot-turns.js +101 -0
  410. package/dist/tally/src/copilot.js +173 -0
  411. package/dist/tally/src/db.js +562 -0
  412. package/dist/tally/src/handoffs.js +126 -0
  413. package/dist/tally/src/host-worker.js +18 -0
  414. package/dist/tally/src/huly-profiles.js +38 -0
  415. package/dist/tally/src/huly.js +123 -0
  416. package/dist/tally/src/limits.js +21 -0
  417. package/dist/tally/src/load.js +89 -0
  418. package/dist/tally/src/local.js +47 -0
  419. package/dist/tally/src/main.js +39 -0
  420. package/dist/tally/src/migrations/2-false-rule-deletions.sql +41 -0
  421. package/dist/tally/src/migrations/3-gpt-family.sql +20 -0
  422. package/dist/tally/src/migrations/4-compactions.sql +49 -0
  423. package/dist/tally/src/migrations/5-milestones.sql +25 -0
  424. package/dist/tally/src/migrations/6-task-sources.sql +6 -0
  425. package/dist/tally/src/migrations/7-task-followups.sql +28 -0
  426. package/dist/tally/src/migrations/8-handoff-headings.sql +12 -0
  427. package/dist/tally/src/migrations/9-codex-stray-counts.sql +15 -0
  428. package/dist/tally/src/migrations.js +80 -0
  429. package/dist/tally/src/opencode-turns.js +79 -0
  430. package/dist/tally/src/opencode.js +115 -0
  431. package/dist/tally/src/parse.js +192 -0
  432. package/dist/tally/src/prompt.js +19 -0
  433. package/dist/tally/src/rollout-turns.js +181 -0
  434. package/dist/tally/src/rollout.js +159 -0
  435. package/dist/tally/src/rule-changes.js +39 -0
  436. package/dist/tally/src/rule-history.js +88 -0
  437. package/dist/tally/src/rule-hook-size.js +23 -0
  438. package/dist/tally/src/rule-hook.js +177 -0
  439. package/dist/tally/src/rule-imports.js +76 -0
  440. package/dist/tally/src/rule-parse.js +178 -0
  441. package/dist/tally/src/rule-repair.js +27 -0
  442. package/dist/tally/src/rule-scan.js +179 -0
  443. package/dist/tally/src/rule-steps.js +144 -0
  444. package/dist/tally/src/rules.js +230 -0
  445. package/dist/tally/src/scan.js +59 -0
  446. package/dist/tally/src/schema.sql +702 -0
  447. package/dist/tally/src/signs.js +179 -0
  448. package/dist/tally/src/step-runs.js +274 -0
  449. package/dist/tally/src/steps.js +140 -0
  450. package/dist/tally/src/stream.js +73 -0
  451. package/dist/tally/src/task.js +16 -0
  452. package/dist/tally/src/turns.js +114 -0
  453. package/dist/web/apple-touch-icon.png +0 -0
  454. package/dist/web/assets/Loader-BoP9PE7x.js +31 -0
  455. package/dist/web/assets/Loader-DJJTBsaF.css +1 -0
  456. package/dist/web/assets/TaskCard-Br2QIllc.css +1 -0
  457. package/dist/web/assets/TaskCard-DNn-Qm8d.js +2 -0
  458. package/dist/web/assets/TranscriptWindow-2afYjYo9.css +1 -0
  459. package/dist/web/assets/TranscriptWindow-CFE2kEuL.js +4 -0
  460. package/dist/web/assets/feedLine-C_AM4QQT.js +1 -0
  461. package/dist/web/assets/index-BY7ediF2.js +25 -0
  462. package/dist/web/assets/index-Di7tEbPf.css +1 -0
  463. package/dist/web/assets/media-DR8nAbPy.css +1 -0
  464. package/dist/web/assets/media-DfHUEnGn.js +1 -0
  465. package/dist/web/assets/rolldown-runtime-XKyaHtl9.js +1 -0
  466. package/dist/web/assets/useModels-1oIk0mDX.css +1 -0
  467. package/dist/web/assets/useModels-BmTGsRQz.js +15 -0
  468. package/dist/web/favicon.svg +1 -0
  469. package/dist/web/icon-192.png +0 -0
  470. package/dist/web/icon-512.png +0 -0
  471. package/dist/web/icon-maskable-512.png +0 -0
  472. package/dist/web/index.html +25 -0
  473. package/dist/web/manifest.webmanifest +16 -0
  474. package/dist/web/stats-preview.png +0 -0
  475. package/even-keel.config.example.json +59 -0
  476. package/package.json +118 -7
  477. package/plugins/checkpoint/.claude-plugin/plugin.json +9 -0
  478. package/plugins/checkpoint/README.md +85 -0
  479. package/plugins/checkpoint/hooks/checkpoint-mark.mjs +26 -0
  480. package/plugins/checkpoint/hooks/context-checkpoint.mjs +28 -0
  481. package/plugins/checkpoint/hooks/evenkeel.mjs +117 -0
  482. package/plugins/checkpoint/hooks/handoff.mjs +9 -0
  483. package/plugins/checkpoint/hooks/hooks.json +120 -0
  484. package/plugins/checkpoint/hooks/mailbox.mjs +36 -0
  485. package/plugins/checkpoint/hooks/merge-check.mjs +28 -0
  486. package/plugins/checkpoint/hooks/merges.mjs +103 -0
  487. package/plugins/checkpoint/hooks/nudges.mjs +78 -0
  488. package/plugins/checkpoint/hooks/permission.mjs +49 -0
  489. package/plugins/checkpoint/hooks/schema-hint.mjs +53 -0
  490. package/plugins/checkpoint/hooks/standard-gate.mjs +51 -0
  491. package/plugins/checkpoint/hooks/standards.mjs +258 -0
  492. package/plugins/checkpoint/opencode/checkpoint.mjs +85 -0
  493. package/plugins/github/.claude-plugin/plugin.json +9 -0
  494. package/plugins/github/.mcp.json +12 -0
  495. package/plugins/github/README.md +73 -0
  496. package/plugins/github/skills/task/SKILL.md +62 -0
  497. package/plugins/gitlab/.claude-plugin/plugin.json +7 -0
  498. package/plugins/gitlab/.mcp.json +13 -0
  499. package/plugins/gitlab/README.md +29 -0
  500. package/plugins/gitlab/skills/task/SKILL.md +55 -0
  501. package/plugins/huly/.claude-plugin/plugin.json +9 -0
  502. package/plugins/huly/.mcp.json +11 -0
  503. package/plugins/huly/README.md +60 -0
  504. package/plugins/huly/hooks/calibration-context.mjs +44 -0
  505. package/plugins/huly/hooks/calibration.mjs +80 -0
  506. package/plugins/huly/hooks/check-marks.mjs +23 -0
  507. package/plugins/huly/hooks/hooks.json +26 -0
  508. package/plugins/huly/hooks/marks.mjs +58 -0
  509. package/plugins/huly/hooks/show-calibration.mjs +19 -0
  510. package/plugins/huly/opencode/huly.mjs +31 -0
  511. package/plugins/huly/skills/setup/SKILL.md +51 -0
  512. package/plugins/huly/skills/split/SKILL.md +53 -0
  513. package/plugins/huly/skills/task/SKILL.md +81 -0
  514. package/plugins/jira/.claude-plugin/plugin.json +7 -0
  515. package/plugins/jira/.mcp.json +11 -0
  516. package/plugins/jira/README.md +39 -0
  517. package/plugins/jira/skills/task/SKILL.md +63 -0
  518. package/plugins/linear/.claude-plugin/plugin.json +9 -0
  519. package/plugins/linear/.mcp.json +11 -0
  520. package/plugins/linear/README.md +71 -0
  521. package/plugins/linear/skills/task/SKILL.md +62 -0
  522. package/plugins/tally/.claude-plugin/plugin.json +9 -0
  523. package/plugins/tally/README.md +42 -0
  524. package/plugins/tally/bin/locate.mjs +48 -0
  525. package/plugins/tally/bin/sql.mjs +21 -0
  526. package/plugins/tally/skills/session-audit/SKILL.md +333 -0
  527. package/service/EvenKeelHost.cs +133 -0
  528. package/service/EvenKeelService.cs +271 -0
  529. package/service/EvenKeelTray.cs +258 -0
  530. package/service/install.ps1 +95 -0
  531. package/service/install.sh +143 -0
  532. package/service/uninstall.ps1 +19 -0
  533. package/service/uninstall.sh +37 -0
  534. package/statusline/statusline.mjs +46 -0
  535. package/tally/bin/rule-hook.mjs +26 -0
  536. package/tally/bin/sql.mjs +166 -0
  537. package/tally/tally.config.json +59 -0
@@ -0,0 +1,234 @@
1
+ import { stripAnsi } from '../ansi.js';
2
+ import { fail, notice } from '../notice/notice.js';
3
+ import { CODEX } from '../codex/name.js';
4
+ import { COPILOT } from '../copilot/name.js';
5
+ import { OPENCODE } from '../opencode/name.js';
6
+ import { isLive } from '../sessions/agents.js';
7
+ import { APP_LABELS } from './apps.js';
8
+ export const GLOBAL_ACCOUNT = 'global';
9
+ /** The key of Codex's own account folder, `CODEX_HOME` or `~/.codex` (`AccountHomes`). */
10
+ export const CODEX_ACCOUNT = 'codex';
11
+ /** ChatGPT's `chatgpt_plan_type` of an account without a subscription. */
12
+ const FREE_PLAN = 'free';
13
+ const URL_PATTERN = /https:\/\/[^\s"'<>\u001b]+/g;
14
+ const TRAILING_PUNCTUATION = /[.,;:!?)\]]+$/;
15
+ /** Codex's code is the line under "Enter this one-time code", OpenCode's follows "Enter code:"; a code counts once its line ends. */
16
+ const DEVICE_CODE = /(?:one-time code[^\n]*\n[ \t]*|Enter code:[ \t]*)([A-Z0-9]+(?:-[A-Z0-9]+)*)[ \t]*\n/g;
17
+ export function parseAuthStatus(text) {
18
+ let body;
19
+ try {
20
+ body = JSON.parse(text);
21
+ }
22
+ catch {
23
+ throw fail('claude.statusNotJson', {}, { detail: text.trim().slice(0, 200) });
24
+ }
25
+ if (typeof body !== 'object' || body === null || Array.isArray(body))
26
+ throw fail('claude.statusNotObject', {});
27
+ const fields = body;
28
+ if (typeof fields.loggedIn !== 'boolean')
29
+ throw fail('claude.statusNoFlag', {});
30
+ return {
31
+ loggedIn: fields.loggedIn,
32
+ authMethod: stringOf(fields.authMethod),
33
+ email: stringOf(fields.email),
34
+ orgName: stringOf(fields.orgName),
35
+ subscriptionType: stringOf(fields.subscriptionType),
36
+ };
37
+ }
38
+ /** An API account signs in with a key from its folder's `settings.json`, not with claude.ai (`docs/accounts.md`). */
39
+ export function isApiKey(status) {
40
+ return status?.authMethod === 'api_key' || status?.authMethod === 'api_key_helper';
41
+ }
42
+ /** An API account has no e-mail: its folder key stands in for it wherever an account is told apart by e-mail. */
43
+ export function withIdentity(status, key) {
44
+ return status.loggedIn && status.email === null && isApiKey(status) ? { ...status, email: key } : status;
45
+ }
46
+ export function cleanOutput(raw) {
47
+ return stripAnsi(raw).replace(/\r\n?/g, '\n');
48
+ }
49
+ export function loginUrls(output) {
50
+ const found = cleanOutput(output).match(URL_PATTERN) ?? [];
51
+ return [...new Set(found.map((url) => url.replace(TRAILING_PUNCTUATION, '')))];
52
+ }
53
+ /** The one-time code a device sign-in printed, Codex's or OpenCode's: the last one, `null` when there is none yet. */
54
+ export function loginCode(output) {
55
+ return [...cleanOutput(output).matchAll(DEVICE_CODE)].at(-1)?.[1] ?? null;
56
+ }
57
+ export function parseCredentialTimes(text) {
58
+ const none = { accessExpiresAt: null, loginExpiresAt: null, erased: false };
59
+ if (text === undefined)
60
+ return none;
61
+ let body;
62
+ try {
63
+ body = JSON.parse(text);
64
+ }
65
+ catch {
66
+ return none;
67
+ }
68
+ const oauth = body?.claudeAiOauth;
69
+ if (typeof oauth !== 'object' || oauth === null)
70
+ return none;
71
+ const { expiresAt, refreshTokenExpiresAt } = oauth;
72
+ const time = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : null);
73
+ return { accessExpiresAt: time(expiresAt), loginExpiresAt: time(refreshTokenExpiresAt), erased: expiresAt === 0 };
74
+ }
75
+ export function needsLogin(status, times, now) {
76
+ if (status?.loggedIn === false || times.erased)
77
+ return true;
78
+ return times.loginExpiresAt !== null && times.loginExpiresAt <= now.getTime();
79
+ }
80
+ export function folderName(email, taken) {
81
+ const base = folderBase(email);
82
+ const used = new Set([...taken.map((name) => name.toLowerCase()), GLOBAL_ACCOUNT, CODEX_ACCOUNT]);
83
+ if (!used.has(base))
84
+ return base;
85
+ let index = 2;
86
+ while (used.has(`${base}-${index}`))
87
+ index++;
88
+ return `${base}-${index}`;
89
+ }
90
+ /** Whether the folder `key` bears the name `folderName` gives a sign-in of `email`, numbered or not. */
91
+ export function namedAfter(key, email) {
92
+ const base = folderBase(email);
93
+ const lower = key.toLowerCase();
94
+ return lower === base || (lower.startsWith(`${base}-`) && /^\d+$/.test(lower.slice(base.length + 1)));
95
+ }
96
+ function folderBase(email) {
97
+ const local = (email ?? '').split('@')[0].toLowerCase();
98
+ return local.replace(/[^a-z0-9._-]+/g, '-').replace(/^[.-]+|[.-]+$/g, '') || 'account';
99
+ }
100
+ /** Machine accounts have no profile email; their folder identifies them in the current view and the ledger alike. */
101
+ export function accountId(key, email) {
102
+ return email === null ? key : `${key}/${email}`;
103
+ }
104
+ export function activeEntry(view) {
105
+ return view.accounts.find((entry) => entry.key === view.active);
106
+ }
107
+ export function switchProblem(view, key) {
108
+ const target = view.accounts.find((entry) => entry.key === key);
109
+ if (target === undefined)
110
+ return notice('account.unknown', { key });
111
+ if (key === view.active)
112
+ return notice('account.alreadyActive', { key });
113
+ if (target.removing)
114
+ return notice('account.removing', { account: target.status?.email ?? key });
115
+ if (copilotOnly(target))
116
+ return copilotUnsupported();
117
+ if (target.needsLogin || target.status?.loggedIn !== true)
118
+ return notice('account.needsLogin', { account: target.status?.email ?? key });
119
+ return freeHeld(view.accounts, key);
120
+ }
121
+ export function removeProblem(view, key) {
122
+ const target = view.accounts.find((entry) => entry.key === key);
123
+ if (target === undefined)
124
+ return notice('account.unknown', { key });
125
+ if (key === view.active)
126
+ return notice('account.activeKept', {});
127
+ const named = view.pool.filter((entry) => entry.account === key && entry.byDefault !== true);
128
+ if (named.some((entry) => entry.project === null))
129
+ return notice('account.sharedPool', { agent: target.agent });
130
+ const projects = [...new Set([...named.map((entry) => entry.project), ...target.projects])];
131
+ if (projects.length > 0)
132
+ return notice('account.projectPool', { agent: target.agent, projects });
133
+ if (view.login?.target === key && view.login.error === null)
134
+ return notice('account.signInRunning', {});
135
+ const user = target.global ? undefined : view.apps.find((app) => app.account === key);
136
+ if (user !== undefined)
137
+ return notice('account.usedByApp', { app: APP_LABELS[user.app] });
138
+ return undefined;
139
+ }
140
+ export function liveSessions(agents, key) {
141
+ return agents
142
+ .filter((agent) => (agent.account ?? GLOBAL_ACCOUNT) === key && isLive(agent))
143
+ .map((agent) => agent.name ?? agent.sessionId.slice(0, 8));
144
+ }
145
+ export function loginBlock(chains, agents, active) {
146
+ const running = chains.filter((chain) => !chain.paused).map((chain) => chain.project);
147
+ const busy = agents.flatMap((agent) => {
148
+ if ((agent.account ?? GLOBAL_ACCOUNT) !== active)
149
+ return [];
150
+ if (agent.state === 'done' || agent.state === 'stopped' || agent.state === 'failed')
151
+ return [];
152
+ if (agent.status !== 'busy' && agent.status !== 'shell' && agent.status !== 'waiting')
153
+ return [];
154
+ const status = agent.status === 'waiting' ? 'waiting' : 'busy';
155
+ return [{ name: agent.name ?? agent.sessionId.slice(0, 8), status, waitingFor: agent.waitingFor ?? null }];
156
+ });
157
+ return running.length === 0 && busy.length === 0 ? null : { chains: running, sessions: busy };
158
+ }
159
+ export function loginBlockNotice(block) {
160
+ if (block.chains.length > 0)
161
+ return notice('account.chainRunning', { project: block.chains.join(', ') });
162
+ return notice('account.sessionsBusy', { sessions: block.sessions.map((session) => session.name) });
163
+ }
164
+ /** Why no session starts in the account folder `key` — the active one, where Claude sessions start, unless given. */
165
+ export function launchBlock(view, key = view.active) {
166
+ const active = view.accounts.find((entry) => entry.key === key);
167
+ if (active === undefined)
168
+ return key === view.active ? notice('account.activeGone', { key }) : notice('account.homeGone', { key });
169
+ if (copilotOnly(active))
170
+ return copilotUnsupported();
171
+ if (view.login !== null && view.login.target === key && view.login.error === null)
172
+ return notice('account.signingIn', {});
173
+ if (active.status?.loggedIn === false)
174
+ return notice('account.signedOut', {});
175
+ if (active.needsLogin)
176
+ return notice('account.loginExpired', { account: active.status?.email ?? active.key });
177
+ return freeHeld(view.accounts, key);
178
+ }
179
+ /**
180
+ * The keys of the Codex accounts on the free plan past the first in the folders' order: OpenAI's terms forbid several
181
+ * free accounts to get round the limits, so only the first of them works (BTN-176).
182
+ */
183
+ export function extraFree(accounts) {
184
+ return freeCodex(accounts)
185
+ .slice(1)
186
+ .map((entry) => entry.key);
187
+ }
188
+ /**
189
+ * Whether an account of the agent just added takes the shared pool's entry for it: it does while that entry names no
190
+ * folder of the agent, so an install that signed in only from the page does not stay on a built-in folder that was
191
+ * never there (BTN-227).
192
+ */
193
+ export function takesShared(pool, homes, agent) {
194
+ const shared = pool.find((entry) => entry.project === null && entry.provider === agent)?.account;
195
+ return shared === undefined || !homes.some((home) => home.key === shared && home.agent === agent);
196
+ }
197
+ /** Why no account of the agent is added: its CLI is not found, with where to get it. */
198
+ export function missingNotice(agent) {
199
+ if (agent === OPENCODE)
200
+ return notice('login.opencodeMissing', {});
201
+ if (agent === COPILOT)
202
+ return notice('login.copilotMissing', {});
203
+ return notice(agent === CODEX ? 'login.codexMissing' : 'login.claudeMissing', {});
204
+ }
205
+ /**
206
+ * Why a Codex sign-in on the free plan is not added: another account already holds the one free plan, or the sign-in
207
+ * is that very account again.
208
+ */
209
+ export function secondFreeProblem(accounts, agent, status) {
210
+ if (agent !== CODEX || !isFree(status))
211
+ return undefined;
212
+ const first = freeCodex(accounts)[0];
213
+ if (first === undefined)
214
+ return undefined;
215
+ const account = first.status?.email ?? first.key;
216
+ const same = status.email !== null && first.status?.email?.toLowerCase() === status.email.toLowerCase();
217
+ return notice(same ? 'account.sameFree' : 'account.secondFree', { account });
218
+ }
219
+ function freeHeld(accounts, key) {
220
+ const [first, ...rest] = freeCodex(accounts);
221
+ if (first === undefined || !rest.some((entry) => entry.key === key))
222
+ return undefined;
223
+ return notice('account.extraFree', { account: first.status?.email ?? first.key });
224
+ }
225
+ function freeCodex(accounts) {
226
+ return accounts.filter((entry) => entry.agent === CODEX && isFree(entry.status));
227
+ }
228
+ function isFree(status) {
229
+ return status?.loggedIn === true && status.subscriptionType === FREE_PLAN;
230
+ }
231
+ function stringOf(value) {
232
+ return typeof value === 'string' && value !== '' ? value : null;
233
+ }
234
+ import { copilotOnly, copilotUnsupported } from '../opencode/support.js';
@@ -0,0 +1,51 @@
1
+ import { CODEX } from '../codex/name.js';
2
+ import { COPILOT } from '../copilot/name.js';
3
+ import { OPENCODE } from '../opencode/name.js';
4
+ const CLAUDE_API = 'https://api.anthropic.com';
5
+ const OPENAI_API = 'https://api.openai.com';
6
+ const OPENCODE_API = 'https://opencode.ai';
7
+ const GITHUB_API = 'https://api.github.com';
8
+ /** The API the agent's sign-in renews its tokens against: account calls wait until it answers (EKR-15). */
9
+ export function providerUrl(agent) {
10
+ if (agent === OPENCODE)
11
+ return OPENCODE_API;
12
+ if (agent === COPILOT)
13
+ return GITHUB_API;
14
+ return agent === CODEX ? OPENAI_API : CLAUDE_API;
15
+ }
16
+ /** Any HTTP answer, an error status too, means the network is up. */
17
+ export async function answers(url, timeoutMs) {
18
+ try {
19
+ await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(timeoutMs) });
20
+ return true;
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ /**
27
+ * Whether a provider answers, probed at most once per `ttlMs`. After a reboot the service starts before the network,
28
+ * and a CLI call that renews an account's single-use token then may lose the renewal and sign the account out.
29
+ */
30
+ export class Reachability {
31
+ options;
32
+ known = new Map();
33
+ constructor(options) {
34
+ this.options = options;
35
+ }
36
+ up(url) {
37
+ const known = this.known.get(url);
38
+ if (known?.asking !== undefined)
39
+ return known.asking;
40
+ if (known !== undefined && this.options.now() - known.at < this.options.ttlMs)
41
+ return Promise.resolve(known.up);
42
+ const asking = this.options.probe(url).then((up) => {
43
+ this.known.set(url, { up, at: this.options.now() });
44
+ if (up !== (known?.up ?? true))
45
+ this.options.onChange(url, up);
46
+ return up;
47
+ });
48
+ this.known.set(url, { up: known?.up ?? true, at: known?.at ?? -Infinity, asking });
49
+ return asking;
50
+ }
51
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The organization each API account's key pays through, as the provider last named it to a working key (BTN-418):
3
+ * a dead key's answer names none, so it is kept while the key works, and a replacing key of another one is refused.
4
+ * Keyed by the account folder's key; table `key_organizations` (step 14, `docs/storage.md`).
5
+ */
6
+ export async function loadKeyOrganizations(db) {
7
+ const { rows } = await db.query('select account, organization from key_organizations order by account');
8
+ return new Map(rows.map((row) => [row.account, row.organization]));
9
+ }
10
+ /** Starts its write at the call: see `Writes`. */
11
+ export async function saveKeyOrganization(db, account, organization, at) {
12
+ await db.query(`insert into key_organizations (account, organization, seen_at) values ($1, $2, $3)
13
+ on conflict (account) do update set organization = excluded.organization, seen_at = excluded.seen_at`, [account, organization, at.toISOString()]);
14
+ }
15
+ export async function forgetKeyOrganization(db, account) {
16
+ await db.query('delete from key_organizations where account = $1', [account]);
17
+ }
@@ -0,0 +1,17 @@
1
+ import { checkCredit, checkOpenAiCredit } from './credit.js';
2
+ import { checkApiKey, checkGitHubToken, checkOpenAiKey } from './keycheck.js';
3
+ export const KEY_CHECK_MS = 60 * 60_000;
4
+ export const KEY_CHECK_TIMEOUT_MS = 15_000;
5
+ export const CREDIT_CHECK_MS = 5 * 60_000;
6
+ /**
7
+ * The key and credit checks by whose key it is (`keyProvider`). GitHub has no way to ask about a token's AI credits: a
8
+ * Copilot account waits out spent credit by its monthly allowance instead (`Agent.keyUsage`, BTN-341).
9
+ */
10
+ export const KEY_CHECKS = { anthropic: checkApiKey, openai: checkOpenAiKey, github: checkGitHubToken };
11
+ export const CREDIT_CHECKS = {
12
+ anthropic: checkCredit,
13
+ openai: checkOpenAiCredit,
14
+ github: async () => ({ state: 'unknown', reason: 'GitHub does not tell the AI credits of a token' }),
15
+ };
16
+ export const APP_POLL_MS = 5_000;
17
+ export const LOGIN_TIMEOUT_MS = 10 * 60_000;
@@ -0,0 +1,275 @@
1
+ import { copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, renameSync, rmdirSync, symlinkSync, unlinkSync, writeFileSync, } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { pathKey, sameSpelling } from '../paths.js';
4
+ import { projectDirName } from '../sessions/paths.js';
5
+ const PROJECT_KEYS = [
6
+ 'hasTrustDialogAccepted',
7
+ 'mcpServers',
8
+ 'allowedTools',
9
+ 'enabledMcpjsonServers',
10
+ 'disabledMcpjsonServers',
11
+ 'disabledMcpServers',
12
+ 'mcpContextUris',
13
+ 'hasClaudeMdExternalIncludesApproved',
14
+ 'hasClaudeMdExternalIncludesWarningShown',
15
+ ];
16
+ const ONBOARDING_KEYS = ['hasCompletedOnboarding', 'lastOnboardingVersion'];
17
+ const LINKED_DIRS = ['agents', 'commands', 'hooks', 'output-styles'];
18
+ const OWN_SKILLS = new Set(['synced', '.trash']);
19
+ export const API_KEY_VARIABLE = 'ANTHROPIC_API_KEY';
20
+ const COPIED_FILES = ['CLAUDE.md', join('plugins', 'installed_plugins.json'), join('plugins', 'known_marketplaces.json')];
21
+ /** The global settings win, except the folder's own API key: it is what makes an API account (`docs/accounts.md`). */
22
+ export function mergeSettings(global, folder) {
23
+ const merged = { ...folder, ...global };
24
+ const key = objectOf(folder?.env)[API_KEY_VARIABLE];
25
+ if (typeof key === 'string')
26
+ merged.env = { ...objectOf(global.env), [API_KEY_VARIABLE]: key };
27
+ if (folder?.apiKeyHelper !== undefined)
28
+ merged.apiKeyHelper = folder.apiKeyHelper;
29
+ return merged;
30
+ }
31
+ /**
32
+ * Project memory lives next to the global folder, not inside it: the global folder and every account folder hold only
33
+ * a link to it. The CLI asks before every write whose real path lies in a `.claude` folder that is not the session's own.
34
+ */
35
+ export function memoryStore(globalDir) {
36
+ return join(dirname(globalDir), '.claude-memory');
37
+ }
38
+ /** Shared skills live next to the global folder for the same reason as memory: `~/.claude/skills/<name>` links to it. */
39
+ export function skillStore(globalDir) {
40
+ return join(dirname(globalDir), '.claude-skills');
41
+ }
42
+ /** `agents`, `commands`, `hooks` and `output-styles` each live in their own store next to the global folder, as skills do. */
43
+ export function linkedStore(globalDir, name) {
44
+ return join(dirname(globalDir), `.claude-${name}`);
45
+ }
46
+ /** The repository's folder in the memory store; chain sessions also get it as `autoMemoryDirectory`. */
47
+ export function sharedMemory(globalDir, repo) {
48
+ return join(memoryStore(globalDir), projectDirName(repo));
49
+ }
50
+ export function linkType(platform) {
51
+ return platform === 'win32' ? 'junction' : 'dir';
52
+ }
53
+ export function shareProjects(global, folder, platform = process.platform) {
54
+ const projects = { ...objectOf(folder.projects) };
55
+ const globalProjects = Object.entries(objectOf(global.projects));
56
+ const keyOf = (path) => pathKey(path, platform);
57
+ const trusted = new Set(globalProjects.filter(([, value]) => objectOf(value).hasTrustDialogAccepted === true).map(([path]) => keyOf(path)));
58
+ for (const [path, value] of globalProjects) {
59
+ const project = objectOf(value);
60
+ if (!trusted.has(keyOf(path)))
61
+ continue;
62
+ const setup = Object.fromEntries(PROJECT_KEYS.filter((key) => key in project).map((key) => [key, project[key]]));
63
+ projects[path] = { ...objectOf(projects[path]), ...setup };
64
+ }
65
+ const shared = { ...folder, projects };
66
+ if (global.mcpServers !== undefined)
67
+ shared.mcpServers = global.mcpServers;
68
+ for (const key of ONBOARDING_KEYS)
69
+ if (shared[key] === undefined && global[key] !== undefined)
70
+ shared[key] = global[key];
71
+ return JSON.stringify(shared) === JSON.stringify(folder) ? undefined : shared;
72
+ }
73
+ export function shareConfig(global, dir, warn = console.warn) {
74
+ mkdirSync(dir, { recursive: true });
75
+ let changed = false;
76
+ const settings = readJson(join(global.dir, 'settings.json'));
77
+ if (settings !== undefined)
78
+ changed = writeIfChanged(join(dir, 'settings.json'), mergeSettings(settings, readJson(join(dir, 'settings.json')))) || changed;
79
+ const config = readJson(global.config);
80
+ if (config !== undefined) {
81
+ const shared = shareProjects(config, readJson(join(dir, '.claude.json')) ?? {});
82
+ if (shared !== undefined) {
83
+ writeFileSync(join(dir, '.claude.json'), JSON.stringify(shared, null, 2));
84
+ changed = true;
85
+ }
86
+ }
87
+ for (const file of COPIED_FILES)
88
+ changed = copyIfChanged(join(global.dir, file), join(dir, file)) || changed;
89
+ for (const name of LINKED_DIRS) {
90
+ const own = join(global.dir, name);
91
+ const stored = linkedStore(global.dir, name);
92
+ changed = absorbFolder(own, stored, warn, name) || changed;
93
+ changed = link(linksTo(own, stored) ? stored : own, join(dir, name)) || changed;
94
+ }
95
+ const skills = skillStore(global.dir);
96
+ for (const name of entries(join(global.dir, 'skills'))) {
97
+ if (OWN_SKILLS.has(name))
98
+ continue;
99
+ const own = join(global.dir, 'skills', name);
100
+ const stored = join(skills, name);
101
+ changed = absorbFolder(own, stored, warn, 'skills') || changed;
102
+ const path = join(dir, 'skills', name);
103
+ const target = linksTo(own, stored) ? stored : own;
104
+ if (!linksElsewhere(path, [own, stored]))
105
+ changed = link(target, path) || changed;
106
+ }
107
+ const store = memoryStore(global.dir);
108
+ const projects = new Set([...entries(join(global.dir, 'projects')), ...entries(join(dir, 'projects'))]);
109
+ for (const project of projects) {
110
+ const shared = join(store, project);
111
+ const owns = [global.dir, dir].map((home) => join(home, 'projects', project, 'memory'));
112
+ for (const own of owns)
113
+ changed = absorbMemory(shared, own, warn) || changed;
114
+ for (const own of owns)
115
+ changed = link(shared, own) || changed;
116
+ }
117
+ return changed;
118
+ }
119
+ /** Moves what a folder's own memory holds into the store; files that differ stay and are warned about. */
120
+ function absorbMemory(shared, own, warn) {
121
+ if (!existsSync(own))
122
+ return false;
123
+ const stat = lstatSync(own);
124
+ if (stat.isSymbolicLink() || !stat.isDirectory())
125
+ return false;
126
+ mkdirSync(shared, { recursive: true });
127
+ return moveInto(own, shared, warn, 'memory');
128
+ }
129
+ /**
130
+ * Moves a global skill or linked folder kept as a real folder into the store and leaves a link in its place; one already linked
131
+ * (to the store or elsewhere) stays, a missing one is linked to the store if it has it. A folder the store already has
132
+ * is merged as memory is; a locked one waits for the next launch.
133
+ */
134
+ function absorbFolder(own, stored, warn, what) {
135
+ const stat = lstatOf(own);
136
+ if (stat === undefined)
137
+ return link(stored, own);
138
+ if (stat.isSymbolicLink() || !stat.isDirectory())
139
+ return false;
140
+ try {
141
+ if (!existsSync(stored)) {
142
+ mkdirSync(dirname(stored), { recursive: true });
143
+ renameSync(own, stored);
144
+ symlinkSync(stored, own, linkType(process.platform));
145
+ return true;
146
+ }
147
+ const moved = moveInto(own, stored, warn, what);
148
+ return link(stored, own) || moved;
149
+ }
150
+ catch (error) {
151
+ warn(`${what}: ${own} was not moved to ${stored} (${error instanceof Error ? error.message : String(error)}); left in place`);
152
+ return false;
153
+ }
154
+ }
155
+ function moveInto(from, to, warn, what) {
156
+ let changed = false;
157
+ for (const entry of readdirSync(from, { withFileTypes: true })) {
158
+ const source = join(from, entry.name);
159
+ const target = join(to, entry.name);
160
+ if (entry.isDirectory()) {
161
+ mkdirSync(target, { recursive: true });
162
+ changed = moveInto(source, target, warn, what) || changed;
163
+ if (readdirSync(source).length === 0)
164
+ rmdirSync(source);
165
+ }
166
+ else if (!existsSync(target)) {
167
+ copyFileSync(source, target);
168
+ unlinkSync(source);
169
+ changed = true;
170
+ }
171
+ else if (readFileSync(source).equals(readFileSync(target))) {
172
+ unlinkSync(source);
173
+ changed = true;
174
+ }
175
+ else {
176
+ warn(`${what}: ${source} differs from ${target}; left in place, merge by hand`);
177
+ }
178
+ }
179
+ return changed;
180
+ }
181
+ function link(target, path) {
182
+ if (!isDirectory(target))
183
+ return false;
184
+ const stat = lstatOf(path);
185
+ if (stat?.isSymbolicLink()) {
186
+ if (sameSpelling(linkTarget(path), target))
187
+ return false;
188
+ unlinkSync(path);
189
+ }
190
+ else if (stat !== undefined) {
191
+ if (!stat.isDirectory() || readdirSync(path).length > 0)
192
+ return false;
193
+ rmdirSync(path);
194
+ }
195
+ mkdirSync(join(path, '..'), { recursive: true });
196
+ symlinkSync(target, path, linkType(process.platform));
197
+ return true;
198
+ }
199
+ function copyIfChanged(from, to) {
200
+ let source;
201
+ try {
202
+ source = readFileSync(from);
203
+ }
204
+ catch {
205
+ return false;
206
+ }
207
+ try {
208
+ if (readFileSync(to).equals(source))
209
+ return false;
210
+ }
211
+ catch {
212
+ mkdirSync(join(to, '..'), { recursive: true });
213
+ }
214
+ copyFileSync(from, to);
215
+ return true;
216
+ }
217
+ function writeIfChanged(file, value) {
218
+ if (JSON.stringify(readJson(file)) === JSON.stringify(value))
219
+ return false;
220
+ writeFileSync(file, JSON.stringify(value, null, 2));
221
+ return true;
222
+ }
223
+ function readJson(file) {
224
+ try {
225
+ const value = JSON.parse(readFileSync(file, 'utf8'));
226
+ return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : undefined;
227
+ }
228
+ catch {
229
+ return undefined;
230
+ }
231
+ }
232
+ function entries(dir) {
233
+ try {
234
+ return readdirSync(dir, { withFileTypes: true })
235
+ .filter((entry) => entry.isDirectory() || entry.isSymbolicLink())
236
+ .map((entry) => entry.name);
237
+ }
238
+ catch {
239
+ return [];
240
+ }
241
+ }
242
+ function lstatOf(path) {
243
+ try {
244
+ return lstatSync(path);
245
+ }
246
+ catch {
247
+ return undefined;
248
+ }
249
+ }
250
+ function linksTo(path, target) {
251
+ return lstatOf(path)?.isSymbolicLink() === true && sameSpelling(linkTarget(path), target);
252
+ }
253
+ /** A live link to a folder other than the given ones: a skill the user pointed somewhere on purpose. */
254
+ function linksElsewhere(path, targets) {
255
+ if (lstatOf(path)?.isSymbolicLink() !== true || !existsSync(path))
256
+ return false;
257
+ return !targets.some((target) => sameSpelling(linkTarget(path), target));
258
+ }
259
+ /** A junction's target comes back as `\\?\C:\…\` on Windows. */
260
+ function linkTarget(path) {
261
+ return readlinkSync(path)
262
+ .replace(/^\\\\\?\\/, '')
263
+ .replace(/[\\/]+$/, '');
264
+ }
265
+ function isDirectory(path) {
266
+ try {
267
+ return lstatSync(path).isDirectory() || lstatSync(path).isSymbolicLink();
268
+ }
269
+ catch {
270
+ return false;
271
+ }
272
+ }
273
+ function objectOf(value) {
274
+ return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : {};
275
+ }
@@ -0,0 +1,29 @@
1
+ import { CLAUDE } from '../claude/name.js';
2
+ export const TRANSPORTS = ['sdk', 'bg'];
3
+ /** The `execution.claude` that runs each account by its kind (`transportOf`), the setting's default (BTN-353). */
4
+ export const AUTO_EXECUTION = 'auto';
5
+ export function isTransport(value) {
6
+ return typeof value === 'string' && TRANSPORTS.includes(value);
7
+ }
8
+ /**
9
+ * The transport of an account's new sessions by `execution.claude`: the one it names, else on `auto` the SDK on an API
10
+ * key and `--bg` on a subscription, which Anthropic's terms do not clearly allow to drive through the SDK. `null` — the
11
+ * account's agent has one way only.
12
+ */
13
+ export function transportOf(agent, execution, apiKey) {
14
+ if (agent !== CLAUDE)
15
+ return null;
16
+ if (isTransport(execution))
17
+ return execution;
18
+ return apiKey ? 'sdk' : 'bg';
19
+ }
20
+ /**
21
+ * The `execution.claude` the accounts' own choices become (BTN-353): `auto` when each is what `auto` gives its account,
22
+ * the SDK when every one chose it, else `auto`, with the accounts whose way changes by it.
23
+ */
24
+ export function executionOfChoices(choices) {
25
+ const moved = choices.filter((choice) => choice.transport !== transportOf(CLAUDE, AUTO_EXECUTION, choice.apiKey));
26
+ if (moved.length > 0 && choices.every((choice) => choice.transport === 'sdk'))
27
+ return { execution: 'sdk', moved: [] };
28
+ return { execution: AUTO_EXECUTION, moved };
29
+ }