@qodeca/xezar 0.10.1

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 (481) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +810 -0
  3. package/dist/agent-config/account-identity.d.ts +44 -0
  4. package/dist/agent-config/account-identity.js +128 -0
  5. package/dist/agent-config/account-identity.js.map +1 -0
  6. package/dist/agent-config/catalog.d.ts +71 -0
  7. package/dist/agent-config/catalog.js +223 -0
  8. package/dist/agent-config/catalog.js.map +1 -0
  9. package/dist/agent-config/files.d.ts +46 -0
  10. package/dist/agent-config/files.js +119 -0
  11. package/dist/agent-config/files.js.map +1 -0
  12. package/dist/agent-config/model-settings/claude.d.ts +2 -0
  13. package/dist/agent-config/model-settings/claude.js +11 -0
  14. package/dist/agent-config/model-settings/claude.js.map +1 -0
  15. package/dist/agent-config/model-settings/codex.d.ts +2 -0
  16. package/dist/agent-config/model-settings/codex.js +16 -0
  17. package/dist/agent-config/model-settings/codex.js.map +1 -0
  18. package/dist/agent-config/model-settings/opencode.d.ts +2 -0
  19. package/dist/agent-config/model-settings/opencode.js +8 -0
  20. package/dist/agent-config/model-settings/opencode.js.map +1 -0
  21. package/dist/agent-config/model-settings/pi.d.ts +12 -0
  22. package/dist/agent-config/model-settings/pi.js +18 -0
  23. package/dist/agent-config/model-settings/pi.js.map +1 -0
  24. package/dist/agent-config/model-settings/shared.d.ts +9 -0
  25. package/dist/agent-config/model-settings/shared.js +93 -0
  26. package/dist/agent-config/model-settings/shared.js.map +1 -0
  27. package/dist/agent-config/model-settings/types.d.ts +9 -0
  28. package/dist/agent-config/model-settings/types.js +2 -0
  29. package/dist/agent-config/model-settings/types.js.map +1 -0
  30. package/dist/agent-config/models.d.ts +9 -0
  31. package/dist/agent-config/models.js +32 -0
  32. package/dist/agent-config/models.js.map +1 -0
  33. package/dist/agent-config/seed.d.ts +7 -0
  34. package/dist/agent-config/seed.js +90 -0
  35. package/dist/agent-config/seed.js.map +1 -0
  36. package/dist/agent-config/service.d.ts +44 -0
  37. package/dist/agent-config/service.js +71 -0
  38. package/dist/agent-config/service.js.map +1 -0
  39. package/dist/agent-config/validate.d.ts +21 -0
  40. package/dist/agent-config/validate.js +76 -0
  41. package/dist/agent-config/validate.js.map +1 -0
  42. package/dist/automations/coordinator.d.ts +26 -0
  43. package/dist/automations/coordinator.js +66 -0
  44. package/dist/automations/coordinator.js.map +1 -0
  45. package/dist/automations/github-poller.d.ts +74 -0
  46. package/dist/automations/github-poller.js +234 -0
  47. package/dist/automations/github-poller.js.map +1 -0
  48. package/dist/automations/scheduler.d.ts +42 -0
  49. package/dist/automations/scheduler.js +192 -0
  50. package/dist/automations/scheduler.js.map +1 -0
  51. package/dist/automations/store.d.ts +65 -0
  52. package/dist/automations/store.js +298 -0
  53. package/dist/automations/store.js.map +1 -0
  54. package/dist/automations/task-template.d.ts +18 -0
  55. package/dist/automations/task-template.js +95 -0
  56. package/dist/automations/task-template.js.map +1 -0
  57. package/dist/automations/types.d.ts +255 -0
  58. package/dist/automations/types.js +159 -0
  59. package/dist/automations/types.js.map +1 -0
  60. package/dist/config.d.ts +77 -0
  61. package/dist/config.js +242 -0
  62. package/dist/config.js.map +1 -0
  63. package/dist/contract/agent-config.d.ts +153 -0
  64. package/dist/contract/agent-profiles.d.ts +347 -0
  65. package/dist/contract/automations.d.ts +935 -0
  66. package/dist/contract/events.d.ts +104 -0
  67. package/dist/contract/github.d.ts +597 -0
  68. package/dist/contract/health.d.ts +102 -0
  69. package/dist/contract/index.d.ts +16 -0
  70. package/dist/contract/index.js +1953 -0
  71. package/dist/contract/projects.d.ts +210 -0
  72. package/dist/contract/repo.d.ts +234 -0
  73. package/dist/contract/runs.d.ts +1527 -0
  74. package/dist/contract/skills.d.ts +234 -0
  75. package/dist/contract/workflows.d.ts +218 -0
  76. package/dist/contract/workspace.d.ts +579 -0
  77. package/dist/core/agent-env.d.ts +31 -0
  78. package/dist/core/agent-env.js +371 -0
  79. package/dist/core/agent-env.js.map +1 -0
  80. package/dist/core/agent-model-policy.d.ts +10 -0
  81. package/dist/core/agent-model-policy.js +36 -0
  82. package/dist/core/agent-model-policy.js.map +1 -0
  83. package/dist/core/agent-profiles.d.ts +62 -0
  84. package/dist/core/agent-profiles.js +90 -0
  85. package/dist/core/agent-profiles.js.map +1 -0
  86. package/dist/core/agent-runner.d.ts +211 -0
  87. package/dist/core/agent-runner.js +68 -0
  88. package/dist/core/agent-runner.js.map +1 -0
  89. package/dist/core/ask.d.ts +115 -0
  90. package/dist/core/ask.js +256 -0
  91. package/dist/core/ask.js.map +1 -0
  92. package/dist/core/backend-detect.d.ts +16 -0
  93. package/dist/core/backend-detect.js +155 -0
  94. package/dist/core/backend-detect.js.map +1 -0
  95. package/dist/core/claude-cli-runner.d.ts +65 -0
  96. package/dist/core/claude-cli-runner.js +479 -0
  97. package/dist/core/claude-cli-runner.js.map +1 -0
  98. package/dist/core/claude-model-catalog.d.ts +24 -0
  99. package/dist/core/claude-model-catalog.js +208 -0
  100. package/dist/core/claude-model-catalog.js.map +1 -0
  101. package/dist/core/claude-ui-mapper.d.ts +79 -0
  102. package/dist/core/claude-ui-mapper.js +597 -0
  103. package/dist/core/claude-ui-mapper.js.map +1 -0
  104. package/dist/core/codex-app-server-runner.d.ts +41 -0
  105. package/dist/core/codex-app-server-runner.js +584 -0
  106. package/dist/core/codex-app-server-runner.js.map +1 -0
  107. package/dist/core/codex-app-server-transport.d.ts +35 -0
  108. package/dist/core/codex-app-server-transport.js +150 -0
  109. package/dist/core/codex-app-server-transport.js.map +1 -0
  110. package/dist/core/codex-model-catalog.d.ts +10 -0
  111. package/dist/core/codex-model-catalog.js +110 -0
  112. package/dist/core/codex-model-catalog.js.map +1 -0
  113. package/dist/core/codex-ui-mapper.d.ts +109 -0
  114. package/dist/core/codex-ui-mapper.js +764 -0
  115. package/dist/core/codex-ui-mapper.js.map +1 -0
  116. package/dist/core/model-identity.d.ts +125 -0
  117. package/dist/core/model-identity.js +132 -0
  118. package/dist/core/model-identity.js.map +1 -0
  119. package/dist/core/model-presets.d.ts +21 -0
  120. package/dist/core/model-presets.js +91 -0
  121. package/dist/core/model-presets.js.map +1 -0
  122. package/dist/core/ndjson.d.ts +6 -0
  123. package/dist/core/ndjson.js +23 -0
  124. package/dist/core/ndjson.js.map +1 -0
  125. package/dist/core/opencode-model-catalog.d.ts +32 -0
  126. package/dist/core/opencode-model-catalog.js +153 -0
  127. package/dist/core/opencode-model-catalog.js.map +1 -0
  128. package/dist/core/opencode-server-runner.d.ts +32 -0
  129. package/dist/core/opencode-server-runner.js +535 -0
  130. package/dist/core/opencode-server-runner.js.map +1 -0
  131. package/dist/core/opencode-ui-mapper.d.ts +111 -0
  132. package/dist/core/opencode-ui-mapper.js +668 -0
  133. package/dist/core/opencode-ui-mapper.js.map +1 -0
  134. package/dist/core/pi-runner.d.ts +24 -0
  135. package/dist/core/pi-runner.js +349 -0
  136. package/dist/core/pi-runner.js.map +1 -0
  137. package/dist/core/pi-ui-mapper.d.ts +34 -0
  138. package/dist/core/pi-ui-mapper.js +270 -0
  139. package/dist/core/pi-ui-mapper.js.map +1 -0
  140. package/dist/core/process-usage.d.ts +66 -0
  141. package/dist/core/process-usage.js +193 -0
  142. package/dist/core/process-usage.js.map +1 -0
  143. package/dist/core/provider-auth.d.ts +154 -0
  144. package/dist/core/provider-auth.js +509 -0
  145. package/dist/core/provider-auth.js.map +1 -0
  146. package/dist/core/provider-availability.d.ts +3 -0
  147. package/dist/core/provider-availability.js +14 -0
  148. package/dist/core/provider-availability.js.map +1 -0
  149. package/dist/core/runner-factory.d.ts +8 -0
  150. package/dist/core/runner-factory.js +25 -0
  151. package/dist/core/runner-factory.js.map +1 -0
  152. package/dist/core/runner-model-catalog.d.ts +28 -0
  153. package/dist/core/runner-model-catalog.js +62 -0
  154. package/dist/core/runner-model-catalog.js.map +1 -0
  155. package/dist/core/secret-redaction.d.ts +37 -0
  156. package/dist/core/secret-redaction.js +115 -0
  157. package/dist/core/secret-redaction.js.map +1 -0
  158. package/dist/core/shell-env.d.ts +28 -0
  159. package/dist/core/shell-env.js +56 -0
  160. package/dist/core/shell-env.js.map +1 -0
  161. package/dist/core/tool-display.d.ts +31 -0
  162. package/dist/core/tool-display.js +179 -0
  163. package/dist/core/tool-display.js.map +1 -0
  164. package/dist/core/ui-events.d.ts +332 -0
  165. package/dist/core/ui-events.js +22 -0
  166. package/dist/core/ui-events.js.map +1 -0
  167. package/dist/core/usage-limit.d.ts +42 -0
  168. package/dist/core/usage-limit.js +209 -0
  169. package/dist/core/usage-limit.js.map +1 -0
  170. package/dist/core/usage.d.ts +11 -0
  171. package/dist/core/usage.js +15 -0
  172. package/dist/core/usage.js.map +1 -0
  173. package/dist/core/v1-text-coalescer.d.ts +36 -0
  174. package/dist/core/v1-text-coalescer.js +72 -0
  175. package/dist/core/v1-text-coalescer.js.map +1 -0
  176. package/dist/git-diff-base.d.ts +85 -0
  177. package/dist/git-diff-base.js +180 -0
  178. package/dist/git-diff-base.js.map +1 -0
  179. package/dist/git-refs.d.ts +11 -0
  180. package/dist/git-refs.js +14 -0
  181. package/dist/git-refs.js.map +1 -0
  182. package/dist/git-worktree.d.ts +161 -0
  183. package/dist/git-worktree.js +508 -0
  184. package/dist/git-worktree.js.map +1 -0
  185. package/dist/handoff.d.ts +62 -0
  186. package/dist/handoff.js +154 -0
  187. package/dist/handoff.js.map +1 -0
  188. package/dist/index.d.ts +2 -0
  189. package/dist/index.js +684 -0
  190. package/dist/index.js.map +1 -0
  191. package/dist/install-as-command.d.ts +40 -0
  192. package/dist/install-as-command.js +52 -0
  193. package/dist/install-as-command.js.map +1 -0
  194. package/dist/pack-check.d.ts +17 -0
  195. package/dist/pack-check.js +27 -0
  196. package/dist/pack-check.js.map +1 -0
  197. package/dist/paths.d.ts +140 -0
  198. package/dist/paths.js +192 -0
  199. package/dist/paths.js.map +1 -0
  200. package/dist/planner.d.ts +27 -0
  201. package/dist/planner.js +268 -0
  202. package/dist/planner.js.map +1 -0
  203. package/dist/release/manifests.d.ts +78 -0
  204. package/dist/release/manifests.js +57 -0
  205. package/dist/release/manifests.js.map +1 -0
  206. package/dist/release/snapshot.d.ts +67 -0
  207. package/dist/release/snapshot.js +97 -0
  208. package/dist/release/snapshot.js.map +1 -0
  209. package/dist/release/stable.d.ts +33 -0
  210. package/dist/release/stable.js +50 -0
  211. package/dist/release/stable.js.map +1 -0
  212. package/dist/runs/agent-tmpdir.d.ts +41 -0
  213. package/dist/runs/agent-tmpdir.js +183 -0
  214. package/dist/runs/agent-tmpdir.js.map +1 -0
  215. package/dist/runs/arm-repo-handle.d.ts +17 -0
  216. package/dist/runs/arm-repo-handle.js +24 -0
  217. package/dist/runs/arm-repo-handle.js.map +1 -0
  218. package/dist/runs/auto-name.d.ts +75 -0
  219. package/dist/runs/auto-name.js +179 -0
  220. package/dist/runs/auto-name.js.map +1 -0
  221. package/dist/runs/event-history.d.ts +50 -0
  222. package/dist/runs/event-history.js +613 -0
  223. package/dist/runs/event-history.js.map +1 -0
  224. package/dist/runs/retention.d.ts +60 -0
  225. package/dist/runs/retention.js +86 -0
  226. package/dist/runs/retention.js.map +1 -0
  227. package/dist/runs/review-gate.d.ts +18 -0
  228. package/dist/runs/review-gate.js +21 -0
  229. package/dist/runs/review-gate.js.map +1 -0
  230. package/dist/runs/run-index.d.ts +19 -0
  231. package/dist/runs/run-index.js +43 -0
  232. package/dist/runs/run-index.js.map +1 -0
  233. package/dist/runs/store.d.ts +490 -0
  234. package/dist/runs/store.js +1283 -0
  235. package/dist/runs/store.js.map +1 -0
  236. package/dist/runs/task-markers.d.ts +28 -0
  237. package/dist/runs/task-markers.js +74 -0
  238. package/dist/runs/task-markers.js.map +1 -0
  239. package/dist/runs/task-refs.d.ts +25 -0
  240. package/dist/runs/task-refs.js +74 -0
  241. package/dist/runs/task-refs.js.map +1 -0
  242. package/dist/runs/ui-event-sink.d.ts +80 -0
  243. package/dist/runs/ui-event-sink.js +209 -0
  244. package/dist/runs/ui-event-sink.js.map +1 -0
  245. package/dist/server/app-type.d.ts +21 -0
  246. package/dist/server/app-type.js +2 -0
  247. package/dist/server/app-type.js.map +1 -0
  248. package/dist/server/capabilities.d.ts +70 -0
  249. package/dist/server/capabilities.js +152 -0
  250. package/dist/server/capabilities.js.map +1 -0
  251. package/dist/server/checkout.d.ts +128 -0
  252. package/dist/server/checkout.js +296 -0
  253. package/dist/server/checkout.js.map +1 -0
  254. package/dist/server/forge/github.d.ts +535 -0
  255. package/dist/server/forge/github.js +2544 -0
  256. package/dist/server/forge/github.js.map +1 -0
  257. package/dist/server/forge/index.d.ts +40 -0
  258. package/dist/server/forge/index.js +72 -0
  259. package/dist/server/forge/index.js.map +1 -0
  260. package/dist/server/forge/types.d.ts +267 -0
  261. package/dist/server/forge/types.js +2 -0
  262. package/dist/server/forge/types.js.map +1 -0
  263. package/dist/server/fs-browse.d.ts +105 -0
  264. package/dist/server/fs-browse.js +182 -0
  265. package/dist/server/fs-browse.js.map +1 -0
  266. package/dist/server/git-changes.d.ts +213 -0
  267. package/dist/server/git-changes.js +555 -0
  268. package/dist/server/git-changes.js.map +1 -0
  269. package/dist/server/git.d.ts +28 -0
  270. package/dist/server/git.js +112 -0
  271. package/dist/server/git.js.map +1 -0
  272. package/dist/server/github.d.ts +9 -0
  273. package/dist/server/github.js +8 -0
  274. package/dist/server/github.js.map +1 -0
  275. package/dist/server/launch-key.d.ts +7 -0
  276. package/dist/server/launch-key.js +33 -0
  277. package/dist/server/launch-key.js.map +1 -0
  278. package/dist/server/open-in-app.d.ts +66 -0
  279. package/dist/server/open-in-app.js +211 -0
  280. package/dist/server/open-in-app.js.map +1 -0
  281. package/dist/server/open-in-terminal.d.ts +70 -0
  282. package/dist/server/open-in-terminal.js +179 -0
  283. package/dist/server/open-in-terminal.js.map +1 -0
  284. package/dist/server/pr.d.ts +7 -0
  285. package/dist/server/pr.js +7 -0
  286. package/dist/server/pr.js.map +1 -0
  287. package/dist/server/project-context.d.ts +120 -0
  288. package/dist/server/project-context.js +192 -0
  289. package/dist/server/project-context.js.map +1 -0
  290. package/dist/server/provider-action-gate.d.ts +8 -0
  291. package/dist/server/provider-action-gate.js +56 -0
  292. package/dist/server/provider-action-gate.js.map +1 -0
  293. package/dist/server/provider-auth-runtime.d.ts +21 -0
  294. package/dist/server/provider-auth-runtime.js +66 -0
  295. package/dist/server/provider-auth-runtime.js.map +1 -0
  296. package/dist/server/server.d.ts +13578 -0
  297. package/dist/server/server.js +5284 -0
  298. package/dist/server/server.js.map +1 -0
  299. package/dist/server/static-ui.d.ts +49 -0
  300. package/dist/server/static-ui.js +92 -0
  301. package/dist/server/static-ui.js.map +1 -0
  302. package/dist/server/validators.d.ts +97 -0
  303. package/dist/server/validators.js +86 -0
  304. package/dist/server/validators.js.map +1 -0
  305. package/dist/server/ws.d.ts +83 -0
  306. package/dist/server/ws.js +216 -0
  307. package/dist/server/ws.js.map +1 -0
  308. package/dist/server/wsl.d.ts +25 -0
  309. package/dist/server/wsl.js +92 -0
  310. package/dist/server/wsl.js.map +1 -0
  311. package/dist/server-install/engine.d.ts +50 -0
  312. package/dist/server-install/engine.js +324 -0
  313. package/dist/server-install/engine.js.map +1 -0
  314. package/dist/server-install/platforms/macosx-ngrok.d.ts +6 -0
  315. package/dist/server-install/platforms/macosx-ngrok.js +348 -0
  316. package/dist/server-install/platforms/macosx-ngrok.js.map +1 -0
  317. package/dist/server-install/platforms/ubuntu-vps.d.ts +63 -0
  318. package/dist/server-install/platforms/ubuntu-vps.js +997 -0
  319. package/dist/server-install/platforms/ubuntu-vps.js.map +1 -0
  320. package/dist/server-install/state.d.ts +52 -0
  321. package/dist/server-install/state.js +182 -0
  322. package/dist/server-install/state.js.map +1 -0
  323. package/dist/server-install/steps.d.ts +109 -0
  324. package/dist/server-install/steps.js +334 -0
  325. package/dist/server-install/steps.js.map +1 -0
  326. package/dist/server-install/strategies.d.ts +4 -0
  327. package/dist/server-install/strategies.js +20 -0
  328. package/dist/server-install/strategies.js.map +1 -0
  329. package/dist/server-install/types.d.ts +287 -0
  330. package/dist/server-install/types.js +121 -0
  331. package/dist/server-install/types.js.map +1 -0
  332. package/dist/server-install/ui.d.ts +42 -0
  333. package/dist/server-install/ui.js +130 -0
  334. package/dist/server-install/ui.js.map +1 -0
  335. package/dist/skills-banner.d.ts +22 -0
  336. package/dist/skills-banner.js +41 -0
  337. package/dist/skills-banner.js.map +1 -0
  338. package/dist/skills-remote.d.ts +84 -0
  339. package/dist/skills-remote.js +468 -0
  340. package/dist/skills-remote.js.map +1 -0
  341. package/dist/skills-update.d.ts +88 -0
  342. package/dist/skills-update.js +392 -0
  343. package/dist/skills-update.js.map +1 -0
  344. package/dist/skills.d.ts +82 -0
  345. package/dist/skills.js +251 -0
  346. package/dist/skills.js.map +1 -0
  347. package/dist/todos.d.ts +48 -0
  348. package/dist/todos.js +201 -0
  349. package/dist/todos.js.map +1 -0
  350. package/dist/ui-state.d.ts +26 -0
  351. package/dist/ui-state.js +38 -0
  352. package/dist/ui-state.js.map +1 -0
  353. package/dist/update-check.d.ts +12 -0
  354. package/dist/update-check.js +41 -0
  355. package/dist/update-check.js.map +1 -0
  356. package/dist/workflows/load.d.ts +15 -0
  357. package/dist/workflows/load.js +56 -0
  358. package/dist/workflows/load.js.map +1 -0
  359. package/dist/workflows/run.d.ts +803 -0
  360. package/dist/workflows/run.js +3483 -0
  361. package/dist/workflows/run.js.map +1 -0
  362. package/dist/workflows/types.d.ts +145 -0
  363. package/dist/workflows/types.js +194 -0
  364. package/dist/workflows/types.js.map +1 -0
  365. package/dist/workspace/agent-accounts.d.ts +153 -0
  366. package/dist/workspace/agent-accounts.js +304 -0
  367. package/dist/workspace/agent-accounts.js.map +1 -0
  368. package/dist/workspace/agent-profiles.d.ts +78 -0
  369. package/dist/workspace/agent-profiles.js +115 -0
  370. package/dist/workspace/agent-profiles.js.map +1 -0
  371. package/dist/workspace/config.d.ts +169 -0
  372. package/dist/workspace/config.js +379 -0
  373. package/dist/workspace/config.js.map +1 -0
  374. package/dist/workspace/migrations.d.ts +45 -0
  375. package/dist/workspace/migrations.js +147 -0
  376. package/dist/workspace/migrations.js.map +1 -0
  377. package/dist/workspace/projects-cli.d.ts +25 -0
  378. package/dist/workspace/projects-cli.js +172 -0
  379. package/dist/workspace/projects-cli.js.map +1 -0
  380. package/dist/workspace/projects.d.ts +109 -0
  381. package/dist/workspace/projects.js +250 -0
  382. package/dist/workspace/projects.js.map +1 -0
  383. package/dist/workspace/semaphore.d.ts +179 -0
  384. package/dist/workspace/semaphore.js +195 -0
  385. package/dist/workspace/semaphore.js.map +1 -0
  386. package/dist/workspace/ui-state.d.ts +34 -0
  387. package/dist/workspace/ui-state.js +53 -0
  388. package/dist/workspace/ui-state.js.map +1 -0
  389. package/package.json +85 -0
  390. package/scripts/check-pack.mjs +46 -0
  391. package/scripts/inline-contract.mjs +112 -0
  392. package/scripts/install-as-command.mjs +120 -0
  393. package/scripts/mock-claude.mjs +524 -0
  394. package/scripts/mock-pi-rpc.mjs +83 -0
  395. package/scripts/sync-readme.mjs +20 -0
  396. package/scripts/test-process-usage.mjs +47 -0
  397. package/web/dist/assets/alert-dialog-CGskMvgc.js +1 -0
  398. package/web/dist/assets/arrow-down-G8adPhbu.js +1 -0
  399. package/web/dist/assets/arrow-left-BJsIW6A0.js +1 -0
  400. package/web/dist/assets/bundle-mjs-BT31bpU6.js +1 -0
  401. package/web/dist/assets/centered-state-B_V_Ai2h.js +43 -0
  402. package/web/dist/assets/chevron-right-CFzn-von.js +1 -0
  403. package/web/dist/assets/chunk-BO2N2NFS-DE6qKn3r.js +131 -0
  404. package/web/dist/assets/collapsible-B_8uYwHb.js +1 -0
  405. package/web/dist/assets/commit-list-C4Dv6vUL.js +1 -0
  406. package/web/dist/assets/compare-variants-SR2zZfZq.js +1 -0
  407. package/web/dist/assets/core-BCsw8oQw.js +12 -0
  408. package/web/dist/assets/css-BsVw1vtW.js +1 -0
  409. package/web/dist/assets/diff-CbozNUJq.js +3 -0
  410. package/web/dist/assets/diff-stat-btAmKFJr.js +1 -0
  411. package/web/dist/assets/diff-view-BpFTcG3R.js +6 -0
  412. package/web/dist/assets/diff-woXpYk--.js +1 -0
  413. package/web/dist/assets/dropdown-menu-Z80KMInL.js +1 -0
  414. package/web/dist/assets/editable-title-wS5NWwob.js +1 -0
  415. package/web/dist/assets/ellipsis-vertical-Bmi2u2TG.js +1 -0
  416. package/web/dist/assets/engine-javascript-FQvzRocB.js +141 -0
  417. package/web/dist/assets/file-DKoYWRsv.js +1 -0
  418. package/web/dist/assets/folder-u01HScRT.js +1 -0
  419. package/web/dist/assets/git-toolbar-DZlZjTGz.js +1 -0
  420. package/web/dist/assets/github-CFURo9kf.js +1 -0
  421. package/web/dist/assets/go-rLFTqkRN.js +1 -0
  422. package/web/dist/assets/highlighted-body-OFNGDK62-BL-MfFgl.js +1 -0
  423. package/web/dist/assets/highlighter-C8DK1_sG.js +3 -0
  424. package/web/dist/assets/html-BY76lXLH.js +1 -0
  425. package/web/dist/assets/image-preview-BR3iIIhf.js +1 -0
  426. package/web/dist/assets/index-BcI5hug3.css +2 -0
  427. package/web/dist/assets/index-DlC81_GI.js +7 -0
  428. package/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  429. package/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  430. package/web/dist/assets/javascript-BgS3c2Ky.js +1 -0
  431. package/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  432. package/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  433. package/web/dist/assets/json-qhed-kSA.js +1 -0
  434. package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
  435. package/web/dist/assets/jsx-Bz0zcwM4.js +1 -0
  436. package/web/dist/assets/lib-BQXq3kEf.js +1 -0
  437. package/web/dist/assets/lib-BxQXEXDF.js +1 -0
  438. package/web/dist/assets/markdown-5FuCZDA4.js +2 -0
  439. package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
  440. package/web/dist/assets/mermaid-GHXKKRXX-C4jxqUUb.js +1 -0
  441. package/web/dist/assets/new-task-form-BydHBbef.js +1 -0
  442. package/web/dist/assets/pill-vNzaaYxj.js +1 -0
  443. package/web/dist/assets/project-router-BbCzrHKs.js +1 -0
  444. package/web/dist/assets/prompt-templates-B7Fs2b1S.js +15 -0
  445. package/web/dist/assets/python-gzcpVVnB.js +1 -0
  446. package/web/dist/assets/react-runtime-CCIEwYL0.js +9 -0
  447. package/web/dist/assets/refresh-cw-DGliez3E.js +1 -0
  448. package/web/dist/assets/repo-git-CJFDMauC.js +1 -0
  449. package/web/dist/assets/rolldown-runtime-QTnfLwEv.js +1 -0
  450. package/web/dist/assets/run-diff-Bq9vkSNK.js +3 -0
  451. package/web/dist/assets/run-header-Cd8mSKBG.js +1 -0
  452. package/web/dist/assets/rust-Cfkwpbl8.js +1 -0
  453. package/web/dist/assets/search-x-69n_672Y.js +1 -0
  454. package/web/dist/assets/shellscript-CLZ0U2zV.js +1 -0
  455. package/web/dist/assets/skill-empty-hint-DTu0LzaJ.js +1 -0
  456. package/web/dist/assets/skills-Cs-auniL.js +1 -0
  457. package/web/dist/assets/skills-D4P-Hgey.js +1 -0
  458. package/web/dist/assets/sparkles-B8agSxBZ.js +1 -0
  459. package/web/dist/assets/sql-BsFa4tDR.js +1 -0
  460. package/web/dist/assets/square-terminal-Cf4Og6ct.js +1 -0
  461. package/web/dist/assets/tab-link-Cp2-CJS8.js +1 -0
  462. package/web/dist/assets/task-changes-DYgNXOyY.js +1 -0
  463. package/web/dist/assets/task-commits-BRpZSz1G.js +1 -0
  464. package/web/dist/assets/task-files-pKOwSyLh.js +2 -0
  465. package/web/dist/assets/task-thread-Bfyqqy-t.js +9 -0
  466. package/web/dist/assets/textarea--cpYshee.js +1 -0
  467. package/web/dist/assets/thread-loading-GyH2Atwo.js +1 -0
  468. package/web/dist/assets/toml-CcmNWLt0.js +1 -0
  469. package/web/dist/assets/trash-2--ZtrzUx3.js +1 -0
  470. package/web/dist/assets/triangle-alert-D5tJwRD2.js +1 -0
  471. package/web/dist/assets/tsx-udAQXfEw.js +1 -0
  472. package/web/dist/assets/typescript-bsJCZSQ-.js +1 -0
  473. package/web/dist/assets/upload-SnwVxKOA.js +1 -0
  474. package/web/dist/assets/use-desktop-C31lOFUD.js +1 -0
  475. package/web/dist/assets/use-submit-shortcut-JY8M_MPL.js +1 -0
  476. package/web/dist/assets/utils-Bb8ohcr3.js +64 -0
  477. package/web/dist/assets/workflows-BmOehP-A.js +11 -0
  478. package/web/dist/assets/yaml-rwi0_p6S.js +1 -0
  479. package/web/dist/assets/zoomable-image-0G3IBXWC.js +1 -0
  480. package/web/dist/index.html +80 -0
  481. package/web/dist/xezar.svg +14 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Server capabilities reported by `/api/health` — the UI hides what the server
