openryoko 2026.10.8 → 2026.10.13

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 (691) hide show
  1. package/dist/bin/jimmy.js +51 -1
  2. package/dist/bin/jimmy.js.map +1 -1
  3. package/dist/package.json +2 -1
  4. package/dist/src/backup/archive.d.ts +65 -0
  5. package/dist/src/backup/archive.d.ts.map +1 -0
  6. package/dist/src/backup/archive.js +214 -0
  7. package/dist/src/backup/archive.js.map +1 -0
  8. package/dist/src/backup/catalog.d.ts +64 -0
  9. package/dist/src/backup/catalog.d.ts.map +1 -0
  10. package/dist/src/backup/catalog.js +252 -0
  11. package/dist/src/backup/catalog.js.map +1 -0
  12. package/dist/src/backup/database-restore.d.ts +63 -0
  13. package/dist/src/backup/database-restore.d.ts.map +1 -0
  14. package/dist/src/backup/database-restore.js +236 -0
  15. package/dist/src/backup/database-restore.js.map +1 -0
  16. package/dist/src/backup/database-source.d.ts +70 -0
  17. package/dist/src/backup/database-source.d.ts.map +1 -0
  18. package/dist/src/backup/database-source.js +70 -0
  19. package/dist/src/backup/database-source.js.map +1 -0
  20. package/dist/src/backup/format.d.ts +4 -0
  21. package/dist/src/backup/format.d.ts.map +1 -0
  22. package/dist/src/backup/format.js +15 -0
  23. package/dist/src/backup/format.js.map +1 -0
  24. package/dist/src/backup/gateway-liveness.d.ts +44 -0
  25. package/dist/src/backup/gateway-liveness.d.ts.map +1 -0
  26. package/dist/src/backup/gateway-liveness.js +120 -0
  27. package/dist/src/backup/gateway-liveness.js.map +1 -0
  28. package/dist/src/backup/inspect.d.ts +45 -0
  29. package/dist/src/backup/inspect.d.ts.map +1 -0
  30. package/dist/src/backup/inspect.js +291 -0
  31. package/dist/src/backup/inspect.js.map +1 -0
  32. package/dist/src/backup/layout.d.ts +11 -0
  33. package/dist/src/backup/layout.d.ts.map +1 -0
  34. package/dist/src/backup/layout.js +16 -0
  35. package/dist/src/backup/layout.js.map +1 -0
  36. package/dist/src/backup/limits.d.ts +54 -0
  37. package/dist/src/backup/limits.d.ts.map +1 -0
  38. package/dist/src/backup/limits.js +59 -0
  39. package/dist/src/backup/limits.js.map +1 -0
  40. package/dist/src/backup/manifest.d.ts +45 -0
  41. package/dist/src/backup/manifest.d.ts.map +1 -0
  42. package/dist/src/backup/manifest.js +77 -0
  43. package/dist/src/backup/manifest.js.map +1 -0
  44. package/dist/src/backup/restore.d.ts +73 -0
  45. package/dist/src/backup/restore.d.ts.map +1 -0
  46. package/dist/src/backup/restore.js +685 -0
  47. package/dist/src/backup/restore.js.map +1 -0
  48. package/dist/src/backup/run.d.ts +76 -0
  49. package/dist/src/backup/run.d.ts.map +1 -0
  50. package/dist/src/backup/run.js +181 -0
  51. package/dist/src/backup/run.js.map +1 -0
  52. package/dist/src/backup/scheduler.d.ts +51 -0
  53. package/dist/src/backup/scheduler.d.ts.map +1 -0
  54. package/dist/src/backup/scheduler.js +198 -0
  55. package/dist/src/backup/scheduler.js.map +1 -0
  56. package/dist/src/backup/settings.d.ts +44 -0
  57. package/dist/src/backup/settings.d.ts.map +1 -0
  58. package/dist/src/backup/settings.js +91 -0
  59. package/dist/src/backup/settings.js.map +1 -0
  60. package/dist/src/backup/space.d.ts +22 -0
  61. package/dist/src/backup/space.d.ts.map +1 -0
  62. package/dist/src/backup/space.js +47 -0
  63. package/dist/src/backup/space.js.map +1 -0
  64. package/dist/src/backup/state.d.ts +46 -0
  65. package/dist/src/backup/state.d.ts.map +1 -0
  66. package/dist/src/backup/state.js +137 -0
  67. package/dist/src/backup/state.js.map +1 -0
  68. package/dist/src/backup/tar.d.ts +64 -0
  69. package/dist/src/backup/tar.d.ts.map +1 -0
  70. package/dist/src/backup/tar.js +274 -0
  71. package/dist/src/backup/tar.js.map +1 -0
  72. package/dist/src/backup/targets.d.ts +85 -0
  73. package/dist/src/backup/targets.d.ts.map +1 -0
  74. package/dist/src/backup/targets.js +104 -0
  75. package/dist/src/backup/targets.js.map +1 -0
  76. package/dist/src/backup/walk.d.ts +42 -0
  77. package/dist/src/backup/walk.d.ts.map +1 -0
  78. package/dist/src/backup/walk.js +324 -0
  79. package/dist/src/backup/walk.js.map +1 -0
  80. package/dist/src/cli/backup.d.ts +45 -0
  81. package/dist/src/cli/backup.d.ts.map +1 -0
  82. package/dist/src/cli/backup.js +344 -0
  83. package/dist/src/cli/backup.js.map +1 -0
  84. package/dist/src/cli/bundled-skills.d.ts +7 -1
  85. package/dist/src/cli/bundled-skills.d.ts.map +1 -1
  86. package/dist/src/cli/bundled-skills.js +12 -2
  87. package/dist/src/cli/bundled-skills.js.map +1 -1
  88. package/dist/src/cli/config-failure.d.ts +18 -0
  89. package/dist/src/cli/config-failure.d.ts.map +1 -0
  90. package/dist/src/cli/config-failure.js +39 -0
  91. package/dist/src/cli/config-failure.js.map +1 -0
  92. package/dist/src/cli/gateway-audit.d.ts +3 -0
  93. package/dist/src/cli/gateway-audit.d.ts.map +1 -1
  94. package/dist/src/cli/gateway-audit.js +61 -14
  95. package/dist/src/cli/gateway-audit.js.map +1 -1
  96. package/dist/src/cli/gateway-url-backup.d.ts +21 -0
  97. package/dist/src/cli/gateway-url-backup.d.ts.map +1 -0
  98. package/dist/src/cli/gateway-url-backup.js +87 -0
  99. package/dist/src/cli/gateway-url-backup.js.map +1 -0
  100. package/dist/src/cli/home-copy.d.ts +37 -0
  101. package/dist/src/cli/home-copy.d.ts.map +1 -0
  102. package/dist/src/cli/home-copy.js +155 -0
  103. package/dist/src/cli/home-copy.js.map +1 -0
  104. package/dist/src/cli/interactive-config.d.ts +5 -0
  105. package/dist/src/cli/interactive-config.d.ts.map +1 -1
  106. package/dist/src/cli/interactive-config.js +16 -7
  107. package/dist/src/cli/interactive-config.js.map +1 -1
  108. package/dist/src/cli/migrate.d.ts +20 -0
  109. package/dist/src/cli/migrate.d.ts.map +1 -1
  110. package/dist/src/cli/migrate.js +365 -68
  111. package/dist/src/cli/migrate.js.map +1 -1
  112. package/dist/src/cli/migration-declarations.d.ts +24 -0
  113. package/dist/src/cli/migration-declarations.d.ts.map +1 -0
  114. package/dist/src/cli/migration-declarations.js +54 -0
  115. package/dist/src/cli/migration-declarations.js.map +1 -0
  116. package/dist/src/cli/start.d.ts.map +1 -1
  117. package/dist/src/cli/start.js +40 -1
  118. package/dist/src/cli/start.js.map +1 -1
  119. package/dist/src/connectors/chunked-send.d.ts +27 -0
  120. package/dist/src/connectors/chunked-send.d.ts.map +1 -0
  121. package/dist/src/connectors/chunked-send.js +40 -0
  122. package/dist/src/connectors/chunked-send.js.map +1 -0
  123. package/dist/src/connectors/cron/index.d.ts +10 -0
  124. package/dist/src/connectors/cron/index.d.ts.map +1 -1
  125. package/dist/src/connectors/cron/index.js +26 -3
  126. package/dist/src/connectors/cron/index.js.map +1 -1
  127. package/dist/src/connectors/discord/attachments.d.ts +25 -0
  128. package/dist/src/connectors/discord/attachments.d.ts.map +1 -0
  129. package/dist/src/connectors/discord/attachments.js +33 -0
  130. package/dist/src/connectors/discord/attachments.js.map +1 -0
  131. package/dist/src/connectors/discord/format.d.ts +17 -1
  132. package/dist/src/connectors/discord/format.d.ts.map +1 -1
  133. package/dist/src/connectors/discord/format.js +36 -6
  134. package/dist/src/connectors/discord/format.js.map +1 -1
  135. package/dist/src/connectors/discord/index.d.ts +12 -0
  136. package/dist/src/connectors/discord/index.d.ts.map +1 -1
  137. package/dist/src/connectors/discord/index.js +47 -40
  138. package/dist/src/connectors/discord/index.js.map +1 -1
  139. package/dist/src/connectors/discord/remote.d.ts +45 -1
  140. package/dist/src/connectors/discord/remote.d.ts.map +1 -1
  141. package/dist/src/connectors/discord/remote.js +147 -22
  142. package/dist/src/connectors/discord/remote.js.map +1 -1
  143. package/dist/src/connectors/slack/attachments.d.ts +25 -0
  144. package/dist/src/connectors/slack/attachments.d.ts.map +1 -0
  145. package/dist/src/connectors/slack/attachments.js +32 -0
  146. package/dist/src/connectors/slack/attachments.js.map +1 -0
  147. package/dist/src/connectors/slack/auth-test-cache.d.ts +22 -0
  148. package/dist/src/connectors/slack/auth-test-cache.d.ts.map +1 -0
  149. package/dist/src/connectors/slack/auth-test-cache.js +34 -0
  150. package/dist/src/connectors/slack/auth-test-cache.js.map +1 -0
  151. package/dist/src/connectors/slack/format.d.ts +6 -1
  152. package/dist/src/connectors/slack/format.d.ts.map +1 -1
  153. package/dist/src/connectors/slack/format.js +65 -13
  154. package/dist/src/connectors/slack/format.js.map +1 -1
  155. package/dist/src/connectors/slack/index.d.ts +4 -0
  156. package/dist/src/connectors/slack/index.d.ts.map +1 -1
  157. package/dist/src/connectors/slack/index.js +64 -52
  158. package/dist/src/connectors/slack/index.js.map +1 -1
  159. package/dist/src/connectors/slack/provider-auth-blocks.d.ts +62 -0
  160. package/dist/src/connectors/slack/provider-auth-blocks.d.ts.map +1 -0
  161. package/dist/src/connectors/slack/provider-auth-blocks.js +204 -0
  162. package/dist/src/connectors/slack/provider-auth-blocks.js.map +1 -0
  163. package/dist/src/connectors/slack/provider-auth.d.ts +126 -0
  164. package/dist/src/connectors/slack/provider-auth.d.ts.map +1 -0
  165. package/dist/src/connectors/slack/provider-auth.js +357 -0
  166. package/dist/src/connectors/slack/provider-auth.js.map +1 -0
  167. package/dist/src/connectors/telegram/index.d.ts +5 -0
  168. package/dist/src/connectors/telegram/index.d.ts.map +1 -1
  169. package/dist/src/connectors/telegram/index.js +14 -22
  170. package/dist/src/connectors/telegram/index.js.map +1 -1
  171. package/dist/src/connectors/whatsapp/index.d.ts +3 -0
  172. package/dist/src/connectors/whatsapp/index.d.ts.map +1 -1
  173. package/dist/src/connectors/whatsapp/index.js +33 -14
  174. package/dist/src/connectors/whatsapp/index.js.map +1 -1
  175. package/dist/src/cron/alert-throttle.d.ts +28 -0
  176. package/dist/src/cron/alert-throttle.d.ts.map +1 -0
  177. package/dist/src/cron/alert-throttle.js +106 -0
  178. package/dist/src/cron/alert-throttle.js.map +1 -0
  179. package/dist/src/cron/jobs.d.ts +8 -0
  180. package/dist/src/cron/jobs.d.ts.map +1 -1
  181. package/dist/src/cron/jobs.js +27 -1
  182. package/dist/src/cron/jobs.js.map +1 -1
  183. package/dist/src/cron/run-outcome.d.ts +28 -0
  184. package/dist/src/cron/run-outcome.d.ts.map +1 -0
  185. package/dist/src/cron/run-outcome.js +66 -0
  186. package/dist/src/cron/run-outcome.js.map +1 -0
  187. package/dist/src/cron/runner.d.ts.map +1 -1
  188. package/dist/src/cron/runner.js +68 -5
  189. package/dist/src/cron/runner.js.map +1 -1
  190. package/dist/src/cron/scheduler.d.ts +8 -1
  191. package/dist/src/cron/scheduler.d.ts.map +1 -1
  192. package/dist/src/cron/scheduler.js +11 -4
  193. package/dist/src/cron/scheduler.js.map +1 -1
  194. package/dist/src/cron/waiting-fires.d.ts +18 -0
  195. package/dist/src/cron/waiting-fires.d.ts.map +1 -0
  196. package/dist/src/cron/waiting-fires.js +49 -0
  197. package/dist/src/cron/waiting-fires.js.map +1 -0
  198. package/dist/src/engines/claude-interactive.d.ts +19 -3
  199. package/dist/src/engines/claude-interactive.d.ts.map +1 -1
  200. package/dist/src/engines/claude-interactive.js +42 -17
  201. package/dist/src/engines/claude-interactive.js.map +1 -1
  202. package/dist/src/engines/claude.d.ts.map +1 -1
  203. package/dist/src/engines/claude.js +38 -5
  204. package/dist/src/engines/claude.js.map +1 -1
  205. package/dist/src/engines/codex.d.ts.map +1 -1
  206. package/dist/src/engines/codex.js +1 -0
  207. package/dist/src/engines/codex.js.map +1 -1
  208. package/dist/src/engines/gemini.d.ts.map +1 -1
  209. package/dist/src/engines/gemini.js +1 -0
  210. package/dist/src/engines/gemini.js.map +1 -1
  211. package/dist/src/gateway/api.d.ts.map +1 -1
  212. package/dist/src/gateway/api.js +184 -160
  213. package/dist/src/gateway/api.js.map +1 -1
  214. package/dist/src/gateway/config-redaction.d.ts +32 -0
  215. package/dist/src/gateway/config-redaction.d.ts.map +1 -0
  216. package/dist/src/gateway/config-redaction.js +139 -0
  217. package/dist/src/gateway/config-redaction.js.map +1 -0
  218. package/dist/src/gateway/config-revision.d.ts +46 -0
  219. package/dist/src/gateway/config-revision.d.ts.map +1 -0
  220. package/dist/src/gateway/config-revision.js +63 -0
  221. package/dist/src/gateway/config-revision.js.map +1 -0
  222. package/dist/src/gateway/connector-config.d.ts +21 -0
  223. package/dist/src/gateway/connector-config.d.ts.map +1 -0
  224. package/dist/src/gateway/connector-config.js +24 -0
  225. package/dist/src/gateway/connector-config.js.map +1 -0
  226. package/dist/src/gateway/daemon-entry.js +5 -0
  227. package/dist/src/gateway/daemon-entry.js.map +1 -1
  228. package/dist/src/gateway/engine-health-monitor.d.ts +67 -0
  229. package/dist/src/gateway/engine-health-monitor.d.ts.map +1 -0
  230. package/dist/src/gateway/engine-health-monitor.js +221 -0
  231. package/dist/src/gateway/engine-health-monitor.js.map +1 -0
  232. package/dist/src/gateway/files.d.ts +7 -1
  233. package/dist/src/gateway/files.d.ts.map +1 -1
  234. package/dist/src/gateway/files.js +110 -217
  235. package/dist/src/gateway/files.js.map +1 -1
  236. package/dist/src/gateway/onboarding-personalize.d.ts +2 -0
  237. package/dist/src/gateway/onboarding-personalize.d.ts.map +1 -1
  238. package/dist/src/gateway/onboarding-personalize.js +17 -1
  239. package/dist/src/gateway/onboarding-personalize.js.map +1 -1
  240. package/dist/src/gateway/portal-config.d.ts +17 -3
  241. package/dist/src/gateway/portal-config.d.ts.map +1 -1
  242. package/dist/src/gateway/portal-config.js +52 -3
  243. package/dist/src/gateway/portal-config.js.map +1 -1
  244. package/dist/src/gateway/request-origin.d.ts +9 -0
  245. package/dist/src/gateway/request-origin.d.ts.map +1 -1
  246. package/dist/src/gateway/request-origin.js +10 -0
  247. package/dist/src/gateway/request-origin.js.map +1 -1
  248. package/dist/src/gateway/server.d.ts.map +1 -1
  249. package/dist/src/gateway/server.js +82 -59
  250. package/dist/src/gateway/server.js.map +1 -1
  251. package/dist/src/gateway/slack-connect-rollback.d.ts +23 -0
  252. package/dist/src/gateway/slack-connect-rollback.d.ts.map +1 -0
  253. package/dist/src/gateway/slack-connect-rollback.js +53 -0
  254. package/dist/src/gateway/slack-connect-rollback.js.map +1 -0
  255. package/dist/src/gateway/status-reconciler.d.ts +25 -7
  256. package/dist/src/gateway/status-reconciler.d.ts.map +1 -1
  257. package/dist/src/gateway/status-reconciler.js +86 -43
  258. package/dist/src/gateway/status-reconciler.js.map +1 -1
  259. package/dist/src/gateway/watcher.d.ts.map +1 -1
  260. package/dist/src/gateway/watcher.js +44 -3
  261. package/dist/src/gateway/watcher.js.map +1 -1
  262. package/dist/src/mcp/gateway-server.js +21 -3
  263. package/dist/src/mcp/gateway-server.js.map +1 -1
  264. package/dist/src/mcp/resolver.d.ts.map +1 -1
  265. package/dist/src/mcp/resolver.js +51 -1
  266. package/dist/src/mcp/resolver.js.map +1 -1
  267. package/dist/src/models/service.d.ts.map +1 -1
  268. package/dist/src/models/service.js +14 -8
  269. package/dist/src/models/service.js.map +1 -1
  270. package/dist/src/provider-auth/discovery.d.ts +16 -0
  271. package/dist/src/provider-auth/discovery.d.ts.map +1 -0
  272. package/dist/src/provider-auth/discovery.js +66 -0
  273. package/dist/src/provider-auth/discovery.js.map +1 -0
  274. package/dist/src/provider-auth/failure-notices.d.ts +29 -0
  275. package/dist/src/provider-auth/failure-notices.d.ts.map +1 -0
  276. package/dist/src/provider-auth/failure-notices.js +48 -0
  277. package/dist/src/provider-auth/failure-notices.js.map +1 -0
  278. package/dist/src/provider-auth/failure-signal.d.ts +22 -0
  279. package/dist/src/provider-auth/failure-signal.d.ts.map +1 -0
  280. package/dist/src/provider-auth/failure-signal.js +79 -0
  281. package/dist/src/provider-auth/failure-signal.js.map +1 -0
  282. package/dist/src/provider-auth/index.d.ts +26 -0
  283. package/dist/src/provider-auth/index.d.ts.map +1 -0
  284. package/dist/src/provider-auth/index.js +51 -0
  285. package/dist/src/provider-auth/index.js.map +1 -0
  286. package/dist/src/provider-auth/input.d.ts +44 -0
  287. package/dist/src/provider-auth/input.d.ts.map +1 -0
  288. package/dist/src/provider-auth/input.js +86 -0
  289. package/dist/src/provider-auth/input.js.map +1 -0
  290. package/dist/src/provider-auth/providers.d.ts +26 -0
  291. package/dist/src/provider-auth/providers.d.ts.map +1 -0
  292. package/dist/src/provider-auth/providers.js +86 -0
  293. package/dist/src/provider-auth/providers.js.map +1 -0
  294. package/dist/src/provider-auth/runtime.d.ts +21 -0
  295. package/dist/src/provider-auth/runtime.d.ts.map +1 -0
  296. package/dist/src/provider-auth/runtime.js +104 -0
  297. package/dist/src/provider-auth/runtime.js.map +1 -0
  298. package/dist/src/provider-auth/service.d.ts +70 -0
  299. package/dist/src/provider-auth/service.d.ts.map +1 -0
  300. package/dist/src/provider-auth/service.js +349 -0
  301. package/dist/src/provider-auth/service.js.map +1 -0
  302. package/dist/src/provider-auth/terminal.d.ts +21 -0
  303. package/dist/src/provider-auth/terminal.d.ts.map +1 -0
  304. package/dist/src/provider-auth/terminal.js +46 -0
  305. package/dist/src/provider-auth/terminal.js.map +1 -0
  306. package/dist/src/provider-auth/types.d.ts +123 -0
  307. package/dist/src/provider-auth/types.d.ts.map +1 -0
  308. package/dist/src/provider-auth/types.js +10 -0
  309. package/dist/src/provider-auth/types.js.map +1 -0
  310. package/dist/src/sessions/accounting.d.ts +3 -0
  311. package/dist/src/sessions/accounting.d.ts.map +1 -1
  312. package/dist/src/sessions/accounting.js +5 -0
  313. package/dist/src/sessions/accounting.js.map +1 -1
  314. package/dist/src/sessions/backup-integrity.d.ts +45 -0
  315. package/dist/src/sessions/backup-integrity.d.ts.map +1 -0
  316. package/dist/src/sessions/backup-integrity.js +122 -0
  317. package/dist/src/sessions/backup-integrity.js.map +1 -0
  318. package/dist/src/sessions/backup.d.ts +40 -2
  319. package/dist/src/sessions/backup.d.ts.map +1 -1
  320. package/dist/src/sessions/backup.js +88 -12
  321. package/dist/src/sessions/backup.js.map +1 -1
  322. package/dist/src/sessions/callbacks.d.ts +9 -0
  323. package/dist/src/sessions/callbacks.d.ts.map +1 -1
  324. package/dist/src/sessions/callbacks.js +55 -10
  325. package/dist/src/sessions/callbacks.js.map +1 -1
  326. package/dist/src/sessions/completion-batching.d.ts +82 -0
  327. package/dist/src/sessions/completion-batching.d.ts.map +1 -0
  328. package/dist/src/sessions/completion-batching.js +126 -0
  329. package/dist/src/sessions/completion-batching.js.map +1 -0
  330. package/dist/src/sessions/connector-command-guard.d.ts +66 -0
  331. package/dist/src/sessions/connector-command-guard.d.ts.map +1 -0
  332. package/dist/src/sessions/connector-command-guard.js +145 -0
  333. package/dist/src/sessions/connector-command-guard.js.map +1 -0
  334. package/dist/src/sessions/context.d.ts.map +1 -1
  335. package/dist/src/sessions/context.js +3 -4
  336. package/dist/src/sessions/context.js.map +1 -1
  337. package/dist/src/sessions/engine-fallback.d.ts +43 -1
  338. package/dist/src/sessions/engine-fallback.d.ts.map +1 -1
  339. package/dist/src/sessions/engine-fallback.js +266 -15
  340. package/dist/src/sessions/engine-fallback.js.map +1 -1
  341. package/dist/src/sessions/engine-health-gate.d.ts +83 -0
  342. package/dist/src/sessions/engine-health-gate.d.ts.map +1 -0
  343. package/dist/src/sessions/engine-health-gate.js +186 -0
  344. package/dist/src/sessions/engine-health-gate.js.map +1 -0
  345. package/dist/src/sessions/engine-login.d.ts +58 -0
  346. package/dist/src/sessions/engine-login.d.ts.map +1 -0
  347. package/dist/src/sessions/engine-login.js +107 -0
  348. package/dist/src/sessions/engine-login.js.map +1 -0
  349. package/dist/src/sessions/fork.d.ts.map +1 -1
  350. package/dist/src/sessions/fork.js +3 -1
  351. package/dist/src/sessions/fork.js.map +1 -1
  352. package/dist/src/sessions/goal-execution.d.ts.map +1 -1
  353. package/dist/src/sessions/goal-execution.js +8 -1
  354. package/dist/src/sessions/goal-execution.js.map +1 -1
  355. package/dist/src/sessions/limit-failure-notice.d.ts +41 -0
  356. package/dist/src/sessions/limit-failure-notice.d.ts.map +1 -0
  357. package/dist/src/sessions/limit-failure-notice.js +87 -0
  358. package/dist/src/sessions/limit-failure-notice.js.map +1 -0
  359. package/dist/src/sessions/lost-turn.d.ts +39 -0
  360. package/dist/src/sessions/lost-turn.d.ts.map +1 -0
  361. package/dist/src/sessions/lost-turn.js +32 -0
  362. package/dist/src/sessions/lost-turn.js.map +1 -0
  363. package/dist/src/sessions/manager.d.ts.map +1 -1
  364. package/dist/src/sessions/manager.js +104 -47
  365. package/dist/src/sessions/manager.js.map +1 -1
  366. package/dist/src/sessions/new-session-engine.d.ts +42 -0
  367. package/dist/src/sessions/new-session-engine.d.ts.map +1 -0
  368. package/dist/src/sessions/new-session-engine.js +100 -0
  369. package/dist/src/sessions/new-session-engine.js.map +1 -0
  370. package/dist/src/sessions/origin-delivery.d.ts +50 -4
  371. package/dist/src/sessions/origin-delivery.d.ts.map +1 -1
  372. package/dist/src/sessions/origin-delivery.js +108 -9
  373. package/dist/src/sessions/origin-delivery.js.map +1 -1
  374. package/dist/src/sessions/queue.d.ts +14 -0
  375. package/dist/src/sessions/queue.d.ts.map +1 -1
  376. package/dist/src/sessions/queue.js +38 -2
  377. package/dist/src/sessions/queue.js.map +1 -1
  378. package/dist/src/sessions/registry.d.ts +4 -2
  379. package/dist/src/sessions/registry.d.ts.map +1 -1
  380. package/dist/src/sessions/registry.js +21 -4
  381. package/dist/src/sessions/registry.js.map +1 -1
  382. package/dist/src/sessions/reply-disposition.d.ts +8 -0
  383. package/dist/src/sessions/reply-disposition.d.ts.map +1 -1
  384. package/dist/src/sessions/reply-disposition.js +8 -0
  385. package/dist/src/sessions/reply-disposition.js.map +1 -1
  386. package/dist/src/sessions/restart-resume.d.ts +234 -0
  387. package/dist/src/sessions/restart-resume.d.ts.map +1 -0
  388. package/dist/src/sessions/restart-resume.js +596 -0
  389. package/dist/src/sessions/restart-resume.js.map +1 -0
  390. package/dist/src/sessions/speaker-prefix.d.ts.map +1 -1
  391. package/dist/src/sessions/speaker-prefix.js +3 -1
  392. package/dist/src/sessions/speaker-prefix.js.map +1 -1
  393. package/dist/src/sessions/turn-delivery.d.ts +26 -0
  394. package/dist/src/sessions/turn-delivery.d.ts.map +1 -0
  395. package/dist/src/sessions/turn-delivery.js +34 -0
  396. package/dist/src/sessions/turn-delivery.js.map +1 -0
  397. package/dist/src/sessions/unrequested-stop.d.ts +26 -0
  398. package/dist/src/sessions/unrequested-stop.d.ts.map +1 -0
  399. package/dist/src/sessions/unrequested-stop.js +19 -0
  400. package/dist/src/sessions/unrequested-stop.js.map +1 -0
  401. package/dist/src/sessions/usage-limit-wait.d.ts +14 -0
  402. package/dist/src/sessions/usage-limit-wait.d.ts.map +1 -0
  403. package/dist/src/sessions/usage-limit-wait.js +9 -0
  404. package/dist/src/sessions/usage-limit-wait.js.map +1 -0
  405. package/dist/src/shared/attachment-download.d.ts +41 -0
  406. package/dist/src/shared/attachment-download.d.ts.map +1 -0
  407. package/dist/src/shared/attachment-download.js +110 -0
  408. package/dist/src/shared/attachment-download.js.map +1 -0
  409. package/dist/src/shared/attachment-notice.d.ts +24 -0
  410. package/dist/src/shared/attachment-notice.d.ts.map +1 -0
  411. package/dist/src/shared/attachment-notice.js +37 -0
  412. package/dist/src/shared/attachment-notice.js.map +1 -0
  413. package/dist/src/shared/attachments.d.ts +65 -0
  414. package/dist/src/shared/attachments.d.ts.map +1 -0
  415. package/dist/src/shared/attachments.js +153 -0
  416. package/dist/src/shared/attachments.js.map +1 -0
  417. package/dist/src/shared/childEnv.d.ts +15 -0
  418. package/dist/src/shared/childEnv.d.ts.map +1 -1
  419. package/dist/src/shared/childEnv.js +15 -0
  420. package/dist/src/shared/childEnv.js.map +1 -1
  421. package/dist/src/shared/claude-auto-memory.d.ts +33 -0
  422. package/dist/src/shared/claude-auto-memory.d.ts.map +1 -0
  423. package/dist/src/shared/claude-auto-memory.js +156 -0
  424. package/dist/src/shared/claude-auto-memory.js.map +1 -0
  425. package/dist/src/shared/claude-limit-reset.d.ts +54 -0
  426. package/dist/src/shared/claude-limit-reset.d.ts.map +1 -0
  427. package/dist/src/shared/claude-limit-reset.js +99 -0
  428. package/dist/src/shared/claude-limit-reset.js.map +1 -0
  429. package/dist/src/shared/config-error.d.ts +21 -0
  430. package/dist/src/shared/config-error.d.ts.map +1 -0
  431. package/dist/src/shared/config-error.js +37 -0
  432. package/dist/src/shared/config-error.js.map +1 -0
  433. package/dist/src/shared/config-file.d.ts +57 -0
  434. package/dist/src/shared/config-file.d.ts.map +1 -0
  435. package/dist/src/shared/config-file.js +217 -0
  436. package/dist/src/shared/config-file.js.map +1 -0
  437. package/dist/src/shared/config-yaml-edit.d.ts +20 -0
  438. package/dist/src/shared/config-yaml-edit.d.ts.map +1 -0
  439. package/dist/src/shared/config-yaml-edit.js +468 -0
  440. package/dist/src/shared/config-yaml-edit.js.map +1 -0
  441. package/dist/src/shared/config.d.ts.map +1 -1
  442. package/dist/src/shared/config.js +12 -4
  443. package/dist/src/shared/config.js.map +1 -1
  444. package/dist/src/shared/engine-fallback.d.ts +22 -0
  445. package/dist/src/shared/engine-fallback.d.ts.map +1 -1
  446. package/dist/src/shared/engine-fallback.js +55 -4
  447. package/dist/src/shared/engine-fallback.js.map +1 -1
  448. package/dist/src/shared/engine-health.d.ts +16 -0
  449. package/dist/src/shared/engine-health.d.ts.map +1 -1
  450. package/dist/src/shared/engine-health.js +42 -0
  451. package/dist/src/shared/engine-health.js.map +1 -1
  452. package/dist/src/shared/list-alignment.d.ts +23 -0
  453. package/dist/src/shared/list-alignment.d.ts.map +1 -0
  454. package/dist/src/shared/list-alignment.js +61 -0
  455. package/dist/src/shared/list-alignment.js.map +1 -0
  456. package/dist/src/shared/logger.d.ts.map +1 -1
  457. package/dist/src/shared/logger.js +104 -33
  458. package/dist/src/shared/logger.js.map +1 -1
  459. package/dist/src/shared/map-limited.d.ts +11 -0
  460. package/dist/src/shared/map-limited.d.ts.map +1 -0
  461. package/dist/src/shared/map-limited.js +30 -0
  462. package/dist/src/shared/map-limited.js.map +1 -0
  463. package/dist/src/shared/oneShotCli.d.ts.map +1 -1
  464. package/dist/src/shared/oneShotCli.js +4 -2
  465. package/dist/src/shared/oneShotCli.js.map +1 -1
  466. package/dist/src/shared/partial-delivery.d.ts +24 -0
  467. package/dist/src/shared/partial-delivery.d.ts.map +1 -0
  468. package/dist/src/shared/partial-delivery.js +46 -0
  469. package/dist/src/shared/partial-delivery.js.map +1 -0
  470. package/dist/src/shared/process-safety-net.d.ts +44 -0
  471. package/dist/src/shared/process-safety-net.d.ts.map +1 -0
  472. package/dist/src/shared/process-safety-net.js +88 -0
  473. package/dist/src/shared/process-safety-net.js.map +1 -0
  474. package/dist/src/shared/rateLimit.d.ts +45 -2
  475. package/dist/src/shared/rateLimit.d.ts.map +1 -1
  476. package/dist/src/shared/rateLimit.js +64 -2
  477. package/dist/src/shared/rateLimit.js.map +1 -1
  478. package/dist/src/shared/redact.d.ts.map +1 -1
  479. package/dist/src/shared/redact.js +11 -1
  480. package/dist/src/shared/redact.js.map +1 -1
  481. package/dist/src/shared/safe-file-name.d.ts +9 -0
  482. package/dist/src/shared/safe-file-name.d.ts.map +1 -0
  483. package/dist/src/shared/safe-file-name.js +16 -0
  484. package/dist/src/shared/safe-file-name.js.map +1 -0
  485. package/dist/src/shared/terminal-safe-text.d.ts +2 -0
  486. package/dist/src/shared/terminal-safe-text.d.ts.map +1 -0
  487. package/dist/src/shared/terminal-safe-text.js +21 -0
  488. package/dist/src/shared/terminal-safe-text.js.map +1 -0
  489. package/dist/src/shared/types.d.ts +75 -4
  490. package/dist/src/shared/types.d.ts.map +1 -1
  491. package/dist/src/skills-sync/apply.d.ts +21 -0
  492. package/dist/src/skills-sync/apply.d.ts.map +1 -0
  493. package/dist/src/skills-sync/apply.js +209 -0
  494. package/dist/src/skills-sync/apply.js.map +1 -0
  495. package/dist/src/skills-sync/backup-run.d.ts +47 -0
  496. package/dist/src/skills-sync/backup-run.d.ts.map +1 -0
  497. package/dist/src/skills-sync/backup-run.js +150 -0
  498. package/dist/src/skills-sync/backup-run.js.map +1 -0
  499. package/dist/src/skills-sync/bounded-read.d.ts +32 -0
  500. package/dist/src/skills-sync/bounded-read.d.ts.map +1 -0
  501. package/dist/src/skills-sync/bounded-read.js +104 -0
  502. package/dist/src/skills-sync/bounded-read.js.map +1 -0
  503. package/dist/src/skills-sync/bundled.d.ts +28 -0
  504. package/dist/src/skills-sync/bundled.d.ts.map +1 -0
  505. package/dist/src/skills-sync/bundled.js +84 -0
  506. package/dist/src/skills-sync/bundled.js.map +1 -0
  507. package/dist/src/skills-sync/fingerprint.d.ts +32 -0
  508. package/dist/src/skills-sync/fingerprint.d.ts.map +1 -0
  509. package/dist/src/skills-sync/fingerprint.js +58 -0
  510. package/dist/src/skills-sync/fingerprint.js.map +1 -0
  511. package/dist/src/skills-sync/history.d.ts +44 -0
  512. package/dist/src/skills-sync/history.d.ts.map +1 -0
  513. package/dist/src/skills-sync/history.js +115 -0
  514. package/dist/src/skills-sync/history.js.map +1 -0
  515. package/dist/src/skills-sync/home-skill.d.ts +28 -0
  516. package/dist/src/skills-sync/home-skill.d.ts.map +1 -0
  517. package/dist/src/skills-sync/home-skill.js +67 -0
  518. package/dist/src/skills-sync/home-skill.js.map +1 -0
  519. package/dist/src/skills-sync/instance.d.ts +26 -0
  520. package/dist/src/skills-sync/instance.d.ts.map +1 -0
  521. package/dist/src/skills-sync/instance.js +128 -0
  522. package/dist/src/skills-sync/instance.js.map +1 -0
  523. package/dist/src/skills-sync/names.d.ts +25 -0
  524. package/dist/src/skills-sync/names.d.ts.map +1 -0
  525. package/dist/src/skills-sync/names.js +56 -0
  526. package/dist/src/skills-sync/names.js.map +1 -0
  527. package/dist/src/skills-sync/plan.d.ts +58 -0
  528. package/dist/src/skills-sync/plan.d.ts.map +1 -0
  529. package/dist/src/skills-sync/plan.js +132 -0
  530. package/dist/src/skills-sync/plan.js.map +1 -0
  531. package/dist/src/skills-sync/receipt.d.ts +61 -0
  532. package/dist/src/skills-sync/receipt.d.ts.map +1 -0
  533. package/dist/src/skills-sync/receipt.js +149 -0
  534. package/dist/src/skills-sync/receipt.js.map +1 -0
  535. package/dist/src/skills-sync/staging.d.ts +21 -0
  536. package/dist/src/skills-sync/staging.d.ts.map +1 -0
  537. package/dist/src/skills-sync/staging.js +81 -0
  538. package/dist/src/skills-sync/staging.js.map +1 -0
  539. package/dist/src/skills-sync/sync.d.ts +37 -0
  540. package/dist/src/skills-sync/sync.d.ts.map +1 -0
  541. package/dist/src/skills-sync/sync.js +201 -0
  542. package/dist/src/skills-sync/sync.js.map +1 -0
  543. package/dist/src/skills-sync/types.d.ts +66 -0
  544. package/dist/src/skills-sync/types.d.ts.map +1 -0
  545. package/dist/src/skills-sync/types.js +2 -0
  546. package/dist/src/skills-sync/types.js.map +1 -0
  547. package/dist/src/stt/config.d.ts +5 -0
  548. package/dist/src/stt/config.d.ts.map +1 -0
  549. package/dist/src/stt/config.js +18 -0
  550. package/dist/src/stt/config.js.map +1 -0
  551. package/dist/src/updates/maintenance.d.ts.map +1 -1
  552. package/dist/src/updates/maintenance.js +24 -4
  553. package/dist/src/updates/maintenance.js.map +1 -1
  554. package/dist/src/workflows/repository-mutex.d.ts +1 -1
  555. package/dist/web/404.html +1 -1
  556. package/dist/web/__next.__PAGE__.txt +11 -11
  557. package/dist/web/__next._full.txt +12 -12
  558. package/dist/web/__next._tree.txt +2 -2
  559. package/dist/web/_next/static/chunks/{0g5c5ppr8zmup.js → 20v6mri7epga5.js} +1 -1
  560. package/dist/web/_next/static/chunks/2u0328ngpt-ek.js +1 -0
  561. package/dist/web/_next/static/chunks/3f9i3f9q6v6_j.css +1 -0
  562. package/dist/web/_next/static/chunks/{08i7uo3uz6_fn.js → 3yy053rafgrzy.js} +1 -1
  563. package/dist/web/_not-found/__next._full.txt +10 -10
  564. package/dist/web/_not-found/__next._not-found.__PAGE__.txt +9 -9
  565. package/dist/web/_not-found/__next._tree.txt +2 -2
  566. package/dist/web/_not-found.html +1 -1
  567. package/dist/web/_not-found.txt +10 -10
  568. package/dist/web/chat/__next._full.txt +12 -12
  569. package/dist/web/chat/__next._tree.txt +2 -2
  570. package/dist/web/chat/__next.chat.__PAGE__.txt +11 -11
  571. package/dist/web/chat.html +1 -1
  572. package/dist/web/chat.txt +12 -12
  573. package/dist/web/cron/__next._full.txt +12 -12
  574. package/dist/web/cron/__next._tree.txt +2 -2
  575. package/dist/web/cron/__next.cron.__PAGE__.txt +11 -11
  576. package/dist/web/cron.html +1 -1
  577. package/dist/web/cron.txt +12 -12
  578. package/dist/web/index.html +1 -1
  579. package/dist/web/index.txt +12 -12
  580. package/dist/web/kanban/__next._full.txt +12 -12
  581. package/dist/web/kanban/__next._tree.txt +2 -2
  582. package/dist/web/kanban/__next.kanban.__PAGE__.txt +11 -11
  583. package/dist/web/kanban.html +1 -1
  584. package/dist/web/kanban.txt +12 -12
  585. package/dist/web/logs/__next._full.txt +12 -12
  586. package/dist/web/logs/__next._tree.txt +2 -2
  587. package/dist/web/logs/__next.logs.__PAGE__.txt +11 -11
  588. package/dist/web/logs.html +1 -1
  589. package/dist/web/logs.txt +12 -12
  590. package/dist/web/models/__next._full.txt +12 -12
  591. package/dist/web/models/__next._tree.txt +2 -2
  592. package/dist/web/models/__next.models.__PAGE__.txt +11 -11
  593. package/dist/web/models.html +1 -1
  594. package/dist/web/models.txt +12 -12
  595. package/dist/web/org/__next._full.txt +12 -12
  596. package/dist/web/org/__next._tree.txt +2 -2
  597. package/dist/web/org/__next.org.__PAGE__.txt +11 -11
  598. package/dist/web/org.html +1 -1
  599. package/dist/web/org.txt +12 -12
  600. package/dist/web/sessions/__next._full.txt +10 -10
  601. package/dist/web/sessions/__next._tree.txt +2 -2
  602. package/dist/web/sessions/__next.sessions.__PAGE__.txt +9 -9
  603. package/dist/web/sessions.html +1 -1
  604. package/dist/web/sessions.txt +10 -10
  605. package/dist/web/settings/__next._full.txt +12 -12
  606. package/dist/web/settings/__next._tree.txt +2 -2
  607. package/dist/web/settings/__next.settings.__PAGE__.txt +11 -11
  608. package/dist/web/settings.html +1 -1
  609. package/dist/web/settings.txt +12 -12
  610. package/dist/web/skills/__next._full.txt +12 -12
  611. package/dist/web/skills/__next._tree.txt +2 -2
  612. package/dist/web/skills/__next.skills.__PAGE__.txt +11 -11
  613. package/dist/web/skills.html +1 -1
  614. package/dist/web/skills.txt +12 -12
  615. package/package.json +2 -1
  616. package/template/AGENTS.md +48 -308
  617. package/template/CLAUDE.md +4 -349
  618. package/template/bundled-skill-history.json +377 -0
  619. package/template/config.default.yaml +52 -3
  620. package/template/docs/architecture.md +41 -3
  621. package/template/docs/backup.md +142 -0
  622. package/template/docs/connectors.md +192 -16
  623. package/template/docs/cron.md +30 -11
  624. package/template/docs/memory.md +47 -0
  625. package/template/docs/overview.md +6 -2
  626. package/template/docs/self-modification.md +11 -10
  627. package/template/docs/skills.md +77 -4
  628. package/template/migrations/0.3.0/MIGRATION.md +6 -0
  629. package/template/migrations/0.7.3/MIGRATION.md +6 -0
  630. package/template/migrations/0.7.5/MIGRATION.md +1 -1
  631. package/template/migrations/0.9.0/MIGRATION.md +6 -0
  632. package/template/migrations/2026.10.13/MIGRATION.md +76 -0
  633. package/template/migrations/2026.10.13/files/docs/architecture.md +112 -0
  634. package/template/migrations/2026.10.13/files/docs/automations.md +110 -0
  635. package/template/migrations/2026.10.13/files/docs/backup.md +142 -0
  636. package/template/migrations/2026.10.13/files/docs/connectors.md +370 -0
  637. package/template/migrations/2026.10.13/files/docs/cron-commands.md +52 -0
  638. package/template/migrations/2026.10.13/files/docs/cron.md +178 -0
  639. package/template/migrations/2026.10.13/files/docs/mcp.md +108 -0
  640. package/template/migrations/2026.10.13/files/docs/memory.md +47 -0
  641. package/template/migrations/2026.10.13/files/docs/org.md +111 -0
  642. package/template/migrations/2026.10.13/files/docs/overview.md +44 -0
  643. package/template/migrations/2026.10.13/files/docs/self-modification.md +71 -0
  644. package/template/migrations/2026.10.13/files/docs/skills.md +140 -0
  645. package/template/migrations/2026.10.13/files/skills/cron-manager/SKILL.md +107 -0
  646. package/template/migrations/2026.10.13/files/skills/management/SKILL.md +227 -0
  647. package/template/migrations/2026.10.13/files/skills/migrate/SKILL.md +194 -0
  648. package/template/migrations/2026.10.13/files/skills/onboarding/SKILL.md +153 -0
  649. package/template/migrations/2026.10.13/files/skills/openryoko-config/SKILL.md +35 -0
  650. package/template/migrations/2026.10.13/files/skills/openryoko-config/references/engine-fallback.md +98 -0
  651. package/template/migrations/2026.10.13/files/skills/openryoko-config/references/jev.md +89 -0
  652. package/template/migrations/2026.10.13/files/skills/openryoko-config/references/maintenance.md +49 -0
  653. package/template/migrations/2026.10.13/files/skills/openryoko-config/references/platform.md +100 -0
  654. package/template/migrations/2026.10.13/files/skills/self-heal/SKILL.md +187 -0
  655. package/template/migrations/2026.10.9/MIGRATION.md +44 -0
  656. package/template/migrations/2026.10.9/files/docs/memory.md +47 -0
  657. package/template/migrations/2026.10.9/files/skills/migrate/SKILL.md +194 -0
  658. package/template/migrations/2026.5.7/MIGRATION.md +6 -0
  659. package/template/migrations/2026.8.28/MIGRATION.md +6 -0
  660. package/template/migrations/2026.9.11/MIGRATION.md +6 -0
  661. package/template/skills/cron-manager/SKILL.md +10 -0
  662. package/template/skills/management/SKILL.md +5 -4
  663. package/template/skills/migrate/SKILL.md +47 -14
  664. package/template/skills/onboarding/SKILL.md +3 -3
  665. package/template/skills/openryoko-config/SKILL.md +1 -1
  666. package/template/skills/openryoko-config/references/engine-fallback.md +16 -5
  667. package/template/skills/openryoko-config/references/platform.md +23 -4
  668. package/template/skills/self-heal/SKILL.md +117 -44
  669. package/dist/src/engines/mock.d.ts +0 -16
  670. package/dist/src/engines/mock.d.ts.map +0 -1
  671. package/dist/src/engines/mock.js +0 -47
  672. package/dist/src/engines/mock.js.map +0 -1
  673. package/dist/src/shared/test-support/posix-mode.d.ts +0 -17
  674. package/dist/src/shared/test-support/posix-mode.d.ts.map +0 -1
  675. package/dist/src/shared/test-support/posix-mode.js +0 -23
  676. package/dist/src/shared/test-support/posix-mode.js.map +0 -1
  677. package/dist/web/_next/static/Oux0UBcCkbX5eOp9-vC4k/_buildManifest.js +0 -11
  678. package/dist/web/_next/static/Oux0UBcCkbX5eOp9-vC4k/_clientMiddlewareManifest.js +0 -1
  679. package/dist/web/_next/static/Oux0UBcCkbX5eOp9-vC4k/_ssgManifest.js +0 -1
  680. package/dist/web/_next/static/chunks/02h65sho8c14h.js +0 -1
  681. package/dist/web/_next/static/chunks/0ic18n-m7ixyw.css +0 -1
  682. package/dist/web/_next/static/chunks/19s8vn2kgy7q1.js +0 -1
  683. package/dist/web/_next/static/chunks/1_jm0szyu2the.js +0 -1
  684. package/dist/web/_next/static/chunks/1omdg4m5w9lgx.js +0 -1
  685. package/dist/web/_next/static/chunks/25nob1new8t4s.js +0 -79
  686. package/dist/web/_next/static/chunks/2iqxlc9wtxzm2.js +0 -1
  687. package/dist/web/_next/static/chunks/3_6klkygi417n.js +0 -1
  688. package/dist/web/_next/static/chunks/3alc-b00c58_3.css +0 -1
  689. /package/dist/web/_next/static/{70PNIyOj8-2kywAG6J41- → ov0mWzyHx0B5oEnTHE_pa}/_buildManifest.js +0 -0
  690. /package/dist/web/_next/static/{70PNIyOj8-2kywAG6J41- → ov0mWzyHx0B5oEnTHE_pa}/_clientMiddlewareManifest.js +0 -0
  691. /package/dist/web/_next/static/{70PNIyOj8-2kywAG6J41- → ov0mWzyHx0B5oEnTHE_pa}/_ssgManifest.js +0 -0
