@stage-labs/metro 0.1.0-beta.23 → 0.1.0-beta.231

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 (457) hide show
  1. package/dist/api.js +9 -101
  2. package/dist/background.js +123 -0
  3. package/dist/claude-settings.js +40 -0
  4. package/dist/claude.js +197 -0
  5. package/dist/cli.js +52 -110
  6. package/dist/control.js +19 -103
  7. package/dist/hold.js +210 -0
  8. package/dist/local.js +43 -0
  9. package/dist/mcp-config.js +25 -0
  10. package/dist/memory.js +222 -0
  11. package/dist/node-name.js +71 -0
  12. package/dist/onboarding.js +44 -0
  13. package/dist/provider-flags.js +9 -0
  14. package/dist/route.js +58 -0
  15. package/dist/runtime-install.js +96 -0
  16. package/dist/runtime.js +29 -52
  17. package/dist/serve.js +157 -0
  18. package/dist/service.js +304 -0
  19. package/dist/tail.js +119 -0
  20. package/dist/update.js +13 -1
  21. package/package.json +2 -23
  22. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  23. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +17 -0
  24. package/runtime/marketplace/plugin/.mcp.json +1 -0
  25. package/runtime/marketplace/plugin/MEMORY.md +78 -0
  26. package/runtime/marketplace/plugin/METRO.md +26 -0
  27. package/runtime/marketplace/plugin/STAGE.md +42 -0
  28. package/runtime/marketplace/plugin/bin/guard.mjs +71 -0
  29. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +30 -0
  30. package/runtime/marketplace/plugin/bin/policy.mjs +97 -0
  31. package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
  32. package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
  33. package/runtime/node_modules/@metro-labs/core/package.json +51 -0
  34. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  35. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/events.ts +11 -7
  36. package/runtime/node_modules/@metro-labs/{mcp/src/db → core/src}/ids.ts +5 -0
  37. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  38. package/runtime/node_modules/@metro-labs/{mcp/src/stations → core/src}/lines.ts +22 -26
  39. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/protocol.ts +7 -4
  40. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/secure-fs.ts +17 -0
  41. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +14 -0
  42. package/runtime/node_modules/@metro-labs/core/src/stations/account-files.ts +41 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +83 -0
  44. package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/attachments.ts +0 -1
  45. package/runtime/node_modules/@metro-labs/core/src/stations/mail-text.ts +72 -0
  46. package/runtime/node_modules/@metro-labs/core/src/stations/mail-trust.ts +29 -0
  47. package/runtime/node_modules/@metro-labs/core/src/stations/mail.ts +212 -0
  48. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +16 -0
  49. package/runtime/node_modules/@metro-labs/core/src/stations/oauth.ts +126 -0
  50. package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +58 -0
  51. package/runtime/node_modules/@metro-labs/core/src/stations/sender-profile.ts +64 -0
  52. package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/station-runtime.ts +6 -11
  53. package/runtime/node_modules/@metro-labs/core/src/stations/train-boot.ts +42 -0
  54. package/runtime/node_modules/@metro-labs/core/src/stations/train-events.ts +84 -0
  55. package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/types.ts +18 -1
  56. package/runtime/node_modules/@metro-labs/core/src/tickets.ts +37 -0
  57. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  58. package/runtime/node_modules/@metro-labs/daemon/package.json +49 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/agent-fs.ts +134 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/crontab.ts +44 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files-api.ts +12 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +89 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/home-fs.ts +113 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/job-env.ts +39 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/provision.ts +80 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedule-detail.ts +99 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules-api.ts +27 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +144 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/unit-files.ts +43 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +91 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +87 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-lookups.ts +55 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +76 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +357 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +30 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +59 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +131 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +132 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +305 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +142 -0
  82. package/runtime/node_modules/@metro-labs/{mcp/src/db/key-map.ts → daemon/src/agents/keys.ts} +1 -11
  83. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +133 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/agents/policy-route.ts +27 -0
  85. package/runtime/node_modules/@metro-labs/{mcp/src/db/agent-scope.ts → daemon/src/agents/scope.ts} +2 -2
  86. package/runtime/node_modules/@metro-labs/daemon/src/agents/sender-cards.ts +94 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/approvals/api.ts +55 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/approvals/pending.ts +150 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/approvals/preview.ts +66 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +223 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +36 -0
  93. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/boot}/crash-guard.ts +1 -1
  94. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +21 -0
  95. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/boot}/paths.ts +6 -34
  96. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +26 -0
  97. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/inbound.ts +75 -45
  98. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/media-note.ts +11 -7
  99. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/pending.ts +4 -1
  100. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/relay.ts +2 -2
  101. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +380 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +359 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +208 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/claude/memory-routine.ts +114 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/claude/memory.ts +32 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +60 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +149 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +75 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +295 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +109 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +346 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +120 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/claude/usage-probe.ts +127 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +113 -0
  115. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-api.ts → daemon/src/connectors/api.ts} +79 -113
  116. package/runtime/node_modules/@metro-labs/{mcp/src/db/connector-config.ts → daemon/src/connectors/config.ts} +67 -41
  117. package/runtime/node_modules/@metro-labs/daemon/src/connectors/gates.ts +72 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
  119. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/connectors}/oauth-client.ts +14 -17
  120. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/connectors}/oauth-discovery.ts +33 -20
  121. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-oauth-routes.ts → daemon/src/connectors/oauth-routes.ts} +30 -32
  122. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-oauth.ts → daemon/src/connectors/oauth.ts} +61 -22
  123. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +131 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-policy.ts +90 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +347 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +385 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +81 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-api.ts +116 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-crypto.ts +44 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-store.ts +48 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +37 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +106 -0
  134. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-grant.ts +4 -4
  135. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-owner.ts +3 -3
  136. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-reaper.ts +174 -0
  137. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-serve.ts +12 -13
  138. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/upload-api.ts +10 -27
  139. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/upload-store.ts +4 -4
  140. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +233 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +214 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +173 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +209 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-version.ts +44 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +155 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/gateway/effort.ts +93 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +237 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/gateway/frames.ts +118 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +307 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +160 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +46 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +150 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +139 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +235 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +208 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +203 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +285 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-lists.ts +57 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-signin.ts +161 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-store.ts +72 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +104 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/gateway/pkce.ts +36 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +118 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +18 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/gateway/subscription.ts +120 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/gateway/text.ts +35 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/gateway/tool-search.ts +126 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage-refresh.ts +102 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +348 -0
  172. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/accounts.ts +28 -17
  173. package/runtime/node_modules/@metro-labs/daemon/src/mcp/attachment-schema.ts +91 -0
  174. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/call-tools.ts +49 -20
  175. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/ctx.ts +4 -4
  176. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/event-store.ts +15 -16
  177. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/group-tools.ts +2 -2
  178. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/index.ts +34 -80
  179. package/runtime/node_modules/@metro-labs/daemon/src/mcp/instructions.ts +19 -0
  180. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/member-tools.ts +2 -2
  181. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-prompt.ts +87 -0
  182. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +88 -0
  183. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +82 -0
  184. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-snapshot.ts +79 -0
  185. package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-lookup.ts +60 -0
  186. package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-tool.ts +96 -0
  187. package/runtime/node_modules/@metro-labs/daemon/src/mcp/read-tool.ts +87 -0
  188. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +46 -0
  189. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-slot.ts +71 -0
  190. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/session.ts +47 -44
  191. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +93 -0
  192. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-def.ts +9 -0
  193. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/tool-dispatch.ts +53 -40
  194. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/tool-schemas.ts +64 -113
  195. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/upload-tool.ts +6 -11
  196. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +126 -0
  197. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/install.ts +37 -0
  198. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/privilege.ts +48 -0
  199. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +132 -0
  200. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +316 -0
  201. package/runtime/node_modules/@metro-labs/daemon/src/policy/policy.ts +118 -0
  202. package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
  203. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  204. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/routes}/http.ts +63 -51
  205. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +209 -0
  206. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +69 -0
  207. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  208. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  209. package/runtime/node_modules/@metro-labs/daemon/src/server/cloudwatch.ts +178 -0
  210. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +39 -0
  211. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +58 -0
  212. package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +26 -0
  213. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +90 -0
  214. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  215. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-browser.ts +102 -0
  216. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-driver.ts +42 -0
  217. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-inline.ts +29 -1
  218. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-interactive.ts +5 -31
  219. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-resolve.ts +38 -13
  220. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/attach-session.ts +32 -22
  221. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-xmtp.ts +8 -9
  222. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach.ts +69 -3
  223. package/runtime/node_modules/@metro-labs/{mcp/src/db → daemon/src/stations}/materialize.ts +60 -179
  224. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/registry.ts +20 -2
  225. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  226. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/supervisor-io.ts +3 -3
  227. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/supervisor.ts +6 -9
  228. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  229. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/train-call.ts +1 -1
  230. package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +51 -0
  231. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +84 -0
  232. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +124 -0
  233. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +15 -0
  234. package/runtime/node_modules/@metro-labs/daemon/src/vault/api.ts +45 -0
  235. package/runtime/node_modules/@metro-labs/daemon/src/vault/config.ts +51 -0
  236. package/runtime/node_modules/@metro-labs/daemon/src/vault/firewall.ts +9 -0
  237. package/runtime/node_modules/@metro-labs/daemon/src/vault/index.ts +96 -0
  238. package/runtime/node_modules/@metro-labs/daemon/src/vault/install.ts +56 -0
  239. package/runtime/node_modules/@metro-labs/daemon/src/vault/paths.ts +26 -0
  240. package/runtime/node_modules/@metro-labs/daemon/src/vault/proxy.ts +147 -0
  241. package/runtime/node_modules/@metro-labs/daemon/src/vault/store.ts +129 -0
  242. package/runtime/node_modules/@metro-labs/daemon/src/vault/trust.ts +44 -0
  243. package/runtime/node_modules/@metro-labs/discord-bot/package.json +2 -4
  244. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +3 -23
  245. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +19 -187
  246. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +2 -2
  247. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +26 -67
  248. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +1 -1
  249. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +2 -2
  250. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +8 -14
  251. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +2 -2
  252. package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +68 -0
  253. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +12 -13
  254. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +5 -1
  255. package/runtime/node_modules/@metro-labs/gmail/package.json +25 -0
  256. package/runtime/node_modules/@metro-labs/gmail/src/accounts.ts +81 -0
  257. package/runtime/node_modules/@metro-labs/gmail/src/actions.ts +14 -0
  258. package/runtime/node_modules/@metro-labs/gmail/src/api.ts +31 -0
  259. package/runtime/node_modules/@metro-labs/gmail/src/attachments.ts +35 -0
  260. package/runtime/node_modules/@metro-labs/gmail/src/auth.ts +105 -0
  261. package/runtime/node_modules/@metro-labs/gmail/src/config.ts +13 -0
  262. package/runtime/node_modules/@metro-labs/gmail/src/inbound.ts +96 -0
  263. package/runtime/node_modules/@metro-labs/gmail/src/index.ts +12 -0
  264. package/runtime/node_modules/@metro-labs/gmail/src/login.ts +37 -0
  265. package/runtime/node_modules/@metro-labs/gmail/src/message.ts +125 -0
  266. package/runtime/node_modules/@metro-labs/gmail/src/mime.ts +70 -0
  267. package/runtime/node_modules/@metro-labs/gmail/src/outbound.ts +124 -0
  268. package/runtime/node_modules/@metro-labs/gmail/src/query.ts +99 -0
  269. package/runtime/node_modules/@metro-labs/gmail/src/state.ts +24 -0
  270. package/runtime/node_modules/@metro-labs/gmail/src/station.ts +15 -0
  271. package/runtime/node_modules/@metro-labs/gmail/src/trust.ts +42 -0
  272. package/runtime/node_modules/@metro-labs/http/package.json +30 -0
  273. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +135 -0
  274. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/session-api.ts → http/src/mode-api.ts} +13 -9
  275. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  276. package/runtime/node_modules/@metro-labs/http/src/sigv4.ts +90 -0
  277. package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +149 -0
  278. package/runtime/node_modules/@metro-labs/outlook/package.json +25 -0
  279. package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +78 -0
  280. package/runtime/node_modules/@metro-labs/outlook/src/actions.ts +14 -0
  281. package/runtime/node_modules/@metro-labs/outlook/src/attachments.ts +81 -0
  282. package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +120 -0
  283. package/runtime/node_modules/@metro-labs/outlook/src/browser.ts +42 -0
  284. package/runtime/node_modules/@metro-labs/outlook/src/config.ts +23 -0
  285. package/runtime/node_modules/@metro-labs/outlook/src/format.ts +59 -0
  286. package/runtime/node_modules/@metro-labs/outlook/src/graph.ts +55 -0
  287. package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +96 -0
  288. package/runtime/node_modules/@metro-labs/outlook/src/index.ts +12 -0
  289. package/runtime/node_modules/@metro-labs/outlook/src/login.ts +129 -0
  290. package/runtime/node_modules/@metro-labs/outlook/src/me.ts +31 -0
  291. package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +103 -0
  292. package/runtime/node_modules/@metro-labs/outlook/src/query.ts +84 -0
  293. package/runtime/node_modules/@metro-labs/outlook/src/state.ts +28 -0
  294. package/runtime/node_modules/@metro-labs/outlook/src/station.ts +15 -0
  295. package/runtime/node_modules/@metro-labs/outlook/src/trust.ts +41 -0
  296. package/runtime/node_modules/@metro-labs/telegram/package.json +1 -3
  297. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +5 -24
  298. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +8 -6
  299. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +1 -1
  300. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +2 -2
  301. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +19 -34
  302. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +3 -3
  303. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +13 -34
  304. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +4 -43
  305. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +2 -3
  306. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +1 -1
  307. package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +70 -0
  308. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +2 -2
  309. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +4 -1
  310. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +0 -1
  311. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +1 -1
  312. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +7 -41
  313. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +22 -77
  314. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +2 -2
  315. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +46 -48
  316. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +15 -15
  317. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +6 -82
  318. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +1 -1
  319. package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +85 -0
  320. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +5 -1
  321. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +20 -12
  322. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  323. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +96 -0
  324. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +124 -0
  325. package/runtime/node_modules/@metro-labs/threema/src/api.ts +152 -0
  326. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +100 -0
  327. package/runtime/node_modules/@metro-labs/threema/src/files.ts +47 -0
  328. package/runtime/node_modules/@metro-labs/threema/src/format.ts +164 -0
  329. package/runtime/node_modules/@metro-labs/threema/src/groups.ts +96 -0
  330. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  331. package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +87 -0
  332. package/runtime/node_modules/@metro-labs/threema/src/index.ts +24 -0
  333. package/runtime/node_modules/@metro-labs/threema/src/messages.ts +264 -0
  334. package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +160 -0
  335. package/runtime/node_modules/@metro-labs/threema/src/station.ts +13 -0
  336. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  337. package/runtime/node_modules/@metro-labs/webhook/package.json +1 -1
  338. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +5 -8
  339. package/runtime/node_modules/@metro-labs/whatsapp/package.json +2 -5
  340. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +6 -28
  341. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +1 -1
  342. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +54 -11
  343. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +2 -2
  344. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +1 -1
  345. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +86 -4
  346. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +7 -58
  347. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +11 -28
  348. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +4 -37
  349. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +1 -1
  350. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +1 -1
  351. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +1 -1
  352. package/runtime/node_modules/@metro-labs/whatsapp/src/names.ts +75 -0
  353. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +44 -0
  354. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  355. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +16 -1
  356. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +5 -9
  357. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +0 -1
  358. package/runtime/node_modules/@metro-labs/xmtp/package.json +3 -2
  359. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +10 -20
  360. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +2 -2
  361. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +15 -204
  362. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +40 -53
  363. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +72 -220
  364. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +3 -3
  365. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +27 -0
  366. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +11 -7
  367. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +7 -24
  368. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +8 -18
  369. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +1 -1
  370. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +2 -2
  371. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +30 -16
  372. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +12 -20
  373. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +57 -33
  374. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +1 -1
  375. package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +61 -0
  376. package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +153 -0
  377. package/runtime/node_modules/@metro-labs/xmtp/src/resolve.ts +44 -0
  378. package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +112 -0
  379. package/runtime/node_modules/@metro-labs/xmtp/src/smart.ts +72 -0
  380. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +7 -2
  381. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +22 -58
  382. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +55 -48
  383. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +4 -6
  384. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +19 -5
  385. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +14 -4
  386. package/runtime/runtime.json +1 -1
  387. package/runtime/server.ts +1 -0
  388. package/runtime/stations.json +38 -0
  389. package/dist/prompt.js +0 -61
  390. package/dist/store.js +0 -70
  391. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +0 -195
  392. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +0 -292
  393. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +0 -197
  394. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +0 -5
  395. package/runtime/node_modules/@metro-labs/mcp/package.json +0 -66
  396. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +0 -24
  397. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +0 -311
  398. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +0 -341
  399. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +0 -129
  400. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +0 -320
  401. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +0 -111
  402. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair.ts +0 -20
  403. package/runtime/node_modules/@metro-labs/mcp/src/daemon/collection-api.ts +0 -202
  404. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +0 -31
  405. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +0 -100
  406. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +0 -57
  407. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +0 -333
  408. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-auth.ts +0 -164
  409. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-oauth.ts +0 -208
  410. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +0 -79
  411. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +0 -65
  412. package/runtime/node_modules/@metro-labs/mcp/src/daemon/pair-codes.ts +0 -58
  413. package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +0 -214
  414. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +0 -125
  415. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-pair.ts +0 -18
  416. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-source.ts +0 -105
  417. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +0 -41
  418. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +0 -218
  419. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +0 -169
  420. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +0 -91
  421. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +0 -215
  422. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +0 -71
  423. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +0 -37
  424. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-collections.ts +0 -218
  425. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +0 -329
  426. package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +0 -300
  427. package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +0 -31
  428. package/runtime/node_modules/@metro-labs/mcp/src/db/runtimes.ts +0 -105
  429. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +0 -131
  430. package/runtime/node_modules/@metro-labs/mcp/src/db/token-carry.ts +0 -45
  431. package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +0 -57
  432. package/runtime/node_modules/@metro-labs/mcp/src/db/whatsapp-login.ts +0 -18
  433. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +0 -29
  434. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +0 -21
  435. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +0 -66
  436. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +0 -73
  437. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +0 -172
  438. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +0 -43
  439. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +0 -240
  440. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +0 -2
  441. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +0 -80
  442. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +0 -88
  443. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +0 -22
  444. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +0 -1
  445. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +0 -3
  446. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +0 -22
  447. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +0 -5
  448. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +0 -87
  449. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +0 -55
  450. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +0 -349
  451. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/log.ts +0 -0
  452. /package/runtime/node_modules/@metro-labs/{mcp/src/mcp → core/src}/str.ts +0 -0
  453. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/train-error.ts +0 -0
  454. /package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/webhook-note.ts +0 -0
  455. /package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/raw-get-stream.ts +0 -0
  456. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → http/src}/api-error.ts +0 -0
  457. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → http/src}/cors.ts +0 -0
