evolcore 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/CHANGELOG.md +813 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATION-0.5.0.md +378 -0
  4. package/README.md +318 -14
  5. package/ROLE_ACCESS_CONTROL.md +174 -0
  6. package/assets/.env.template +4 -0
  7. package/assets/brand/evolcore/README.md +19 -0
  8. package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
  9. package/assets/brand/evolcore/evolcore-app-icon.svg +13 -0
  10. package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
  11. package/assets/brand/evolcore/evolcore-brand-board.svg +126 -0
  12. package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
  13. package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
  14. package/assets/brand/evolcore/evolcore-logo-reverse.svg +14 -0
  15. package/assets/brand/evolcore/evolcore-logo.png +0 -0
  16. package/assets/brand/evolcore/evolcore-logo.svg +14 -0
  17. package/assets/brand/evolcore/evolcore-mark.png +0 -0
  18. package/assets/brand/evolcore/evolcore-mark.svg +10 -0
  19. package/bin/ec-safe-output.js +161 -0
  20. package/bin/ec.js +29 -0
  21. package/dist/agents/baseagent.js +163 -0
  22. package/dist/agents/claude-runner.js +2385 -0
  23. package/dist/agents/codex-app-server-client.js +448 -0
  24. package/dist/agents/codex-runner.js +2639 -0
  25. package/dist/agents/gemini-runner.js +666 -0
  26. package/dist/agents/runner-types.js +75 -0
  27. package/dist/aun/aid/agentmd.js +216 -0
  28. package/dist/aun/aid/client.js +132 -0
  29. package/dist/aun/aid/control-aid.js +91 -0
  30. package/dist/aun/aid/identity.js +518 -0
  31. package/dist/aun/aid/index.js +4 -0
  32. package/dist/aun/aid/store.js +74 -0
  33. package/dist/aun/aid/types.js +1 -0
  34. package/dist/aun/aid/validation.js +21 -0
  35. package/dist/aun/group-identity.js +10 -0
  36. package/dist/aun/msg/group-index.js +6 -0
  37. package/dist/aun/msg/group.js +1231 -0
  38. package/dist/aun/msg/history.js +123 -0
  39. package/dist/aun/msg/index.js +5 -0
  40. package/dist/aun/msg/p2p.js +393 -0
  41. package/dist/aun/msg/payload-type.js +27 -0
  42. package/dist/aun/msg/upload.js +137 -0
  43. package/dist/aun/outbox.js +160 -0
  44. package/dist/aun/rpc/caller.js +42 -0
  45. package/dist/aun/rpc/connection.js +25 -0
  46. package/dist/aun/rpc/index.js +2 -0
  47. package/dist/aun/service-proxy.js +225 -0
  48. package/dist/aun/storage/download.js +29 -0
  49. package/dist/aun/storage/index.js +3 -0
  50. package/dist/aun/storage/manage.js +10 -0
  51. package/dist/aun/storage/upload.js +68 -0
  52. package/dist/channels/aun.js +4147 -0
  53. package/dist/channels/daemon.js +422 -0
  54. package/dist/channels/dingtalk.js +649 -0
  55. package/dist/channels/feishu.js +1789 -0
  56. package/dist/channels/qqbot.js +409 -0
  57. package/dist/channels/wechat.js +817 -0
  58. package/dist/channels/wecom.js +565 -0
  59. package/dist/cli/agent-command.js +641 -0
  60. package/dist/cli/agent.js +1059 -0
  61. package/dist/cli/aun-commands.js +1948 -0
  62. package/dist/cli/bench.js +1228 -0
  63. package/dist/cli/cli-argv.js +66 -0
  64. package/dist/cli/code-stats.js +329 -0
  65. package/dist/cli/command-log.js +82 -0
  66. package/dist/cli/config-selector.js +69 -0
  67. package/dist/cli/config.js +261 -0
  68. package/dist/cli/ctl-command.js +62 -0
  69. package/dist/cli/daemon-commands.js +2887 -0
  70. package/dist/cli/fs-command.js +1447 -0
  71. package/dist/cli/handoff-command.js +302 -0
  72. package/dist/cli/help.js +31 -0
  73. package/dist/cli/index.js +358 -0
  74. package/dist/cli/init-channel.js +1377 -0
  75. package/dist/cli/init.js +553 -0
  76. package/dist/cli/link-rules.js +240 -0
  77. package/dist/cli/model.js +590 -0
  78. package/dist/cli/net-check.js +723 -0
  79. package/dist/cli/queue-command.js +126 -0
  80. package/dist/cli/response.js +345 -0
  81. package/dist/cli/restart-monitor.js +456 -0
  82. package/dist/cli/stats.js +607 -0
  83. package/dist/cli/task-context.js +80 -0
  84. package/dist/cli/trigger-command.js +505 -0
  85. package/dist/cli/version.js +88 -0
  86. package/dist/cli/watch-logs.js +33 -0
  87. package/dist/cli/watch-msg.js +673 -0
  88. package/dist/config/boot-log.js +266 -0
  89. package/dist/config/builtin-role-templates.js +30 -0
  90. package/dist/config/builtin-roles.js +85 -0
  91. package/dist/config/config-batch-get.js +11 -0
  92. package/dist/config/config-field-policy.js +261 -0
  93. package/dist/config/config-manager.js +916 -0
  94. package/dist/config/config-operation-service.js +384 -0
  95. package/dist/config/contact-book.js +371 -0
  96. package/dist/config/gateway-config.js +858 -0
  97. package/dist/config/lifecycle.js +17 -0
  98. package/dist/config/mention-mode.js +27 -0
  99. package/dist/config/merge.js +161 -0
  100. package/dist/config/owner-policy.js +4 -0
  101. package/dist/config/peer-role-resolver.js +139 -0
  102. package/dist/config/resolved-config-op.js +483 -0
  103. package/dist/config/role-config-v4-startup.js +32 -0
  104. package/dist/config/role-config-v5-startup.js +27 -0
  105. package/dist/config/role-schema.js +105 -0
  106. package/dist/config/role-service.js +159 -0
  107. package/dist/config/role-store.js +204 -0
  108. package/dist/config/roles.js +55 -0
  109. package/dist/config/schema-registry.js +154 -0
  110. package/dist/config/snapshot.js +598 -0
  111. package/dist/config-store.js +503 -0
  112. package/dist/core/auth/agent-delegation.js +111 -0
  113. package/dist/core/auth/auth-gateway.js +166 -0
  114. package/dist/core/auth/authenticated-actor.js +6 -0
  115. package/dist/core/auth/authorization-audit.js +110 -0
  116. package/dist/core/auth/operation-authorizer.js +718 -0
  117. package/dist/core/auth/operation-catalog.js +675 -0
  118. package/dist/core/baseagent-loader.js +54 -0
  119. package/dist/core/bootstrap-service.js +175 -0
  120. package/dist/core/capability/capability-manager.js +316 -0
  121. package/dist/core/capability/providers/claude-capability-provider.js +176 -0
  122. package/dist/core/capability/providers/codex-capability-provider.js +148 -0
  123. package/dist/core/capability/providers/gemini-capability-provider.js +10 -0
  124. package/dist/core/capability/types.js +27 -0
  125. package/dist/core/causation/audit.js +103 -0
  126. package/dist/core/causation/aun-association.js +111 -0
  127. package/dist/core/causation/context.js +93 -0
  128. package/dist/core/causation/index.js +4 -0
  129. package/dist/core/causation/types.js +2 -0
  130. package/dist/core/channel-loader.js +277 -0
  131. package/dist/core/command/agent-control.js +616 -0
  132. package/dist/core/command/cli-intent-parser.js +225 -0
  133. package/dist/core/command/command-handler.js +1638 -0
  134. package/dist/core/command/menu-handler.js +3354 -0
  135. package/dist/core/command/menu-protocol.js +247 -0
  136. package/dist/core/command/role-menu.js +1524 -0
  137. package/dist/core/command/slash-gate.js +148 -0
  138. package/dist/core/command/slash-handler.js +3056 -0
  139. package/dist/core/daemon-file-cache.js +216 -0
  140. package/dist/core/event-bus.js +32 -0
  141. package/dist/core/event-catalog.js +740 -0
  142. package/dist/core/evolagent-registry.js +546 -0
  143. package/dist/core/evolagent.js +331 -0
  144. package/dist/core/handoff/dispatcher.js +229 -0
  145. package/dist/core/handoff/mutex.js +46 -0
  146. package/dist/core/handoff/runtime.js +324 -0
  147. package/dist/core/handoff/store.js +537 -0
  148. package/dist/core/handoff/types.js +12 -0
  149. package/dist/core/inference/text-inference.js +173 -0
  150. package/dist/core/interaction-registration.js +10 -0
  151. package/dist/core/interaction-router.js +278 -0
  152. package/dist/core/message/create-status.js +67 -0
  153. package/dist/core/message/im-renderer.js +657 -0
  154. package/dist/core/message/items-formatter.js +76 -0
  155. package/dist/core/message/logical-queue-bridge.js +123 -0
  156. package/dist/core/message/message-bridge.js +803 -0
  157. package/dist/core/message/message-cache.js +56 -0
  158. package/dist/core/message/message-log.js +339 -0
  159. package/dist/core/message/message-processor.js +4 -0
  160. package/dist/core/message/message-queue.js +1436 -0
  161. package/dist/core/message/message-utils.js +70 -0
  162. package/dist/core/message/peer-mode.js +105 -0
  163. package/dist/core/message/pending-hints.js +232 -0
  164. package/dist/core/message/response-depth.js +33 -0
  165. package/dist/core/message/response-engine.js +3776 -0
  166. package/dist/core/message/response-snapshot.js +83 -0
  167. package/dist/core/message/stream-debouncer.js +130 -0
  168. package/dist/core/message/stream-idle-monitor.js +124 -0
  169. package/dist/core/model/config-scope.js +162 -0
  170. package/dist/core/model/field-scope.js +78 -0
  171. package/dist/core/model/model-catalog.js +227 -0
  172. package/dist/core/model/model-diagnostics.js +182 -0
  173. package/dist/core/model/model-permission.js +90 -0
  174. package/dist/core/permission/approval-gateway.js +1017 -0
  175. package/dist/core/permission/ec-command-parser.js +347 -0
  176. package/dist/core/permission/execution-sandbox.js +16 -0
  177. package/dist/core/permission/index.js +6 -0
  178. package/dist/core/permission/mode.js +24 -0
  179. package/dist/core/permission/sandbox-runtime.js +265 -0
  180. package/dist/core/permission/tool-policy.js +987 -0
  181. package/dist/core/permission/unix-socket-policy.js +99 -0
  182. package/dist/core/protected-paths.js +330 -0
  183. package/dist/core/relation/peer-identity.js +222 -0
  184. package/dist/core/relation/peer-key.js +1 -0
  185. package/dist/core/role/runtime-policy.js +141 -0
  186. package/dist/core/session/adapters/claude-session-file-adapter.js +218 -0
  187. package/dist/core/session/adapters/codex-session-file-adapter.js +333 -0
  188. package/dist/core/session/adapters/gemini-session-file-adapter.js +181 -0
  189. package/dist/core/session/session-file-adapter.js +7 -0
  190. package/dist/core/session/session-file-health.js +45 -0
  191. package/dist/core/session/session-fs-store.js +232 -0
  192. package/dist/core/session/session-key.js +24 -0
  193. package/dist/core/session/session-manager.js +1587 -0
  194. package/dist/core/session/session-mapper.js +100 -0
  195. package/dist/core/session/session-renew.js +314 -0
  196. package/dist/core/session/session-title.js +128 -0
  197. package/dist/core/session/session-turn-coordinator.js +205 -0
  198. package/dist/core/session/session-turns.js +67 -0
  199. package/dist/core/system-channels.js +29 -0
  200. package/dist/eck/baseagent-caps.js +18 -0
  201. package/dist/eck/detect.js +47 -0
  202. package/dist/eck/group-rules-sync.js +345 -0
  203. package/dist/eck/init.js +77 -0
  204. package/dist/eck/kit-renderer.js +359 -0
  205. package/dist/eck/manifest-engine.js +446 -0
  206. package/dist/eck/message-renderer.js +199 -0
  207. package/dist/eck/rules-loader.js +28 -0
  208. package/dist/index.js +2870 -4
  209. package/dist/ipc.js +748 -0
  210. package/dist/paths.js +262 -0
  211. package/dist/product.js +18 -0
  212. package/dist/response-system/context-builder.js +71 -0
  213. package/dist/response-system/coordinator.js +117 -0
  214. package/dist/response-system/decision-executor.js +86 -0
  215. package/dist/response-system/engines/v1/index.js +21 -0
  216. package/dist/response-system/engines/v1/interactive-flow.js +27 -0
  217. package/dist/response-system/engines/v1/proactive-flow.js +137 -0
  218. package/dist/response-system/engines/v1/types.js +1 -0
  219. package/dist/response-system/extensions.js +41 -0
  220. package/dist/response-system/index.js +6 -0
  221. package/dist/response-system/modes/index.js +7 -0
  222. package/dist/response-system/modes/single-session/index.js +72 -0
  223. package/dist/response-system/queues/fifo-queue.js +44 -0
  224. package/dist/response-system/queues/index.js +6 -0
  225. package/dist/response-system/queues/lifo-queue.js +42 -0
  226. package/dist/response-system/queues/priority-queue.js +63 -0
  227. package/dist/response-system/registry.js +97 -0
  228. package/dist/response-system/resolver.js +37 -0
  229. package/dist/response-system/selector.js +23 -0
  230. package/dist/response-system/types.js +7 -0
  231. package/dist/stats/billing.js +151 -0
  232. package/dist/stats/budget.js +93 -0
  233. package/dist/stats/db.js +403 -0
  234. package/dist/stats/eck-vars.js +89 -0
  235. package/dist/stats/index.js +11 -0
  236. package/dist/stats/normalizer.js +80 -0
  237. package/dist/stats/price-resolver.js +138 -0
  238. package/dist/stats/query.js +763 -0
  239. package/dist/stats/role-budget.js +168 -0
  240. package/dist/stats/writer.js +151 -0
  241. package/dist/trigger/anomaly-store.js +258 -0
  242. package/dist/trigger/audit.js +152 -0
  243. package/dist/trigger/event-source.js +119 -0
  244. package/dist/trigger/feedback.js +685 -0
  245. package/dist/trigger/history.js +290 -0
  246. package/dist/trigger/manager.js +291 -0
  247. package/dist/trigger/parser.js +520 -0
  248. package/dist/trigger/patch.js +148 -0
  249. package/dist/trigger/scheduler.js +1600 -0
  250. package/dist/trigger/script-executor.js +155 -0
  251. package/dist/trigger/state.js +145 -0
  252. package/dist/trigger/types.js +1 -0
  253. package/dist/trigger/validation.js +621 -0
  254. package/dist/types.js +12 -0
  255. package/dist/utils/aid-bind.js +299 -0
  256. package/dist/utils/atomic-write.js +95 -0
  257. package/dist/utils/avatar-upload.js +123 -0
  258. package/dist/utils/cross-platform.js +297 -0
  259. package/dist/utils/ecweb-utils.js +73 -0
  260. package/dist/utils/error-dict.json +153 -0
  261. package/dist/utils/error-utils.js +349 -0
  262. package/dist/utils/instance-registry.js +437 -0
  263. package/dist/utils/locale.js +21 -0
  264. package/dist/utils/log-writer.js +224 -0
  265. package/dist/utils/logger.js +89 -0
  266. package/dist/utils/markdown-to-plain-text.js +20 -0
  267. package/dist/utils/media-cache.js +271 -0
  268. package/dist/utils/model-prices.jsonl +17 -0
  269. package/dist/utils/npm-ops.js +210 -0
  270. package/dist/utils/process-introspect.js +133 -0
  271. package/dist/utils/process-tree-stats.js +271 -0
  272. package/dist/utils/project-path.js +74 -0
  273. package/dist/utils/stats.js +410 -0
  274. package/dist/utils/tool-summary.js +284 -0
  275. package/dist/utils/welcome.js +268 -0
  276. package/kits/docs/GUIDE.md +20 -0
  277. package/kits/docs/INDEX.md +65 -0
  278. package/kits/docs/aun/CHEATSHEET.md +19 -0
  279. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
  280. package/kits/docs/channels/aun.md +65 -0
  281. package/kits/docs/channels/feishu.md +56 -0
  282. package/kits/docs/context-assembly.md +366 -0
  283. package/kits/docs/eck_templates/GUIDE.template.md +22 -0
  284. package/kits/docs/eck_templates/INDEX.template.md +28 -0
  285. package/kits/docs/eck_templates/path-registry.template.md +33 -0
  286. package/kits/docs/eck_templates/runtime.template.md +19 -0
  287. package/kits/docs/evolcore/INDEX.md +66 -0
  288. package/kits/docs/evolcore/agent.md +69 -0
  289. package/kits/docs/evolcore/aid.md +49 -0
  290. package/kits/docs/evolcore/config.md +149 -0
  291. package/kits/docs/evolcore/ctl.md +46 -0
  292. package/kits/docs/evolcore/event.md +216 -0
  293. package/kits/docs/evolcore/fs-architecture.md +1215 -0
  294. package/kits/docs/evolcore/fs.md +101 -0
  295. package/kits/docs/evolcore/group-fs.md +17 -0
  296. package/kits/docs/evolcore/group-rules.md +226 -0
  297. package/kits/docs/evolcore/group.md +141 -0
  298. package/kits/docs/evolcore/model.md +47 -0
  299. package/kits/docs/evolcore/msg.md +130 -0
  300. package/kits/docs/evolcore/response.md +80 -0
  301. package/kits/docs/evolcore/rpc.md +35 -0
  302. package/kits/docs/evolcore/self-summary.md +29 -0
  303. package/kits/docs/evolcore/stats.md +70 -0
  304. package/kits/docs/evolcore/storage.md +49 -0
  305. package/kits/docs/evolcore/trigger.md +524 -0
  306. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -0
  307. package/kits/docs/identity/PATH_OPS.md +16 -0
  308. package/kits/docs/identity/ROLE_DETAIL.md +23 -0
  309. package/kits/docs/identity/identity-tools.md +26 -0
  310. package/kits/docs/path-registry.md +43 -0
  311. package/kits/docs/prompt-loading-architecture.md +266 -0
  312. package/kits/docs/venues/aun-group.md +45 -0
  313. package/kits/docs/venues/aun-private.md +10 -0
  314. package/kits/docs/venues/client-desktop.md +10 -0
  315. package/kits/docs/venues/client-mobile.md +10 -0
  316. package/kits/docs/venues/feishu-group.md +13 -0
  317. package/kits/docs/venues/feishu-private.md +9 -0
  318. package/kits/docs/venues/group.md +25 -0
  319. package/kits/docs/venues/private.md +10 -0
  320. package/kits/eck_manifest.auxiliary.json +43 -0
  321. package/kits/eck_manifest.json +191 -0
  322. package/kits/eck_message_manifest.json +63 -0
  323. package/kits/migrations/README-role-config-v4.md +32 -0
  324. package/kits/migrations/migrate-role-config-v4.mjs +623 -0
  325. package/kits/migrations/migrate-role-config-v5.mjs +346 -0
  326. package/kits/migrations/rename-config-file.mjs +99 -0
  327. package/kits/rules/01-overview.md +142 -0
  328. package/kits/rules/02-navigation.md +76 -0
  329. package/kits/rules/03-identity.md +34 -0
  330. package/kits/rules/04-relation.md +59 -0
  331. package/kits/rules/05-venue.md +44 -0
  332. package/kits/rules/06-channel.md +59 -0
  333. package/kits/schemas/_meta.json +29 -0
  334. package/kits/schemas/agent-config.schema.1.json +177 -0
  335. package/kits/schemas/agent-config.schema.2.json +239 -0
  336. package/kits/schemas/agent-config.schema.3.json +119 -0
  337. package/kits/schemas/agent-config.schema.4.json +208 -0
  338. package/kits/schemas/agent-config.schema.5.json +326 -0
  339. package/kits/schemas/contact-book.schema.1.json +36 -0
  340. package/kits/schemas/daemon.schema.1.json +90 -0
  341. package/kits/schemas/defaults.schema.1.json +81 -0
  342. package/kits/schemas/menu-exec-schema-commands.md +208 -0
  343. package/kits/schemas/migrations/README.md +28 -0
  344. package/kits/schemas/relation-config.schema.1.json +158 -0
  345. package/kits/schemas/relation-config.schema.2.json +73 -0
  346. package/kits/schemas/relation-config.schema.3.json +50 -0
  347. package/kits/schemas/relation-config.schema.4.json +47 -0
  348. package/kits/schemas/role-config.schema.1.json +200 -0
  349. package/kits/schemas/role-registry.schema.1.json +35 -0
  350. package/kits/schemas/single-session.schema.1.json +31 -0
  351. package/kits/templates/bootstrap-welcome.md +15 -0
  352. package/kits/templates/message-fragments/handoff-request-to-target.md +13 -0
  353. package/kits/templates/message-fragments/handoff-response-to-origin.md +10 -0
  354. package/kits/templates/message-fragments/inject-default.md +2 -0
  355. package/kits/templates/message-fragments/item.md +2 -0
  356. package/kits/templates/roles/admin.json +8 -0
  357. package/kits/templates/roles/member.json +40 -0
  358. package/kits/templates/roles/owner.json +8 -0
  359. package/kits/templates/roles/visitor.json +39 -0
  360. package/kits/templates/system-fragments/baseagent.md +14 -0
  361. package/kits/templates/system-fragments/bootstrap.md +16 -0
  362. package/kits/templates/system-fragments/channel.md +48 -0
  363. package/kits/templates/system-fragments/commands.md +26 -0
  364. package/kits/templates/system-fragments/identity.md +11 -0
  365. package/kits/templates/system-fragments/relation.md +19 -0
  366. package/kits/templates/system-fragments/session.md +53 -0
  367. package/kits/templates/system-fragments/venue.md +31 -0
  368. package/package.json +50 -15
  369. package/dist/index.d.ts +0 -7
  370. package/dist/index.d.ts.map +0 -1
  371. package/dist/index.js.map +0 -1