@@ -0,0 +1,685 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { TMP_DIR } from "../shared/paths.js";
5
+ import { ArchiveTooLargeError, createHomeArchive, inspectHomeArchive, NothingToArchiveError, readHomeArchive, } from "./archive.js";
6
+ import { reserveArchiveName } from "./catalog.js";
7
+ import { errorMessage } from "./format.js";
8
+ import { inHome } from "./layout.js";
9
+ import { findManifestEntry } from "./manifest.js";
10
+ import { DEFAULT_ARCHIVE_LIMITS } from "./settings.js";
11
+ import { insufficientSpace, sameFilesystem } from "./space.js";
12
+ import { BACKUP_KINDS, HOME_ARCHIVE_TARGETS, kindOfPath, unsafePathReason } from "./targets.js";
13
+ import { absolutePathOf, collectHomeEntries } from "./walk.js";
14
+ const HASH_CHUNK_BYTES = 64 * 1024;
15
+ const COPY_CHUNK_BYTES = 64 * 1024;
16
+ const MAX_LISTED = 5;
17
+ /** True for the actions that extract a file from the archive. */
18
+ const writesContent = (action) => action === "create" || action === "replace";
19
+ /** An entry's kind. Its type goes along because a directory is never one of the documents in the home. */
20
+ function kindOf(entry) {
21
+ return kindOfPath(entry.path, entry.type);
22
+ }
23
+ function parentOf(entryPath) {
24
+ const cut = entryPath.lastIndexOf("/");
25
+ return cut === -1 ? null : entryPath.slice(0, cut);
26
+ }
27
+ function ancestorsOf(entryPath) {
28
+ const segments = entryPath.split("/");
29
+ return segments.slice(1).map((_, index) => segments.slice(0, index + 1).join("/"));
30
+ }
31
+ /** A directory entry no kind claims, that exists because a target lives under it. */
32
+ function holdsATarget(entryPath) {
33
+ return HOME_ARCHIVE_TARGETS.some((target) => target.paths.some((root) => root.startsWith(`${entryPath}/`)));
34
+ }
35
+ function lstatOrNull(file) {
36
+ try {
37
+ return fs.lstatSync(file);
38
+ }
39
+ catch (error) {
40
+ if (error.code === "ENOENT")
41
+ return null;
42
+ throw error;
43
+ }
44
+ }
45
+ function sha256OfFile(file) {
46
+ const hash = crypto.createHash("sha256");
47
+ const buffer = Buffer.allocUnsafe(HASH_CHUNK_BYTES);
48
+ const descriptor = fs.openSync(file, "r");
49
+ try {
50
+ for (let read = fs.readSync(descriptor, buffer); read > 0; read = fs.readSync(descriptor, buffer)) {
51
+ hash.update(buffer.subarray(0, read));
52
+ }
53
+ }
54
+ finally {
55
+ fs.closeSync(descriptor);
56
+ }
57
+ return hash.digest("hex");
58
+ }
59
+ /**
60
+ * True when a symlink at `entryPath` would lead out of `home`. Judged from the
61
+ * link text alone: nothing is resolved on disk, so a link cannot be made to
62
+ * look safe by what happens to exist where it points.
63
+ */
64
+ function leadsOutside(home, entryPath, target) {
65
+ const resolved = path.resolve(path.dirname(absolutePathOf(home, entryPath)), target);
66
+ const relative = path.relative(home, resolved);
67
+ return relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative);
68
+ }
69
+ /** Outside the home under every name the home goes by: as given, and with its own symlinks resolved. */
70
+ function leadsOutsideEvery(homes, entry) {
71
+ return homes.every((home) => leadsOutside(home, entry.path, entry.target ?? ""));
72
+ }
73
+ function listed(items) {
74
+ const shown = items.slice(0, MAX_LISTED).join("; ");
75
+ return items.length > MAX_LISTED ? `${shown}; and ${items.length - MAX_LISTED} more` : shown;
76
+ }
77
+ function selectEntries(manifest, only) {
78
+ const wanted = new Set(only ?? BACKUP_KINDS);
79
+ const selected = manifest.entries.filter((entry) => {
80
+ const kind = kindOf(entry);
81
+ return kind !== null && wanted.has(kind);
82
+ });
83
+ // The inspection has already refused these. They are checked again here
84
+ // because this is the list that becomes destinations: `skills/../../x` sits
85
+ // under a target by its prefix, and joined to the home it is outside it.
86
+ for (const entry of selected) {
87
+ const reason = unsafePathReason(entry.path);
88
+ if (reason)
89
+ throw new Error(`refusing to restore an unsafe path (${reason}): ${JSON.stringify(entry.path)}`);
90
+ }
91
+ if (selected.length > 0)
92
+ return selected;
93
+ throw new Error(only
94
+ ? `the archive holds nothing of the kind asked for (${only.join(", ")})`
95
+ : "the archive holds nothing this command restores");
96
+ }
97
+ /**
98
+ * Refuses a plan that would put a symlink leading out of the home in place.
99
+ * Only links the restore would actually write count: one that is already in
100
+ * the home with the same target is left as it is, and leaving it introduces
101
+ * nothing - which matters, because an operator's own links into `skills/` are
102
+ * in every archive of their home.
103
+ */
104
+ function refuseExternalSymlinks(homes, plan) {
105
+ const external = plan
106
+ .filter(({ entry, action }) => action !== "unchanged" && entry.type === "symlink" && leadsOutsideEvery(homes, entry))
107
+ .map(({ entry }) => `${entry.path} -> ${entry.target}`);
108
+ if (external.length === 0)
109
+ return;
110
+ throw new Error(`restoring this would create ${external.length} symlink(s) that point outside the home (${listed(external)}). `
111
+ + "That is not done by default; nothing was restored. Pass --allow-external-symlinks to restore them as they were.");
112
+ }
113
+ /** What it takes to make one path match the archive, given what is in the home now. */
114
+ function compareWithHome(home, entry, conflicts) {
115
+ const absolute = absolutePathOf(home, entry.path);
116
+ const stat = lstatOrNull(absolute);
117
+ if (!stat)
118
+ return "create";
119
+ if (entry.type === "directory")
120
+ return stat.isDirectory() ? "unchanged" : "replace";
121
+ if (stat.isDirectory()) {
122
+ // Putting a file here means deleting a tree, and part of a tree (.git,
123
+ // node_modules) is in no snapshot. That is the operator's call, not ours.
124
+ conflicts.push(`${entry.path} is a directory in the home but a ${entry.type} in the backup`);
125
+ return "replace";
126
+ }
127
+ if (entry.type === "symlink")
128
+ return stat.isSymbolicLink() && fs.readlinkSync(absolute) === entry.target ? "unchanged" : "replace";
129
+ if (!stat.isFile() || stat.size !== entry.size || !sameContent(absolute, entry))
130
+ return "replace";
131
+ // Same bytes is not the same file: a script that has lost its execute bit is still broken.
132
+ const sameMode = process.platform === "win32" || (stat.mode & 0o777) === (entry.mode & 0o777);
133
+ return sameMode ? "unchanged" : "mode";
134
+ }
135
+ /** Whether a file in the home has the content the archive recorded. One that cannot be read counts as different. */
136
+ function sameContent(file, entry) {
137
+ try {
138
+ return sha256OfFile(file) === entry.sha256;
139
+ }
140
+ catch {
141
+ return false;
142
+ }
143
+ }
144
+ /**
145
+ * Works out, path by path, what a restore would do. Every directory above a
146
+ * selected entry is part of the plan too, so by the time a file is written its
147
+ * whole parent chain is known to be real directories - which is what keeps a
148
+ * symlink sitting in the home from being written through.
149
+ */
150
+ function buildPlan(home, manifest, selected) {
151
+ const byPath = new Map(manifest.entries.map((entry) => [entry.path, entry]));
152
+ const wanted = new Map();
153
+ for (const entry of selected) {
154
+ for (const ancestor of ancestorsOf(entry.path)) {
155
+ if (wanted.has(ancestor))
156
+ continue;
157
+ const recorded = byPath.get(ancestor);
158
+ wanted.set(ancestor, recorded?.type === "directory" ? recorded : { path: ancestor, type: "directory", mode: 0o700 });
159
+ }
160
+ wanted.set(entry.path, entry);
161
+ }
162
+ const ordered = [...wanted.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
163
+ const conflicts = [];
164
+ // Directories this restore makes anew. Nothing under one exists yet, and
165
+ // what the home shows at that path may be the far side of a symlink.
166
+ const fresh = new Set();
167
+ const plan = ordered.map((entry) => {
168
+ const parent = parentOf(entry.path);
169
+ const action = parent !== null && fresh.has(parent) ? "create" : compareWithHome(home, entry, conflicts);
170
+ if (entry.type === "directory" && action !== "unchanged")
171
+ fresh.add(entry.path);
172
+ return { entry, action };
173
+ });
174
+ if (conflicts.length > 0) {
175
+ throw new Error(`${listed(conflicts)}. Move it out of the way and run the restore again; nothing was restored.`);
176
+ }
177
+ return plan;
178
+ }
179
+ /**
180
+ * What is in the home under the restored kinds and not in the archive. It is
181
+ * for the operator to read, nothing depends on it, so a home too large to list
182
+ * yields an empty answer rather than stopping a dry run; the restore itself
183
+ * will say so when it comes to saving the current state.
184
+ */
185
+ async function untouchedInHome(home, manifest, kinds, backupDirectory, limits) {
186
+ const inArchive = new Set(manifest.entries.map((entry) => entry.path));
187
+ let entries;
188
+ try {
189
+ ({ entries } = await collectHomeEntries(home, { excludePaths: [backupDirectory], limits }));
190
+ }
191
+ catch (error) {
192
+ if (error instanceof ArchiveTooLargeError)
193
+ return [];
194
+ throw error;
195
+ }
196
+ return entries
197
+ .filter((entry) => entry.type !== "directory" && !inArchive.has(entry.path))
198
+ .filter((entry) => {
199
+ const kind = kindOf(entry);
200
+ return kind !== null && kinds.includes(kind);
201
+ })
202
+ .map((entry) => entry.path);
203
+ }
204
+ /** The bytes the plan will extract into the staging directory, and so into the home. */
205
+ function bytesToStage(plan) {
206
+ return plan.reduce((total, { entry, action }) => (writesContent(action) && entry.type === "file" ? total + (entry.size ?? 0) : total), 0);
207
+ }
208
+ /** Refuses a restore whose files would not leave the home's disk room for the gateway to keep writing. */
209
+ function assertRoomToStage(options, home, staged) {
210
+ const reason = insufficientSpace(home, staged, options.diskSpace);
211
+ if (reason)
212
+ throw new Error(`${reason}; nothing was restored`);
213
+ }
214
+ /** The paths whose present contents or permissions the plan is about to do away with. */
215
+ function pathsOverwritten(plan) {
216
+ return plan.filter(({ action }) => action === "replace" || action === "mode").map(({ entry }) => entry.path);
217
+ }
218
+ /**
219
+ * Archives what the restore is about to overwrite, as it stands, so that it can
220
+ * be had back: the files the plan replaces and the ones whose permissions it
221
+ * changes. Not the rest of the home. A snapshot of everything made a restore
222
+ * of one file archive the whole home first, and a home that had grown past the
223
+ * limits since its last backup could then restore nothing at all.
224
+ *
225
+ * Null when the plan overwrites nothing: what a restore only creates leaves
226
+ * nothing to go back to. It needs room too, and when it shares a disk with the
227
+ * home, room beside the files about to be staged there.
228
+ */
229
+ async function snapshotCurrentState(options, home, staged, plan) {
230
+ const overwritten = pathsOverwritten(plan);
231
+ if (overwritten.length === 0)
232
+ return null;
233
+ const { id, file } = reserveArchiveName(options.backupDirectory, "pre-restore", options.now);
234
+ const alsoStaged = sameFilesystem(home, options.backupDirectory) ? staged : 0;
235
+ try {
236
+ const { manifest } = await createHomeArchive({
237
+ home, destination: file, id, createdAt: options.now, excludePaths: [options.backupDirectory],
238
+ limits: options.limits ?? DEFAULT_ARCHIVE_LIMITS,
239
+ only: overwritten,
240
+ beforeWrite: ({ bytes }) => {
241
+ const reason = insufficientSpace(options.backupDirectory, bytes + alsoStaged, options.diskSpace);
242
+ if (reason)
243
+ throw new Error(reason);
244
+ },
245
+ });
246
+ return { id, file, manifest };
247
+ }
248
+ catch (error) {
249
+ if (error instanceof NothingToArchiveError)
250
+ return null;
251
+ if (error instanceof ArchiveTooLargeError) {
252
+ // Not the home: only what this restore would replace. So the ways out are to replace less, or to allow more.
253
+ throw new Error(`what this restore would replace is too much to save first, so nothing was restored: ${errorMessage(error)}. `
254
+ + "Only the files about to be replaced are saved. Restore fewer kinds at a time with --only, "
255
+ + "or raise backup.maxArchiveMb / backup.maxEntries in config.yaml, and run the restore again.");
256
+ }
257
+ throw new Error(`the current state could not be saved first, so nothing was restored: ${errorMessage(error)}`);
258
+ }
259
+ }
260
+ /**
261
+ * Stops a restore that would overwrite something the snapshot does not hold.
262
+ *
263
+ * The snapshot leaves out what it cannot read and reports it as skipped; that
264
+ * is right for a backup and wrong as the last word before a replacement. If the
265
+ * file about to be replaced is not in the snapshot just taken, "the previous
266
+ * state was saved" would be false for exactly the file that matters. Checked
267
+ * against the snapshot's own manifest, path by path, so it does not depend on
268
+ * a list of skips that is only kept up to a point.
269
+ */
270
+ function refuseToReplaceUnsaved(plan, snapshot) {
271
+ const unsaved = pathsOverwritten(plan)
272
+ .filter((entryPath) => !snapshot || !findManifestEntry(snapshot.manifest.entries, entryPath))
273
+ .map((entryPath) => {
274
+ const why = snapshot?.manifest.skipped.find((skipped) => skipped.path === entryPath)?.reason;
275
+ return why ? `${entryPath} (${why})` : entryPath;
276
+ });
277
+ if (unsaved.length === 0)
278
+ return;
279
+ // The snapshot is of a restore that is not going to happen.
280
+ if (snapshot)
281
+ fs.rmSync(snapshot.file, { force: true });
282
+ throw new Error(`${listed(unsaved)} could not be saved before being replaced, so nothing was restored. `
283
+ + "Make it readable or move it out of the way, and run the restore again.");
284
+ }
285
+ const identityOf = (stat) => ({ device: stat.dev, inode: stat.ino });
286
+ const sameFile = (a, b) => a.device === b.device && a.inode === b.inode;
287
+ /**
288
+ * The directory a restore extracts into before anything is moved into place,
289
+ * and a record of every file it puts there.
290
+ *
291
+ * The record is what lets the directory be cleared away safely. It used to be
292
+ * removed with a recursive delete, and a recursive delete walks paths: swap a
293
+ * directory inside for a symlink while it walks, and it follows the link and
294
+ * deletes what is on the far side, outside the home. Nothing here is deleted
295
+ * by walking. The files this restore created are removed one by one, by the
296
+ * names it gave them; the directory is removed only if that leaves it empty;
297
+ * and anything else found in it was put there by someone else and stays.
298
+ */
299
+ class StagingDirectory {
300
+ directory;
301
+ identity;
302
+ /** The files created here, in the order they were made. */
303
+ made = [];
304
+ constructor(directory, identity) {
305
+ this.directory = directory;
306
+ this.identity = identity;
307
+ }
308
+ /** A new, empty, owner-only directory under `scratch`, with a name nobody could have guessed. */
309
+ static createIn(scratch) {
310
+ const directory = fs.mkdtempSync(path.join(scratch, "restore-"));
311
+ return new StagingDirectory(directory, identityOf(fs.lstatSync(directory, { bigint: true })));
312
+ }
313
+ /** The next file to extract into. Created exclusively, so that nothing already under its name is written through. */
314
+ create() {
315
+ const file = path.join(this.directory, String(this.made.length).padStart(6, "0"));
316
+ const descriptor = fs.openSync(file, "wx", 0o600);
317
+ this.made.push(file);
318
+ return { file, descriptor };
319
+ }
320
+ /**
321
+ * Why the directory must be left alone, or null while it is still the one
322
+ * that was made: a real directory, inside a real directory, and the same
323
+ * one - not another that has been put under its name.
324
+ */
325
+ displaced() {
326
+ const here = fs.lstatSync(this.directory, { bigint: true, throwIfNoEntry: false });
327
+ const above = lstatOrNull(path.dirname(this.directory));
328
+ if (above?.isDirectory() && here?.isDirectory() && sameFile(identityOf(here), this.identity))
329
+ return null;
330
+ return `${this.directory} is no longer the directory this restore made, so nothing was removed from it and it was left as it is`;
331
+ }
332
+ /**
333
+ * Clears the directory away: its own files by name, then the directory if
334
+ * that emptied it. Asked before every removal whether this is still the
335
+ * directory that was made, because the names are known and a name removed
336
+ * through a directory that has become a link is somebody else's file.
337
+ * Returns what had to be left, in words, or null when nothing was.
338
+ */
339
+ remove() {
340
+ if (!lstatOrNull(this.directory))
341
+ return null;
342
+ for (const file of this.made) {
343
+ const moved = this.displaced();
344
+ if (moved)
345
+ return moved;
346
+ try {
347
+ // unlink, which removes a name and nothing under it: a link under that name goes, what it points at does not.
348
+ fs.unlinkSync(file);
349
+ }
350
+ catch {
351
+ // Already moved into place, or no longer a file of ours. If anything is still there, the directory will not be empty.
352
+ }
353
+ }
354
+ const moved = this.displaced();
355
+ if (moved)
356
+ return moved;
357
+ try {
358
+ fs.rmdirSync(this.directory);
359
+ return null;
360
+ }
361
+ catch (error) {
362
+ const code = error.code ?? errorMessage(error);
363
+ return `${this.directory} holds something this restore did not put there (${code}), so it was left as it is`;
364
+ }
365
+ }
366
+ }
367
+ /**
368
+ * Extracts the files the plan needs into a private staging directory, hashing
369
+ * each as it lands. Staging holds flat numbered files and nothing else - no
370
+ * directories, no links - so no entry name from the archive is ever used as a
371
+ * path here, and the archive is held to the manifest that was just verified.
372
+ *
373
+ * Each file is finished through the descriptor it was written through: its
374
+ * permissions are put on that way, and which file it is - the device and
375
+ * inode behind the descriptor - is noted that way. A descriptor cannot be
376
+ * made to mean another file. A name can, and the name is all that is left of
377
+ * the file between now and the moment it is moved into place.
378
+ */
379
+ async function stageFiles(archive, staging, needed, limits) {
380
+ const staged = new Map();
381
+ // The one descriptor that may still be open if the archive fails mid-entry.
382
+ const open = {};
383
+ try {
384
+ await readHomeArchive(archive, (entry) => {
385
+ const expected = needed.get(entry.path);
386
+ if (!expected || entry.type !== "file" || staged.has(entry.path))
387
+ return undefined;
388
+ const hash = crypto.createHash("sha256");
389
+ const { file, descriptor } = staging.create();
390
+ open.descriptor = descriptor;
391
+ return {
392
+ write: (chunk) => {
393
+ hash.update(chunk);
394
+ fs.writeSync(descriptor, chunk);
395
+ },
396
+ end: () => {
397
+ try {
398
+ fs.fchmodSync(descriptor, expected.mode & 0o777);
399
+ }
400
+ catch { /* Windows has no POSIX modes */ }
401
+ const identity = identityOf(fs.fstatSync(descriptor, { bigint: true }));
402
+ fs.closeSync(descriptor);
403
+ open.descriptor = undefined;
404
+ if (entry.size !== expected.size || hash.digest("hex") !== expected.sha256) {
405
+ throw new Error(`${entry.path} no longer matches the manifest that was verified; the archive changed underneath the restore`);
406
+ }
407
+ staged.set(entry.path, { file, identity });
408
+ },
409
+ };
410
+ }, limits);
411
+ }
412
+ finally {
413
+ if (open.descriptor !== undefined)
414
+ fs.closeSync(open.descriptor);
415
+ }
416
+ const missing = [...needed.keys()].filter((entryPath) => !staged.has(entryPath));
417
+ if (missing.length > 0)
418
+ throw new Error(`the archive no longer holds ${listed(missing)}; it changed underneath the restore`);
419
+ return staged;
420
+ }
421
+ /**
422
+ * Asks again, at the moment of writing, that every directory from the home
423
+ * down to `entryPath` is a real directory. The plan established this once, but
424
+ * the plan was made before the current state was saved, which takes seconds in
425
+ * a large home, and a directory swapped for a symlink in that time would be
426
+ * written through. Every level is looked at, not only the last: through a link
427
+ * higher up, the immediate parent is a perfectly real directory - elsewhere.
428
+ *
429
+ * What remains is the instant between this check and the write that follows
430
+ * it. Closing that takes resolving each component against an open directory
431
+ * (openat, renameat), which Node does not offer.
432
+ */
433
+ function assertParentsAreDirectories(home, entryPath) {
434
+ if (!lstatOrNull(home)?.isDirectory())
435
+ throw new Error(`${home} is no longer a directory`);
436
+ for (const ancestor of ancestorsOf(entryPath)) {
437
+ if (!lstatOrNull(absolutePathOf(home, ancestor))?.isDirectory())
438
+ throw new Error(`${ancestor} is no longer a directory`);
439
+ }
440
+ }
441
+ function makeDirectory(destination, mode) {
442
+ const stat = lstatOrNull(destination);
443
+ if (stat?.isDirectory())
444
+ return;
445
+ // A file or a link in the way is unlinked - never followed, never recursed into.
446
+ // unlink, not rm: from Node 23 on, rmSync refuses a link that points at a directory.
447
+ if (stat)
448
+ fs.unlinkSync(destination);
449
+ fs.mkdirSync(destination, { mode: (mode & 0o777) | 0o700 });
450
+ }
451
+ const replacedMeanwhile = (staged) => new Error(`${staged.file} is not the file that was extracted there: it was replaced while the restore was running, so nothing was put in its place`);
452
+ /**
453
+ * Holds what sits under an extracted file's name to still being that file: a
454
+ * regular file, and the one that was written - not a link put there since,
455
+ * and not another file.
456
+ *
457
+ * What was extracted was checked against the manifest as it landed. What gets
458
+ * moved into place is whatever is under the name by then, and the working
459
+ * directory is in the home, where others can reach it. A link there used to be
460
+ * given the file's permissions - which went to whatever it pointed at - and
461
+ * was then itself moved into the home.
462
+ *
463
+ * Two things this does not close. The instant between this look and the move,
464
+ * which takes moving by descriptor (renameat on an open directory) and Node
465
+ * does not offer it. And, for telling one regular file from another, a file
466
+ * system that hands a freed inode number straight to the next file made:
467
+ * there, a different file under the name could pass for the one extracted. A
468
+ * link never does.
469
+ */
470
+ function assertStillExtracted(staged) {
471
+ const found = fs.lstatSync(staged.file, { bigint: true, throwIfNoEntry: false });
472
+ if (!found?.isFile() || !sameFile(identityOf(found), staged.identity))
473
+ throw replacedMeanwhile(staged);
474
+ }
475
+ /**
476
+ * Opens the extracted file for reading - refusing a link under its name, and
477
+ * not waiting on a pipe put there, which a plain open would do for ever - and
478
+ * makes sure of it through the descriptor. From here on what is read is that
479
+ * file, whatever happens to the name.
480
+ */
481
+ function openExtracted(staged) {
482
+ let descriptor;
483
+ try {
484
+ descriptor = fs.openSync(staged.file, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0) | (fs.constants.O_NONBLOCK ?? 0));
485
+ }
486
+ catch {
487
+ throw replacedMeanwhile(staged);
488
+ }
489
+ if (sameFile(identityOf(fs.fstatSync(descriptor, { bigint: true })), staged.identity))
490
+ return descriptor;
491
+ fs.closeSync(descriptor);
492
+ throw replacedMeanwhile(staged);
493
+ }
494
+ /**
495
+ * Copies an extracted file to `temporary`, for when it cannot be moved. Read
496
+ * from a descriptor that is known to be the file, written to a file created
497
+ * exclusively, and given its permissions through that file's own descriptor:
498
+ * copying by name would follow a link and bring what it points at into the home.
499
+ */
500
+ function copyExtracted(staged, temporary, mode) {
501
+ const source = openExtracted(staged);
502
+ try {
503
+ const target = fs.openSync(temporary, "wx", 0o600);
504
+ try {
505
+ const buffer = Buffer.allocUnsafe(COPY_CHUNK_BYTES);
506
+ for (let read = fs.readSync(source, buffer); read > 0; read = fs.readSync(source, buffer))
507
+ fs.writeSync(target, buffer, 0, read);
508
+ try {
509
+ fs.fchmodSync(target, mode & 0o777);
510
+ }
511
+ catch { /* Windows has no POSIX modes */ }
512
+ }
513
+ finally {
514
+ fs.closeSync(target);
515
+ }
516
+ }
517
+ finally {
518
+ fs.closeSync(source);
519
+ }
520
+ }
521
+ function placeFile(staged, destination, mode) {
522
+ assertStillExtracted(staged);
523
+ try {
524
+ // Replaces a file or a symlink at the destination in one step; a link is replaced, not followed.
525
+ fs.renameSync(staged.file, destination);
526
+ }
527
+ catch (error) {
528
+ if (error.code !== "EXDEV")
529
+ throw error;
530
+ const temporary = `${destination}.restore-${crypto.randomBytes(4).toString("hex")}`;
531
+ try {
532
+ copyExtracted(staged, temporary, mode);
533
+ fs.renameSync(temporary, destination);
534
+ }
535
+ catch (failure) {
536
+ fs.rmSync(temporary, { force: true });
537
+ throw failure;
538
+ }
539
+ }
540
+ }
541
+ /**
542
+ * Puts back the permissions of a file whose content already matches. Checked
543
+ * again that it is still a plain file: chmod follows a symlink, and the plan
544
+ * was made a moment ago.
545
+ */
546
+ function restoreMode(destination, mode) {
547
+ if (!lstatOrNull(destination)?.isFile())
548
+ throw new Error(`${destination} is no longer a regular file`);
549
+ fs.chmodSync(destination, mode & 0o777);
550
+ }
551
+ function placeSymlink(destination, target) {
552
+ const temporary = `${destination}.restore-${crypto.randomBytes(4).toString("hex")}`;
553
+ fs.symlinkSync(target, temporary);
554
+ fs.renameSync(temporary, destination);
555
+ }
556
+ function applyPlan(home, plan, staged) {
557
+ for (const { entry, action } of plan) {
558
+ if (action === "unchanged")
559
+ continue;
560
+ assertParentsAreDirectories(home, entry.path);
561
+ const destination = absolutePathOf(home, entry.path);
562
+ if (action === "mode")
563
+ restoreMode(destination, entry.mode);
564
+ else if (entry.type === "directory")
565
+ makeDirectory(destination, entry.mode);
566
+ else if (entry.type === "symlink")
567
+ placeSymlink(destination, entry.target ?? "");
568
+ else
569
+ placeFile(staged.get(entry.path), destination, entry.mode);
570
+ }
571
+ }
572
+ /**
573
+ * Where files are extracted before they are moved into place: the home's own
574
+ * tmp/. It has to be a real directory. A symlink there would have the contents
575
+ * of whatever is being restored - config.yaml and its tokens among them -
576
+ * written wherever it points, outside the home.
577
+ */
578
+ function stagingParent(home) {
579
+ const scratch = inHome(home, TMP_DIR);
580
+ const stat = lstatOrNull(scratch);
581
+ if (stat?.isSymbolicLink()) {
582
+ throw new Error(`${scratch} is a symlink (to ${fs.readlinkSync(scratch)}). Files are extracted there on their way back and must not leave the home: `
583
+ + "replace it with a real directory and run the restore again; nothing was restored");
584
+ }
585
+ if (stat && !stat.isDirectory()) {
586
+ throw new Error(`${scratch} is not a directory, and files are extracted there on their way back; nothing was restored`);
587
+ }
588
+ return scratch;
589
+ }
590
+ /** Extracts what the plan needs into `staging` and moves it into place. */
591
+ async function extractAndPlace(options, home, plan, preRestore, staging) {
592
+ const needed = new Map(plan
593
+ .filter((item) => writesContent(item.action) && item.entry.type === "file")
594
+ .map((item) => [item.entry.path, item.entry]));
595
+ const staged = await stageFiles(options.archive, staging, needed, options.limits ?? DEFAULT_ARCHIVE_LIMITS);
596
+ try {
597
+ applyPlan(home, plan, staged);
598
+ }
599
+ catch (error) {
600
+ // Said exactly: going back is itself a restore, and a restore deletes nothing.
601
+ const way = preRestore
602
+ ? ` The state before it is saved as ${preRestore.id}: restoring that puts back what was overwritten. `
603
+ + "Files this restore had already created are not removed by it."
604
+ : "";
605
+ throw new Error(`the restore stopped partway: ${errorMessage(error)}.${way}`);
606
+ }
607
+ }
608
+ /**
609
+ * Writes the plan into the home through a working directory under its tmp/,
610
+ * and clears that directory away whichever way it ends. Returns what there is
611
+ * to warn about; a failure carries the same in its message.
612
+ */
613
+ async function writeIntoHome(options, home, plan, preRestore) {
614
+ fs.mkdirSync(home, { recursive: true, mode: 0o700 });
615
+ // Asked again here, at the moment of use; the first time was before the snapshot.
616
+ const scratch = stagingParent(home);
617
+ fs.mkdirSync(scratch, { recursive: true, mode: 0o700 });
618
+ const staging = StagingDirectory.createIn(scratch);
619
+ let failure;
620
+ try {
621
+ await extractAndPlace(options, home, plan, preRestore, staging);
622
+ }
623
+ catch (error) {
624
+ failure = error ?? new Error("the restore failed");
625
+ }
626
+ const left = staging.remove();
627
+ if (failure)
628
+ throw left ? new Error(`${errorMessage(failure)} ${left}.`, { cause: failure }) : failure;
629
+ return left ? [left] : [];
630
+ }
631
+ /**
632
+ * Restores files from a home archive into `home`.
633
+ *
634
+ * Nothing is written until the archive has been read end to end and checked
635
+ * against its manifest, the plan has been worked out, and what the plan will
636
+ * overwrite has been archived as it stands, as a pre-restore snapshot. Files
637
+ * are then extracted into a staging directory, verified again, and moved into
638
+ * place.
639
+ *
640
+ * A restore brings back what the archive has. It deletes nothing: a file added
641
+ * to the home since the backup stays where it is and is reported as untouched.
642
+ * It writes only under the paths a home archive is made of, never through a
643
+ * symlink, and never recreates a symlink leading out of the home unless asked.
644
+ */
645
+ export async function restoreHomeArchive(options) {
646
+ const limits = options.limits ?? DEFAULT_ARCHIVE_LIMITS;
647
+ const inspection = await (options.inspect ?? inspectHomeArchive)(options.archive, { limits });
648
+ if (!inspection.ok || !inspection.manifest) {
649
+ throw new Error(`refusing to restore an archive that failed verification - ${listed(inspection.problems)}`);
650
+ }
651
+ const manifest = inspection.manifest;
652
+ const home = fs.existsSync(options.home) ? fs.realpathSync(options.home) : path.resolve(options.home);
653
+ const selected = selectEntries(manifest, options.only);
654
+ const plan = buildPlan(home, manifest, selected);
655
+ if (!options.allowExternalSymlinks)
656
+ refuseExternalSymlinks([home, path.resolve(options.home)], plan);
657
+ const kinds = BACKUP_KINDS.filter((kind) => selected.some((entry) => kindOf(entry) === kind));
658
+ const changes = plan
659
+ .filter((item) => item.action !== "unchanged")
660
+ .map((item) => ({ path: item.entry.path, type: item.entry.type, kind: kindOf(item.entry), action: item.action }));
661
+ const result = {
662
+ id: manifest.id,
663
+ archive: options.archive,
664
+ home,
665
+ dryRun: options.dryRun === true,
666
+ kinds,
667
+ changes,
668
+ unchanged: plan.length - changes.length,
669
+ untouched: await untouchedInHome(home, manifest, kinds, options.backupDirectory, limits),
670
+ ignored: manifest.entries.filter((entry) => kindOf(entry) === null && !holdsATarget(entry.path)).map((entry) => entry.path),
671
+ preRestore: null,
672
+ warnings: [],
673
+ };
674
+ if (result.dryRun || changes.length === 0)
675
+ return result;
676
+ stagingParent(home);
677
+ const staged = bytesToStage(plan);
678
+ assertRoomToStage(options, home, staged);
679
+ const snapshot = await snapshotCurrentState(options, home, staged, plan);
680
+ refuseToReplaceUnsaved(plan, snapshot);
681
+ const preRestore = snapshot ? { id: snapshot.id, file: snapshot.file } : null;
682
+ const warnings = await writeIntoHome(options, home, plan, preRestore);
683
+ return { ...result, preRestore, warnings };
684
+ }
685
+ //# sourceMappingURL=restore.js.map