@@ -0,0 +1,109 @@
1
+ import { existsSync, readFileSync, realpathSync, statSync } from '../agent-user/agent-fs.js';
2
+ import { join } from 'node:path';
3
+ import { ApiError } from '@metro-labs/http/api-error';
4
+ import { claudeDir, listClaudeProjects } from './files.js';
5
+ import { isRecord } from '@metro-labs/core/is-record';
6
+ import { errMsg } from '@metro-labs/core/log';
7
+ import { writeHomeText } from '../agent-user/home-fs.js';
8
+
9
+ export const SETTINGS_MAX = 256 * 1024;
10
+ const USER_ID = 'user';
11
+ const LOCAL_SUFFIX = '.local';
12
+
13
+ type SettingsScope = 'user' | 'project' | 'local';
14
+
15
+ export interface ClaudeSettingsFile {
16
+ id: string;
17
+ scope: SettingsScope;
18
+ label: string;
19
+ path: string;
20
+ exists: boolean;
21
+ editable: boolean;
22
+ text: string;
23
+ modifiedAt: string | null;
24
+ }
25
+
26
+ const missing = (id: string, scope: SettingsScope, label: string, path: string): ClaudeSettingsFile => ({
27
+ id,
28
+ scope,
29
+ label,
30
+ path,
31
+ exists: false,
32
+ editable: true,
33
+ text: '',
34
+ modifiedAt: null,
35
+ });
36
+
37
+ function entryOf(id: string, scope: SettingsScope, label: string, path: string): ClaudeSettingsFile {
38
+ if (!existsSync(path)) return missing(id, scope, label, path);
39
+ const stat = statSync(path);
40
+ const editable = stat.size <= SETTINGS_MAX;
41
+ return {
42
+ id,
43
+ scope,
44
+ label,
45
+ path,
46
+ exists: true,
47
+ editable,
48
+ text: editable ? readFileSync(path, 'utf8') : '',
49
+ modifiedAt: stat.mtime.toISOString(),
50
+ };
51
+ }
52
+
53
+ const PROJECT_FILES: [string, SettingsScope, string][] = [
54
+ ['', 'project', 'settings.json'],
55
+ [LOCAL_SUFFIX, 'local', 'settings.local.json'],
56
+ ];
57
+
58
+ function projectEntries(dir: string): ClaudeSettingsFile[] {
59
+ const out: ClaudeSettingsFile[] = [];
60
+ for (const project of listClaudeProjects(dir)) {
61
+ const cwd = project.cwd;
62
+ if (cwd === null) continue;
63
+ for (const [suffix, scope, name] of PROJECT_FILES) {
64
+ const path = join(cwd, '.claude', name);
65
+ if (existsSync(path)) out.push(entryOf(`${project.id}${suffix}`, scope, cwd, path));
66
+ }
67
+ }
68
+ return out;
69
+ }
70
+
71
+ const sameFile = (a: string, b: string): boolean => {
72
+ try {
73
+ return realpathSync(a) === realpathSync(b);
74
+ } catch {
75
+ return a === b;
76
+ }
77
+ };
78
+
79
+ export function listClaudeSettings(dir = claudeDir()): ClaudeSettingsFile[] {
80
+ const user = entryOf(USER_ID, 'user', 'This machine', join(dir, 'settings.json'));
81
+ return [user, ...projectEntries(dir).filter((p) => !sameFile(p.path, user.path))];
82
+ }
83
+
84
+ function assertSettingsJson(text: string): void {
85
+ if (text.length > SETTINGS_MAX) throw new ApiError('that is more text than a settings file may hold', 413);
86
+ let parsed: unknown;
87
+ try {
88
+ parsed = JSON.parse(text);
89
+ } catch (err) {
90
+ throw new ApiError(`that is not valid JSON: ${errMsg(err)}`, 400);
91
+ }
92
+ if (!isRecord(parsed)) throw new ApiError('Claude Code settings must be a JSON object', 400);
93
+ }
94
+
95
+ export function writeClaudeSettings(
96
+ id: string,
97
+ text: string,
98
+ seenAt: string | null | undefined,
99
+ dir = claudeDir(),
100
+ ): ClaudeSettingsFile {
101
+ const target = listClaudeSettings(dir).find((file) => file.id === id);
102
+ if (target === undefined) throw new ApiError('no such settings file', 404);
103
+ if (!target.editable) throw new ApiError('that settings file is too large to edit here', 409);
104
+ assertSettingsJson(text);
105
+ if (seenAt !== undefined && seenAt !== target.modifiedAt)
106
+ throw new ApiError('that file changed on disk since you opened it; reload it before saving', 409);
107
+ writeHomeText(target.path, text, existsSync(target.path) ? statSync(target.path).mode & 0o777 : 0o644);
108
+ return entryOf(target.id, target.scope, target.label, target.path);
109
+ }
@@ -0,0 +1,346 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdirSync, rmSync } from 'node:fs';
3
+ import { existsSync, readFileSync, statSync } from '../agent-user/agent-fs.js';
4
+ import { join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { errMsg, log } from '@metro-labs/core/log';
7
+ import { isRecord } from '@metro-labs/core/is-record';
8
+ import { readJson, writeAtomic, writeJson } from '@metro-labs/core/secure-fs';
9
+ import { agentsDir } from '../agents/files.js';
10
+ import { moveHome, removeHome, writeHomeText } from '../agent-user/home-fs.js';
11
+ import { claudeDir } from './files.js';
12
+ import { memoryJobStatus, type MemoryJob } from './memory-routine.js';
13
+ import { stagedMarketplaceDir } from './plugin-install.js';
14
+ import { readModelConfig, routedConnection, type ModelConfig } from '../gateway/model-config.js';
15
+
16
+ export const PRIVACY_ENV: Record<string, string> = {
17
+ DISABLE_TELEMETRY: '1',
18
+ DISABLE_ERROR_REPORTING: '1',
19
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
20
+ CLAUDE_CODE_GB_DISK_CACHE_WHEN_TELEMETRY_OFF: '1',
21
+ };
22
+ export const RETENTION_DAYS = 7;
23
+ const STATE_FILE = 'claude-setup.json';
24
+ const RULES_FILE = 'METRO.md';
25
+ const RULES_SKILL = 'metro';
26
+ const RENAMED_SKILL = 'metro-orchestrator';
27
+ const RENAMED_NAME = /^name:[ \t]*(["']?)metro-orchestrator\1[ \t]*$/m;
28
+ const SYSTEM_PROMPT_FILE = 'system-prompt.md';
29
+ export const SYSTEM_PROMPT_MAX = 64 * 1024;
30
+
31
+ const WORKER_AGENT = `---
32
+ name: worker
33
+ description: Default execution agent for delegated work. Full tool access and maximum reasoning effort. Use for any substantive task the orchestrator main thread cannot perform itself - reading and writing code, running commands, research, analysis.
34
+ effort: xhigh
35
+ ---
36
+
37
+ You are a worker agent. The main thread that dispatched you is an orchestrator with no file, shell or network access; it can only delegate and relay messages. That means you are where the actual work happens, and the quality of your output is the quality of the result.
38
+
39
+ Work to completion. Do not hand back a partial answer with a suggestion that someone else finish it; there is no one else. If part of the task is genuinely blocked, complete every other part in full and say plainly what you left undone and why. Before you finish, read your own last paragraph: if it is a plan, a question or a promise about work you have not done, do that work now.
40
+
41
+ Keep the change to what the task asks for. A pre-existing bug, a performance problem or behaviour the task does not mention is a follow-up to report at the end, not something to fix now, unless the requested behaviour cannot work without it. Where the task is ambiguous, build the reading its wording and the surrounding code support best, and state that assumption. Commit tests only where the task asks for them or the repository already keeps tests for that kind of change, sized like the neighbouring test files; scratch checks you used to verify yourself need not be kept. This is about extras only: every behaviour the task does ask for, implement completely.
42
+
43
+ Never call AskUserQuestion or ExitPlanMode. They are blocked by policy, because nobody is watching the terminal around the clock and a blocking prompt stalls indefinitely. When you hit an ambiguity: do everything that does not depend on it, then choose the most reasonable interpretation, state the assumption explicitly in your report, and continue.
44
+
45
+ Your final message is a report to the orchestrator, not a message to a human. It gets relayed onward, so make it self-contained and concise. Lead with the outcome. Include concrete evidence, command output, file paths with line numbers, test results, for anything you claim to have verified. Report failures faithfully: if a test failed, say so and show the output; if you skipped a step, say that too.
46
+ `;
47
+
48
+ export interface SetupDeps {
49
+ dir?: string;
50
+ agents?: string;
51
+ plugin?: string;
52
+ env?: NodeJS.ProcessEnv;
53
+ }
54
+
55
+ export type Placed = 'written' | 'present' | 'updated' | 'missing';
56
+ export type SettingsOutcome = 'written' | 'unchanged' | 'unreadable';
57
+
58
+ export interface SetupReport {
59
+ privacy: boolean;
60
+ guard: 'plugin';
61
+ worker: Placed;
62
+ skill: Placed;
63
+ stage: Placed;
64
+ memory: Placed;
65
+ settings: SettingsOutcome;
66
+ }
67
+
68
+ export interface SetupStatus {
69
+ privacy: boolean;
70
+ permissionMode: PermissionMode;
71
+ systemPrompt: string;
72
+ liveEvents: boolean;
73
+ memoryRoutine: boolean;
74
+ memoryJob: MemoryJob;
75
+ guard: 'plugin';
76
+ worker: boolean;
77
+ skill: boolean;
78
+ stage: boolean;
79
+ memory: boolean;
80
+ privacyApplied: boolean;
81
+ retentionDays: number | null;
82
+ }
83
+
84
+ const statePath = (agents: string): string => join(agents, STATE_FILE);
85
+
86
+ const PERMISSION_MODES = ['auto', 'bypass'] as const;
87
+ export type PermissionMode = (typeof PERMISSION_MODES)[number];
88
+
89
+ function readState(agents: string): Record<string, unknown> {
90
+ const raw = readJson<unknown>(statePath(agents), null);
91
+ return isRecord(raw) ? raw : {};
92
+ }
93
+
94
+ function writeState(agents: string, patch: Record<string, unknown>): void {
95
+ mkdirSync(agents, { recursive: true });
96
+ writeJson(statePath(agents), { ...readState(agents), ...patch });
97
+ }
98
+
99
+ const privacyEnabled = (agents = agentsDir()): boolean => readState(agents).privacy !== false;
100
+
101
+ export function setPrivacy(enabled: boolean, agents = agentsDir()): void {
102
+ writeState(agents, { privacy: enabled });
103
+ }
104
+
105
+ export const isPermissionMode = (value: unknown): value is PermissionMode => PERMISSION_MODES.some((m) => m === value);
106
+
107
+ export function permissionMode(agents = agentsDir()): PermissionMode {
108
+ const mode = readState(agents).permissionMode;
109
+ return isPermissionMode(mode) ? mode : 'auto';
110
+ }
111
+
112
+ export function setPermissionMode(mode: PermissionMode, agents = agentsDir()): void {
113
+ writeState(agents, { permissionMode: mode });
114
+ }
115
+
116
+ export const liveEvents = (agents = agentsDir()): boolean => readState(agents).liveEvents !== false;
117
+
118
+ export function setLiveEvents(on: boolean, agents = agentsDir()): void {
119
+ writeState(agents, { liveEvents: on });
120
+ }
121
+
122
+ export const memoryRoutine = (agents = agentsDir()): boolean => readState(agents).memoryRoutine !== false;
123
+
124
+ export function setMemoryRoutine(on: boolean, agents = agentsDir()): void {
125
+ writeState(agents, { memoryRoutine: on });
126
+ }
127
+
128
+ const promptPath = (agents: string): string => join(agents, SYSTEM_PROMPT_FILE);
129
+
130
+ export function systemPrompt(agents = agentsDir()): string {
131
+ const path = promptPath(agents);
132
+ if (!existsSync(path)) return '';
133
+ return readFileSync(path, 'utf8').trim();
134
+ }
135
+
136
+ export function setSystemPrompt(text: string, agents = agentsDir()): void {
137
+ const trimmed = text.trim();
138
+ if (trimmed === '') {
139
+ rmSync(promptPath(agents), { force: true });
140
+ return;
141
+ }
142
+ writeAtomic(promptPath(agents), `${trimmed}\n`, 0o600);
143
+ }
144
+
145
+ function pluginDir(env: NodeJS.ProcessEnv = process.env): string {
146
+ const staged = stagedMarketplaceDir(env);
147
+ if (staged !== null) {
148
+ const dir = join(staged, 'plugin');
149
+ if (existsSync(join(dir, RULES_FILE))) return dir;
150
+ }
151
+ return fileURLToPath(new URL('../../../../plugin', import.meta.url));
152
+ }
153
+
154
+ const PRIOR_WORKER: ReadonlySet<string> = new Set(['08a0cd8710df285d6512245246bb8b658b7cfc7f3e89490514ee5376779eb2ef']);
155
+ const PRIOR_METRO: ReadonlySet<string> = new Set([
156
+ '0b3d122ed95beff09d579cf912cd4238e1db524c41fce4b314de57d6ff5908ad',
157
+ '36f4fb57f231a119d717b5e3d6ccb654f94960fcca708683f376679aead8df25',
158
+ '3840bc50253e51f431691376cf29a925b8245d87b77b8b20804a159e378a6e67',
159
+ 'e6b59eba825b4568dfa9ace5049fd9568a2e10d731719976ad2657084fd4e5f9',
160
+ '5ce3f76d610aae91adc922d0edc6c8be1f27aa2fec4a5e3a6d6231a04fb9310b',
161
+ 'b910597d24f31fa479b130a2545f94dd23684d802d99e36b637fc57c818a33be',
162
+ ]);
163
+ const PRIOR_STAGE: ReadonlySet<string> = new Set(['dd7d30474765de3865ddc87c1cf3220161ded09502027cdff18597e5df0aab43']);
164
+ const PRIOR_MEMORY: ReadonlySet<string> = new Set();
165
+
166
+ interface ShippedSkill {
167
+ name: string;
168
+ file: string;
169
+ prior: ReadonlySet<string>;
170
+ }
171
+
172
+ const METRO_SKILL: ShippedSkill = { name: RULES_SKILL, file: RULES_FILE, prior: PRIOR_METRO };
173
+ const STAGE_SKILL: ShippedSkill = { name: 'stage', file: 'STAGE.md', prior: PRIOR_STAGE };
174
+ const MEMORY_SKILL: ShippedSkill = { name: 'memory', file: 'MEMORY.md', prior: PRIOR_MEMORY };
175
+
176
+ const digest = (text: string): string => createHash('sha256').update(text).digest('hex');
177
+
178
+ export function placeFile(path: string, text: string, prior: ReadonlySet<string> = new Set()): Placed {
179
+ if (!existsSync(path)) {
180
+ writeHomeText(path, text, 0o644);
181
+ return 'written';
182
+ }
183
+ const current = readFileSync(path, 'utf8');
184
+ if (current === text || !prior.has(digest(current))) return 'present';
185
+ writeHomeText(path, text, 0o644);
186
+ return 'updated';
187
+ }
188
+
189
+ function readSettings(path: string): Record<string, unknown> | null {
190
+ if (!existsSync(path)) return {};
191
+ try {
192
+ const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));
193
+ return isRecord(parsed) ? parsed : null;
194
+ } catch {
195
+ return null;
196
+ }
197
+ }
198
+
199
+ function withPrivacy(settings: Record<string, unknown>, enabled: boolean): Record<string, unknown> {
200
+ const current = isRecord(settings.env) ? settings.env : {};
201
+ if (enabled)
202
+ return { ...settings, env: { ...current, ...PRIVACY_ENV }, cleanupPeriodDays: settings.cleanupPeriodDays ?? RETENTION_DAYS };
203
+ const env = Object.fromEntries(Object.entries(current).filter(([key]) => !(key in PRIVACY_ENV)));
204
+ const rest = Object.fromEntries(Object.entries(settings).filter(([key]) => key !== 'env'));
205
+ return Object.keys(env).length === 0 ? rest : { ...rest, env };
206
+ }
207
+
208
+ function mergeSettings(dir: string, change: (settings: Record<string, unknown>) => Record<string, unknown>): SettingsOutcome {
209
+ const path = join(dir, 'settings.json');
210
+ const current = readSettings(path);
211
+ if (current === null) return 'unreadable';
212
+ const next = change(current);
213
+ if (existsSync(path) && JSON.stringify(next) === JSON.stringify(current)) return 'unchanged';
214
+ writeHomeText(path, `${JSON.stringify(next, null, 2)}\n`, existsSync(path) ? statSync(path).mode & 0o777 : 0o644);
215
+ return 'written';
216
+ }
217
+
218
+ function applyPrivacy(dir: string, enabled: boolean): SettingsOutcome {
219
+ return mergeSettings(dir, (current) => withPrivacy(current, enabled));
220
+ }
221
+
222
+ const skillPath = (dir: string, name: string): string => join(dir, 'skills', name, 'SKILL.md');
223
+ const workerPath = (dir: string): string => join(dir, 'agents', 'worker.md');
224
+
225
+ function placeSkill(dir: string, plugin: string, skill: ShippedSkill): Placed {
226
+ const source = join(plugin, skill.file);
227
+ if (!existsSync(source)) return 'missing';
228
+ return placeFile(skillPath(dir, skill.name), readFileSync(source, 'utf8'), skill.prior);
229
+ }
230
+
231
+ function moveRenamedSkill(dir: string): void {
232
+ const from = join(dir, 'skills', RENAMED_SKILL);
233
+ const old = skillPath(dir, RENAMED_SKILL);
234
+ if (!existsSync(old)) return;
235
+ const shipped = PRIOR_METRO.has(digest(readFileSync(old, 'utf8')));
236
+ if (existsSync(join(dir, 'skills', RULES_SKILL))) {
237
+ if (shipped) removeHome(from, true);
238
+ return;
239
+ }
240
+ moveHome(from, join(dir, 'skills', RULES_SKILL));
241
+ log.info({ from, shipped }, 'claude-setup: moved the metro-orchestrator skill to metro');
242
+ if (shipped) return;
243
+ const path = skillPath(dir, RULES_SKILL);
244
+ const text = readFileSync(path, 'utf8');
245
+ const renamed = text.replace(RENAMED_NAME, `name: ${RULES_SKILL}`);
246
+ if (renamed !== text) writeHomeText(path, renamed, statSync(path).mode & 0o777);
247
+ }
248
+
249
+ function tryMoveRenamedSkill(dir: string): void {
250
+ try {
251
+ moveRenamedSkill(dir);
252
+ } catch (err) {
253
+ log.warn({ err: errMsg(err) }, 'claude-setup: could not move the metro-orchestrator skill to metro');
254
+ }
255
+ }
256
+
257
+ const written = (placed: Placed): boolean => placed === 'written' || placed === 'updated';
258
+
259
+ export function ensureClaudeSetup(deps: SetupDeps = {}): SetupReport {
260
+ const dir = deps.dir ?? claudeDir();
261
+ const agents = deps.agents ?? agentsDir();
262
+ const privacy = privacyEnabled(agents);
263
+ const plugin = deps.plugin ?? pluginDir(deps.env);
264
+ tryMoveRenamedSkill(dir);
265
+ const report: SetupReport = {
266
+ privacy,
267
+ guard: 'plugin',
268
+ worker: placeFile(workerPath(dir), WORKER_AGENT, PRIOR_WORKER),
269
+ skill: placeSkill(dir, plugin, METRO_SKILL),
270
+ stage: placeSkill(dir, plugin, STAGE_SKILL),
271
+ memory: placeSkill(dir, plugin, MEMORY_SKILL),
272
+ settings: applyPrivacy(dir, privacy),
273
+ };
274
+ syncAvailableModelsQuietly({ ...deps, dir, agents });
275
+ if ([report.worker, report.skill, report.stage, report.memory].some(written) || report.settings === 'written')
276
+ log.info(report, 'claude-setup: applied the Claude Code setup for a metro box');
277
+ if (report.settings === 'unreadable') log.warn({ path: join(dir, 'settings.json') }, 'claude-setup: settings.json is not valid JSON, so the privacy settings were not written');
278
+ return report;
279
+ }
280
+
281
+ export function claudeSetupStatus(deps: SetupDeps = {}): SetupStatus {
282
+ const dir = deps.dir ?? claudeDir();
283
+ const agents = deps.agents ?? agentsDir();
284
+ const settings = readSettings(join(dir, 'settings.json'));
285
+ const env = settings !== null && isRecord(settings.env) ? settings.env : {};
286
+ const days = settings?.cleanupPeriodDays;
287
+ return {
288
+ privacy: privacyEnabled(agents),
289
+ permissionMode: permissionMode(agents),
290
+ systemPrompt: systemPrompt(agents),
291
+ liveEvents: liveEvents(agents),
292
+ memoryRoutine: memoryRoutine(agents),
293
+ memoryJob: memoryJobStatus(),
294
+ guard: 'plugin',
295
+ worker: existsSync(workerPath(dir)),
296
+ skill: existsSync(skillPath(dir, METRO_SKILL.name)),
297
+ stage: existsSync(skillPath(dir, STAGE_SKILL.name)),
298
+ memory: existsSync(skillPath(dir, MEMORY_SKILL.name)),
299
+ privacyApplied: Object.keys(PRIVACY_ENV).every((key) => env[key] === '1'),
300
+ retentionDays: typeof days === 'number' ? days : null,
301
+ };
302
+ }
303
+
304
+ export function tryClaudeSetup(deps: SetupDeps = {}): void {
305
+ try {
306
+ ensureClaudeSetup(deps);
307
+ } catch (err) {
308
+ log.warn({ err: errMsg(err) }, 'claude-setup: could not apply the Claude Code setup');
309
+ }
310
+ }
311
+
312
+ const MODELS_KEY = 'availableModels';
313
+ const ENFORCE_KEY = 'enforceAvailableModels';
314
+
315
+ export function routeOf(cfg: ModelConfig): string | null {
316
+ const conn = routedConnection(cfg);
317
+ if (conn === null || conn.provider === 'anthropic' || conn.model === '') return null;
318
+ return `${conn.provider}:${conn.model}`;
319
+ }
320
+
321
+ function withAvailableModels(settings: Record<string, unknown>, route: string | null, metroWrote: boolean): Record<string, unknown> {
322
+ if (route !== null) return { ...settings, [MODELS_KEY]: [route], [ENFORCE_KEY]: true };
323
+ if (!metroWrote) return settings;
324
+ return Object.fromEntries(Object.entries(settings).filter(([key]) => key !== MODELS_KEY && key !== ENFORCE_KEY));
325
+ }
326
+
327
+ export function syncAvailableModels(cfg: ModelConfig, deps: SetupDeps = {}): SettingsOutcome {
328
+ const dir = deps.dir ?? claudeDir();
329
+ const agents = deps.agents ?? agentsDir();
330
+ const route = routeOf(cfg);
331
+ const state = readJson<unknown>(statePath(agents), null);
332
+ const metroWrote = isRecord(state) && state.modelsByMetro === true;
333
+ return mergeSettings(dir, (current) => {
334
+ mkdirSync(agents, { recursive: true });
335
+ writeJson(statePath(agents), { ...(isRecord(state) ? state : {}), modelsByMetro: route !== null });
336
+ return withAvailableModels(current, route, metroWrote);
337
+ });
338
+ }
339
+
340
+ export function syncAvailableModelsQuietly(deps: SetupDeps = {}, cfg?: ModelConfig): void {
341
+ try {
342
+ syncAvailableModels(cfg ?? readModelConfig(deps.agents ?? agentsDir()), deps);
343
+ } catch (err) {
344
+ log.warn({ err: errMsg(err) }, 'claude-setup: could not sync the model allowlist');
345
+ }
346
+ }
@@ -0,0 +1,120 @@
1
+ import { existsSync, readdirNames, readFileSync, statSync } from '../agent-user/agent-fs.js';
2
+ import { basename, dirname, join } from 'node:path';
3
+ import { ApiError } from '@metro-labs/http/api-error';
4
+ import { claudeDir } from './files.js';
5
+ import { removeHome, writeHomeText } from '../agent-user/home-fs.js';
6
+
7
+ const SKILL_MAX = 256 * 1024;
8
+ const SKILL_NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
9
+ const FILE = 'SKILL.md';
10
+ const DEFAULT_MODE = 0o644;
11
+ const SUMMARY_MAX = 300;
12
+
13
+ export interface ClaudeSkill {
14
+ id: string;
15
+ name: string;
16
+ title: string;
17
+ description: string;
18
+ path: string;
19
+ editable: boolean;
20
+ updatedAt: string | null;
21
+ }
22
+
23
+ const frontmatterField = (text: string, field: string): string => {
24
+ const head = text.startsWith('---') ? text.slice(3, text.indexOf('\n---', 3)) : '';
25
+ for (const line of head.split('\n')) {
26
+ const at = line.indexOf(':');
27
+ if (at > 0 && line.slice(0, at).trim() === field) return line.slice(at + 1).trim().replace(/^["']|["']$/g, '').slice(0, SUMMARY_MAX);
28
+ }
29
+ return '';
30
+ };
31
+
32
+ function entryOf(name: string, path: string): ClaudeSkill {
33
+ const stat = statSync(path);
34
+ const editable = stat.size <= SKILL_MAX;
35
+ const text = editable ? readFileSync(path, 'utf8') : '';
36
+ return {
37
+ id: `user:${name}`,
38
+ name,
39
+ title: frontmatterField(text, 'name') || name,
40
+ description: frontmatterField(text, 'description'),
41
+ path,
42
+ editable,
43
+ updatedAt: stat.mtime.toISOString(),
44
+ };
45
+ }
46
+
47
+ const userSkillsRoot = (dir: string): string => join(dir, 'skills');
48
+
49
+ function isFolder(path: string): boolean {
50
+ try {
51
+ return statSync(path).isDirectory();
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+
57
+ export function listClaudeSkills(dir = claudeDir()): ClaudeSkill[] {
58
+ const root = userSkillsRoot(dir);
59
+ if (!existsSync(root)) return [];
60
+ const out: ClaudeSkill[] = [];
61
+ for (const name of readdirNames(root)) {
62
+ if (!SKILL_NAME_RE.test(name) || !isFolder(join(root, name))) continue;
63
+ const path = join(root, name, FILE);
64
+ if (existsSync(path)) out.push(entryOf(name, path));
65
+ }
66
+ return out.sort((a, b) => (b.updatedAt ?? '').localeCompare(a.updatedAt ?? '') || a.name.localeCompare(b.name));
67
+ }
68
+
69
+ function found(id: string, dir: string): ClaudeSkill {
70
+ const skill = listClaudeSkills(dir).find((entry) => entry.id === id);
71
+ if (skill === undefined) throw new ApiError('no such skill', 404);
72
+ return skill;
73
+ }
74
+
75
+ export function readClaudeSkill(id: string, dir = claudeDir()): ClaudeSkill & { text: string } {
76
+ const skill = found(id, dir);
77
+ return { ...skill, text: skill.editable ? readFileSync(skill.path, 'utf8') : '' };
78
+ }
79
+
80
+ function assertText(text: string): void {
81
+ if (text.length > SKILL_MAX) throw new ApiError('that is more text than a skill may hold', 413);
82
+ if (text.trim() === '') throw new ApiError('a skill needs some text', 400);
83
+ }
84
+
85
+ export function writeClaudeSkill(
86
+ id: string,
87
+ text: string,
88
+ seenAt: string | null | undefined,
89
+ dir = claudeDir(),
90
+ ): ClaudeSkill {
91
+ const skill = found(id, dir);
92
+ if (!skill.editable) throw new ApiError('that skill is too large to edit here', 409);
93
+ assertText(text);
94
+ if (seenAt !== undefined && seenAt !== skill.updatedAt)
95
+ throw new ApiError('that skill changed on disk since you opened it; reload it before saving', 409);
96
+ writeHomeText(skill.path, text, DEFAULT_MODE);
97
+ return entryOf(skill.name, skill.path);
98
+ }
99
+
100
+ const skillTemplate = (name: string): string =>
101
+ ['---', `name: ${name}`, 'description: what this skill does, and when Claude should reach for it', '---', '', `# ${name}`, '', 'Write the instructions here.', ''].join('\n');
102
+
103
+ export function createClaudeSkill(name: string, text: string | undefined, dir = claudeDir()): ClaudeSkill {
104
+ if (typeof name !== 'string' || !SKILL_NAME_RE.test(name))
105
+ throw new ApiError('a skill name is lowercase letters, digits and dashes, up to 64 characters', 400);
106
+ const path = join(userSkillsRoot(dir), name, FILE);
107
+ if (existsSync(path)) throw new ApiError('a skill by that name already lives there', 409);
108
+ const body = text === undefined || text.trim() === '' ? skillTemplate(name) : text;
109
+ assertText(body);
110
+ writeHomeText(path, body, DEFAULT_MODE);
111
+ return entryOf(name, path);
112
+ }
113
+
114
+ export function deleteClaudeSkill(id: string, dir = claudeDir()): string {
115
+ const skill = found(id, dir);
116
+ const folder = dirname(skill.path);
117
+ if (basename(folder) !== skill.name) throw new ApiError('that skill does not live in its own folder', 409);
118
+ removeHome(folder, true);
119
+ return id;
120
+ }