3
+ * says isn't there, and the matching endpoints refuse as defense in depth.
4
+ *
5
+ * `localHandoff` (cockpit-ui redesign spec §"Deployment modes — local vs
6
+ * hosted"): the default deployment is `npx @qodeca/xezar` on localhost, where
7
+ * handing a session off to a local terminal/editor makes sense. On a VPS/remote
8
+ * box it doesn't: `XEZ_REMOTE=1` (or binding a non-loopback host) switches to
9
+ * hosted mode — the UI hides every local-machine affordance and the open-in-*
10
+ * endpoints 409.
11
+ *
12
+ * `followups` (spec 007, #444, #471): the global follow-up inbox is **opt-in**
13
+ * via `XEZ_FOLLOWUPS=1` and off by default. Off, agents are never told to write
14
+ * `todos.json` (they get `HANDOFF_ONLY_INSTRUCTIONS` and an empty
15
+ * `XEZ_TODOS_FILE`), the Inbox nav item is gone and the inbox endpoints refuse.
16
+ * The per-task handoff journal is independent and runs either way.
17
+ *
18
+ * `singleProject` (spec 2026-07-21-xez-single-project): the workspace is
19
+ * deliberately constrained to the launch project when `XEZ_SINGLE_PROJECT=1`.
20
+ * Like the other opt-in capabilities, activation is strict: no other spelling
21
+ * enables it.
22
+ *
23
+ * `automations` (spec 2026-07-25-github-automations, #801): GitHub automations
24
+ * are **opt-in** via `XEZ_AUTOMATIONS=1` and off by default. Off, the
25
+ * `Automations` nav item is gone everywhere it is rendered, the
26
+ * `/automations*` endpoints refuse, and the workspace scheduler never polls
27
+ * GitHub — the flag removes the behavior, not only the UI. Activation is
28
+ * strict, like the two capabilities above. Nothing on disk is touched:
29
+ * definitions, receipts and high-watermarks survive the flag being off, so
30
+ * unsetting it and restarting restores the feature wholesale.
31
+ *
32
+ * Usage presentation: token counts and monetary cost stay visible by default.
33
+ * `XEZ_HIDE_TOKEN_USAGE=1` and `XEZ_HIDE_COST=1` hide them independently;
34
+ * legacy `XEZ_HIDE_TOKEN_METRICS=1` remains the master hide-all switch. None
35
+ * changes collection, persistence, events, or API run records.
36
+ */
37
+ import type { Capabilities } from '../contract/index.js';
38
+ /** Canonical hostname of an authority: port and IPv6 brackets removed, IPv6
39
+ * expanded, zone id and FQDN trailing dot dropped, lowercased.
40
+ * `[::1]:4321` → `0:0:0:0:0:0:0:1`, `localhost.:4321` → `localhost`.
41
+ *
42
+ * Strict by construction: an authority that does not parse as one of the three
43
+ * legal shapes returns `''`, which no allowlist matches. Being lax here is a
44
+ * security bug, not a convenience — a parser that merely *finds* a loopback
45
+ * name inside the header would accept `[::1]@evil.com` or `127.0.0.1:evil.com`. */
46
+ export declare function normalizeHostname(host: string): string;
47
+ /** True for bind hosts that only the local machine can reach. Undefined = the
48
+ * default bind (127.0.0.1), hence trusted — this is a *configuration* value we
49
+ * chose, not a request header. Do NOT use this on attacker-controlled input;
50
+ * use `isLoopbackHostHeader`, which fails closed on a missing host. */
51
+ export declare function isLoopbackHost(host: string | undefined): boolean;
52
+ /** True for a request's `Host`/`Origin` hostname when it names this machine.
53
+ * The untrusted-input twin of `isLoopbackHost`: a missing or unparseable host
54
+ * is **untrusted** (false), because absent is not the same as "we defaulted to
55
+ * loopback" once the value arrives over the wire (#426). */
56
+ export declare function isLoopbackHostHeader(host: string | null | undefined): boolean;
57
+ /** `XEZ_REMOTE=1` or a non-loopback bind host ⇒ hosted mode (no local handoff).
58
+ * `XEZ_FOLLOWUPS=1` ⇒ the follow-up inbox exists (#471).
59
+ * `XEZ_AUTOMATIONS=1` ⇒ GitHub automations exist (#801).
60
+ *
61
+ * Read per request — cheap, and tests/ops can flip `XEZ_REMOTE` live. `followups` is honest
62
+ * per request too, but flipping it ON at runtime is only half a switch: the per-dataDir
63
+ * todos watch (step 2.3) is created by an SSE subscription, so connections opened while the
64
+ * flag was off never subscribed and get no live inbox updates until they reconnect. Hence
65
+ * the UI's "set XEZ_FOLLOWUPS=1 and restart xezar" wording — treat it as a boot-time flag.
66
+ *
67
+ * `automations` carries the same caveat and for the same reason: the workspace scheduler is
68
+ * started once, on the server's `listening` event, so flipping the flag on afterwards gates
69
+ * the routes open without ever starting the poller. Boot-time flag, same wording. */
70
+ export declare function resolveCapabilities(env?: NodeJS.ProcessEnv, bindHost?: string): Capabilities;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Server capabilities reported by `/api/health` — the UI hides what the server
3
+ * says isn't there, and the matching endpoints refuse as defense in depth.
4
+ *
5
+ * `localHandoff` (cockpit-ui redesign spec §"Deployment modes — local vs
6
+ * hosted"): the default deployment is `npx @qodeca/xezar` on localhost, where
7
+ * handing a session off to a local terminal/editor makes sense. On a VPS/remote
8
+ * box it doesn't: `XEZ_REMOTE=1` (or binding a non-loopback host) switches to
9
+ * hosted mode — the UI hides every local-machine affordance and the open-in-*
10
+ * endpoints 409.
11
+ *
12
+ * `followups` (spec 007, #444, #471): the global follow-up inbox is **opt-in**
13
+ * via `XEZ_FOLLOWUPS=1` and off by default. Off, agents are never told to write
14
+ * `todos.json` (they get `HANDOFF_ONLY_INSTRUCTIONS` and an empty
15
+ * `XEZ_TODOS_FILE`), the Inbox nav item is gone and the inbox endpoints refuse.
16
+ * The per-task handoff journal is independent and runs either way.
17
+ *
18
+ * `singleProject` (spec 2026-07-21-xez-single-project): the workspace is
19
+ * deliberately constrained to the launch project when `XEZ_SINGLE_PROJECT=1`.
20
+ * Like the other opt-in capabilities, activation is strict: no other spelling
21
+ * enables it.
22
+ *
23
+ * `automations` (spec 2026-07-25-github-automations, #801): GitHub automations
24
+ * are **opt-in** via `XEZ_AUTOMATIONS=1` and off by default. Off, the
25
+ * `Automations` nav item is gone everywhere it is rendered, the
26
+ * `/automations*` endpoints refuse, and the workspace scheduler never polls
27
+ * GitHub — the flag removes the behavior, not only the UI. Activation is
28
+ * strict, like the two capabilities above. Nothing on disk is touched:
29
+ * definitions, receipts and high-watermarks survive the flag being off, so
30
+ * unsetting it and restarting restores the feature wholesale.
31
+ *
32
+ * Usage presentation: token counts and monetary cost stay visible by default.
33
+ * `XEZ_HIDE_TOKEN_USAGE=1` and `XEZ_HIDE_COST=1` hide them independently;
34
+ * legacy `XEZ_HIDE_TOKEN_METRICS=1` remains the master hide-all switch. None
35
+ * changes collection, persistence, events, or API run records.
36
+ */
37
+ import { followupsEnabled } from '../handoff.js';
38
+ /** Every IPv4 address in 127.0.0.0/8, anchored. Anchoring is load-bearing: a
39
+ * `startsWith('127.')` test also matches attacker-controlled *hostnames* like
40
+ * `127.0.0.1.evil.com`, which is exactly the DNS-rebinding bypass #426 is
41
+ * about — see `isLoopbackHostHeader`. */
42
+ const LOOPBACK_V4 = /^127(?:\.(?:25[0-5]|2[0-4]\d|1?\d?\d)){3}$/;
43
+ /** The IPv6 loopback in the canonical form `expandIpv6` emits. */
44
+ const LOOPBACK_V6 = '0:0:0:0:0:0:0:1';
45
+ /** Expand an IPv6 literal to all 8 groups, lowercase and without leading zeros
46
+ * (`::1` → `0:0:0:0:0:0:0:1`), or `null` when it is not a valid literal.
47
+ *
48
+ * Canonicalizing rather than merely *recognizing* matters: `Host` carries
49
+ * whatever spelling the client sent, but `new URL().hostname` compresses
50
+ * (`[0:0:0:0:0:0:0:1]` → `[::1]`), so the two only compare equal on a common
51
+ * form. */
52
+ function expandIpv6(h) {
53
+ const halves = h.split('::');
54
+ if (halves.length > 2)
55
+ return null;
56
+ let groups;
57
+ if (halves.length === 2) {
58
+ const head = halves[0] ? halves[0].split(':') : [];
59
+ const tail = halves[1] ? halves[1].split(':') : [];
60
+ if (head.length + tail.length > 7)
61
+ return null;
62
+ groups = [...head, ...Array(8 - head.length - tail.length).fill('0'), ...tail];
63
+ }
64
+ else {
65
+ groups = h.split(':');
66
+ }
67
+ if (groups.length !== 8 || !groups.every((g) => /^[0-9a-f]{1,4}$/.test(g)))
68
+ return null;
69
+ return groups.map((g) => parseInt(g, 16).toString(16)).join(':');
70
+ }
71
+ /** Canonical hostname of an authority: port and IPv6 brackets removed, IPv6
72
+ * expanded, zone id and FQDN trailing dot dropped, lowercased.
73
+ * `[::1]:4321` → `0:0:0:0:0:0:0:1`, `localhost.:4321` → `localhost`.
74
+ *
75
+ * Strict by construction: an authority that does not parse as one of the three
76
+ * legal shapes returns `''`, which no allowlist matches. Being lax here is a
77
+ * security bug, not a convenience — a parser that merely *finds* a loopback
78
+ * name inside the header would accept `[::1]@evil.com` or `127.0.0.1:evil.com`. */
79
+ export function normalizeHostname(host) {
80
+ // 1. Bracketed IPv6, optionally with a port: `[::1]`, `[::1%25eth0]:4321`.
81
+ // The end anchor is load-bearing — without it `[::1]@evil.com` → `::1`.
82
+ const bracketed = host.match(/^\[([^\]]+)\](?::\d+)?$/);
83
+ if (bracketed)
84
+ return expandIpv6(stripZone(bracketed[1].toLowerCase())) ?? '';
85
+ // 2. A bare IPv6 literal cannot carry a port (no brackets ⇒ every colon
86
+ // belongs to the address), so anything with >1 colon is taken whole.
87
+ if (host.split(':').length > 2)
88
+ return expandIpv6(stripZone(host.toLowerCase())) ?? '';
89
+ // 3. `name` or `name:port`, where the port must actually be digits.
90
+ const plain = host.match(/^([^:[\]]*)(?::(\d+))?$/);
91
+ if (plain)
92
+ return plain[1].toLowerCase().replace(/\.$/, '');
93
+ return ''; // unparseable ⇒ matches no allowlist
94
+ }
95
+ /** Drop an IPv6 zone id (`fe80::1%eth0`), anchored to the end so a `%` inside a
96
+ * registrable hostname can never truncate it down to a loopback name. */
97
+ function stripZone(h) {
98
+ return h.replace(/%[a-z0-9._~-]+$/, '');
99
+ }
100
+ /** True when the hostname names this machine and nothing else. Expects the
101
+ * canonical output of `normalizeHostname`. */
102
+ function isLoopbackName(hostname) {
103
+ return hostname === 'localhost' || LOOPBACK_V4.test(hostname) || hostname === LOOPBACK_V6;
104
+ }
105
+ /** True for bind hosts that only the local machine can reach. Undefined = the
106
+ * default bind (127.0.0.1), hence trusted — this is a *configuration* value we
107
+ * chose, not a request header. Do NOT use this on attacker-controlled input;
108
+ * use `isLoopbackHostHeader`, which fails closed on a missing host. */
109
+ export function isLoopbackHost(host) {
110
+ if (!host)
111
+ return true;
112
+ return isLoopbackName(normalizeHostname(host));
113
+ }
114
+ /** True for a request's `Host`/`Origin` hostname when it names this machine.
115
+ * The untrusted-input twin of `isLoopbackHost`: a missing or unparseable host
116
+ * is **untrusted** (false), because absent is not the same as "we defaulted to
117
+ * loopback" once the value arrives over the wire (#426). */
118
+ export function isLoopbackHostHeader(host) {
119
+ if (!host)
120
+ return false;
121
+ return isLoopbackName(normalizeHostname(host));
122
+ }
123
+ /** `XEZ_REMOTE=1` or a non-loopback bind host ⇒ hosted mode (no local handoff).
124
+ * `XEZ_FOLLOWUPS=1` ⇒ the follow-up inbox exists (#471).
125
+ * `XEZ_AUTOMATIONS=1` ⇒ GitHub automations exist (#801).
126
+ *
127
+ * Read per request — cheap, and tests/ops can flip `XEZ_REMOTE` live. `followups` is honest
128
+ * per request too, but flipping it ON at runtime is only half a switch: the per-dataDir
129
+ * todos watch (step 2.3) is created by an SSE subscription, so connections opened while the
130
+ * flag was off never subscribed and get no live inbox updates until they reconnect. Hence
131
+ * the UI's "set XEZ_FOLLOWUPS=1 and restart xezar" wording — treat it as a boot-time flag.
132
+ *
133
+ * `automations` carries the same caveat and for the same reason: the workspace scheduler is
134
+ * started once, on the server's `listening` event, so flipping the flag on afterwards gates
135
+ * the routes open without ever starting the poller. Boot-time flag, same wording. */
136
+ export function resolveCapabilities(env = process.env, bindHost) {
137
+ const hideAllUsage = env.XEZ_HIDE_TOKEN_METRICS === '1';
138
+ const tokenUsageMetrics = !hideAllUsage && env.XEZ_HIDE_TOKEN_USAGE !== '1';
139
+ const costMetrics = !hideAllUsage && env.XEZ_HIDE_COST !== '1';
140
+ return {
141
+ localHandoff: env.XEZ_REMOTE !== '1' && isLoopbackHost(bindHost),
142
+ // Deliberately not re-derived here: RunManager enforces the same predicate,
143
+ // and two spellings of "is the inbox on" would eventually disagree.
144
+ followups: followupsEnabled(env),
145
+ singleProject: env.XEZ_SINGLE_PROJECT === '1',
146
+ automations: env.XEZ_AUTOMATIONS === '1',
147
+ tokenMetrics: tokenUsageMetrics && costMetrics,
148
+ tokenUsageMetrics,
149
+ costMetrics,
150
+ };
151
+ }
152
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../src/server/capabilities.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,128 @@
1
+ /** One `checkout-progress` SSE payload (workspace-level event, step 2.8's bus).
2
+ * `checkoutId` is echoed from the request so a cockpit only renders its own
3
+ * clone — two tabs cloning at once share the one workspace stream. */
4
+ export interface CheckoutProgressEvent {
5
+ checkoutId?: string;
6
+ /** The target folder name, so a payload is readable without the id too. */
7
+ name: string;
8
+ phase: 'cloning' | 'done' | 'error';
9
+ /** One line of `git clone` progress (present on `cloning`). */
10
+ line?: string;
11
+ /** Human-readable failure (present on `error`). */
12
+ error?: string;
13
+ }
14
+ export type CheckoutFailure = {
15
+ ok: false;
16
+ status: 400 | 409 | 500;
17
+ error: string;
18
+ }
19
+ /** `gh` is missing or unauthenticated — the spec's `{ error, reason }`
20
+ * degradation, mirroring the GitHub pane's contract. */
21
+ | {
22
+ ok: false;
23
+ status: 503;
24
+ error: string;
25
+ reason: string;
26
+ };
27
+ export type CheckoutResult = {
28
+ ok: true;
29
+ target: string;
30
+ name: string;
31
+ } | CheckoutFailure;
32
+ /** A GitHub repo reference the clone flow accepts. */
33
+ export interface RepoRef {
34
+ owner: string;
35
+ repo: string;
36
+ /** What `gh repo clone` is handed — always the normalized `owner/repo`, so a
37
+ * URL spelling can never smuggle flags or a different host past `gh`. */
38
+ slug: string;
39
+ }
40
+ /**
41
+ * Parse `owner/repo`, `https://github.com/owner/repo(.git)`, `git@github.com:owner/repo.git`
42
+ * or `github.com/owner/repo` into a normalized ref. `null` when it is not a
43
+ * GitHub repo reference — the route answers 400 rather than handing an
44
+ * arbitrary string to `gh`.
45
+ *
46
+ * Only github.com: `gh repo clone` would happily take an enterprise host, but
47
+ * this flow's contract (and its `gh`-availability degradation) is GitHub's, and
48
+ * silently accepting `evil.example/owner/repo` would make the "which host am I
49
+ * cloning from" question unanswerable from the dialog.
50
+ */
51
+ export declare function parseRepoRef(input: string): RepoRef | null;
52
+ /**
53
+ * Validate the target folder name (the dialog lets the user edit it, and it
54
+ * defaults to the repo name).
55
+ *
56
+ * A name is one path segment and nothing else. `.` / `..` and anything with a
57
+ * separator are rejected outright rather than sanitized: a silently rewritten
58
+ * name would clone somewhere other than the path the dialog previewed, which
59
+ * is the one thing a checkout target must never do. A leading dot is refused
60
+ * too — a project named `.ssh` under the checkout root is not a project.
61
+ */
62
+ export declare function isValidCheckoutName(name: string): boolean;
63
+ /**
64
+ * Delete a partially-cloned checkout — the ONE destructive path in this module.
65
+ *
66
+ * Called only after `mkdir(target)` (non-recursive, so it succeeded only
67
+ * because the directory did not exist and WE created it). Even with that proof
68
+ * in hand, every one of these must hold or nothing is deleted:
69
+ *
70
+ * - `target` is still a real directory and NOT a symlink (`lstat`, not `stat`):
71
+ * between the mkdir and the failure, the directory could have been swapped
72
+ * for a link pointing anywhere.
73
+ * - `projectsDir` and `target` both resolve (`realpath`), and the resolved
74
+ * target's PARENT is exactly the resolved checkout root. That is strict
75
+ * containment (nothing outside the root) *and* a depth limit (a direct child
76
+ * only, never the root itself, never a nested path).
77
+ *
78
+ * Any surprise — a vanished path, an unresolvable root, a `rm` that fails —
79
+ * leaves the directory alone. Leaving a stray folder is a nuisance; deleting
80
+ * the wrong one is unrecoverable, so every ambiguous case resolves toward "do
81
+ * nothing".
82
+ */
83
+ export declare function cleanupCheckout(projectsDir: string, target: string): Promise<boolean>;
84
+ /** Injected so the tests can drive a fake clone without a network or a `gh`
85
+ * binary. `dir` already exists (we created it); the runner clones INTO it. */
86
+ export type CloneRunner = (ref: RepoRef, dir: string, onLine: (line: string) => void, signal: AbortSignal | undefined) => Promise<{
87
+ ok: true;
88
+ } | {
89
+ ok: false;
90
+ error: string;
91
+ notFound?: boolean;
92
+ }>;
93
+ /**
94
+ * `gh repo clone <owner/repo> <dir> -- --progress`.
95
+ *
96
+ * `spawn`, not `execFile`, because the whole point of this route is that the
97
+ * dialog sees progress while it happens: `git clone --progress` writes its
98
+ * counters to stderr, and each line becomes a `checkout-progress` event.
99
+ * (`--progress` is needed explicitly — git suppresses it when stderr is not a
100
+ * TTY, which it never is here.)
101
+ */
102
+ export declare const ghCloneRunner: CloneRunner;
103
+ /** `XEZ_DRY_RUN=1` — a fake clone so the dialog (and the tests) can exercise
104
+ * the whole flow offline: a few progress lines and a plausible repo on disk.
105
+ * It writes only INSIDE the directory the caller already created. */
106
+ export declare const dryRunCloneRunner: CloneRunner;
107
+ export interface CheckoutOptions {
108
+ /** Raw user input: `owner/repo` or a GitHub URL. */
109
+ url: string;
110
+ /** Target folder name; defaults to the repo name. */
111
+ name?: string | undefined;
112
+ /** The checkout root, ALREADY `~`-expanded by the caller. */
113
+ projectsDir: string;
114
+ onProgress: (event: CheckoutProgressEvent) => void;
115
+ checkoutId?: string | undefined;
116
+ signal?: AbortSignal | undefined;
117
+ /** Test seam; defaults to `gh` (or the dry-run fake under `XEZ_DRY_RUN=1`). */
118
+ run?: CloneRunner;
119
+ }
120
+ /**
121
+ * Clone a GitHub repo into `<projectsDir>/<name>`, streaming progress.
122
+ *
123
+ * Answers only when the clone has finished (the spec's "long-running: answers
124
+ * when the clone finishes"); the dialog's liveness comes from `onProgress`.
125
+ * On any failure the partially-written directory is removed and NOTHING is
126
+ * registered — registration is the caller's job, and only on `ok: true`.
127
+ */
128
+ export declare function checkoutRepo(opts: CheckoutOptions): Promise<CheckoutResult>;
@@ -0,0 +1,296 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { lstat, mkdir, realpath, rm, writeFile } from 'node:fs/promises';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ /**
5
+ * `POST /api/projects/checkout` — the "Add project → Clone from GitHub" flow
6
+ * (spec 2026-07-20-multi-project-workspace, step 4.3).
7
+ *
8
+ * The route itself (server.ts) owns the registry write; this module owns the
9
+ * only two things that are genuinely dangerous about cloning on the operator's
10
+ * behalf:
11
+ *
12
+ * 1. **Where the clone lands.** The target is always `<projectsDir>/<name>`
13
+ * with `name` a single, boring path segment. It is never a nested path,
14
+ * never `..`, never absolute — the checkout root is a root, not a starting
15
+ * point for traversal.
16
+ * 2. **What cleanup may delete.** A failed clone must not leave a half-written
17
+ * directory behind (a later attempt would trip over it, and registering it
18
+ * would put a broken project in the sidebar). But "rm -rf the target" is a
19
+ * destructive path reached by a *network failure*, so it is guarded as
20
+ * tightly as `fs-browse.ts` guards containment — see `cleanupCheckout`.
21
+ *
22
+ * The load-bearing trick for (2) is that the target directory is created HERE,
23
+ * with a non-recursive `mkdir`, before `gh` ever runs. That single syscall is
24
+ * both the atomic existence check (EEXIST ⇒ 409, and we never touched what was
25
+ * there) and the proof of ownership that authorizes cleanup: we only ever
26
+ * delete a directory this operation is known to have created.
27
+ */
28
+ /** How long a clone may run before it is killed. Long enough for a large repo
29
+ * on a slow link, short enough that a hung `gh` (an auth prompt that will
30
+ * never be answered — `gh` is non-interactive here, but a proxy can still
31
+ * stall) does not pin a request forever. */
32
+ const CLONE_TIMEOUT_MS = 10 * 60_000;
33
+ /** Progress lines kept for the error message. `git clone` is chatty and the
34
+ * useful part of a failure is always at the end. */
35
+ const ERROR_TAIL_LINES = 6;
36
+ /** `owner` and `repo` as GitHub itself allows them: alphanumerics, `-`, `_`,
37
+ * `.`. Deliberately strict — this string becomes a `gh` argv entry and half of
38
+ * a filesystem path, and every character outside this set is someone trying
39
+ * something. */
40
+ const NAME_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
41
+ /**
42
+ * Parse `owner/repo`, `https://github.com/owner/repo(.git)`, `git@github.com:owner/repo.git`
43
+ * or `github.com/owner/repo` into a normalized ref. `null` when it is not a
44
+ * GitHub repo reference — the route answers 400 rather than handing an
45
+ * arbitrary string to `gh`.
46
+ *
47
+ * Only github.com: `gh repo clone` would happily take an enterprise host, but
48
+ * this flow's contract (and its `gh`-availability degradation) is GitHub's, and
49
+ * silently accepting `evil.example/owner/repo` would make the "which host am I
50
+ * cloning from" question unanswerable from the dialog.
51
+ */
52
+ export function parseRepoRef(input) {
53
+ const trimmed = input.trim();
54
+ if (trimmed === '' || trimmed.length > 512)
55
+ return null;
56
+ // Strip the scheme/host spellings down to `owner/repo`, then validate ONE
57
+ // shape. Doing it the other way round (a regex per spelling) is how a fourth
58
+ // spelling eventually gets a weaker check than the other three.
59
+ let path = trimmed;
60
+ const ssh = /^(?:ssh:\/\/)?git@github\.com[:/](.+)$/.exec(path);
61
+ if (ssh?.[1])
62
+ path = ssh[1];
63
+ else {
64
+ const https = /^(?:https?:\/\/)?(?:www\.)?github\.com\/(.+)$/.exec(path);
65
+ if (https?.[1])
66
+ path = https[1];
67
+ }
68
+ path = path.replace(/\/+$/, '').replace(/\.git$/, '');
69
+ const parts = path.split('/');
70
+ if (parts.length !== 2)
71
+ return null;
72
+ const [owner, repo] = parts;
73
+ if (!owner || !repo || !NAME_SEGMENT.test(owner) || !NAME_SEGMENT.test(repo))
74
+ return null;
75
+ return { owner, repo, slug: `${owner}/${repo}` };
76
+ }
77
+ /**
78
+ * Validate the target folder name (the dialog lets the user edit it, and it
79
+ * defaults to the repo name).
80
+ *
81
+ * A name is one path segment and nothing else. `.` / `..` and anything with a
82
+ * separator are rejected outright rather than sanitized: a silently rewritten
83
+ * name would clone somewhere other than the path the dialog previewed, which
84
+ * is the one thing a checkout target must never do. A leading dot is refused
85
+ * too — a project named `.ssh` under the checkout root is not a project.
86
+ */
87
+ export function isValidCheckoutName(name) {
88
+ return name.length <= 128 && NAME_SEGMENT.test(name) && !name.includes('/') && !name.includes('\\');
89
+ }
90
+ /**
91
+ * Delete a partially-cloned checkout — the ONE destructive path in this module.
92
+ *
93
+ * Called only after `mkdir(target)` (non-recursive, so it succeeded only
94
+ * because the directory did not exist and WE created it). Even with that proof
95
+ * in hand, every one of these must hold or nothing is deleted:
96
+ *
97
+ * - `target` is still a real directory and NOT a symlink (`lstat`, not `stat`):
98
+ * between the mkdir and the failure, the directory could have been swapped
99
+ * for a link pointing anywhere.
100
+ * - `projectsDir` and `target` both resolve (`realpath`), and the resolved
101
+ * target's PARENT is exactly the resolved checkout root. That is strict
102
+ * containment (nothing outside the root) *and* a depth limit (a direct child
103
+ * only, never the root itself, never a nested path).
104
+ *
105
+ * Any surprise — a vanished path, an unresolvable root, a `rm` that fails —
106
+ * leaves the directory alone. Leaving a stray folder is a nuisance; deleting
107
+ * the wrong one is unrecoverable, so every ambiguous case resolves toward "do
108
+ * nothing".
109
+ */
110
+ export async function cleanupCheckout(projectsDir, target) {
111
+ let realRoot;
112
+ let realTarget;
113
+ try {
114
+ const info = await lstat(target);
115
+ if (!info.isDirectory() || info.isSymbolicLink())
116
+ return false;
117
+ realRoot = await realpath(projectsDir);
118
+ realTarget = await realpath(target);
119
+ }
120
+ catch {
121
+ return false; // gone, or unresolvable — either way, not ours to remove
122
+ }
123
+ if (realTarget === realRoot)
124
+ return false;
125
+ if (dirname(realTarget) !== realRoot)
126
+ return false;
127
+ try {
128
+ await rm(realTarget, { recursive: true, force: true });
129
+ return true;
130
+ }
131
+ catch {
132
+ return false;
133
+ }
134
+ }
135
+ /**
136
+ * `gh repo clone <owner/repo> <dir> -- --progress`.
137
+ *
138
+ * `spawn`, not `execFile`, because the whole point of this route is that the
139
+ * dialog sees progress while it happens: `git clone --progress` writes its
140
+ * counters to stderr, and each line becomes a `checkout-progress` event.
141
+ * (`--progress` is needed explicitly — git suppresses it when stderr is not a
142
+ * TTY, which it never is here.)
143
+ */
144
+ export const ghCloneRunner = (ref, dir, onLine, signal) => new Promise((resolvePromise) => {
145
+ const child = spawn('gh', ['repo', 'clone', ref.slug, dir, '--', '--progress'], {
146
+ stdio: ['ignore', 'pipe', 'pipe'],
147
+ timeout: CLONE_TIMEOUT_MS,
148
+ // No inherited stdin and `GH_PROMPT_DISABLED`: an unauthenticated `gh`
149
+ // must fail with a message the dialog can show, not block on a prompt
150
+ // nobody can see.
151
+ env: { ...process.env, GH_PROMPT_DISABLED: '1', GIT_TERMINAL_PROMPT: '0' },
152
+ });
153
+ const tail = [];
154
+ let settled = false;
155
+ const finish = (result) => {
156
+ if (settled)
157
+ return;
158
+ settled = true;
159
+ resolvePromise(result);
160
+ };
161
+ // The client hung up (dialog closed, tab gone). Kill the clone rather than
162
+ // let it keep writing into a directory nobody is waiting for — the caller
163
+ // then takes the failure path, which cleans up.
164
+ const onAbort = () => {
165
+ child.kill('SIGTERM');
166
+ finish({ ok: false, error: 'checkout cancelled' });
167
+ };
168
+ signal?.addEventListener('abort', onAbort, { once: true });
169
+ // git's progress is carriage-return-separated, not newline-separated —
170
+ // splitting on `\n` alone would buffer the whole "Receiving objects" phase
171
+ // into one line delivered at the end, which is exactly the silent spinner
172
+ // this stream exists to avoid.
173
+ let pending = '';
174
+ const consume = (chunk) => {
175
+ pending += chunk;
176
+ const parts = pending.split(/\r\n|\r|\n/);
177
+ pending = parts.pop() ?? '';
178
+ for (const part of parts) {
179
+ const line = part.trim();
180
+ if (line === '')
181
+ continue;
182
+ tail.push(line);
183
+ if (tail.length > ERROR_TAIL_LINES)
184
+ tail.shift();
185
+ onLine(line);
186
+ }
187
+ };
188
+ child.stderr?.setEncoding('utf8');
189
+ child.stderr?.on('data', consume);
190
+ child.stdout?.setEncoding('utf8');
191
+ child.stdout?.on('data', consume);
192
+ child.on('error', (err) => {
193
+ signal?.removeEventListener('abort', onAbort);
194
+ // ENOENT is the `gh`-not-installed case, which the route degrades on
195
+ // rather than reports as a clone failure.
196
+ const notFound = err.code === 'ENOENT';
197
+ finish({ ok: false, error: err.message, notFound });
198
+ });
199
+ child.on('close', (code) => {
200
+ signal?.removeEventListener('abort', onAbort);
201
+ if (code === 0)
202
+ return finish({ ok: true });
203
+ // The tail of gh/git's own output IS the error message — `gh` writes
204
+ // "could not find repository", "authentication required" and the network
205
+ // errors itself, and paraphrasing them would only lose detail.
206
+ const detail = tail.join('\n').trim();
207
+ finish({ ok: false, error: detail === '' ? `gh repo clone exited with code ${code}` : detail });
208
+ });
209
+ });
210
+ /** `XEZ_DRY_RUN=1` — a fake clone so the dialog (and the tests) can exercise
211
+ * the whole flow offline: a few progress lines and a plausible repo on disk.
212
+ * It writes only INSIDE the directory the caller already created. */
213
+ export const dryRunCloneRunner = async (ref, dir, onLine) => {
214
+ onLine(`Cloning into '${dir}'...`);
215
+ onLine('remote: Enumerating objects: 3, done.');
216
+ // A `.git` directory so the registered project probes as a git repo the way
217
+ // a real clone would — the point of the dry run is the same shape, not the
218
+ // same bytes.
219
+ await mkdir(join(dir, '.git'), { recursive: true });
220
+ await writeFile(join(dir, 'README.md'), `# ${ref.repo}\n\n(XEZ_DRY_RUN=1 fake clone)\n`, 'utf8');
221
+ onLine('Receiving objects: 100% (3/3), done.');
222
+ return { ok: true };
223
+ };
224
+ /**
225
+ * Clone a GitHub repo into `<projectsDir>/<name>`, streaming progress.
226
+ *
227
+ * Answers only when the clone has finished (the spec's "long-running: answers
228
+ * when the clone finishes"); the dialog's liveness comes from `onProgress`.
229
+ * On any failure the partially-written directory is removed and NOTHING is
230
+ * registered — registration is the caller's job, and only on `ok: true`.
231
+ */
232
+ export async function checkoutRepo(opts) {
233
+ const ref = parseRepoRef(opts.url);
234
+ if (!ref) {
235
+ return { ok: false, status: 400, error: `not a GitHub repository: ${opts.url.trim().slice(0, 200)}` };
236
+ }
237
+ const name = (opts.name ?? '').trim() === '' ? ref.repo : (opts.name ?? '').trim();
238
+ if (!isValidCheckoutName(name)) {
239
+ return { ok: false, status: 400, error: `not a valid folder name: ${name.slice(0, 200)}` };
240
+ }
241
+ if (!opts.projectsDir.startsWith('/')) {
242
+ return { ok: false, status: 500, error: `checkout root is not an absolute path: ${opts.projectsDir}` };
243
+ }
244
+ const root = resolve(opts.projectsDir);
245
+ const target = join(root, name);
246
+ // The checkout root is created on demand — a fresh install has never had one,
247
+ // and failing "clone" because a directory the user never asked about is
248
+ // missing would be an odd first experience. Its writability is validated on
249
+ // `PUT /api/workspace/config`; a failure here is reported as one.
250
+ try {
251
+ await mkdir(root, { recursive: true });
252
+ }
253
+ catch (err) {
254
+ return { ok: false, status: 500, error: `checkout root is not writable: ${errText(err)}` };
255
+ }
256
+ // THE ownership token (see the module docstring): non-recursive, so it
257
+ // succeeds only when it created the directory itself. EEXIST is the spec's
258
+ // 409 — and note nothing has touched the existing directory to learn that.
259
+ try {
260
+ await mkdir(target);
261
+ }
262
+ catch (err) {
263
+ if (err.code === 'EEXIST') {
264
+ return { ok: false, status: 409, error: `folder already exists: ${target}` };
265
+ }
266
+ return { ok: false, status: 500, error: `could not create ${target}: ${errText(err)}` };
267
+ }
268
+ const emit = (event) => opts.onProgress({ ...event, name, ...(opts.checkoutId ? { checkoutId: opts.checkoutId } : {}) });
269
+ const run = opts.run ?? (process.env.XEZ_DRY_RUN === '1' ? dryRunCloneRunner : ghCloneRunner);
270
+ let outcome;
271
+ try {
272
+ outcome = await run(ref, target, (line) => emit({ phase: 'cloning', line }), opts.signal);
273
+ }
274
+ catch (err) {
275
+ // A runner that throws is still a failed clone — same cleanup, same shape.
276
+ outcome = { ok: false, error: errText(err) };
277
+ }
278
+ if (!outcome.ok) {
279
+ // Cleanup FIRST, then answer: the dialog's "try again" must not race a
280
+ // directory that is still on disk (it would get the 409 instead).
281
+ await cleanupCheckout(root, target);
282
+ if (outcome.notFound) {
283
+ const reason = 'gh CLI not found — install it and run `gh auth login`';
284
+ emit({ phase: 'error', error: reason });
285
+ return { ok: false, status: 503, error: reason, reason };
286
+ }
287
+ emit({ phase: 'error', error: outcome.error });
288
+ return { ok: false, status: 500, error: outcome.error };
289
+ }
290
+ emit({ phase: 'done' });
291
+ return { ok: true, target, name };
292
+ }
293
+ function errText(err) {
294
+ return err instanceof Error ? err.message : String(err);
295
+ }
296
+ //# sourceMappingURL=checkout.js.map