@@ -0,0 +1,1447 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { TextDecoder } from 'util';
4
+ import { getArgValue, isHelpFlag, wantsHelp } from './help.js';
5
+ import { agentmdGet, getAidStore, isValidAid, loadClient, SLOT } from '../aun/aid/index.js';
6
+ class FsCliError extends Error {
7
+ code;
8
+ suggestion;
9
+ details;
10
+ constructor(code, message, suggestion, details) {
11
+ super(message);
12
+ this.name = 'FsCliError';
13
+ this.code = code;
14
+ this.suggestion = suggestion;
15
+ this.details = details;
16
+ }
17
+ }
18
+ const HELP = `用法: ec fs <command> [args...] [options]
19
+
20
+ Commands:
21
+ ls <AID>:<path> 列目录
22
+ stat [-L] <AID>:<path> 查看节点元数据(默认不跟随软链;-L 跟随)
23
+ lstat <AID>:<path> 查看节点本身(不跟随末级软链)
24
+ cat <AID>:<path> 输出文本文件内容;二进制/大文件返回头部摘要
25
+ cp <local> <AID>:<path> 上传本地文件
26
+ cp <AID>:<path> <local> 下载远程文件
27
+ cp <AID>:<path> <AID>:<path> 远程复制(同后端)
28
+ mv <AID>:<path> <AID>:<path> 移动/改名(同后端)
29
+ rm [-r] <AID>:<path> 删除文件或目录
30
+ mkdir [-p] <AID>:<path> 创建目录
31
+ ln -s <target> <AID>:<path> 创建软链(personal storage)
32
+ chmod [mode] <AID>:<path> 切换公开/私有(personal storage)
33
+ setfacl <AID>:<path> -m|-x ... 设置/移除 ACL(personal storage)
34
+ getfacl <AID>:<path> 查看 ACL(personal storage)
35
+ token issue|revoke|ls <path> 管理访问 token(personal storage)
36
+ find <AID>:<path> [filters] 查找节点
37
+ df <AID>: 查看容量/配额
38
+ mount <target> --volume <id> 挂载实体卷
39
+ mount <target> --source <src> 挂载远程子树
40
+ approve <AID>:<path> 批准待审挂载(personal storage)
41
+ reject <AID>:<path> 拒绝待审挂载(personal storage)
42
+ umount <AID>:<path> 卸载挂载点
43
+
44
+ Options:
45
+ --as <aid> 操作者 AID;也可由 EVOLCORE_SELF_AID 注入
46
+ --format json 输出 JSON
47
+ --overwrite, --force 覆盖目标(用于 cp/mv/ln)或强制删除(用于 rm)
48
+ -r, --recursive 递归(用于 rm/cp)
49
+ -p, --parents 自动创建父目录(用于 mkdir/group 上传)
50
+ -L, --follow stat 跟随末级软链
51
+ --token <token> 读取时携带访问 token(personal storage)
52
+ --content-type <mime> 上传时指定内容类型
53
+ --public 上传后公开可读(personal storage)
54
+ --visibility <public|private> chmod 可见性
55
+ --allow-roles <roles> chmod 角色约束,逗号分隔
56
+ -m aid:<aid>:<perms> setfacl 新增/更新 ACL
57
+ -x aid:<aid> setfacl 移除 ACL
58
+ --source <AID>:<path> mount 虚拟卷来源
59
+ --volume <id> mount 实体卷 ID
60
+ --require-approval mount 需要来源 owner 审批
61
+ --expires <time> mount/token/ACL 过期时间,Unix 秒或 ISO 日期
62
+ --max-uses <n> ACL 最大使用次数
63
+ --max-reads <n> token 最大读取次数
64
+ --readonly, --readwrite mount 只读/读写
65
+ --name <glob> find 名称过滤
66
+ --type <f|d|l> find 类型过滤
67
+ --size <expr> find 大小过滤,如 +1M
68
+ --mtime <expr> find 修改时间过滤
69
+ --head-bytes <n> cat 二进制头部字节数,默认 256
70
+ --max-bytes <n> cat 最大直接输出字节数,默认 1048576
71
+ --aun-path <path> 指定 AUN keystore 根
72
+ --app <name> 指定 AUN app slot
73
+
74
+ 示例:
75
+ ec fs ls alice.agentid.pub:/private/
76
+ ec fs cp ./report.md alice.agentid.pub:/private/report.md --as alice.agentid.pub
77
+ ec fs cp alice.agentid.pub:/private/report.md ./report.md --as alice.agentid.pub
78
+ ec fs cp alice.agentid.pub:/private/a.md bob.agentid.pub:/inbox/a.md --as alice.agentid.pub
79
+ ec fs cat alice.agentid.pub:/private/report.md --as alice.agentid.pub
80
+ ec fs ln -s alice.agentid.pub:/private/report.md alice.agentid.pub:/public/report.md --as alice.agentid.pub
81
+ ec fs chmod +r alice.agentid.pub:/public/report.md --as alice.agentid.pub
82
+ ec fs setfacl alice.agentid.pub:/private/report.md -m aid:bob.agentid.pub:r --as alice.agentid.pub
83
+ ec fs token issue alice.agentid.pub:/public/report.md --expires 2026-12-31 --as alice.agentid.pub
84
+ ec fs find alice.agentid.pub:/private/ --name "*.md" --as alice.agentid.pub
85
+ ec fs df alice.agentid.pub: --as alice.agentid.pub`;
86
+ const VALUE_FLAGS = new Set([
87
+ '--as',
88
+ '--format',
89
+ '--aun-path',
90
+ '--app',
91
+ '--token',
92
+ '--content-type',
93
+ '--max-bytes',
94
+ '--head-bytes',
95
+ '--expected-version',
96
+ '--expires',
97
+ '--name',
98
+ '--pattern',
99
+ '--type',
100
+ '--node-type',
101
+ '--size',
102
+ '--page',
103
+ '--page-size',
104
+ '--mtime',
105
+ '--volume',
106
+ '--source',
107
+ '--source-bucket',
108
+ '--expires-at',
109
+ '--max-uses',
110
+ '--max-reads',
111
+ '--visibility',
112
+ '--allow-roles',
113
+ '--mount-id',
114
+ '--request-id',
115
+ ]);
116
+ let routeProbeClient;
117
+ const SHORT_VALUE_FLAGS = new Set(['-m', '-x']);
118
+ export async function cmdFs(args) {
119
+ const sub = args[0];
120
+ const formatJson = getArgValue(args, '--format') === 'json';
121
+ let opts;
122
+ try {
123
+ opts = parseCommonOptions(args);
124
+ }
125
+ catch (e) {
126
+ outputError(e, formatJson);
127
+ process.exit(1);
128
+ }
129
+ if (!sub || isHelpFlag(sub) || wantsHelp(args)) {
130
+ console.log(HELP);
131
+ return;
132
+ }
133
+ try {
134
+ switch (sub) {
135
+ case 'ls':
136
+ await fsLs(args.slice(1), opts);
137
+ return;
138
+ case 'stat':
139
+ await fsStat(args.slice(1), opts, hasShortFlag(args.slice(1), 'L') || args.includes('--follow'));
140
+ return;
141
+ case 'lstat':
142
+ await fsStat(args.slice(1), opts, false);
143
+ return;
144
+ case 'cat':
145
+ await fsCat(args.slice(1), opts);
146
+ return;
147
+ case 'cp':
148
+ await fsCp(args.slice(1), opts);
149
+ return;
150
+ case 'mv':
151
+ await fsMv(args.slice(1), opts);
152
+ return;
153
+ case 'rm':
154
+ await fsRm(args.slice(1), opts);
155
+ return;
156
+ case 'mkdir':
157
+ await fsMkdir(args.slice(1), opts);
158
+ return;
159
+ case 'ln':
160
+ await fsLn(args.slice(1), opts);
161
+ return;
162
+ case 'chmod':
163
+ await fsChmod(args.slice(1), opts);
164
+ return;
165
+ case 'setfacl':
166
+ await fsSetfacl(args.slice(1), opts);
167
+ return;
168
+ case 'getfacl':
169
+ await fsGetfacl(args.slice(1), opts);
170
+ return;
171
+ case 'token':
172
+ await fsToken(args.slice(1), opts);
173
+ return;
174
+ case 'find':
175
+ await fsFind(args.slice(1), opts);
176
+ return;
177
+ case 'df':
178
+ await fsDf(args.slice(1), opts);
179
+ return;
180
+ case 'mount':
181
+ await fsMount(args.slice(1), opts);
182
+ return;
183
+ case 'approve':
184
+ await fsApproveReject(args.slice(1), opts, 'approve');
185
+ return;
186
+ case 'reject':
187
+ await fsApproveReject(args.slice(1), opts, 'reject');
188
+ return;
189
+ case 'umount':
190
+ case 'unmount':
191
+ await fsUmount(args.slice(1), opts);
192
+ return;
193
+ default:
194
+ throw new FsCliError('UNKNOWN_COMMAND', `未知子命令: ${sub}`, '运行 ec fs --help 查看可用命令。');
195
+ }
196
+ }
197
+ catch (e) {
198
+ outputError(e, opts.formatJson);
199
+ process.exit(1);
200
+ }
201
+ }
202
+ function parseCommonOptions(args) {
203
+ const maxCatBytesRaw = getArgValue(args, '--max-bytes');
204
+ const maxCatBytes = maxCatBytesRaw === undefined ? 1024 * 1024 : Number(maxCatBytesRaw);
205
+ if (!Number.isFinite(maxCatBytes) || maxCatBytes <= 0) {
206
+ throw new FsCliError('INVALID_ARGUMENT', `--max-bytes 必须是正数: ${maxCatBytesRaw}`);
207
+ }
208
+ const headBytesRaw = getArgValue(args, '--head-bytes');
209
+ const headBytes = headBytesRaw === undefined ? 256 : Number(headBytesRaw);
210
+ if (!Number.isInteger(headBytes) || headBytes < 0) {
211
+ throw new FsCliError('INVALID_ARGUMENT', `--head-bytes 必须是非负整数: ${headBytesRaw}`);
212
+ }
213
+ return {
214
+ formatJson: getArgValue(args, '--format') === 'json',
215
+ actorAid: getArgValue(args, '--as') ?? process.env.EVOLCORE_SELF_AID,
216
+ aunPath: getArgValue(args, '--aun-path') ?? process.env.AUN_HOME,
217
+ slotId: getArgValue(args, '--app'),
218
+ token: getArgValue(args, '--token'),
219
+ overwrite: args.includes('--overwrite') || args.includes('--force') || hasShortFlag(args, 'f'),
220
+ publicRead: args.includes('--public'),
221
+ contentType: getArgValue(args, '--content-type'),
222
+ maxCatBytes,
223
+ headBytes,
224
+ };
225
+ }
226
+ function requireActor(opts) {
227
+ const aid = opts.actorAid?.trim();
228
+ if (!aid) {
229
+ throw new FsCliError('MISSING_ACTOR', '缺少操作者身份', '请添加 --as <aid>,或在 agent 会话中注入 EVOLCORE_SELF_AID。');
230
+ }
231
+ if (!isValidAid(aid)) {
232
+ throw new FsCliError('INVALID_AID', `无效操作者 AID: ${aid}`);
233
+ }
234
+ return aid;
235
+ }
236
+ async function withClient(opts, fn) {
237
+ const actorAid = requireActor(opts);
238
+ const store = await getAidStore({ slotId: opts.slotId ?? SLOT.cli, aunPath: opts.aunPath });
239
+ let client;
240
+ try {
241
+ client = await loadClient(store, actorAid);
242
+ await client.connect({ connection_kind: 'short', short_ttl_ms: 30000, auto_reconnect: false });
243
+ const previousRouteProbeClient = routeProbeClient;
244
+ routeProbeClient = client;
245
+ try {
246
+ return await fn({ client, store, actorAid });
247
+ }
248
+ finally {
249
+ routeProbeClient = previousRouteProbeClient;
250
+ }
251
+ }
252
+ catch (e) {
253
+ if (e instanceof FsCliError)
254
+ throw e;
255
+ throw mapBackendError(e);
256
+ }
257
+ finally {
258
+ if (client) {
259
+ try {
260
+ await client.close();
261
+ }
262
+ catch { /* ignore */ }
263
+ }
264
+ try {
265
+ store.close();
266
+ }
267
+ catch { /* ignore */ }
268
+ }
269
+ }
270
+ async function fsLs(args, opts) {
271
+ const target = parseRemoteRequired(firstPositional(args), { command: 'ls' });
272
+ await withClient(opts, async ({ client, store }) => {
273
+ const route = await resolveRoute(target.aid, store);
274
+ if (route.backend === 'group') {
275
+ const result = await client.group.fs.ls(remoteRef(target));
276
+ outputSuccess(opts, {
277
+ command: 'ls',
278
+ backend: route.backend,
279
+ route,
280
+ path: remoteRef(target),
281
+ items: extractItems(result),
282
+ raw: result,
283
+ }, () => printList(target, result));
284
+ }
285
+ else {
286
+ const items = await client.storage.list(target.path || '/', { owner: target.aid, token: opts.token });
287
+ outputSuccess(opts, {
288
+ command: 'ls',
289
+ backend: route.backend,
290
+ route,
291
+ path: remoteRef(target),
292
+ items,
293
+ }, () => printList(target, items));
294
+ }
295
+ });
296
+ }
297
+ async function fsStat(args, opts, follow) {
298
+ const command = follow ? 'stat' : 'lstat';
299
+ const target = parseRemoteRequired(firstPositional(args), { command });
300
+ await withClient(opts, async ({ client, store }) => {
301
+ const route = await resolveRoute(target.aid, store);
302
+ const node = route.backend === 'group'
303
+ ? follow
304
+ ? await client.group.fs.stat(remoteRef(target))
305
+ : await client.group.fs.lstat(remoteRef(target))
306
+ : follow
307
+ ? await client.storage.stat(target.path, { owner: target.aid, token: opts.token })
308
+ : await client.storage.lstat(target.path, { owner: target.aid, token: opts.token });
309
+ outputSuccess(opts, {
310
+ command,
311
+ backend: route.backend,
312
+ route,
313
+ path: remoteRef(target),
314
+ node,
315
+ }, () => printNode(target.aid, node));
316
+ });
317
+ }
318
+ async function fsCat(args, opts) {
319
+ const target = parseRemoteRequired(firstPositional(args), { command: 'cat' });
320
+ await withClient(opts, async ({ client, store }) => {
321
+ const route = await resolveRoute(target.aid, store);
322
+ let statInfo;
323
+ let bytes;
324
+ let binaryHeadOnly = false;
325
+ if (route.backend === 'group') {
326
+ statInfo = await client.group.fs.stat(remoteRef(target));
327
+ const size = numberFrom(statInfo, ['size', 'size_bytes', 'bytes']);
328
+ const downloaded = await client.group.fs.cp(remoteRef(target), { kind: 'blob' }, { verifyHash: true });
329
+ bytes = bytesFromDownload(downloaded);
330
+ statInfo = { ...statInfo, size: bytes.byteLength };
331
+ if (size !== undefined && size > opts.maxCatBytes) {
332
+ binaryHeadOnly = true;
333
+ }
334
+ }
335
+ else {
336
+ statInfo = await client.storage.stat(target.path, { owner: target.aid, token: opts.token });
337
+ const size = numberFrom(statInfo, ['size', 'sizeBytes', 'size_bytes']);
338
+ if (size !== undefined && size > opts.maxCatBytes) {
339
+ bytes = await client.storage.readBytes(target.path, { owner: target.aid, token: opts.token, offset: 0, limit: opts.headBytes });
340
+ binaryHeadOnly = true;
341
+ }
342
+ else {
343
+ bytes = await client.storage.readBytes(target.path, { owner: target.aid, token: opts.token });
344
+ }
345
+ }
346
+ const binary = looksBinary(bytes, contentTypeFrom(statInfo));
347
+ if (binary || binaryHeadOnly) {
348
+ const size = numberFrom(statInfo, ['size', 'sizeBytes', 'size_bytes']) ?? bytes.byteLength;
349
+ const payload = {
350
+ command: 'cat',
351
+ backend: route.backend,
352
+ route,
353
+ path: remoteRef(target),
354
+ size,
355
+ contentType: contentTypeFrom(statInfo),
356
+ binary: true,
357
+ head: {
358
+ encoding: 'base64',
359
+ bytes: Math.min(opts.headBytes, bytes.byteLength),
360
+ data: Buffer.from(bytes.subarray(0, Math.min(opts.headBytes, bytes.byteLength))).toString('base64'),
361
+ },
362
+ };
363
+ outputSuccess(opts, payload, () => {
364
+ console.log(JSON.stringify({
365
+ path: remoteRef(target),
366
+ size,
367
+ content_type: contentTypeFrom(statInfo),
368
+ binary: true,
369
+ head: payload.head,
370
+ }, null, 2));
371
+ });
372
+ return;
373
+ }
374
+ const text = decodeUtf8(bytes, remoteRef(target));
375
+ outputSuccess(opts, {
376
+ command: 'cat',
377
+ backend: route.backend,
378
+ route,
379
+ path: remoteRef(target),
380
+ size: bytes.byteLength,
381
+ contentType: contentTypeFrom(statInfo),
382
+ text,
383
+ }, () => process.stdout.write(text.endsWith('\n') ? text : `${text}\n`));
384
+ });
385
+ }
386
+ async function fsFind(args, opts) {
387
+ const target = parseRemoteRequired(firstPositional(args), { command: 'find' });
388
+ const name = getArgValue(args, '--name') ?? getArgValue(args, '--pattern');
389
+ const nodeType = getArgValue(args, '--type') ?? getArgValue(args, '--node-type');
390
+ const size = getArgValue(args, '--size');
391
+ const mtime = getArgValue(args, '--mtime');
392
+ const page = parseOptionalPositiveInt(getArgValue(args, '--page'), '--page');
393
+ const pageSize = parseOptionalPositiveInt(getArgValue(args, '--page-size'), '--page-size');
394
+ await withClient(opts, async ({ client, store }) => {
395
+ const route = await resolveRoute(target.aid, store);
396
+ if (route.backend === 'group') {
397
+ const result = await client.group.fs.find(remoteRef(target), {
398
+ name,
399
+ pattern: name,
400
+ type: nodeType,
401
+ size,
402
+ mtime,
403
+ page,
404
+ page_size: pageSize,
405
+ });
406
+ outputSuccess(opts, {
407
+ command: 'find',
408
+ backend: route.backend,
409
+ route,
410
+ path: remoteRef(target),
411
+ items: extractItems(result),
412
+ raw: result,
413
+ }, () => printList(target, result));
414
+ }
415
+ else {
416
+ const items = await client.storage.find(target.path, {
417
+ owner: target.aid,
418
+ token: opts.token,
419
+ name,
420
+ nodeType,
421
+ size,
422
+ mtime,
423
+ page,
424
+ pageSize,
425
+ });
426
+ outputSuccess(opts, {
427
+ command: 'find',
428
+ backend: route.backend,
429
+ route,
430
+ path: remoteRef(target),
431
+ items,
432
+ }, () => printList(target, items));
433
+ }
434
+ });
435
+ }
436
+ async function fsCp(args, opts) {
437
+ const positional = collectPositionals(args);
438
+ const srcArg = positional[0];
439
+ const dstArg = positional[1];
440
+ if (!srcArg || !dstArg) {
441
+ throw new FsCliError('INVALID_ARGUMENT', '用法: ec fs cp <src> <dst>', '至少一端必须是 <AID>:<path>。');
442
+ }
443
+ const srcRemote = parseRemoteMaybe(srcArg);
444
+ const dstRemote = parseRemoteMaybe(dstArg);
445
+ if (!srcRemote && !dstRemote) {
446
+ throw new FsCliError('INVALID_ARGUMENT', 'cp 需要至少一端是远程路径', '远程路径格式为 <AID>:<absolute-path>。');
447
+ }
448
+ await withClient(opts, async ({ client, store, actorAid }) => {
449
+ if (srcRemote && dstRemote) {
450
+ const srcRoute = await resolveRoute(srcRemote.aid, store);
451
+ const dstRoute = await resolveRoute(dstRemote.aid, store);
452
+ if (srcRoute.backend !== dstRoute.backend) {
453
+ throw new FsCliError('UNSUPPORTED', '当前版本不支持 personal/group 混合远程复制', '请先下载到本地再上传。');
454
+ }
455
+ if (srcRoute.backend === 'group') {
456
+ const result = await client.group.fs.cp(remoteRef(srcRemote), remoteRef(dstRemote), {
457
+ force: opts.overwrite,
458
+ overwrite: opts.overwrite,
459
+ recursive: hasShortFlag(args, 'r') || args.includes('--recursive'),
460
+ followSymlinks: args.includes('--follow-symlinks'),
461
+ });
462
+ const publishHint = groupRulesPublishHint(dstRemote, actorAid);
463
+ outputSuccess(opts, {
464
+ command: 'cp',
465
+ direction: 'remote-to-remote',
466
+ backend: srcRoute.backend,
467
+ route: srcRoute,
468
+ src: remoteRef(srcRemote),
469
+ dst: remoteRef(dstRemote),
470
+ result,
471
+ ...(publishHint ? { rulesPublishRequired: publishHint } : {}),
472
+ }, () => {
473
+ console.log(`✓ 已复制: ${remoteRef(srcRemote)} -> ${remoteRef(dstRemote)}`);
474
+ printRulesPublishHint(publishHint);
475
+ });
476
+ return;
477
+ }
478
+ const node = await client.storage.copy(srcRemote.path, dstRemote.path, {
479
+ owner: srcRemote.aid,
480
+ dstOwner: dstRemote.aid,
481
+ overwrite: opts.overwrite,
482
+ recursive: hasShortFlag(args, 'r') || args.includes('--recursive'),
483
+ followSymlinks: args.includes('--follow-symlinks'),
484
+ });
485
+ outputSuccess(opts, {
486
+ command: 'cp',
487
+ direction: 'remote-to-remote',
488
+ backend: srcRoute.backend,
489
+ route: srcRoute,
490
+ src: remoteRef(srcRemote),
491
+ dst: remoteRef(dstRemote),
492
+ node,
493
+ }, () => console.log(`✓ 已复制: ${remoteRef(srcRemote)} -> ${remoteRef(dstRemote)}`));
494
+ return;
495
+ }
496
+ if (dstRemote) {
497
+ const localPath = srcArg;
498
+ assertLocalFile(localPath);
499
+ const route = await resolveRoute(dstRemote.aid, store);
500
+ if (route.backend === 'group') {
501
+ const result = await client.group.fs.cp(localPath, remoteRef(dstRemote), {
502
+ force: opts.overwrite,
503
+ overwrite: opts.overwrite,
504
+ contentType: opts.contentType,
505
+ parents: true,
506
+ });
507
+ const publishHint = groupRulesPublishHint(dstRemote, actorAid);
508
+ outputSuccess(opts, {
509
+ command: 'cp',
510
+ direction: 'upload',
511
+ backend: route.backend,
512
+ route,
513
+ localPath,
514
+ path: remoteRef(dstRemote),
515
+ result,
516
+ ...(publishHint ? { rulesPublishRequired: publishHint } : {}),
517
+ }, () => {
518
+ console.log(`✓ 已上传: ${localPath} -> ${remoteRef(dstRemote)}`);
519
+ printRulesPublishHint(publishHint);
520
+ });
521
+ }
522
+ else {
523
+ const node = await client.storage.uploadFile(localPath, dstRemote.path, {
524
+ owner: dstRemote.aid,
525
+ overwrite: opts.overwrite,
526
+ contentType: opts.contentType,
527
+ public: opts.publicRead,
528
+ });
529
+ outputSuccess(opts, {
530
+ command: 'cp',
531
+ direction: 'upload',
532
+ backend: route.backend,
533
+ route,
534
+ localPath,
535
+ path: remoteRef(dstRemote),
536
+ node,
537
+ }, () => console.log(`✓ 已上传: ${localPath} -> ${remoteRef(dstRemote)}`));
538
+ }
539
+ return;
540
+ }
541
+ if (srcRemote) {
542
+ const localPath = dstArg;
543
+ const route = await resolveRoute(srcRemote.aid, store);
544
+ if (route.backend === 'group') {
545
+ const result = await client.group.fs.cp(remoteRef(srcRemote), localPath, {
546
+ force: opts.overwrite,
547
+ overwrite: opts.overwrite,
548
+ verifyHash: true,
549
+ });
550
+ outputSuccess(opts, {
551
+ command: 'cp',
552
+ direction: 'download',
553
+ backend: route.backend,
554
+ route,
555
+ path: remoteRef(srcRemote),
556
+ localPath: result?.localPath ?? localPath,
557
+ result,
558
+ }, () => console.log(`✓ 已下载: ${remoteRef(srcRemote)} -> ${result?.localPath ?? localPath}`));
559
+ }
560
+ else {
561
+ const result = await client.storage.downloadFile(srcRemote.path, localPath, {
562
+ owner: srcRemote.aid,
563
+ token: opts.token,
564
+ overwrite: opts.overwrite,
565
+ verifyHash: true,
566
+ });
567
+ outputSuccess(opts, {
568
+ command: 'cp',
569
+ direction: 'download',
570
+ backend: route.backend,
571
+ route,
572
+ path: remoteRef(srcRemote),
573
+ localPath: result.localPath ?? localPath,
574
+ size: result.size,
575
+ verified: result.verified,
576
+ }, () => console.log(`✓ 已下载: ${remoteRef(srcRemote)} -> ${result.localPath ?? localPath} (${result.size} bytes)`));
577
+ }
578
+ }
579
+ });
580
+ }
581
+ async function fsMv(args, opts) {
582
+ const positional = collectPositionals(args);
583
+ const src = parseRemoteRequired(positional[0], { command: 'mv' });
584
+ const dst = parseRemoteRequired(positional[1], { command: 'mv' });
585
+ const expectedVersion = parseOptionalPositiveInt(getArgValue(args, '--expected-version'), '--expected-version');
586
+ await withClient(opts, async ({ client, store, actorAid }) => {
587
+ const srcRoute = await resolveRoute(src.aid, store);
588
+ const dstRoute = await resolveRoute(dst.aid, store);
589
+ if (srcRoute.backend !== dstRoute.backend) {
590
+ throw new FsCliError('UNSUPPORTED', '当前版本不支持 personal/group 混合远程移动', '请先 cp 到目标,再确认后 rm 源路径。');
591
+ }
592
+ if (srcRoute.backend === 'group') {
593
+ const result = await client.group.fs.mv(remoteRef(src), remoteRef(dst), {
594
+ force: opts.overwrite,
595
+ overwrite: opts.overwrite,
596
+ });
597
+ const publishHint = groupRulesPublishHint(dst, actorAid);
598
+ outputSuccess(opts, {
599
+ command: 'mv',
600
+ backend: srcRoute.backend,
601
+ route: srcRoute,
602
+ src: remoteRef(src),
603
+ dst: remoteRef(dst),
604
+ result,
605
+ ...(publishHint ? { rulesPublishRequired: publishHint } : {}),
606
+ }, () => {
607
+ console.log(`✓ 已移动: ${remoteRef(src)} -> ${remoteRef(dst)}`);
608
+ printRulesPublishHint(publishHint);
609
+ });
610
+ return;
611
+ }
612
+ if (src.aid !== dst.aid) {
613
+ throw new FsCliError('UNSUPPORTED', 'personal storage 不支持跨 owner 原子移动', `请使用 ec fs cp ${remoteRef(src)} ${remoteRef(dst)},确认后再 rm 源路径。`);
614
+ }
615
+ const node = await client.storage.rename(src.path, dst.path, {
616
+ owner: src.aid,
617
+ overwrite: opts.overwrite,
618
+ expectedVersion,
619
+ });
620
+ outputSuccess(opts, {
621
+ command: 'mv',
622
+ backend: srcRoute.backend,
623
+ route: srcRoute,
624
+ src: remoteRef(src),
625
+ dst: remoteRef(dst),
626
+ node,
627
+ }, () => console.log(`✓ 已移动: ${remoteRef(src)} -> ${remoteRef(dst)}`));
628
+ });
629
+ }
630
+ async function fsRm(args, opts) {
631
+ const target = parseRemoteRequired(firstPositional(args), { command: 'rm' });
632
+ const recursive = hasShortFlag(args, 'r') || args.includes('--recursive');
633
+ await withClient(opts, async ({ client, store }) => {
634
+ const route = await resolveRoute(target.aid, store);
635
+ if (route.backend === 'group') {
636
+ const result = await client.group.fs.rm(remoteRef(target), { recursive, force: opts.overwrite });
637
+ outputSuccess(opts, {
638
+ command: 'rm',
639
+ backend: route.backend,
640
+ route,
641
+ path: remoteRef(target),
642
+ result,
643
+ recursive,
644
+ }, () => console.log(`✓ 已删除: ${remoteRef(target)}`));
645
+ }
646
+ else {
647
+ const result = await client.storage.remove(target.path, { owner: target.aid, recursive });
648
+ outputSuccess(opts, {
649
+ command: 'rm',
650
+ backend: route.backend,
651
+ route,
652
+ path: remoteRef(target),
653
+ result,
654
+ recursive,
655
+ }, () => console.log(`✓ 已删除: ${remoteRef(target)}`));
656
+ }
657
+ });
658
+ }
659
+ async function fsMkdir(args, opts) {
660
+ const target = parseRemoteRequired(firstPositional(args), { command: 'mkdir' });
661
+ const parents = hasShortFlag(args, 'p') || args.includes('--parents');
662
+ await withClient(opts, async ({ client, store }) => {
663
+ const route = await resolveRoute(target.aid, store);
664
+ if (route.backend === 'group') {
665
+ const result = await client.group.fs.mkdir(remoteRef(target), { parents });
666
+ outputSuccess(opts, {
667
+ command: 'mkdir',
668
+ backend: route.backend,
669
+ route,
670
+ path: remoteRef(target),
671
+ result,
672
+ }, () => console.log(`✓ 已创建目录: ${remoteRef(target)}`));
673
+ return;
674
+ }
675
+ const node = await client.storage.mkdir(target.path, { owner: target.aid, parents });
676
+ outputSuccess(opts, {
677
+ command: 'mkdir',
678
+ backend: route.backend,
679
+ route,
680
+ path: remoteRef(target),
681
+ node,
682
+ }, () => console.log(`✓ 已创建目录: ${remoteRef(target)}`));
683
+ });
684
+ }
685
+ async function fsLn(args, opts) {
686
+ if (!hasShortFlag(args, 's') && !args.includes('--symbolic')) {
687
+ throw new FsCliError('UNSUPPORTED', 'ec fs ln 只支持软链接', '请使用 ec fs ln -s <target> <link-path>。');
688
+ }
689
+ const positional = collectPositionals(args);
690
+ const target = positional[0];
691
+ if (!target) {
692
+ throw new FsCliError('INVALID_ARGUMENT', '用法: ec fs ln -s <target> <link-path>');
693
+ }
694
+ const link = parseRemoteRequired(positional[1], { command: 'ln' });
695
+ const linkTarget = linkTargetValue(target, link.aid);
696
+ await withClient(opts, async ({ client, store }) => {
697
+ const linkRoute = await resolveRoute(link.aid, store);
698
+ if (linkRoute.backend === 'group') {
699
+ throw new FsCliError('UNSUPPORTED', 'group fs 当前 SDK facade 没有软链创建接口', '请在 personal storage 中创建软链,或等待 group fs 暴露 symlink 能力。');
700
+ }
701
+ const node = opts.overwrite
702
+ ? await client.storage.repoint(link.path, linkTarget, {
703
+ owner: link.aid,
704
+ expectedVersion: parseOptionalPositiveInt(getArgValue(args, '--expected-version'), '--expected-version'),
705
+ })
706
+ : await client.storage.symlink(linkTarget, link.path, {
707
+ owner: link.aid,
708
+ overwrite: false,
709
+ });
710
+ outputSuccess(opts, {
711
+ command: 'ln',
712
+ backend: linkRoute.backend,
713
+ route: linkRoute,
714
+ target,
715
+ link: remoteRef(link),
716
+ node,
717
+ }, () => console.log(`✓ 已创建软链: ${remoteRef(link)} -> ${target}`));
718
+ });
719
+ }
720
+ async function fsChmod(args, opts) {
721
+ const positional = collectPositionals(args);
722
+ const explicitVisibility = getArgValue(args, '--visibility');
723
+ const allowRoles = parseCsv(getArgValue(args, '--allow-roles'));
724
+ const mode = explicitVisibility ? undefined : positional.length >= 2 ? positional[0] : undefined;
725
+ const targetArg = explicitVisibility || allowRoles.length > 0 ? positional[positionalsTargetIndex(positional)] : positional[1];
726
+ const target = parseRemoteRequired(targetArg, { command: 'chmod' });
727
+ const requestedVisibility = parseVisibility(explicitVisibility ?? mode);
728
+ if (!requestedVisibility) {
729
+ throw new FsCliError('INVALID_ARGUMENT', 'chmod 缺少可见性或角色约束', '请使用 +r/o-r,或 --visibility public|private,或 --allow-roles <roles>。');
730
+ }
731
+ await withClient(opts, async ({ client, store }) => {
732
+ const route = await resolveRoute(target.aid, store);
733
+ if (route.backend === 'group') {
734
+ throw new FsCliError('UNSUPPORTED', 'group fs 当前 SDK facade 没有 chmod/setVisibility 接口', '请等待 group fs 暴露权限修改能力,或使用 personal storage 的 chmod。');
735
+ }
736
+ const node = await client.storage.setVisibility(target.path, {
737
+ owner: target.aid,
738
+ visibility: requestedVisibility,
739
+ allowRoles: allowRoles.length > 0 ? allowRoles : undefined,
740
+ });
741
+ outputSuccess(opts, {
742
+ command: 'chmod',
743
+ backend: route.backend,
744
+ route,
745
+ path: remoteRef(target),
746
+ visibility: requestedVisibility,
747
+ allowRoles,
748
+ node,
749
+ }, () => console.log(`✓ 已更新权限: ${remoteRef(target)} (${requestedVisibility})`));
750
+ });
751
+ }
752
+ async function fsSetfacl(args, opts) {
753
+ const positional = collectPositionals(args);
754
+ const target = parseRemoteRequired(positional[0], { command: 'setfacl' });
755
+ const modify = getArgValue(args, '-m');
756
+ const remove = getArgValue(args, '-x');
757
+ const expiresAt = parseOptionalTimestamp(getArgValue(args, '--expires'), '--expires');
758
+ const maxUses = parseOptionalPositiveInt(getArgValue(args, '--max-uses'), '--max-uses');
759
+ if (!!modify === !!remove) {
760
+ throw new FsCliError('INVALID_ARGUMENT', 'setfacl 必须且只能指定 -m 或 -x', '用法: ec fs setfacl <AID>:<path> -m aid:bob.aid.pub:r');
761
+ }
762
+ await withClient(opts, async ({ client, store }) => {
763
+ const route = await resolveRoute(target.aid, store);
764
+ if (route.backend === 'group') {
765
+ throw new FsCliError('UNSUPPORTED', 'group fs 当前 SDK facade 没有 setfacl 接口', '请在 personal storage 中使用 setfacl。');
766
+ }
767
+ if (modify) {
768
+ const [grantee, perms] = parseAclSpec(modify, true);
769
+ const result = await client.storage.setAcl(target.path, {
770
+ owner: target.aid,
771
+ granteeAid: grantee,
772
+ perms,
773
+ expiresAt,
774
+ maxUses,
775
+ });
776
+ outputSuccess(opts, { command: 'setfacl', backend: route.backend, route, path: remoteRef(target), result }, () => console.log(`✓ 已设置 ACL: ${remoteRef(target)}`));
777
+ return;
778
+ }
779
+ const [grantee] = parseAclSpec(remove || '', false);
780
+ const result = await client.storage.removeAcl(target.path, {
781
+ owner: target.aid,
782
+ granteeAid: grantee,
783
+ });
784
+ outputSuccess(opts, { command: 'setfacl', backend: route.backend, route, path: remoteRef(target), result }, () => console.log(`✓ 已移除 ACL: ${remoteRef(target)}`));
785
+ });
786
+ }
787
+ async function fsGetfacl(args, opts) {
788
+ const target = parseRemoteRequired(firstPositional(args), { command: 'getfacl' });
789
+ await withClient(opts, async ({ client, store }) => {
790
+ const route = await resolveRoute(target.aid, store);
791
+ if (route.backend === 'group') {
792
+ throw new FsCliError('UNSUPPORTED', 'group fs 当前 SDK facade 没有 getfacl/listAcl 接口', '请在 personal storage 中使用 getfacl。');
793
+ }
794
+ const result = await client.storage.listAcl(target.path, { owner: target.aid });
795
+ outputSuccess(opts, { command: 'getfacl', backend: route.backend, route, path: remoteRef(target), result }, () => console.log(JSON.stringify(result, null, 2)));
796
+ });
797
+ }
798
+ async function fsToken(args, opts) {
799
+ const sub = args[0];
800
+ if (!sub || isHelpFlag(sub) || wantsHelp(args)) {
801
+ console.log(`用法: ec fs token <issue|revoke|ls> <AID>:<path> [options]`);
802
+ return;
803
+ }
804
+ switch (sub) {
805
+ case 'issue':
806
+ await fsTokenIssue(args.slice(1), opts);
807
+ return;
808
+ case 'revoke':
809
+ await fsTokenRevoke(args.slice(1), opts);
810
+ return;
811
+ case 'ls':
812
+ await fsTokenLs(args.slice(1), opts);
813
+ return;
814
+ default:
815
+ throw new FsCliError('UNKNOWN_COMMAND', `未知 token 子命令: ${sub}`);
816
+ }
817
+ }
818
+ async function fsTokenIssue(args, opts) {
819
+ const target = parseRemoteRequired(firstPositional(args), { command: 'token issue' });
820
+ const expiresAt = parseOptionalTimestamp(getArgValue(args, '--expires'), '--expires');
821
+ const maxReads = parseOptionalPositiveInt(getArgValue(args, '--max-reads'), '--max-reads');
822
+ await withClient(opts, async ({ client, store }) => {
823
+ const route = await resolveRoute(target.aid, store);
824
+ if (route.backend === 'group') {
825
+ throw new FsCliError('UNSUPPORTED', 'group fs 当前 SDK facade 没有 token 接口', '请在 personal storage 中签发 token。');
826
+ }
827
+ const result = await client.storage.issueToken(target.path, {
828
+ owner: target.aid,
829
+ expiresAt,
830
+ maxReads,
831
+ });
832
+ outputSuccess(opts, { command: 'token issue', backend: route.backend, route, path: remoteRef(target), result }, () => {
833
+ const token = String(result?.token ?? result?.accessToken ?? '');
834
+ console.log('✓ 已签发访问令牌');
835
+ if (token)
836
+ console.log(` 令牌: ${token}`);
837
+ });
838
+ });
839
+ }
840
+ async function fsTokenRevoke(args, opts) {
841
+ const target = parseRemoteRequired(firstPositional(args), { command: 'token revoke' });
842
+ const token = getArgValue(args, '--token');
843
+ if (!token) {
844
+ throw new FsCliError('INVALID_ARGUMENT', 'token revoke 需要 --token <token>');
845
+ }
846
+ await withClient(opts, async ({ client, store }) => {
847
+ const route = await resolveRoute(target.aid, store);
848
+ if (route.backend === 'group') {
849
+ throw new FsCliError('UNSUPPORTED', 'group fs 当前 SDK facade 没有 token 接口', '请在 personal storage 中吊销 token。');
850
+ }
851
+ const result = await client.storage.revokeToken(target.path, { owner: target.aid, token });
852
+ outputSuccess(opts, { command: 'token revoke', backend: route.backend, route, path: remoteRef(target), result }, () => console.log(`✓ 已吊销 token: ${remoteRef(target)}`));
853
+ });
854
+ }
855
+ async function fsTokenLs(args, opts) {
856
+ const target = parseRemoteRequired(firstPositional(args), { command: 'token ls' });
857
+ await withClient(opts, async ({ client, store }) => {
858
+ const route = await resolveRoute(target.aid, store);
859
+ if (route.backend === 'group') {
860
+ throw new FsCliError('UNSUPPORTED', 'group fs 当前 SDK facade 没有 token 接口', '请在 personal storage 中使用 token ls。');
861
+ }
862
+ const result = await client.storage.listTokens(target.path, { owner: target.aid });
863
+ outputSuccess(opts, { command: 'token ls', backend: route.backend, route, path: remoteRef(target), result }, () => console.log(JSON.stringify(result, null, 2)));
864
+ });
865
+ }
866
+ async function fsApproveReject(args, opts, action) {
867
+ const target = parseRemoteRequired(firstPositional(args), { command: action });
868
+ const requestId = getArgValue(args, '--request-id');
869
+ const mountId = getArgValue(args, '--mount-id');
870
+ await withClient(opts, async ({ client, store }) => {
871
+ const route = await resolveRoute(target.aid, store);
872
+ if (route.backend === 'group') {
873
+ throw new FsCliError('UNSUPPORTED', `group fs 当前 SDK facade 没有 ${action} 接口`, `请在 personal storage 中使用 ${action}。`);
874
+ }
875
+ const callOpts = { owner: target.aid, requestId, mountId };
876
+ const result = action === 'approve'
877
+ ? await client.storage.approveMount(target.path, callOpts)
878
+ : await client.storage.rejectMount(target.path, callOpts);
879
+ outputSuccess(opts, { command: action, backend: route.backend, route, path: remoteRef(target), result }, () => console.log(`✓ 已${action === 'approve' ? '批准' : '拒绝'}: ${remoteRef(target)}`));
880
+ });
881
+ }
882
+ async function fsDf(args, opts) {
883
+ const target = parseRemoteRequired(firstPositional(args), { command: 'df', allowEmptyPath: true });
884
+ await withClient(opts, async ({ client, store }) => {
885
+ const route = await resolveRoute(target.aid, store);
886
+ if (route.backend === 'group') {
887
+ const result = await client.group.fs.df(`${target.aid}:`);
888
+ outputSuccess(opts, {
889
+ command: 'df',
890
+ backend: route.backend,
891
+ route,
892
+ path: `${target.aid}:`,
893
+ usage: result,
894
+ }, () => printUsage(target.aid, result));
895
+ }
896
+ else {
897
+ const usage = await client.storage.df({ owner: target.aid });
898
+ outputSuccess(opts, {
899
+ command: 'df',
900
+ backend: route.backend,
901
+ route,
902
+ path: `${target.aid}:`,
903
+ usage,
904
+ }, () => printUsage(target.aid, usage));
905
+ }
906
+ });
907
+ }
908
+ async function fsMount(args, opts) {
909
+ const positional = collectPositionals(args);
910
+ const volumeId = getArgValue(args, '--volume');
911
+ const sourceValue = getArgValue(args, '--source');
912
+ let mountTargetArg = positional[0];
913
+ let sourceArg = sourceValue;
914
+ if (!volumeId && !sourceArg && positional.length >= 2) {
915
+ sourceArg = positional[0];
916
+ mountTargetArg = positional[1];
917
+ }
918
+ if ((volumeId && sourceArg) || (!volumeId && !sourceArg)) {
919
+ throw new FsCliError('INVALID_ARGUMENT', 'mount 必须且只能指定 --volume 或 --source', '用法: ec fs mount <target> --volume <id>,或 ec fs mount <target> --source <AID>:<path>。');
920
+ }
921
+ const target = parseRemoteRequired(mountTargetArg, { command: 'mount' });
922
+ const source = sourceArg ? parseRemoteRequired(sourceArg, { command: 'mount' }) : undefined;
923
+ const expiresRaw = getArgValue(args, '--expires') ?? getArgValue(args, '--expires-at');
924
+ const expiresAt = parseOptionalTimestamp(expiresRaw, expiresRaw === getArgValue(args, '--expires') ? '--expires' : '--expires-at');
925
+ const readonly = parseReadonly(args) ?? true;
926
+ const requireApproval = args.includes('--request-approval') || args.includes('--require-approval') || args.includes('--require_approval');
927
+ await withClient(opts, async ({ client, store }) => {
928
+ const route = await resolveRoute(target.aid, store);
929
+ if (route.backend === 'group') {
930
+ const result = await client.group.fs.mount(remoteRef(target), {
931
+ volume_id: volumeId,
932
+ volumeId,
933
+ source_aid: source?.aid,
934
+ source_path: source?.path,
935
+ source: source ? remoteRef(source) : undefined,
936
+ source_bucket: getArgValue(args, '--source-bucket'),
937
+ readonly,
938
+ require_approval: requireApproval || undefined,
939
+ requireApproval: requireApproval || undefined,
940
+ expires_at: expiresAt,
941
+ expiresAt,
942
+ });
943
+ outputSuccess(opts, {
944
+ command: 'mount',
945
+ backend: route.backend,
946
+ route,
947
+ target: remoteRef(target),
948
+ source: source ? remoteRef(source) : undefined,
949
+ volumeId,
950
+ result,
951
+ }, () => console.log(`✓ 已挂载: ${remoteRef(target)}`));
952
+ return;
953
+ }
954
+ const common = {
955
+ owner: target.aid,
956
+ readonly,
957
+ requireApproval,
958
+ expiresAt,
959
+ sourceBucket: getArgValue(args, '--source-bucket'),
960
+ };
961
+ const node = volumeId
962
+ ? await client.storage.mountVolume(volumeId, remoteRef(target), common)
963
+ : await client.storage.mount(remoteRef(source), remoteRef(target), {
964
+ ...common,
965
+ sourceOwner: source.aid,
966
+ });
967
+ outputSuccess(opts, {
968
+ command: 'mount',
969
+ backend: route.backend,
970
+ route,
971
+ target: remoteRef(target),
972
+ source: source ? remoteRef(source) : undefined,
973
+ volumeId,
974
+ node,
975
+ }, () => console.log(`✓ 已挂载: ${remoteRef(target)}`));
976
+ });
977
+ }
978
+ async function fsUmount(args, opts) {
979
+ const target = parseRemoteRequired(firstPositional(args), { command: 'umount' });
980
+ await withClient(opts, async ({ client, store }) => {
981
+ const route = await resolveRoute(target.aid, store);
982
+ if (route.backend === 'group') {
983
+ const result = await client.group.fs.umount(remoteRef(target));
984
+ outputSuccess(opts, {
985
+ command: 'umount',
986
+ backend: route.backend,
987
+ route,
988
+ path: remoteRef(target),
989
+ result,
990
+ }, () => console.log(`✓ 已卸载: ${remoteRef(target)}(数据未删除)`));
991
+ return;
992
+ }
993
+ const result = await client.storage.unmount(remoteRef(target), { owner: target.aid });
994
+ outputSuccess(opts, {
995
+ command: 'umount',
996
+ backend: route.backend,
997
+ route,
998
+ path: remoteRef(target),
999
+ result,
1000
+ }, () => console.log(`✓ 已卸载: ${remoteRef(target)}(数据未删除)`));
1001
+ });
1002
+ }
1003
+ async function resolveRoute(aid, store) {
1004
+ try {
1005
+ const content = await agentmdGet(aid, { store });
1006
+ const aidType = parseAgentMdType(String(content));
1007
+ return {
1008
+ backend: aidType?.toLowerCase() === 'group' ? 'group' : 'personal',
1009
+ aidType,
1010
+ source: 'agentmd',
1011
+ };
1012
+ }
1013
+ catch (e) {
1014
+ if (routeProbeClient && await canReadGroupInfo(routeProbeClient, aid)) {
1015
+ return {
1016
+ backend: 'group',
1017
+ source: 'group.get_info',
1018
+ };
1019
+ }
1020
+ return {
1021
+ backend: 'personal',
1022
+ source: 'default',
1023
+ warning: `无法读取 ${aid} 的 agent.md,已按 personal storage 尝试: ${String(e?.message || e).slice(0, 120)}`,
1024
+ };
1025
+ }
1026
+ }
1027
+ async function canReadGroupInfo(client, groupId) {
1028
+ try {
1029
+ const result = typeof client.group.getInfo === 'function'
1030
+ ? await client.group.getInfo({ group_id: groupId })
1031
+ : await client.call('group.get_info', { group_id: groupId });
1032
+ return !!result;
1033
+ }
1034
+ catch {
1035
+ return false;
1036
+ }
1037
+ }
1038
+ function parseAgentMdType(content) {
1039
+ const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1040
+ if (!m)
1041
+ return undefined;
1042
+ for (const rawLine of m[1].split(/\r?\n/)) {
1043
+ const line = rawLine.trim();
1044
+ if (!line || line.startsWith('#'))
1045
+ continue;
1046
+ const match = line.match(/^type\s*:\s*(.+)$/);
1047
+ if (!match)
1048
+ continue;
1049
+ return stripYamlScalar(match[1]);
1050
+ }
1051
+ return undefined;
1052
+ }
1053
+ function stripYamlScalar(value) {
1054
+ let out = value.trim();
1055
+ const hash = out.indexOf(' #');
1056
+ if (hash >= 0)
1057
+ out = out.slice(0, hash).trim();
1058
+ if ((out.startsWith('"') && out.endsWith('"')) || (out.startsWith("'") && out.endsWith("'"))) {
1059
+ out = out.slice(1, -1);
1060
+ }
1061
+ return out.trim();
1062
+ }
1063
+ function parseRemoteRequired(value, opts) {
1064
+ if (!value) {
1065
+ throw new FsCliError('INVALID_ARGUMENT', `用法: ec fs ${opts.command} <AID>:<path>`);
1066
+ }
1067
+ const parsed = parseRemoteMaybe(value, opts.allowEmptyPath);
1068
+ if (!parsed) {
1069
+ throw new FsCliError('INVALID_PATH', `不是合法远程路径: ${value}`, '远程路径格式必须是 <AID>:<absolute-path>。');
1070
+ }
1071
+ return parsed;
1072
+ }
1073
+ function parseRemoteMaybe(value, allowEmptyPath = false) {
1074
+ const idx = value.indexOf(':');
1075
+ if (idx <= 0)
1076
+ return null;
1077
+ const aid = value.slice(0, idx).trim();
1078
+ const remotePath = value.slice(idx + 1);
1079
+ if (!isValidAid(aid)) {
1080
+ if (remotePath.startsWith('/') || remotePath === '') {
1081
+ throw new FsCliError('INVALID_AID', `无效 AID: ${aid}`);
1082
+ }
1083
+ return null;
1084
+ }
1085
+ if (remotePath === '' && allowEmptyPath) {
1086
+ return { aid, path: '', raw: value };
1087
+ }
1088
+ if (!remotePath.startsWith('/')) {
1089
+ throw new FsCliError('INVALID_PATH', `远程路径必须是绝对路径: ${value}`, '请使用 <AID>:/path/to/file。');
1090
+ }
1091
+ return { aid, path: remotePath || '/', raw: value };
1092
+ }
1093
+ function remoteRef(p) {
1094
+ return `${p.aid}:${p.path}`;
1095
+ }
1096
+ const GROUP_RULES_PATH = '/rules.md';
1097
+ function normalizeRemoteFsPath(value) {
1098
+ const normalized = path.posix.normalize(value.startsWith('/') ? value : `/${value}`);
1099
+ return normalized === '/' ? '/' : normalized;
1100
+ }
1101
+ function isGroupRulesPath(value) {
1102
+ return normalizeRemoteFsPath(value) === GROUP_RULES_PATH;
1103
+ }
1104
+ function groupRulesPublishHint(target, actorAid) {
1105
+ if (!isGroupRulesPath(target.path))
1106
+ return undefined;
1107
+ return {
1108
+ groupId: target.aid,
1109
+ path: GROUP_RULES_PATH,
1110
+ suggestedCommand: `ec group rules ${actorAid} ${target.aid} publish`,
1111
+ };
1112
+ }
1113
+ function printRulesPublishHint(hint) {
1114
+ if (!hint)
1115
+ return;
1116
+ console.error(`⚠ 已写入 ${hint.groupId}:${hint.path},但尚未发布为有效群规则;请执行: ${hint.suggestedCommand}`);
1117
+ }
1118
+ function linkTargetValue(target, linkOwner) {
1119
+ const parsed = parseRemoteMaybe(target);
1120
+ if (parsed) {
1121
+ if (parsed.aid === linkOwner)
1122
+ return parsed.path;
1123
+ return remoteRef(parsed);
1124
+ }
1125
+ return target.replace(/\\/g, '/');
1126
+ }
1127
+ function hasShortFlag(args, flag) {
1128
+ for (const arg of args) {
1129
+ if (!isShortOptionBundle(arg))
1130
+ continue;
1131
+ if (arg.slice(1).includes(flag))
1132
+ return true;
1133
+ }
1134
+ return false;
1135
+ }
1136
+ function isShortOptionBundle(arg) {
1137
+ return /^-[A-Za-z]+$/.test(arg);
1138
+ }
1139
+ function collectPositionals(args) {
1140
+ const out = [];
1141
+ for (let i = 0; i < args.length; i++) {
1142
+ const arg = args[i];
1143
+ if (arg === '--') {
1144
+ out.push(...args.slice(i + 1));
1145
+ break;
1146
+ }
1147
+ if (VALUE_FLAGS.has(arg)) {
1148
+ i++;
1149
+ continue;
1150
+ }
1151
+ if (SHORT_VALUE_FLAGS.has(arg)) {
1152
+ i++;
1153
+ continue;
1154
+ }
1155
+ if (arg.startsWith('--'))
1156
+ continue;
1157
+ if (isShortOptionBundle(arg))
1158
+ continue;
1159
+ out.push(arg);
1160
+ }
1161
+ return out;
1162
+ }
1163
+ function firstPositional(args) {
1164
+ return collectPositionals(args)[0];
1165
+ }
1166
+ function parseOptionalPositiveInt(value, flagName) {
1167
+ if (value === undefined)
1168
+ return undefined;
1169
+ const n = Number(value);
1170
+ if (!Number.isInteger(n) || n <= 0) {
1171
+ throw new FsCliError('INVALID_ARGUMENT', `${flagName} 必须是正整数: ${value}`);
1172
+ }
1173
+ return n;
1174
+ }
1175
+ function parseOptionalTimestamp(value, flagName) {
1176
+ if (value === undefined)
1177
+ return undefined;
1178
+ const numeric = Number(value);
1179
+ if (Number.isFinite(numeric) && numeric > 0)
1180
+ return Math.trunc(numeric);
1181
+ const ms = Date.parse(value);
1182
+ if (!Number.isFinite(ms)) {
1183
+ throw new FsCliError('INVALID_ARGUMENT', `${flagName} 必须是 Unix 时间戳或 ISO 时间: ${value}`);
1184
+ }
1185
+ return Math.floor(ms / 1000);
1186
+ }
1187
+ function parseReadonly(args) {
1188
+ if (args.includes('--rw') || args.includes('--readwrite') || args.includes('--read-write'))
1189
+ return false;
1190
+ if (args.includes('--readonly') || args.includes('--read-only'))
1191
+ return true;
1192
+ return undefined;
1193
+ }
1194
+ function parseCsv(value) {
1195
+ if (!value)
1196
+ return [];
1197
+ return value
1198
+ .split(',')
1199
+ .map(item => item.trim())
1200
+ .filter(Boolean);
1201
+ }
1202
+ function positionalsTargetIndex(positionals) {
1203
+ return positionals.length >= 2 && parseVisibility(positionals[0]) ? 1 : 0;
1204
+ }
1205
+ function parseVisibility(value) {
1206
+ if (!value)
1207
+ return undefined;
1208
+ const mode = value.trim().toLowerCase();
1209
+ if (['public', '+r', 'a+r', 'o+r', 'go+r', 'ugo+r'].includes(mode))
1210
+ return 'public';
1211
+ if (['private', '-r', 'a-r', 'o-r', 'go-r', 'ugo-r'].includes(mode))
1212
+ return 'private';
1213
+ throw new FsCliError('INVALID_ARGUMENT', `不支持的 chmod 模式: ${value}`, '当前 ec fs chmod 只支持 +r/o-r,或 --visibility public|private。');
1214
+ }
1215
+ function parseAclSpec(value, requirePerms) {
1216
+ const parts = value.split(':');
1217
+ if (parts.length < 2 || parts[0] !== 'aid' || !parts[1]) {
1218
+ throw new FsCliError('INVALID_ARGUMENT', 'ACL 条目格式应为 aid:<AID>:<perms> 或 aid:<AID>');
1219
+ }
1220
+ if (requirePerms) {
1221
+ if (parts.length !== 3 || !parts[2]) {
1222
+ throw new FsCliError('INVALID_ARGUMENT', 'setfacl -m 格式应为 aid:<AID>:<perms>');
1223
+ }
1224
+ return [parts[1], parts[2]];
1225
+ }
1226
+ if (parts.length !== 2) {
1227
+ throw new FsCliError('INVALID_ARGUMENT', 'setfacl -x 格式应为 aid:<AID>');
1228
+ }
1229
+ return [parts[1]];
1230
+ }
1231
+ function assertLocalFile(localPath) {
1232
+ let st;
1233
+ try {
1234
+ st = fs.statSync(localPath);
1235
+ }
1236
+ catch (e) {
1237
+ throw new FsCliError('LOCAL_IO_ERROR', `本地文件不存在: ${localPath}`, undefined, { cause: String(e?.message || e) });
1238
+ }
1239
+ if (!st.isFile()) {
1240
+ throw new FsCliError('LOCAL_IO_ERROR', `当前版本只支持上传单个文件: ${localPath}`);
1241
+ }
1242
+ }
1243
+ function extractItems(value) {
1244
+ if (Array.isArray(value))
1245
+ return value;
1246
+ if (Array.isArray(value?.items))
1247
+ return value.items;
1248
+ if (Array.isArray(value?.nodes))
1249
+ return value.nodes;
1250
+ if (Array.isArray(value?.entries))
1251
+ return value.entries;
1252
+ if (Array.isArray(value?.result?.items))
1253
+ return value.result.items;
1254
+ if (Array.isArray(value?.result?.nodes))
1255
+ return value.result.nodes;
1256
+ return [];
1257
+ }
1258
+ function printList(target, value) {
1259
+ const items = extractItems(value);
1260
+ if (items.length === 0) {
1261
+ console.log(`(空) ${remoteRef(target)}`);
1262
+ return;
1263
+ }
1264
+ for (const item of items) {
1265
+ const path = printableItemPath(target.aid, item);
1266
+ const type = String(item?.type ?? item?.node_type ?? item?.kind ?? '-');
1267
+ const size = numberFrom(item, ['size', 'size_bytes', 'bytes']);
1268
+ const suffix = size !== undefined ? ` ${size} bytes` : '';
1269
+ console.log(`${type.padEnd(6)} ${path}${suffix}`);
1270
+ }
1271
+ }
1272
+ function printNode(ownerAid, node) {
1273
+ const path = printableItemPath(ownerAid, node);
1274
+ const type = String(node?.type ?? node?.node_type ?? node?.kind ?? '-');
1275
+ const size = numberFrom(node, ['size', 'size_bytes', 'bytes']);
1276
+ const version = numberFrom(node, ['version', 'etag_version']);
1277
+ const mtime = numberFrom(node, ['mtime', 'updated_at', 'modified_at']);
1278
+ const contentType = contentTypeFrom(node);
1279
+ const target = node?.target ? String(node.target) : undefined;
1280
+ const mountSource = node?.mountSource ?? node?.mount_source;
1281
+ const visibility = node?.isPublic === true || node?.is_public === true || node?.is_private === false
1282
+ ? 'public'
1283
+ : node?.isPublic === false || node?.is_private === true
1284
+ ? 'private'
1285
+ : undefined;
1286
+ console.log(`${type.padEnd(8)} ${path}`);
1287
+ if (size !== undefined)
1288
+ console.log(` Size: ${formatBytes(size)} (${size} bytes)`);
1289
+ if (contentType)
1290
+ console.log(` Type: ${contentType}`);
1291
+ if (visibility)
1292
+ console.log(` Visibility: ${visibility}`);
1293
+ if (version !== undefined)
1294
+ console.log(` Version: ${version}`);
1295
+ if (mtime !== undefined)
1296
+ console.log(` Modified: ${formatTimestamp(mtime)}`);
1297
+ if (target)
1298
+ console.log(` Target: ${target}`);
1299
+ if (mountSource)
1300
+ console.log(` Mount: ${String(mountSource)}`);
1301
+ if (node?.mode)
1302
+ console.log(` Mode: ${String(node.mode)}`);
1303
+ }
1304
+ function printableItemPath(ownerAid, item) {
1305
+ const raw = String(item?.path ?? item?.key ?? item?.object_key ?? item?.name ?? '');
1306
+ if (!raw)
1307
+ return `${ownerAid}:/`;
1308
+ if (raw.includes(':/'))
1309
+ return raw;
1310
+ return `${ownerAid}:${raw.startsWith('/') ? raw : `/${raw}`}`;
1311
+ }
1312
+ function printUsage(aid, usage) {
1313
+ const quota = numberFrom(usage, ['quotaBytes', 'quota_bytes', 'quota', 'size']);
1314
+ const used = numberFrom(usage, ['usedBytes', 'used_bytes', 'used']);
1315
+ const avail = numberFrom(usage, ['availBytes', 'avail_bytes', 'available', 'free']);
1316
+ const count = numberFrom(usage, ['objectCount', 'object_count', 'objects']);
1317
+ console.log(`Filesystem: ${aid}:`);
1318
+ if (quota !== undefined)
1319
+ console.log(` Quota: ${formatBytes(quota)}`);
1320
+ if (used !== undefined)
1321
+ console.log(` Used: ${formatBytes(used)}`);
1322
+ if (avail !== undefined)
1323
+ console.log(` Avail: ${formatBytes(avail)}`);
1324
+ if (count !== undefined)
1325
+ console.log(` Objects: ${count}`);
1326
+ if (quota === undefined && used === undefined && avail === undefined && count === undefined) {
1327
+ console.log(JSON.stringify(usage, null, 2));
1328
+ }
1329
+ }
1330
+ function formatBytes(n) {
1331
+ if (n >= 1024 ** 4)
1332
+ return `${(n / 1024 ** 4).toFixed(1)}TB`;
1333
+ if (n >= 1024 ** 3)
1334
+ return `${(n / 1024 ** 3).toFixed(1)}GB`;
1335
+ if (n >= 1024 ** 2)
1336
+ return `${(n / 1024 ** 2).toFixed(1)}MB`;
1337
+ if (n >= 1024)
1338
+ return `${(n / 1024).toFixed(1)}KB`;
1339
+ return `${n}B`;
1340
+ }
1341
+ function formatTimestamp(value) {
1342
+ const ms = value > 10_000_000_000 ? value : value * 1000;
1343
+ const d = new Date(ms);
1344
+ if (Number.isNaN(d.getTime()))
1345
+ return String(value);
1346
+ return d.toISOString();
1347
+ }
1348
+ function numberFrom(value, keys) {
1349
+ for (const key of keys) {
1350
+ const raw = value?.[key];
1351
+ if (raw === undefined || raw === null || raw === '')
1352
+ continue;
1353
+ const n = Number(raw);
1354
+ if (Number.isFinite(n))
1355
+ return n;
1356
+ }
1357
+ return undefined;
1358
+ }
1359
+ function contentTypeFrom(value) {
1360
+ const raw = value?.contentType ?? value?.content_type ?? value?.mime ?? value?.mime_type;
1361
+ return raw ? String(raw).toLowerCase() : undefined;
1362
+ }
1363
+ function bytesFromDownload(value) {
1364
+ const data = value?.data;
1365
+ if (data instanceof Uint8Array)
1366
+ return data;
1367
+ if (data instanceof ArrayBuffer)
1368
+ return new Uint8Array(data);
1369
+ throw new FsCliError('BACKEND_ERROR', '下载接口未返回字节内容', undefined, value);
1370
+ }
1371
+ function looksBinary(bytes, contentType) {
1372
+ if (contentType) {
1373
+ if (contentType.startsWith('text/'))
1374
+ return false;
1375
+ if (['application/json', 'application/xml', 'application/yaml', 'application/x-yaml', 'application/javascript'].includes(contentType))
1376
+ return false;
1377
+ if (contentType && contentType !== 'application/octet-stream')
1378
+ return true;
1379
+ }
1380
+ const sample = bytes.subarray(0, Math.min(bytes.byteLength, 4096));
1381
+ if (sample.includes(0))
1382
+ return true;
1383
+ let control = 0;
1384
+ for (const b of sample) {
1385
+ if (b < 32 && b !== 9 && b !== 10 && b !== 13)
1386
+ control++;
1387
+ }
1388
+ return sample.length > 0 && control / sample.length > 0.08;
1389
+ }
1390
+ function decodeUtf8(bytes, path) {
1391
+ try {
1392
+ return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
1393
+ }
1394
+ catch {
1395
+ throw new FsCliError('BINARY_FILE', `文件不是有效 UTF-8 文本: ${path}`, `请使用 ec fs cp ${path} <local-path> 下载。`);
1396
+ }
1397
+ }
1398
+ function outputSuccess(opts, payload, human) {
1399
+ if (opts.formatJson) {
1400
+ console.log(JSON.stringify({ ok: true, ...payload }, null, 2));
1401
+ return;
1402
+ }
1403
+ const route = payload.route;
1404
+ if (route?.warning) {
1405
+ console.error(`⚠ ${route.warning}`);
1406
+ }
1407
+ human();
1408
+ }
1409
+ function outputError(error, formatJson) {
1410
+ const e = error instanceof FsCliError ? error : mapBackendError(error);
1411
+ if (formatJson) {
1412
+ console.log(JSON.stringify({
1413
+ ok: false,
1414
+ code: e.code,
1415
+ error: e.message,
1416
+ suggestion: e.suggestion,
1417
+ details: e.details,
1418
+ }, null, 2));
1419
+ return;
1420
+ }
1421
+ console.error(`✗ ${e.message}`);
1422
+ if (e.suggestion)
1423
+ console.error(` ${e.suggestion}`);
1424
+ }
1425
+ function mapBackendError(error) {
1426
+ if (error instanceof FsCliError)
1427
+ return error;
1428
+ const code = String(error?.code ?? error?.name ?? 'BACKEND_ERROR');
1429
+ const message = String(error?.message || error);
1430
+ const upper = code.toUpperCase();
1431
+ if (upper.includes('ENOENT') || upper.includes('NOT_FOUND')) {
1432
+ return new FsCliError('NOT_FOUND', message);
1433
+ }
1434
+ if (upper.includes('EACCES') || upper.includes('FORBIDDEN') || upper.includes('UNAUTHORIZED')) {
1435
+ return new FsCliError('EACCES', message, '请确认操作者 AID 具备该路径权限。');
1436
+ }
1437
+ if (upper.includes('EEXIST')) {
1438
+ return new FsCliError('EEXIST', message, '如需覆盖,请添加 --overwrite。');
1439
+ }
1440
+ if (upper.includes('EISDIR')) {
1441
+ return new FsCliError('EISDIR', message);
1442
+ }
1443
+ if (upper.includes('EUNSUPPORTED') || upper.includes('UNSUPPORTED')) {
1444
+ return new FsCliError('UNSUPPORTED', message);
1445
+ }
1446
+ return new FsCliError('BACKEND_ERROR', message, undefined, { code: error?.code, data: error?.data });
1447
+ }