@aigne/afs-cli 1.11.0-beta.1 → 1.11.0-beta.11

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 (348) hide show
  1. package/README.md +262 -15
  2. package/dist/_virtual/rolldown_runtime.cjs +29 -0
  3. package/dist/_virtual/rolldown_runtime.mjs +7 -0
  4. package/dist/cli.cjs +40 -0
  5. package/dist/cli.d.cts +2 -0
  6. package/dist/cli.d.mts +2 -1
  7. package/dist/cli.mjs +36 -24
  8. package/dist/cli.mjs.map +1 -0
  9. package/dist/config/afs-loader.cjs +578 -0
  10. package/dist/config/afs-loader.d.cts +19 -0
  11. package/dist/config/afs-loader.d.cts.map +1 -0
  12. package/dist/config/afs-loader.d.mts +19 -0
  13. package/dist/config/afs-loader.d.mts.map +1 -0
  14. package/dist/config/afs-loader.mjs +576 -0
  15. package/dist/config/afs-loader.mjs.map +1 -0
  16. package/dist/config/env.cjs +46 -0
  17. package/dist/config/env.mjs +46 -0
  18. package/dist/config/env.mjs.map +1 -0
  19. package/dist/config/loader.cjs +219 -0
  20. package/dist/config/loader.mjs +217 -0
  21. package/dist/config/loader.mjs.map +1 -0
  22. package/dist/config/mount-commands.cjs +226 -0
  23. package/dist/config/mount-commands.d.cts +14 -0
  24. package/dist/config/mount-commands.d.cts.map +1 -0
  25. package/dist/config/mount-commands.d.mts +14 -0
  26. package/dist/config/mount-commands.d.mts.map +1 -0
  27. package/dist/config/mount-commands.mjs +220 -0
  28. package/dist/config/mount-commands.mjs.map +1 -0
  29. package/dist/config/schema.cjs +99 -0
  30. package/dist/config/schema.mjs +98 -0
  31. package/dist/config/schema.mjs.map +1 -0
  32. package/dist/core/commands/delete.cjs +41 -0
  33. package/dist/core/commands/delete.d.cts +18 -0
  34. package/dist/core/commands/delete.d.cts.map +1 -0
  35. package/dist/core/commands/delete.d.mts +18 -0
  36. package/dist/core/commands/delete.d.mts.map +1 -0
  37. package/dist/core/commands/delete.mjs +42 -0
  38. package/dist/core/commands/delete.mjs.map +1 -0
  39. package/dist/core/commands/exec.cjs +98 -0
  40. package/dist/core/commands/exec.d.cts +26 -0
  41. package/dist/core/commands/exec.d.cts.map +1 -0
  42. package/dist/core/commands/exec.d.mts +26 -0
  43. package/dist/core/commands/exec.d.mts.map +1 -0
  44. package/dist/core/commands/exec.mjs +99 -0
  45. package/dist/core/commands/exec.mjs.map +1 -0
  46. package/dist/core/commands/explain.cjs +278 -0
  47. package/dist/core/commands/explain.d.cts +25 -0
  48. package/dist/core/commands/explain.d.cts.map +1 -0
  49. package/dist/core/commands/explain.d.mts +25 -0
  50. package/dist/core/commands/explain.d.mts.map +1 -0
  51. package/dist/core/commands/explain.mjs +279 -0
  52. package/dist/core/commands/explain.mjs.map +1 -0
  53. package/dist/core/commands/explore.cjs +30 -0
  54. package/dist/core/commands/explore.d.mts +2 -0
  55. package/dist/core/commands/explore.mjs +31 -0
  56. package/dist/core/commands/explore.mjs.map +1 -0
  57. package/dist/core/commands/index.cjs +36 -0
  58. package/dist/core/commands/index.d.cts +21 -0
  59. package/dist/core/commands/index.d.cts.map +1 -0
  60. package/dist/core/commands/index.d.mts +24 -0
  61. package/dist/core/commands/index.d.mts.map +1 -0
  62. package/dist/core/commands/index.mjs +37 -0
  63. package/dist/core/commands/index.mjs.map +1 -0
  64. package/dist/core/commands/ls.cjs +57 -0
  65. package/dist/core/commands/ls.d.cts +21 -0
  66. package/dist/core/commands/ls.d.cts.map +1 -0
  67. package/dist/core/commands/ls.d.mts +21 -0
  68. package/dist/core/commands/ls.d.mts.map +1 -0
  69. package/dist/core/commands/ls.mjs +58 -0
  70. package/dist/core/commands/ls.mjs.map +1 -0
  71. package/dist/core/commands/mount.cjs +222 -0
  72. package/dist/core/commands/mount.d.cts +35 -0
  73. package/dist/core/commands/mount.d.cts.map +1 -0
  74. package/dist/core/commands/mount.d.mts +35 -0
  75. package/dist/core/commands/mount.d.mts.map +1 -0
  76. package/dist/core/commands/mount.mjs +223 -0
  77. package/dist/core/commands/mount.mjs.map +1 -0
  78. package/dist/core/commands/read.cjs +48 -0
  79. package/dist/core/commands/read.d.cts +17 -0
  80. package/dist/core/commands/read.d.cts.map +1 -0
  81. package/dist/core/commands/read.d.mts +17 -0
  82. package/dist/core/commands/read.d.mts.map +1 -0
  83. package/dist/core/commands/read.mjs +49 -0
  84. package/dist/core/commands/read.mjs.map +1 -0
  85. package/dist/core/commands/search.cjs +40 -0
  86. package/dist/core/commands/search.d.mts +2 -0
  87. package/dist/core/commands/search.mjs +41 -0
  88. package/dist/core/commands/search.mjs.map +1 -0
  89. package/dist/core/commands/serve.cjs +267 -0
  90. package/dist/core/commands/serve.d.mts +2 -0
  91. package/dist/core/commands/serve.mjs +267 -0
  92. package/dist/core/commands/serve.mjs.map +1 -0
  93. package/dist/core/commands/stat.cjs +53 -0
  94. package/dist/core/commands/stat.d.cts +17 -0
  95. package/dist/core/commands/stat.d.cts.map +1 -0
  96. package/dist/core/commands/stat.d.mts +17 -0
  97. package/dist/core/commands/stat.d.mts.map +1 -0
  98. package/dist/core/commands/stat.mjs +54 -0
  99. package/dist/core/commands/stat.mjs.map +1 -0
  100. package/dist/core/commands/types.cjs +18 -0
  101. package/dist/core/commands/types.d.cts +54 -0
  102. package/dist/core/commands/types.d.cts.map +1 -0
  103. package/dist/core/commands/types.d.mts +54 -0
  104. package/dist/core/commands/types.d.mts.map +1 -0
  105. package/dist/core/commands/types.mjs +19 -0
  106. package/dist/core/commands/types.mjs.map +1 -0
  107. package/dist/core/commands/write.cjs +70 -0
  108. package/dist/core/commands/write.d.cts +20 -0
  109. package/dist/core/commands/write.d.cts.map +1 -0
  110. package/dist/core/commands/write.d.mts +20 -0
  111. package/dist/core/commands/write.d.mts.map +1 -0
  112. package/dist/core/commands/write.mjs +71 -0
  113. package/dist/core/commands/write.mjs.map +1 -0
  114. package/dist/core/executor/index.cjs +196 -0
  115. package/dist/core/executor/index.d.cts +77 -0
  116. package/dist/core/executor/index.d.cts.map +1 -0
  117. package/dist/core/executor/index.d.mts +77 -0
  118. package/dist/core/executor/index.d.mts.map +1 -0
  119. package/dist/core/executor/index.mjs +195 -0
  120. package/dist/core/executor/index.mjs.map +1 -0
  121. package/dist/core/formatters/delete.cjs +37 -0
  122. package/dist/core/formatters/delete.d.cts +18 -0
  123. package/dist/core/formatters/delete.d.cts.map +1 -0
  124. package/dist/core/formatters/delete.d.mts +18 -0
  125. package/dist/core/formatters/delete.d.mts.map +1 -0
  126. package/dist/core/formatters/delete.mjs +37 -0
  127. package/dist/core/formatters/delete.mjs.map +1 -0
  128. package/dist/core/formatters/exec.cjs +60 -0
  129. package/dist/core/formatters/exec.d.cts +18 -0
  130. package/dist/core/formatters/exec.d.cts.map +1 -0
  131. package/dist/core/formatters/exec.d.mts +18 -0
  132. package/dist/core/formatters/exec.d.mts.map +1 -0
  133. package/dist/core/formatters/exec.mjs +60 -0
  134. package/dist/core/formatters/exec.mjs.map +1 -0
  135. package/dist/core/formatters/explain.cjs +99 -0
  136. package/dist/core/formatters/explain.d.cts +11 -0
  137. package/dist/core/formatters/explain.d.cts.map +1 -0
  138. package/dist/core/formatters/explain.d.mts +11 -0
  139. package/dist/core/formatters/explain.d.mts.map +1 -0
  140. package/dist/core/formatters/explain.mjs +98 -0
  141. package/dist/core/formatters/explain.mjs.map +1 -0
  142. package/dist/core/formatters/index.d.mts +9 -0
  143. package/dist/core/formatters/ls.cjs +179 -0
  144. package/dist/core/formatters/ls.d.cts +20 -0
  145. package/dist/core/formatters/ls.d.cts.map +1 -0
  146. package/dist/core/formatters/ls.d.mts +20 -0
  147. package/dist/core/formatters/ls.d.mts.map +1 -0
  148. package/dist/core/formatters/ls.mjs +179 -0
  149. package/dist/core/formatters/ls.mjs.map +1 -0
  150. package/dist/core/formatters/mount.cjs +55 -0
  151. package/dist/core/formatters/mount.d.cts +15 -0
  152. package/dist/core/formatters/mount.d.cts.map +1 -0
  153. package/dist/core/formatters/mount.d.mts +15 -0
  154. package/dist/core/formatters/mount.d.mts.map +1 -0
  155. package/dist/core/formatters/mount.mjs +55 -0
  156. package/dist/core/formatters/mount.mjs.map +1 -0
  157. package/dist/core/formatters/read.cjs +100 -0
  158. package/dist/core/formatters/read.d.cts +22 -0
  159. package/dist/core/formatters/read.d.cts.map +1 -0
  160. package/dist/core/formatters/read.d.mts +22 -0
  161. package/dist/core/formatters/read.d.mts.map +1 -0
  162. package/dist/core/formatters/read.mjs +100 -0
  163. package/dist/core/formatters/read.mjs.map +1 -0
  164. package/dist/core/formatters/search.cjs +44 -0
  165. package/dist/core/formatters/search.d.mts +1 -0
  166. package/dist/core/formatters/search.mjs +44 -0
  167. package/dist/core/formatters/search.mjs.map +1 -0
  168. package/dist/core/formatters/stat.cjs +155 -0
  169. package/dist/core/formatters/stat.d.cts +15 -0
  170. package/dist/core/formatters/stat.d.cts.map +1 -0
  171. package/dist/core/formatters/stat.d.mts +15 -0
  172. package/dist/core/formatters/stat.d.mts.map +1 -0
  173. package/dist/core/formatters/stat.mjs +155 -0
  174. package/dist/core/formatters/stat.mjs.map +1 -0
  175. package/dist/core/formatters/write.cjs +51 -0
  176. package/dist/core/formatters/write.d.cts +22 -0
  177. package/dist/core/formatters/write.d.cts.map +1 -0
  178. package/dist/core/formatters/write.d.mts +22 -0
  179. package/dist/core/formatters/write.d.mts.map +1 -0
  180. package/dist/core/formatters/write.mjs +51 -0
  181. package/dist/core/formatters/write.mjs.map +1 -0
  182. package/dist/core/helpers/exec-args.cjs +142 -0
  183. package/dist/core/helpers/exec-args.d.cts +46 -0
  184. package/dist/core/helpers/exec-args.d.cts.map +1 -0
  185. package/dist/core/helpers/exec-args.d.mts +46 -0
  186. package/dist/core/helpers/exec-args.d.mts.map +1 -0
  187. package/dist/core/helpers/exec-args.mjs +139 -0
  188. package/dist/core/helpers/exec-args.mjs.map +1 -0
  189. package/dist/core/helpers/stdin.cjs +41 -0
  190. package/dist/core/helpers/stdin.d.cts +15 -0
  191. package/dist/core/helpers/stdin.d.cts.map +1 -0
  192. package/dist/core/helpers/stdin.d.mts +15 -0
  193. package/dist/core/helpers/stdin.d.mts.map +1 -0
  194. package/dist/core/helpers/stdin.mjs +41 -0
  195. package/dist/core/helpers/stdin.mjs.map +1 -0
  196. package/dist/core/index.cjs +49 -0
  197. package/dist/core/index.d.cts +24 -0
  198. package/dist/core/index.d.mts +25 -0
  199. package/dist/core/index.mjs +24 -0
  200. package/dist/core/path-utils.cjs +1 -0
  201. package/dist/core/path-utils.mjs +3 -0
  202. package/dist/core/types.d.cts +24 -0
  203. package/dist/core/types.d.cts.map +1 -0
  204. package/dist/core/types.d.mts +24 -0
  205. package/dist/core/types.d.mts.map +1 -0
  206. package/dist/credential/auth-server.cjs +247 -0
  207. package/dist/credential/auth-server.mjs +247 -0
  208. package/dist/credential/auth-server.mjs.map +1 -0
  209. package/dist/credential/cli-auth-context.cjs +86 -0
  210. package/dist/credential/cli-auth-context.d.mts +1 -0
  211. package/dist/credential/cli-auth-context.mjs +86 -0
  212. package/dist/credential/cli-auth-context.mjs.map +1 -0
  213. package/dist/credential/index.cjs +5 -0
  214. package/dist/credential/index.d.mts +4 -0
  215. package/dist/credential/index.mjs +7 -0
  216. package/dist/credential/mcp-auth-context.cjs +192 -0
  217. package/dist/credential/mcp-auth-context.d.mts +1 -0
  218. package/dist/credential/mcp-auth-context.mjs +192 -0
  219. package/dist/credential/mcp-auth-context.mjs.map +1 -0
  220. package/dist/credential/resolver.cjs +127 -0
  221. package/dist/credential/resolver.d.mts +1 -0
  222. package/dist/credential/resolver.mjs +127 -0
  223. package/dist/credential/resolver.mjs.map +1 -0
  224. package/dist/credential/store.cjs +106 -0
  225. package/dist/credential/store.d.cts +30 -0
  226. package/dist/credential/store.d.cts.map +1 -0
  227. package/dist/credential/store.d.mts +30 -0
  228. package/dist/credential/store.d.mts.map +1 -0
  229. package/dist/credential/store.mjs +106 -0
  230. package/dist/credential/store.mjs.map +1 -0
  231. package/dist/errors.cjs +18 -0
  232. package/dist/errors.mjs +18 -0
  233. package/dist/errors.mjs.map +1 -0
  234. package/dist/explorer/actions.cjs +311 -0
  235. package/dist/explorer/actions.mjs +305 -0
  236. package/dist/explorer/actions.mjs.map +1 -0
  237. package/dist/explorer/components/dialog.cjs +508 -0
  238. package/dist/explorer/components/dialog.mjs +509 -0
  239. package/dist/explorer/components/dialog.mjs.map +1 -0
  240. package/dist/explorer/components/file-list.cjs +107 -0
  241. package/dist/explorer/components/file-list.mjs +107 -0
  242. package/dist/explorer/components/file-list.mjs.map +1 -0
  243. package/dist/explorer/components/function-bar.cjs +55 -0
  244. package/dist/explorer/components/function-bar.mjs +55 -0
  245. package/dist/explorer/components/function-bar.mjs.map +1 -0
  246. package/dist/explorer/components/index.cjs +5 -0
  247. package/dist/explorer/components/index.mjs +7 -0
  248. package/dist/explorer/components/metadata-panel.cjs +219 -0
  249. package/dist/explorer/components/metadata-panel.mjs +219 -0
  250. package/dist/explorer/components/metadata-panel.mjs.map +1 -0
  251. package/dist/explorer/components/status-bar.cjs +53 -0
  252. package/dist/explorer/components/status-bar.mjs +54 -0
  253. package/dist/explorer/components/status-bar.mjs.map +1 -0
  254. package/dist/explorer/keybindings.cjs +214 -0
  255. package/dist/explorer/keybindings.mjs +213 -0
  256. package/dist/explorer/keybindings.mjs.map +1 -0
  257. package/dist/explorer/screen.cjs +251 -0
  258. package/dist/explorer/screen.d.cts +23 -0
  259. package/dist/explorer/screen.d.cts.map +1 -0
  260. package/dist/explorer/screen.d.mts +23 -0
  261. package/dist/explorer/screen.d.mts.map +1 -0
  262. package/dist/explorer/screen.mjs +250 -0
  263. package/dist/explorer/screen.mjs.map +1 -0
  264. package/dist/explorer/state.cjs +53 -0
  265. package/dist/explorer/state.mjs +53 -0
  266. package/dist/explorer/state.mjs.map +1 -0
  267. package/dist/explorer/theme.cjs +160 -0
  268. package/dist/explorer/theme.mjs +157 -0
  269. package/dist/explorer/theme.mjs.map +1 -0
  270. package/dist/index.cjs +12 -0
  271. package/dist/index.d.cts +6 -0
  272. package/dist/index.d.mts +7 -4
  273. package/dist/index.mjs +7 -2
  274. package/dist/mcp/http-transport.cjs +87 -0
  275. package/dist/mcp/http-transport.mjs +87 -0
  276. package/dist/mcp/http-transport.mjs.map +1 -0
  277. package/dist/mcp/prompts.cjs +48 -0
  278. package/dist/mcp/prompts.mjs +48 -0
  279. package/dist/mcp/prompts.mjs.map +1 -0
  280. package/dist/mcp/resources.cjs +25 -0
  281. package/dist/mcp/resources.mjs +25 -0
  282. package/dist/mcp/resources.mjs.map +1 -0
  283. package/dist/mcp/server.cjs +74 -0
  284. package/dist/mcp/server.mjs +73 -0
  285. package/dist/mcp/server.mjs.map +1 -0
  286. package/dist/mcp/tools.cjs +152 -0
  287. package/dist/mcp/tools.mjs +152 -0
  288. package/dist/mcp/tools.mjs.map +1 -0
  289. package/dist/node_modules/.pnpm/urlpattern-polyfill@10.1.0/node_modules/urlpattern-polyfill/dist/index.d.cts +10 -0
  290. package/dist/node_modules/.pnpm/urlpattern-polyfill@10.1.0/node_modules/urlpattern-polyfill/dist/index.d.cts.map +1 -0
  291. package/dist/node_modules/.pnpm/urlpattern-polyfill@10.1.0/node_modules/urlpattern-polyfill/dist/index.d.mts +10 -0
  292. package/dist/node_modules/.pnpm/urlpattern-polyfill@10.1.0/node_modules/urlpattern-polyfill/dist/index.d.mts.map +1 -0
  293. package/dist/node_modules/.pnpm/urlpattern-polyfill@10.1.0/node_modules/urlpattern-polyfill/dist/types.d.cts +46 -0
  294. package/dist/node_modules/.pnpm/urlpattern-polyfill@10.1.0/node_modules/urlpattern-polyfill/dist/types.d.cts.map +1 -0
  295. package/dist/node_modules/.pnpm/urlpattern-polyfill@10.1.0/node_modules/urlpattern-polyfill/dist/types.d.mts +46 -0
  296. package/dist/node_modules/.pnpm/urlpattern-polyfill@10.1.0/node_modules/urlpattern-polyfill/dist/types.d.mts.map +1 -0
  297. package/dist/node_modules/.pnpm/urlpattern-polyfill@10.1.0/node_modules/urlpattern-polyfill/dist/urlpattern.cjs +902 -0
  298. package/dist/node_modules/.pnpm/urlpattern-polyfill@10.1.0/node_modules/urlpattern-polyfill/dist/urlpattern.mjs +902 -0
  299. package/dist/node_modules/.pnpm/urlpattern-polyfill@10.1.0/node_modules/urlpattern-polyfill/dist/urlpattern.mjs.map +1 -0
  300. package/dist/node_modules/.pnpm/urlpattern-polyfill@10.1.0/node_modules/urlpattern-polyfill/index.cjs +6 -0
  301. package/dist/node_modules/.pnpm/urlpattern-polyfill@10.1.0/node_modules/urlpattern-polyfill/index.mjs +8 -0
  302. package/dist/node_modules/.pnpm/urlpattern-polyfill@10.1.0/node_modules/urlpattern-polyfill/index.mjs.map +1 -0
  303. package/dist/path-utils.cjs +105 -0
  304. package/dist/path-utils.d.cts +50 -0
  305. package/dist/path-utils.d.cts.map +1 -0
  306. package/dist/path-utils.d.mts +50 -0
  307. package/dist/path-utils.d.mts.map +1 -0
  308. package/dist/path-utils.mjs +104 -0
  309. package/dist/path-utils.mjs.map +1 -0
  310. package/dist/repl.cjs +491 -0
  311. package/dist/repl.d.cts +15 -0
  312. package/dist/repl.d.cts.map +1 -0
  313. package/dist/repl.d.mts +16 -0
  314. package/dist/repl.d.mts.map +1 -0
  315. package/dist/repl.mjs +491 -0
  316. package/dist/repl.mjs.map +1 -0
  317. package/dist/serve.cjs +146 -0
  318. package/dist/serve.d.cts +41 -0
  319. package/dist/serve.d.cts.map +1 -0
  320. package/dist/serve.d.mts +41 -0
  321. package/dist/serve.d.mts.map +1 -0
  322. package/dist/serve.mjs +146 -0
  323. package/dist/serve.mjs.map +1 -0
  324. package/dist/ui/header.cjs +12 -0
  325. package/dist/ui/header.mjs +13 -0
  326. package/dist/ui/header.mjs.map +1 -0
  327. package/dist/ui/index.cjs +8 -0
  328. package/dist/ui/index.mjs +9 -0
  329. package/dist/ui/index.mjs.map +1 -0
  330. package/dist/ui/terminal.cjs +88 -0
  331. package/dist/ui/terminal.mjs +88 -0
  332. package/dist/ui/terminal.mjs.map +1 -0
  333. package/dist/version.cjs +9 -0
  334. package/dist/version.d.cts +5 -0
  335. package/dist/version.d.cts.map +1 -0
  336. package/dist/version.d.mts +5 -0
  337. package/dist/version.d.mts.map +1 -0
  338. package/dist/version.mjs +9 -0
  339. package/dist/version.mjs.map +1 -0
  340. package/package.json +77 -11
  341. package/.turbo/turbo-build.log +0 -18
  342. package/.turbo/turbo-check-types.log +0 -4
  343. package/dist/version--p6A8sKX.mjs +0 -5
  344. package/src/cli.test.ts +0 -8
  345. package/src/cli.ts +0 -29
  346. package/src/index.ts +0 -7
  347. package/src/version.ts +0 -1
  348. package/tsconfig.json +0 -16
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.mjs","names":[],"sources":["../../src/config/loader.ts"],"sourcesContent":["import { access, readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { parse } from \"smol-toml\";\nimport { resolveEnvVarsInObject } from \"./env.js\";\nimport { type AFSConfig, ConfigSchema, type MountConfig, type ServeConfig } from \"./schema.js\";\n\nexport const CONFIG_DIR_NAME = \".afs-config\";\nexport const CONFIG_FILE_NAME = \"config.toml\";\n\nexport interface ConfigLoaderOptions {\n /** Custom path to user-level config directory (for testing) */\n userConfigDir?: string;\n}\n\nexport interface LoadWithSourcesResult {\n config: AFSConfig;\n /** Map from \"namespace:path\" → config directory that defines this mount */\n mountSources: Map<string, string>;\n}\n\n/**\n * Environment variable to override user config directory.\n * Useful for testing to isolate from real user config.\n */\nexport const AFS_USER_CONFIG_DIR_ENV = \"AFS_USER_CONFIG_DIR\";\n\n/**\n * Loads and merges AFS configuration from multiple layers\n *\n * Layer priority (lowest to highest):\n * 1. User-level: ~/.afs-config/config.toml\n * 2. All intermediate directories from project root to cwd\n *\n * Example: if cwd is /project/packages/cli, configs are merged from:\n * ~/.afs-config/config.toml (user)\n * /project/.afs-config/config.toml (project root, has .git)\n * /project/packages/.afs-config/config.toml (intermediate)\n * /project/packages/cli/.afs-config/config.toml (cwd)\n */\nexport class ConfigLoader {\n private userConfigDir: string;\n\n constructor(options: ConfigLoaderOptions = {}) {\n // Priority: options > environment variable > default (~/.afs-config)\n this.userConfigDir =\n options.userConfigDir ??\n process.env[AFS_USER_CONFIG_DIR_ENV] ??\n join(homedir(), CONFIG_DIR_NAME);\n }\n\n /**\n * Load and merge configuration from all layers\n *\n * @param cwd - Current working directory (defaults to process.cwd())\n * @returns Merged configuration\n * @throws Error on invalid config, TOML parse error, or duplicate mount paths\n */\n async load(cwd: string = process.cwd()): Promise<AFSConfig> {\n const result = await this.loadWithSources(cwd);\n return result.config;\n }\n\n /**\n * Load and merge configuration, also returning the config directory for each mount.\n *\n * mountSources maps \"namespace:path\" → config directory path (dirname of config file).\n * Used by credential store to scope credentials per config location.\n */\n async loadWithSources(cwd: string = process.cwd()): Promise<LoadWithSourcesResult> {\n const configPaths = await this.getConfigPaths(cwd);\n const entries: { config: AFSConfig; configDir: string }[] = [];\n\n for (const configPath of configPaths) {\n const config = await this.loadSingleConfig(configPath);\n entries.push({ config, configDir: dirname(configPath) });\n }\n\n return this.mergeConfigsWithSources(entries);\n }\n\n /**\n * Get paths to all existing config files\n *\n * Collects configs from:\n * 1. User-level: ~/.afs-config/config.toml\n * 2. Project root (or topmost .afs-config dir) to cwd: all .afs-config/config.toml files\n */\n async getConfigPaths(cwd: string = process.cwd()): Promise<string[]> {\n const paths: string[] = [];\n\n // 1. User-level config\n const userConfigPath = join(this.userConfigDir, CONFIG_FILE_NAME);\n if (await this.fileExists(userConfigPath)) {\n paths.push(userConfigPath);\n }\n\n // 2. Find project root (look for .git going up)\n const projectRoot = await this.findProjectRoot(cwd);\n\n // 3. Determine start directory\n // If project root found, use it; otherwise find topmost .afs-config directory\n const startDir = projectRoot ?? (await this.findTopmostAfsDir(cwd)) ?? cwd;\n\n // 4. Collect all config files from start to cwd\n // Exclude user config directory to avoid loading it twice\n const intermediatePaths = await this.collectConfigsFromTo(startDir, cwd, this.userConfigDir);\n paths.push(...intermediatePaths);\n\n return paths;\n }\n\n /**\n * Find the topmost directory containing .afs-config from startDir going up\n */\n private async findTopmostAfsDir(startDir: string): Promise<string | null> {\n let currentDir = startDir;\n let topmostAfsDir: string | null = null;\n\n while (true) {\n if (await this.fileExists(join(currentDir, CONFIG_DIR_NAME))) {\n topmostAfsDir = currentDir;\n }\n\n const parentDir = dirname(currentDir);\n if (parentDir === currentDir) {\n // Reached filesystem root\n break;\n }\n currentDir = parentDir;\n }\n\n return topmostAfsDir;\n }\n\n /**\n * Collect all config files from startDir to endDir (inclusive)\n * Returns paths in order from startDir to endDir (parent to child)\n *\n * @param excludeConfigDir - Optional config directory to exclude (to avoid duplicates)\n */\n private async collectConfigsFromTo(\n startDir: string,\n endDir: string,\n excludeConfigDir?: string,\n ): Promise<string[]> {\n const paths: string[] = [];\n\n // Build list of directories from startDir to endDir\n const dirs: string[] = [];\n let current = endDir;\n\n while (true) {\n dirs.unshift(current); // prepend to maintain parent-to-child order\n\n if (current === startDir) {\n break;\n }\n\n const parent = dirname(current);\n if (parent === current) {\n // Reached filesystem root without finding startDir\n // This shouldn't happen if startDir is an ancestor of endDir\n break;\n }\n current = parent;\n }\n\n // Check each directory for config file\n for (const dir of dirs) {\n const configDir = join(dir, CONFIG_DIR_NAME);\n // Skip if this is the excluded config directory (e.g., user config already loaded)\n if (excludeConfigDir && configDir === excludeConfigDir) {\n continue;\n }\n const configPath = join(configDir, CONFIG_FILE_NAME);\n if (await this.fileExists(configPath)) {\n paths.push(configPath);\n }\n }\n\n return paths;\n }\n\n /**\n * Load a single config file\n */\n private async loadSingleConfig(configPath: string): Promise<AFSConfig> {\n const content = await readFile(configPath, \"utf-8\");\n\n let parsed: unknown;\n try {\n parsed = parse(content);\n } catch (error) {\n throw new Error(\n `Failed to parse TOML config at ${configPath}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Resolve environment variables with friendly error messages\n let resolved: unknown;\n try {\n resolved = resolveEnvVarsInObject(parsed);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n // Extract variable name from error message like \"Environment variable GITHUB_TOKEN is not defined\"\n const match = message.match(/Environment variable (\\w+) is not defined/);\n if (match) {\n const varName = match[1];\n throw new Error(\n `Missing environment variable ${varName} in ${configPath}.\\n` +\n ` Set it in your shell: export ${varName}=your_value\\n` +\n ` Or add to .env file: ${varName}=your_value`,\n );\n }\n throw new Error(`Failed to resolve environment variables in ${configPath}: ${message}`);\n }\n\n // Validate against schema\n const result = ConfigSchema.safeParse(resolved);\n if (!result.success) {\n const errors = result.error.issues.map((e) => `${e.path.join(\".\")}: ${e.message}`).join(\"; \");\n throw new Error(`Invalid config at ${configPath}: ${errors}`);\n }\n\n return result.data;\n }\n\n /**\n * Create a composite key for namespace+path duplicate detection\n * Uses empty string for undefined namespace (default namespace)\n */\n private makeNamespacePathKey(namespace: string | undefined, path: string): string {\n return `${namespace ?? \"\"}:${path}`;\n }\n\n /**\n * Merge configs with source tracking.\n * Returns merged config plus a map of mount key → config directory.\n */\n private mergeConfigsWithSources(\n entries: { config: AFSConfig; configDir: string }[],\n ): LoadWithSourcesResult {\n const mountIndexByKey = new Map<string, number>();\n const allMounts: MountConfig[] = [];\n const mountSources = new Map<string, string>();\n let mergedServe: ServeConfig | undefined;\n\n for (const { config, configDir } of entries) {\n for (const mount of config.mounts) {\n const key = this.makeNamespacePathKey(mount.namespace, mount.path);\n const existingIndex = mountIndexByKey.get(key);\n if (existingIndex !== undefined) {\n allMounts[existingIndex] = mount;\n } else {\n mountIndexByKey.set(key, allMounts.length);\n allMounts.push(mount);\n }\n // Track source (child overrides parent)\n mountSources.set(key, configDir);\n }\n\n if (config.serve) {\n mergedServe = mergedServe ? { ...mergedServe, ...config.serve } : config.serve;\n }\n }\n\n return {\n config: { mounts: allMounts, serve: mergedServe },\n mountSources,\n };\n }\n\n /**\n * Find project root by looking for .git\n * Note: Only .git is used as project root marker, not .afs-config,\n * because .afs-config can exist at multiple levels for hierarchical config\n */\n async findProjectRoot(startDir: string): Promise<string | null> {\n let currentDir = startDir;\n\n while (true) {\n // Check for .git directory\n if (await this.fileExists(join(currentDir, \".git\"))) {\n return currentDir;\n }\n\n const parentDir = dirname(currentDir);\n if (parentDir === currentDir) {\n // Reached filesystem root\n return null;\n }\n currentDir = parentDir;\n }\n }\n\n /**\n * Check if a file or directory exists\n */\n private async fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n }\n}\n\n// Default singleton instance\nexport const configLoader = new ConfigLoader();\n"],"mappings":"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"}
@@ -0,0 +1,226 @@
1
+ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
2
+ const require_schema = require('./schema.cjs');
3
+ const require_loader = require('./loader.cjs');
4
+ let node_fs_promises = require("node:fs/promises");
5
+ let node_os = require("node:os");
6
+ let node_path = require("node:path");
7
+ let smol_toml = require("smol-toml");
8
+
9
+ //#region src/config/mount-commands.ts
10
+ /**
11
+ * Mount Configuration Commands
12
+ *
13
+ * CLI-specific commands for managing mount configuration files.
14
+ * These operate on afs.toml config files, not the AFS instance directly.
15
+ */
16
+ /**
17
+ * Check if a path looks like a remote Git URL
18
+ */
19
+ function isRemoteGitUrl(path) {
20
+ if (/^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+:/.test(path)) return true;
21
+ if (/^(https?|ssh|git):\/\//.test(path)) return true;
22
+ return false;
23
+ }
24
+ /**
25
+ * Resolve relative paths in URI to absolute paths
26
+ */
27
+ function resolveUriPath(uri, cwd) {
28
+ const schemeMatch = uri.match(/^([a-z][a-z0-9+.-]*):\/\//i);
29
+ if (!schemeMatch) {
30
+ if (isRemoteGitUrl(uri)) return `git://${uri}`;
31
+ return `fs://${(0, node_path.isAbsolute)(uri) ? uri : (0, node_path.resolve)(cwd, uri)}`;
32
+ }
33
+ const scheme = schemeMatch[1];
34
+ const pathPart = uri.slice(schemeMatch[0].length);
35
+ if (![
36
+ "fs",
37
+ "git",
38
+ "sqlite",
39
+ "json"
40
+ ].includes(scheme)) return uri;
41
+ if (scheme === "git" && isRemoteGitUrl(pathPart)) return uri;
42
+ if ((0, node_path.isAbsolute)(pathPart)) return uri;
43
+ return `${scheme}://${(0, node_path.resolve)(cwd, pathPart)}`;
44
+ }
45
+ /**
46
+ * List all mounts from config (merged from all config layers)
47
+ */
48
+ async function configMountListCommand(cwd) {
49
+ return { mounts: (await new require_loader.ConfigLoader().load(cwd)).mounts };
50
+ }
51
+ /**
52
+ * Remove a mount from config
53
+ */
54
+ async function mountRemoveCommand(cwd, path) {
55
+ const configPath = (0, node_path.join)(cwd, require_loader.CONFIG_DIR_NAME, require_loader.CONFIG_FILE_NAME);
56
+ try {
57
+ const config = (0, smol_toml.parse)(await (0, node_fs_promises.readFile)(configPath, "utf-8"));
58
+ const mounts = config.mounts ?? [];
59
+ const index = mounts.findIndex((m) => m.path === path);
60
+ if (index === -1) return {
61
+ success: false,
62
+ message: `Mount path "${path}" not found`
63
+ };
64
+ mounts.splice(index, 1);
65
+ config.mounts = mounts;
66
+ await (0, node_fs_promises.writeFile)(configPath, (0, smol_toml.stringify)(config), "utf-8");
67
+ return { success: true };
68
+ } catch {
69
+ return {
70
+ success: false,
71
+ message: `Mount path "${path}" not found`
72
+ };
73
+ }
74
+ }
75
+ /**
76
+ * Resolve a PersistScope to the config directory path.
77
+ *
78
+ * - "cwd" → <cwd>/.afs-config/
79
+ * - "project" → <projectRoot>/.afs-config/ (falls back to cwd if no .git found)
80
+ * - "user" → ~/.afs-config/
81
+ */
82
+ async function resolveScopeDir(cwd, scope) {
83
+ if (scope === "user") return (0, node_path.join)((0, node_os.homedir)(), require_loader.CONFIG_DIR_NAME);
84
+ if (scope === "project") {
85
+ const projectRoot = await new require_loader.ConfigLoader().findProjectRoot(cwd);
86
+ if (projectRoot) return (0, node_path.join)(projectRoot, require_loader.CONFIG_DIR_NAME);
87
+ return (0, node_path.join)(cwd, require_loader.CONFIG_DIR_NAME);
88
+ }
89
+ return (0, node_path.join)(cwd, require_loader.CONFIG_DIR_NAME);
90
+ }
91
+ /**
92
+ * Persist a mount entry to config.toml at the given scope.
93
+ *
94
+ * Uses upsert semantics: if a mount with the same path already exists,
95
+ * it is replaced. Otherwise the new entry is appended.
96
+ */
97
+ async function persistMount(cwd, entry, scope = "cwd") {
98
+ const configDir = await resolveScopeDir(cwd, scope);
99
+ const configPath = (0, node_path.join)(configDir, require_loader.CONFIG_FILE_NAME);
100
+ const config = { mounts: [] };
101
+ try {
102
+ const parsed = (0, smol_toml.parse)(await (0, node_fs_promises.readFile)(configPath, "utf-8"));
103
+ config.mounts = parsed.mounts ?? [];
104
+ for (const key of Object.keys(parsed)) if (key !== "mounts") config[key] = parsed[key];
105
+ } catch {}
106
+ const existingIndex = config.mounts.findIndex((m) => m.path === entry.path);
107
+ if (existingIndex >= 0) config.mounts[existingIndex] = entry;
108
+ else config.mounts.push(entry);
109
+ try {
110
+ await (0, node_fs_promises.mkdir)(configDir, { recursive: true });
111
+ } catch {}
112
+ await (0, node_fs_promises.writeFile)(configPath, (0, smol_toml.stringify)(config), "utf-8");
113
+ return {
114
+ success: true,
115
+ configPath
116
+ };
117
+ }
118
+ /**
119
+ * Update specific options on an existing mount entry in a config file.
120
+ *
121
+ * Merges `optionUpdates` into the mount's `options` field (shallow merge).
122
+ * If the mount is not found in the given configDir, this is a no-op.
123
+ */
124
+ async function updateMountOptions(configDir, mountPath, optionUpdates) {
125
+ const configPath = (0, node_path.join)(configDir, require_loader.CONFIG_FILE_NAME);
126
+ const parsed = (0, smol_toml.parse)(await (0, node_fs_promises.readFile)(configPath, "utf-8"));
127
+ const entry = (parsed.mounts ?? []).find((m) => m.path === mountPath);
128
+ if (!entry) return;
129
+ entry.options = {
130
+ ...entry.options,
131
+ ...optionUpdates
132
+ };
133
+ await (0, node_fs_promises.writeFile)(configPath, (0, smol_toml.stringify)(parsed), "utf-8");
134
+ }
135
+ /**
136
+ * Remove a mount entry from config.toml.
137
+ *
138
+ * If `scope` is provided, only searches the specific config file for that scope.
139
+ * If `scope` is undefined, searches all config files (cwd, project, user) and
140
+ * removes from the first one that contains the mount path.
141
+ */
142
+ async function unpersistMount(cwd, mountPath, scope) {
143
+ if (scope) return removeFromConfigFile(cwd, mountPath, scope);
144
+ for (const s of [
145
+ "cwd",
146
+ "project",
147
+ "user"
148
+ ]) {
149
+ const result = await removeFromConfigFile(cwd, mountPath, s);
150
+ if (result.success) return result;
151
+ }
152
+ return {
153
+ success: false,
154
+ message: `Mount path "${mountPath}" not found in any config`
155
+ };
156
+ }
157
+ /**
158
+ * Remove a mount from a specific config file.
159
+ */
160
+ async function removeFromConfigFile(cwd, mountPath, scope) {
161
+ const configPath = (0, node_path.join)(await resolveScopeDir(cwd, scope), require_loader.CONFIG_FILE_NAME);
162
+ try {
163
+ const parsed = (0, smol_toml.parse)(await (0, node_fs_promises.readFile)(configPath, "utf-8"));
164
+ const mounts = parsed.mounts ?? [];
165
+ const index = mounts.findIndex((m) => m.path === mountPath);
166
+ if (index === -1) return {
167
+ success: false,
168
+ message: `Mount path "${mountPath}" not found in ${configPath}`
169
+ };
170
+ mounts.splice(index, 1);
171
+ parsed.mounts = mounts;
172
+ await (0, node_fs_promises.writeFile)(configPath, (0, smol_toml.stringify)(parsed), "utf-8");
173
+ return {
174
+ success: true,
175
+ configPath
176
+ };
177
+ } catch {
178
+ return {
179
+ success: false,
180
+ message: `Config file not found or unreadable: ${configPath}`
181
+ };
182
+ }
183
+ }
184
+ /**
185
+ * Validate mount configuration
186
+ */
187
+ async function mountValidateCommand(cwd) {
188
+ const configPath = (0, node_path.join)(cwd, require_loader.CONFIG_DIR_NAME, require_loader.CONFIG_FILE_NAME);
189
+ const errors = [];
190
+ try {
191
+ const mounts = (0, smol_toml.parse)(await (0, node_fs_promises.readFile)(configPath, "utf-8")).mounts ?? [];
192
+ for (const mount of mounts) {
193
+ const validation = require_schema.MountSchema.safeParse(mount);
194
+ if (!validation.success) {
195
+ for (const err of validation.error.issues) errors.push(`Mount "${mount.path}": ${err.message}`);
196
+ continue;
197
+ }
198
+ if (mount.uri.startsWith("fs://")) {
199
+ const targetPath = mount.uri.replace("fs://", "");
200
+ try {
201
+ await (0, node_fs_promises.access)(targetPath);
202
+ } catch {
203
+ errors.push(`Mount target "${targetPath}" does not exist`);
204
+ }
205
+ }
206
+ }
207
+ return {
208
+ valid: errors.length === 0,
209
+ errors
210
+ };
211
+ } catch {
212
+ return {
213
+ valid: true,
214
+ errors: []
215
+ };
216
+ }
217
+ }
218
+
219
+ //#endregion
220
+ exports.configMountListCommand = configMountListCommand;
221
+ exports.mountRemoveCommand = mountRemoveCommand;
222
+ exports.mountValidateCommand = mountValidateCommand;
223
+ exports.persistMount = persistMount;
224
+ exports.resolveUriPath = resolveUriPath;
225
+ exports.unpersistMount = unpersistMount;
226
+ exports.updateMountOptions = updateMountOptions;
@@ -0,0 +1,14 @@
1
+ //#region src/config/mount-commands.d.ts
2
+ interface ConfigMountEntry {
3
+ path: string;
4
+ uri: string;
5
+ namespace?: string;
6
+ description?: string;
7
+ access_mode?: "readonly" | "readwrite";
8
+ auth?: string;
9
+ token?: string;
10
+ options?: Record<string, unknown>;
11
+ }
12
+ //#endregion
13
+ export { ConfigMountEntry };
14
+ //# sourceMappingURL=mount-commands.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount-commands.d.cts","names":[],"sources":["../../src/config/mount-commands.ts"],"mappings":";UAwBiB,gBAAA;EACf,IAAA;EACA,GAAA;EACA,SAAA;EACA,WAAA;EACA,WAAA;EACA,IAAA;EACA,KAAA;EACA,OAAA,GAAU,MAAA;AAAA"}
@@ -0,0 +1,14 @@
1
+ //#region src/config/mount-commands.d.ts
2
+ interface ConfigMountEntry {
3
+ path: string;
4
+ uri: string;
5
+ namespace?: string;
6
+ description?: string;
7
+ access_mode?: "readonly" | "readwrite";
8
+ auth?: string;
9
+ token?: string;
10
+ options?: Record<string, unknown>;
11
+ }
12
+ //#endregion
13
+ export { ConfigMountEntry };
14
+ //# sourceMappingURL=mount-commands.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount-commands.d.mts","names":[],"sources":["../../src/config/mount-commands.ts"],"mappings":";UAwBiB,gBAAA;EACf,IAAA;EACA,GAAA;EACA,SAAA;EACA,WAAA;EACA,WAAA;EACA,IAAA;EACA,KAAA;EACA,OAAA,GAAU,MAAA;AAAA"}
@@ -0,0 +1,220 @@
1
+ import { MountSchema } from "./schema.mjs";
2
+ import { CONFIG_DIR_NAME, CONFIG_FILE_NAME, ConfigLoader } from "./loader.mjs";
3
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import { isAbsolute, join, resolve } from "node:path";
6
+ import { parse, stringify } from "smol-toml";
7
+
8
+ //#region src/config/mount-commands.ts
9
+ /**
10
+ * Mount Configuration Commands
11
+ *
12
+ * CLI-specific commands for managing mount configuration files.
13
+ * These operate on afs.toml config files, not the AFS instance directly.
14
+ */
15
+ /**
16
+ * Check if a path looks like a remote Git URL
17
+ */
18
+ function isRemoteGitUrl(path) {
19
+ if (/^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+:/.test(path)) return true;
20
+ if (/^(https?|ssh|git):\/\//.test(path)) return true;
21
+ return false;
22
+ }
23
+ /**
24
+ * Resolve relative paths in URI to absolute paths
25
+ */
26
+ function resolveUriPath(uri, cwd) {
27
+ const schemeMatch = uri.match(/^([a-z][a-z0-9+.-]*):\/\//i);
28
+ if (!schemeMatch) {
29
+ if (isRemoteGitUrl(uri)) return `git://${uri}`;
30
+ return `fs://${isAbsolute(uri) ? uri : resolve(cwd, uri)}`;
31
+ }
32
+ const scheme = schemeMatch[1];
33
+ const pathPart = uri.slice(schemeMatch[0].length);
34
+ if (![
35
+ "fs",
36
+ "git",
37
+ "sqlite",
38
+ "json"
39
+ ].includes(scheme)) return uri;
40
+ if (scheme === "git" && isRemoteGitUrl(pathPart)) return uri;
41
+ if (isAbsolute(pathPart)) return uri;
42
+ return `${scheme}://${resolve(cwd, pathPart)}`;
43
+ }
44
+ /**
45
+ * List all mounts from config (merged from all config layers)
46
+ */
47
+ async function configMountListCommand(cwd) {
48
+ return { mounts: (await new ConfigLoader().load(cwd)).mounts };
49
+ }
50
+ /**
51
+ * Remove a mount from config
52
+ */
53
+ async function mountRemoveCommand(cwd, path) {
54
+ const configPath = join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);
55
+ try {
56
+ const config = parse(await readFile(configPath, "utf-8"));
57
+ const mounts = config.mounts ?? [];
58
+ const index = mounts.findIndex((m) => m.path === path);
59
+ if (index === -1) return {
60
+ success: false,
61
+ message: `Mount path "${path}" not found`
62
+ };
63
+ mounts.splice(index, 1);
64
+ config.mounts = mounts;
65
+ await writeFile(configPath, stringify(config), "utf-8");
66
+ return { success: true };
67
+ } catch {
68
+ return {
69
+ success: false,
70
+ message: `Mount path "${path}" not found`
71
+ };
72
+ }
73
+ }
74
+ /**
75
+ * Resolve a PersistScope to the config directory path.
76
+ *
77
+ * - "cwd" → <cwd>/.afs-config/
78
+ * - "project" → <projectRoot>/.afs-config/ (falls back to cwd if no .git found)
79
+ * - "user" → ~/.afs-config/
80
+ */
81
+ async function resolveScopeDir(cwd, scope) {
82
+ if (scope === "user") return join(homedir(), CONFIG_DIR_NAME);
83
+ if (scope === "project") {
84
+ const projectRoot = await new ConfigLoader().findProjectRoot(cwd);
85
+ if (projectRoot) return join(projectRoot, CONFIG_DIR_NAME);
86
+ return join(cwd, CONFIG_DIR_NAME);
87
+ }
88
+ return join(cwd, CONFIG_DIR_NAME);
89
+ }
90
+ /**
91
+ * Persist a mount entry to config.toml at the given scope.
92
+ *
93
+ * Uses upsert semantics: if a mount with the same path already exists,
94
+ * it is replaced. Otherwise the new entry is appended.
95
+ */
96
+ async function persistMount(cwd, entry, scope = "cwd") {
97
+ const configDir = await resolveScopeDir(cwd, scope);
98
+ const configPath = join(configDir, CONFIG_FILE_NAME);
99
+ const config = { mounts: [] };
100
+ try {
101
+ const parsed = parse(await readFile(configPath, "utf-8"));
102
+ config.mounts = parsed.mounts ?? [];
103
+ for (const key of Object.keys(parsed)) if (key !== "mounts") config[key] = parsed[key];
104
+ } catch {}
105
+ const existingIndex = config.mounts.findIndex((m) => m.path === entry.path);
106
+ if (existingIndex >= 0) config.mounts[existingIndex] = entry;
107
+ else config.mounts.push(entry);
108
+ try {
109
+ await mkdir(configDir, { recursive: true });
110
+ } catch {}
111
+ await writeFile(configPath, stringify(config), "utf-8");
112
+ return {
113
+ success: true,
114
+ configPath
115
+ };
116
+ }
117
+ /**
118
+ * Update specific options on an existing mount entry in a config file.
119
+ *
120
+ * Merges `optionUpdates` into the mount's `options` field (shallow merge).
121
+ * If the mount is not found in the given configDir, this is a no-op.
122
+ */
123
+ async function updateMountOptions(configDir, mountPath, optionUpdates) {
124
+ const configPath = join(configDir, CONFIG_FILE_NAME);
125
+ const parsed = parse(await readFile(configPath, "utf-8"));
126
+ const entry = (parsed.mounts ?? []).find((m) => m.path === mountPath);
127
+ if (!entry) return;
128
+ entry.options = {
129
+ ...entry.options,
130
+ ...optionUpdates
131
+ };
132
+ await writeFile(configPath, stringify(parsed), "utf-8");
133
+ }
134
+ /**
135
+ * Remove a mount entry from config.toml.
136
+ *
137
+ * If `scope` is provided, only searches the specific config file for that scope.
138
+ * If `scope` is undefined, searches all config files (cwd, project, user) and
139
+ * removes from the first one that contains the mount path.
140
+ */
141
+ async function unpersistMount(cwd, mountPath, scope) {
142
+ if (scope) return removeFromConfigFile(cwd, mountPath, scope);
143
+ for (const s of [
144
+ "cwd",
145
+ "project",
146
+ "user"
147
+ ]) {
148
+ const result = await removeFromConfigFile(cwd, mountPath, s);
149
+ if (result.success) return result;
150
+ }
151
+ return {
152
+ success: false,
153
+ message: `Mount path "${mountPath}" not found in any config`
154
+ };
155
+ }
156
+ /**
157
+ * Remove a mount from a specific config file.
158
+ */
159
+ async function removeFromConfigFile(cwd, mountPath, scope) {
160
+ const configPath = join(await resolveScopeDir(cwd, scope), CONFIG_FILE_NAME);
161
+ try {
162
+ const parsed = parse(await readFile(configPath, "utf-8"));
163
+ const mounts = parsed.mounts ?? [];
164
+ const index = mounts.findIndex((m) => m.path === mountPath);
165
+ if (index === -1) return {
166
+ success: false,
167
+ message: `Mount path "${mountPath}" not found in ${configPath}`
168
+ };
169
+ mounts.splice(index, 1);
170
+ parsed.mounts = mounts;
171
+ await writeFile(configPath, stringify(parsed), "utf-8");
172
+ return {
173
+ success: true,
174
+ configPath
175
+ };
176
+ } catch {
177
+ return {
178
+ success: false,
179
+ message: `Config file not found or unreadable: ${configPath}`
180
+ };
181
+ }
182
+ }
183
+ /**
184
+ * Validate mount configuration
185
+ */
186
+ async function mountValidateCommand(cwd) {
187
+ const configPath = join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);
188
+ const errors = [];
189
+ try {
190
+ const mounts = parse(await readFile(configPath, "utf-8")).mounts ?? [];
191
+ for (const mount of mounts) {
192
+ const validation = MountSchema.safeParse(mount);
193
+ if (!validation.success) {
194
+ for (const err of validation.error.issues) errors.push(`Mount "${mount.path}": ${err.message}`);
195
+ continue;
196
+ }
197
+ if (mount.uri.startsWith("fs://")) {
198
+ const targetPath = mount.uri.replace("fs://", "");
199
+ try {
200
+ await access(targetPath);
201
+ } catch {
202
+ errors.push(`Mount target "${targetPath}" does not exist`);
203
+ }
204
+ }
205
+ }
206
+ return {
207
+ valid: errors.length === 0,
208
+ errors
209
+ };
210
+ } catch {
211
+ return {
212
+ valid: true,
213
+ errors: []
214
+ };
215
+ }
216
+ }
217
+
218
+ //#endregion
219
+ export { configMountListCommand, mountRemoveCommand, mountValidateCommand, persistMount, resolveUriPath, unpersistMount, updateMountOptions };
220
+ //# sourceMappingURL=mount-commands.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount-commands.mjs","names":[],"sources":["../../src/config/mount-commands.ts"],"sourcesContent":["/**\n * Mount Configuration Commands\n *\n * CLI-specific commands for managing mount configuration files.\n * These operate on afs.toml config files, not the AFS instance directly.\n */\n\nimport { access, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { parse, stringify } from \"smol-toml\";\nimport { CONFIG_DIR_NAME, CONFIG_FILE_NAME, ConfigLoader } from \"./loader.js\";\nimport { MountSchema } from \"./schema.js\";\n\n// ─── Persistence Types ───────────────────────────────────────────────────\n\nexport type PersistScope = \"cwd\" | \"project\" | \"user\";\n\nexport interface PersistResult {\n success: boolean;\n configPath?: string;\n message?: string;\n}\n\nexport interface ConfigMountEntry {\n path: string;\n uri: string;\n namespace?: string;\n description?: string;\n access_mode?: \"readonly\" | \"readwrite\";\n auth?: string;\n token?: string;\n options?: Record<string, unknown>;\n}\n\nexport interface ConfigMountListResult {\n mounts: ConfigMountEntry[];\n}\n\nexport interface MountCommandResult {\n success: boolean;\n message?: string;\n normalizedPath?: string;\n}\n\nexport interface MountValidateResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Check if a path looks like a remote Git URL\n */\nfunction isRemoteGitUrl(path: string): boolean {\n if (/^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+:/.test(path)) {\n return true;\n }\n if (/^(https?|ssh|git):\\/\\//.test(path)) {\n return true;\n }\n return false;\n}\n\n/**\n * Resolve relative paths in URI to absolute paths\n */\nexport function resolveUriPath(uri: string, cwd: string): string {\n const schemeMatch = uri.match(/^([a-z][a-z0-9+.-]*):\\/\\//i);\n\n if (!schemeMatch) {\n if (isRemoteGitUrl(uri)) {\n return `git://${uri}`;\n }\n const absolutePath = isAbsolute(uri) ? uri : resolve(cwd, uri);\n return `fs://${absolutePath}`;\n }\n\n const scheme = schemeMatch[1];\n const pathPart = uri.slice(schemeMatch[0].length);\n\n if (![\"fs\", \"git\", \"sqlite\", \"json\"].includes(scheme!)) {\n return uri;\n }\n\n if (scheme === \"git\" && isRemoteGitUrl(pathPart)) {\n return uri;\n }\n\n if (isAbsolute(pathPart)) {\n return uri;\n }\n\n const absolutePath = resolve(cwd, pathPart);\n return `${scheme}://${absolutePath}`;\n}\n\n/**\n * List all mounts from config (merged from all config layers)\n */\nexport async function configMountListCommand(cwd: string): Promise<ConfigMountListResult> {\n const loader = new ConfigLoader();\n const config = await loader.load(cwd);\n return {\n mounts: config.mounts as ConfigMountEntry[],\n };\n}\n\n/**\n * Add a mount to config\n */\nexport async function mountAddCommand(\n cwd: string,\n path: string,\n uri: string,\n options: { description?: string; token?: string } = {},\n): Promise<MountCommandResult> {\n if (!uri || uri.trim() === \"\") {\n return {\n success: false,\n message: \"URI is required\",\n };\n }\n\n const resolvedUri = resolveUriPath(uri, cwd);\n\n const validation = MountSchema.safeParse({\n path,\n uri: resolvedUri,\n ...options,\n });\n if (!validation.success) {\n const errors = validation.error.issues.map((e) => e.message).join(\"; \");\n return {\n success: false,\n message: errors,\n };\n }\n\n const newMount: ConfigMountEntry = {\n path: validation.data.path,\n uri: validation.data.uri,\n ...(options.description && { description: options.description }),\n ...(options.token && { token: options.token }),\n };\n\n const configDir = join(cwd, CONFIG_DIR_NAME);\n const configPath = join(configDir, CONFIG_FILE_NAME);\n\n const config: { mounts: ConfigMountEntry[] } = { mounts: [] };\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const parsed = parse(content) as { mounts?: ConfigMountEntry[] };\n config.mounts = parsed.mounts ?? [];\n } catch {\n // Config doesn't exist, will create\n }\n\n const normalizedPath = validation.data.path;\n if (config.mounts.some((m) => m.path === normalizedPath)) {\n return {\n success: false,\n message: `Mount path \"${normalizedPath}\" already exists`,\n };\n }\n\n config.mounts.push(newMount);\n\n try {\n await mkdir(configDir, { recursive: true });\n } catch {\n // Directory might already exist\n }\n\n await writeFile(configPath, stringify(config), \"utf-8\");\n\n return {\n success: true,\n normalizedPath,\n };\n}\n\n/**\n * Remove a mount from config\n */\nexport async function mountRemoveCommand(cwd: string, path: string): Promise<MountCommandResult> {\n const configPath = join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const config = parse(content) as { mounts?: ConfigMountEntry[] };\n const mounts = config.mounts ?? [];\n\n const index = mounts.findIndex((m) => m.path === path);\n if (index === -1) {\n return {\n success: false,\n message: `Mount path \"${path}\" not found`,\n };\n }\n\n mounts.splice(index, 1);\n config.mounts = mounts;\n\n await writeFile(configPath, stringify(config), \"utf-8\");\n\n return {\n success: true,\n };\n } catch {\n return {\n success: false,\n message: `Mount path \"${path}\" not found`,\n };\n }\n}\n\n// ─── Persistence Functions ────────────────────────────────────────────────\n\n/**\n * Resolve a PersistScope to the config directory path.\n *\n * - \"cwd\" → <cwd>/.afs-config/\n * - \"project\" → <projectRoot>/.afs-config/ (falls back to cwd if no .git found)\n * - \"user\" → ~/.afs-config/\n */\nexport async function resolveScopeDir(cwd: string, scope: PersistScope): Promise<string> {\n if (scope === \"user\") {\n return join(homedir(), CONFIG_DIR_NAME);\n }\n\n if (scope === \"project\") {\n const loader = new ConfigLoader();\n const projectRoot = await loader.findProjectRoot(cwd);\n if (projectRoot) {\n return join(projectRoot, CONFIG_DIR_NAME);\n }\n // Fall back to cwd when no .git found\n return join(cwd, CONFIG_DIR_NAME);\n }\n\n // \"cwd\"\n return join(cwd, CONFIG_DIR_NAME);\n}\n\n/**\n * Persist a mount entry to config.toml at the given scope.\n *\n * Uses upsert semantics: if a mount with the same path already exists,\n * it is replaced. Otherwise the new entry is appended.\n */\nexport async function persistMount(\n cwd: string,\n entry: ConfigMountEntry,\n scope: PersistScope = \"cwd\",\n): Promise<PersistResult> {\n const configDir = await resolveScopeDir(cwd, scope);\n const configPath = join(configDir, CONFIG_FILE_NAME);\n\n // Read existing config (or start empty)\n const config: Record<string, unknown> & { mounts: ConfigMountEntry[] } = { mounts: [] };\n try {\n const content = await readFile(configPath, \"utf-8\");\n const parsed = parse(content) as Record<string, unknown>;\n config.mounts = (parsed.mounts as ConfigMountEntry[] | undefined) ?? [];\n // Preserve non-mount sections (serve, registry, etc.)\n for (const key of Object.keys(parsed)) {\n if (key !== \"mounts\") {\n config[key] = parsed[key];\n }\n }\n } catch {\n // Config doesn't exist yet, will create\n }\n\n // Upsert: replace existing entry at same path, or append\n const existingIndex = config.mounts.findIndex((m) => m.path === entry.path);\n if (existingIndex >= 0) {\n config.mounts[existingIndex] = entry;\n } else {\n config.mounts.push(entry);\n }\n\n // Write back\n try {\n await mkdir(configDir, { recursive: true });\n } catch {\n // Directory might already exist\n }\n\n await writeFile(configPath, stringify(config), \"utf-8\");\n\n return { success: true, configPath };\n}\n\n/**\n * Update specific options on an existing mount entry in a config file.\n *\n * Merges `optionUpdates` into the mount's `options` field (shallow merge).\n * If the mount is not found in the given configDir, this is a no-op.\n */\nexport async function updateMountOptions(\n configDir: string,\n mountPath: string,\n optionUpdates: Record<string, unknown>,\n): Promise<void> {\n const configPath = join(configDir, CONFIG_FILE_NAME);\n const content = await readFile(configPath, \"utf-8\");\n const parsed = parse(content) as Record<string, unknown>;\n const mounts = (parsed.mounts as ConfigMountEntry[]) ?? [];\n const entry = mounts.find((m) => m.path === mountPath);\n if (!entry) return;\n entry.options = { ...entry.options, ...optionUpdates };\n await writeFile(configPath, stringify(parsed as Record<string, unknown>), \"utf-8\");\n}\n\n/**\n * Remove a mount entry from config.toml.\n *\n * If `scope` is provided, only searches the specific config file for that scope.\n * If `scope` is undefined, searches all config files (cwd, project, user) and\n * removes from the first one that contains the mount path.\n */\nexport async function unpersistMount(\n cwd: string,\n mountPath: string,\n scope?: PersistScope,\n): Promise<PersistResult> {\n if (scope) {\n return removeFromConfigFile(cwd, mountPath, scope);\n }\n\n // Search all scopes: cwd → project → user\n for (const s of [\"cwd\", \"project\", \"user\"] as PersistScope[]) {\n const result = await removeFromConfigFile(cwd, mountPath, s);\n if (result.success) return result;\n }\n\n return { success: false, message: `Mount path \"${mountPath}\" not found in any config` };\n}\n\n/**\n * Remove a mount from a specific config file.\n */\nasync function removeFromConfigFile(\n cwd: string,\n mountPath: string,\n scope: PersistScope,\n): Promise<PersistResult> {\n const configDir = await resolveScopeDir(cwd, scope);\n const configPath = join(configDir, CONFIG_FILE_NAME);\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const parsed = parse(content) as Record<string, unknown>;\n const mounts = (parsed.mounts as ConfigMountEntry[] | undefined) ?? [];\n\n const index = mounts.findIndex((m) => m.path === mountPath);\n if (index === -1) {\n return { success: false, message: `Mount path \"${mountPath}\" not found in ${configPath}` };\n }\n\n mounts.splice(index, 1);\n parsed.mounts = mounts;\n\n await writeFile(configPath, stringify(parsed), \"utf-8\");\n\n return { success: true, configPath };\n } catch {\n return { success: false, message: `Config file not found or unreadable: ${configPath}` };\n }\n}\n\n/**\n * Validate mount configuration\n */\nexport async function mountValidateCommand(cwd: string): Promise<MountValidateResult> {\n const configPath = join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n const errors: string[] = [];\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const config = parse(content) as { mounts?: ConfigMountEntry[] };\n const mounts = config.mounts ?? [];\n\n for (const mount of mounts) {\n const validation = MountSchema.safeParse(mount);\n if (!validation.success) {\n for (const err of validation.error.issues) {\n errors.push(`Mount \"${mount.path}\": ${err.message}`);\n }\n continue;\n }\n\n if (mount.uri.startsWith(\"fs://\")) {\n const targetPath = mount.uri.replace(\"fs://\", \"\");\n try {\n await access(targetPath);\n } catch {\n errors.push(`Mount target \"${targetPath}\" does not exist`);\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n } catch {\n return {\n valid: true,\n errors: [],\n };\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
2
+ let _aigne_afs = require("@aigne/afs");
3
+ let zod = require("zod");
4
+
5
+ //#region src/config/schema.ts
6
+ /**
7
+ * Characters forbidden in namespace names (security-sensitive)
8
+ */
9
+ const NAMESPACE_FORBIDDEN_CHARS = [
10
+ "/",
11
+ "\\",
12
+ ":",
13
+ ";",
14
+ "|",
15
+ "&",
16
+ "`",
17
+ "$",
18
+ "(",
19
+ ")",
20
+ ">",
21
+ "<",
22
+ "\n",
23
+ "\r",
24
+ " ",
25
+ "\0"
26
+ ];
27
+ /**
28
+ * Validate namespace name
29
+ */
30
+ function validateNamespace(namespace) {
31
+ if (!namespace || namespace.trim() === "") throw new Error("Namespace cannot be empty or whitespace-only");
32
+ for (const char of NAMESPACE_FORBIDDEN_CHARS) if (namespace.includes(char)) throw new Error(`Namespace contains forbidden character: '${char}'`);
33
+ return namespace;
34
+ }
35
+ /**
36
+ * Mount configuration schema
37
+ */
38
+ const MountSchema = zod.z.object({
39
+ path: zod.z.string().transform((val, ctx) => {
40
+ try {
41
+ return (0, _aigne_afs.validatePath)(val);
42
+ } catch (e) {
43
+ const message = e instanceof _aigne_afs.AFSPathError ? e.message : "Invalid path";
44
+ ctx.addIssue({
45
+ code: zod.z.ZodIssueCode.custom,
46
+ message
47
+ });
48
+ return zod.z.NEVER;
49
+ }
50
+ }),
51
+ uri: zod.z.string().min(1, "URI is required"),
52
+ namespace: zod.z.string().transform((val, ctx) => {
53
+ try {
54
+ return validateNamespace(val);
55
+ } catch (e) {
56
+ ctx.addIssue({
57
+ code: zod.z.ZodIssueCode.custom,
58
+ message: e instanceof Error ? e.message : "Invalid namespace"
59
+ });
60
+ return zod.z.NEVER;
61
+ }
62
+ }).optional(),
63
+ description: zod.z.string().optional(),
64
+ access_mode: zod.z.enum(["readonly", "readwrite"]).optional(),
65
+ auth: zod.z.string().optional(),
66
+ token: zod.z.string().optional(),
67
+ options: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
68
+ });
69
+ /**
70
+ * Serve configuration schema
71
+ */
72
+ const ServeSchema = zod.z.object({
73
+ host: zod.z.string().default("localhost"),
74
+ port: zod.z.number().int().positive().default(3e3),
75
+ path: zod.z.string().default("/afs"),
76
+ readonly: zod.z.boolean().default(false),
77
+ cors: zod.z.boolean().default(false),
78
+ max_body_size: zod.z.number().int().positive().default(10 * 1024 * 1024),
79
+ token: zod.z.string().optional()
80
+ });
81
+ /**
82
+ * Registry configuration schema
83
+ */
84
+ const RegistrySchema = zod.z.object({
85
+ enabled: zod.z.boolean().default(true),
86
+ providers: zod.z.array(zod.z.record(zod.z.string(), zod.z.unknown())).optional()
87
+ });
88
+ /**
89
+ * Root configuration schema for afs.toml
90
+ */
91
+ const ConfigSchema = zod.z.object({
92
+ mounts: zod.z.array(MountSchema).default([]),
93
+ serve: ServeSchema.optional(),
94
+ registry: RegistrySchema.optional()
95
+ });
96
+
97
+ //#endregion
98
+ exports.ConfigSchema = ConfigSchema;
99
+ exports.MountSchema = MountSchema;