@aigne/afs-cli 1.11.0-beta.3 → 1.11.0-beta.5

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 (70) hide show
  1. package/README.md +224 -31
  2. package/dist/cli.cjs +119 -27
  3. package/dist/cli.mjs +119 -27
  4. package/dist/cli.mjs.map +1 -1
  5. package/dist/commands/ls.cjs +6 -4
  6. package/dist/commands/ls.mjs +7 -4
  7. package/dist/commands/ls.mjs.map +1 -1
  8. package/dist/commands/mount.cjs +39 -15
  9. package/dist/commands/mount.mjs +39 -15
  10. package/dist/commands/mount.mjs.map +1 -1
  11. package/dist/commands/serve.cjs +9 -6
  12. package/dist/commands/serve.mjs +9 -6
  13. package/dist/commands/serve.mjs.map +1 -1
  14. package/dist/config/loader.cjs +30 -10
  15. package/dist/config/loader.mjs +30 -10
  16. package/dist/config/loader.mjs.map +1 -1
  17. package/dist/config/provider-factory.cjs +1 -0
  18. package/dist/config/provider-factory.mjs +1 -0
  19. package/dist/config/provider-factory.mjs.map +1 -1
  20. package/dist/config/schema.cjs +58 -3
  21. package/dist/config/schema.mjs +57 -3
  22. package/dist/config/schema.mjs.map +1 -1
  23. package/dist/explorer/actions.cjs +246 -0
  24. package/dist/explorer/actions.mjs +240 -0
  25. package/dist/explorer/actions.mjs.map +1 -0
  26. package/dist/explorer/components/dialog.cjs +231 -0
  27. package/dist/explorer/components/dialog.mjs +232 -0
  28. package/dist/explorer/components/dialog.mjs.map +1 -0
  29. package/dist/explorer/components/file-list.cjs +107 -0
  30. package/dist/explorer/components/file-list.mjs +107 -0
  31. package/dist/explorer/components/file-list.mjs.map +1 -0
  32. package/dist/explorer/components/function-bar.cjs +55 -0
  33. package/dist/explorer/components/function-bar.mjs +55 -0
  34. package/dist/explorer/components/function-bar.mjs.map +1 -0
  35. package/dist/explorer/components/index.cjs +5 -0
  36. package/dist/explorer/components/index.mjs +7 -0
  37. package/dist/explorer/components/metadata-panel.cjs +122 -0
  38. package/dist/explorer/components/metadata-panel.mjs +122 -0
  39. package/dist/explorer/components/metadata-panel.mjs.map +1 -0
  40. package/dist/explorer/components/status-bar.cjs +53 -0
  41. package/dist/explorer/components/status-bar.mjs +54 -0
  42. package/dist/explorer/components/status-bar.mjs.map +1 -0
  43. package/dist/explorer/keybindings.cjs +214 -0
  44. package/dist/explorer/keybindings.mjs +213 -0
  45. package/dist/explorer/keybindings.mjs.map +1 -0
  46. package/dist/explorer/screen.cjs +200 -0
  47. package/dist/explorer/screen.mjs +199 -0
  48. package/dist/explorer/screen.mjs.map +1 -0
  49. package/dist/explorer/state.cjs +53 -0
  50. package/dist/explorer/state.mjs +53 -0
  51. package/dist/explorer/state.mjs.map +1 -0
  52. package/dist/explorer/theme.cjs +158 -0
  53. package/dist/explorer/theme.mjs +155 -0
  54. package/dist/explorer/theme.mjs.map +1 -0
  55. package/dist/path-utils.cjs +104 -0
  56. package/dist/path-utils.mjs +104 -0
  57. package/dist/path-utils.mjs.map +1 -0
  58. package/dist/runtime.cjs +47 -33
  59. package/dist/runtime.mjs +47 -33
  60. package/dist/runtime.mjs.map +1 -1
  61. package/dist/ui/header.cjs +60 -0
  62. package/dist/ui/header.mjs +59 -0
  63. package/dist/ui/header.mjs.map +1 -0
  64. package/dist/ui/index.cjs +17 -0
  65. package/dist/ui/index.mjs +15 -0
  66. package/dist/ui/index.mjs.map +1 -0
  67. package/dist/ui/terminal.cjs +97 -0
  68. package/dist/ui/terminal.mjs +95 -0
  69. package/dist/ui/terminal.mjs.map +1 -0
  70. package/package.json +9 -7
package/README.md CHANGED
@@ -1,29 +1,140 @@
1
1
  # @aigne/afs-cli
2
2
 
3
- AFS 命令行工具
3
+ AFS 命令行工具 - AFS-UI 的文本投影 (reference implementation)
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install -g @aigne/afs-cli
9
+ # 或
10
+ pnpm add -g @aigne/afs-cli
11
+ ```
12
+
13
+ ## 快速开始
14
+
15
+ ```bash
16
+ # 配置挂载点
17
+ afs mount add /src fs:///path/to/project/src
18
+ afs mount add /docs fs:///path/to/docs
19
+
20
+ # 列出目录
21
+ afs list /src
22
+
23
+ # 读取文件
24
+ afs read /src/index.ts
25
+
26
+ # 搜索内容
27
+ afs search /src "TODO"
28
+ ```
29
+
30
+ ## 配置文件
31
+
32
+ AFS CLI 使用 `.afs-config/config.toml` 配置挂载点和服务器设置。
33
+
34
+ ### 配置文件位置
35
+
36
+ ```
37
+ ~/.afs-config/config.toml # 用户级 (最低优先级)
38
+ <project>/.afs-config/config.toml # 项目级
39
+ <subdir>/.afs-config/config.toml # 子目录级 (最高优先级)
40
+ ```
41
+
42
+ ### 配置示例
43
+
44
+ ```toml
45
+ # 本地文件系统
46
+ [[mounts]]
47
+ path = "/src"
48
+ uri = "fs:///Users/dev/project/src"
49
+ description = "项目源码"
50
+
51
+ # Git 仓库
52
+ [[mounts]]
53
+ path = "/upstream"
54
+ uri = "git:///path/to/repo?branch=main"
55
+ access_mode = "readonly"
56
+
57
+ # SQLite 数据库
58
+ [[mounts]]
59
+ path = "/db"
60
+ uri = "sqlite:///path/to/app.db"
61
+
62
+ # 远程 AFS 服务器 (带认证)
63
+ [[mounts]]
64
+ path = "/remote"
65
+ uri = "https://afs.example.com/afs"
66
+ token = "${AFS_TOKEN}" # 使用环境变量
67
+
68
+ # HTTP 服务器配置
69
+ [serve]
70
+ host = "localhost"
71
+ port = 3000
72
+ path = "/afs"
73
+ readonly = false
74
+ cors = false
75
+ max_body_size = 10485760 # 10MB
76
+ token = "${AFS_SERVER_TOKEN}" # 服务器认证 token
77
+ ```
78
+
79
+ ### 挂载配置字段
80
+
81
+ | 字段 | 必填 | 说明 |
82
+ |------|------|------|
83
+ | `path` | 是 | 挂载路径 (如 `/src`) |
84
+ | `uri` | 是 | Provider URI (如 `fs:///path`) |
85
+ | `description` | 否 | 人类/LLM 可读描述 |
86
+ | `access_mode` | 否 | 访问模式: `readonly` 或 `readwrite` |
87
+ | `token` | 否 | HTTP Provider 认证 token (支持 `${ENV_VAR}`) |
88
+ | `auth` | 否 | 认证字符串 (支持 `${ENV_VAR}`) |
89
+ | `namespace` | 否 | 命名空间 (用于隔离不同环境) |
90
+ | `options` | 否 | Provider 特定选项 |
4
91
 
5
92
  ## 命令
6
93
 
94
+ ### 文件操作
95
+
7
96
  | 命令 | 功能 |
8
97
  |------|------|
9
98
  | `afs list [path]` (别名: `ls`) | 列出目录内容 |
10
99
  | `afs read <path>` | 读取文件内容 |
11
100
  | `afs write <path> [--content]` | 写入文件 (--content 或 stdin) |
12
- | `afs stat <path>` | 获取文件/目录信息 |
101
+ | `afs stat <path>` | 获取文件/目录元信息 |
102
+ | `afs search <path> <query>` | 搜索文件内容 |
13
103
  | `afs exec <path> <action>` | 执行操作 |
104
+
105
+ ### 挂载管理
106
+
107
+ | 命令 | 功能 |
108
+ |------|------|
14
109
  | `afs mount list` (别名: `ls`) | 列出挂载配置 |
15
110
  | `afs mount add <path> <uri>` | 添加挂载 |
16
111
  | `afs mount remove <path>` (别名: `rm`) | 移除挂载 |
17
112
  | `afs mount validate` | 验证挂载配置 |
18
- | `afs explain [topic]` | 解释概念 (mount, uri, /path) |
19
- | `afs serve` | 启动 HTTP 服务器暴露 AFS 提供者 |
20
113
 
21
- ## HTTP 服务器和远程挂载
114
+ ### 其他命令
115
+
116
+ | 命令 | 功能 |
117
+ |------|------|
118
+ | `afs explain [topic]` | 解释概念 (命令或路径) |
119
+ | `afs serve` | 启动 HTTP 服务器 |
120
+
121
+ ## 支持的 URI 方案
122
+
123
+ | 方案 | 示例 | 说明 |
124
+ |------|------|------|
125
+ | `fs://` | `fs:///path/to/dir` | 本地文件系统 |
126
+ | `git://` | `git:///path/to/repo?branch=main` | Git 仓库 |
127
+ | `sqlite://` | `sqlite:///path/to/db.sqlite` | SQLite 数据库 |
128
+ | `json://` | `json:///path/to/config.json` | JSON/YAML 文件 |
129
+ | `http://` | `http://localhost:3000/afs` | HTTP 远程 AFS |
130
+ | `https://` | `https://api.example.com/afs` | HTTPS 远程 AFS |
131
+
132
+ ## HTTP 服务器
22
133
 
23
- ### 启动 AFS HTTP 服务器
134
+ ### 启动服务器
24
135
 
25
136
  ```bash
26
- # 启动服务器,暴露所有配置的挂载
137
+ # 使用配置文件中的设置启动
27
138
  afs serve
28
139
 
29
140
  # 自定义主机和端口
@@ -39,7 +150,22 @@ afs serve --cors
39
150
  afs serve --path /api/afs
40
151
  ```
41
152
 
42
- ### 挂载远程 AFS 服务器
153
+ ### 服务器选项
154
+
155
+ | 选项 | 默认值 | 说明 |
156
+ |------|--------|------|
157
+ | `--host` | localhost | 监听主机地址 |
158
+ | `--port` | 3000 | 监听端口 |
159
+ | `--path` | /afs | 服务路径前缀 |
160
+ | `--readonly` | false | 只读模式 |
161
+ | `--cors` | false | 启用 CORS |
162
+ | `--maxBodySize` | 10MB | 最大请求体大小 |
163
+
164
+ ### 配置优先级
165
+
166
+ `命令行参数 > config.toml > 默认值`
167
+
168
+ ### 挂载远程服务器
43
169
 
44
170
  ```bash
45
171
  # 添加远程 HTTP 挂载
@@ -48,45 +174,55 @@ afs mount add /remote http://localhost:3000/afs
48
174
  # 添加 HTTPS 挂载
49
175
  afs mount add /api https://api.example.com/afs
50
176
 
51
- # 带描述的挂载
52
- afs mount add /remote http://localhost:3000/afs --description "Remote AFS server"
53
-
54
177
  # 访问远程挂载
55
178
  afs list /remote
56
179
  afs read /remote/file.txt
57
180
  ```
58
181
 
59
- ### 支持的 URI 方案
60
-
61
- | 方案 | 示例 | 说明 |
62
- |------|------|------|
63
- | `fs://` | `fs:///path/to/dir` | 本地文件系统 |
64
- | `git://` | `git:///path/to/repo` | Git 仓库 |
65
- | `sqlite://` | `sqlite:///path/to/db.sqlite` | SQLite 数据库 |
66
- | `json://` | `json:///path/to/config.json` | JSON/YAML 文件 |
67
- | `http://` | `http://localhost:3000/afs` | HTTP 远程 AFS |
68
- | `https://` | `https://api.example.com/afs` | HTTPS 远程 AFS |
69
-
70
182
  ## 输出模式
71
183
 
72
184
  ```bash
73
185
  # 默认: Machine Truth (LLM/脚本友好)
74
- afs list /modules/fs
186
+ afs list /src
187
+ # 输出: 每行一个路径
75
188
 
76
189
  # JSON 结构化输出
77
- afs list /modules/fs --json
190
+ afs list /src --json
78
191
 
79
- # LLM 优化输出
80
- afs list /modules/fs --view=llm
192
+ # LLM 优化输出 (token 高效)
193
+ afs list /src --view=llm
81
194
 
82
- # 人类友好输出
83
- afs list /modules/fs --view=human
195
+ # 人类友好输出 (树形结构)
196
+ afs list /src --view=human
84
197
  ```
85
198
 
86
- ## 运行模式
199
+ ### 输出模式对比
200
+
201
+ | 模式 | 特点 | 适用场景 |
202
+ |------|------|----------|
203
+ | default | 每行一个路径,pipe-safe | 脚本、grep、awk |
204
+ | json | 结构化 JSON | 程序解析 |
205
+ | llm | 语义事实,token 高效 | LLM/Agent |
206
+ | human | 树形结构,带图标 | 人类阅读 |
87
207
 
88
- - **直接模式** (默认): 直接调用 `@aigne/afs` 库
89
- - **daemon 模式** (`--daemon`): 通过 afsd 代理,支持状态持久化
208
+ ## explain 命令
209
+
210
+ explain 是 AFS CLI 的一等公民,专为 LLM/Agent 设计:
211
+
212
+ ```bash
213
+ # 解释命令行为
214
+ afs explain afs ls
215
+
216
+ # 解释 AFS 对象
217
+ afs explain /src/index.ts
218
+
219
+ # JSON 输出 (供 agent 缓存)
220
+ afs explain --json afs read
221
+ ```
222
+
223
+ **explain vs help**:
224
+ - `help`: 解决"我怎么用" - 面向人类
225
+ - `explain`: 解决"它到底做了什么" - 面向 LLM/Agent,输出稳定可解析
90
226
 
91
227
  ## 退出码
92
228
 
@@ -97,3 +233,60 @@ afs list /modules/fs --view=human
97
233
  | 2 | 参数错误 |
98
234
  | 3 | 资源未找到 |
99
235
  | 4 | 权限拒绝 |
236
+ | 5 | 运行时错误 |
237
+
238
+ ## 使用场景
239
+
240
+ ### 本地开发
241
+
242
+ ```bash
243
+ # 配置项目挂载
244
+ afs mount add /src fs://$(pwd)/src
245
+ afs mount add /docs fs://$(pwd)/docs
246
+
247
+ # 启动开发服务器供团队访问
248
+ afs serve --host 0.0.0.0 --port 3000
249
+ ```
250
+
251
+ ### CI/CD 测试
252
+
253
+ ```bash
254
+ # 启动测试数据服务器
255
+ afs serve --readonly --port 8080 &
256
+
257
+ # 测试用例访问
258
+ curl http://localhost:8080/afs/rpc \
259
+ -d '{"method":"read","params":{"path":"/test-fixtures/data.json"}}'
260
+ ```
261
+
262
+ ### 临时文件共享
263
+
264
+ ```bash
265
+ # 快速共享当前目录
266
+ afs mount add /share fs://$(pwd)
267
+ afs serve --host 0.0.0.0
268
+
269
+ # 其他人访问
270
+ afs mount add /remote-share http://your-ip:3000/afs
271
+ afs list /remote-share
272
+ ```
273
+
274
+ ### 使用配置文件
275
+
276
+ ```bash
277
+ # 创建配置
278
+ cat > .afs-config/config.toml << 'EOF'
279
+ [[mounts]]
280
+ path = "/data"
281
+ uri = "fs:///var/data"
282
+
283
+ [serve]
284
+ host = "0.0.0.0"
285
+ port = 8080
286
+ readonly = true
287
+ cors = true
288
+ EOF
289
+
290
+ # 直接启动,无需参数
291
+ afs serve
292
+ ```
package/dist/cli.cjs CHANGED
@@ -2,6 +2,8 @@
2
2
  const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
3
3
  const require_version = require('./version.cjs');
4
4
  const require_exec = require('./commands/exec.cjs');
5
+ const require_loader = require('./config/loader.cjs');
6
+ const require_index = require('./ui/index.cjs');
5
7
  const require_mount = require('./commands/mount.cjs');
6
8
  const require_explain = require('./commands/explain.cjs');
7
9
  const require_ls = require('./commands/ls.cjs');
@@ -12,6 +14,7 @@ const require_stat = require('./commands/stat.cjs');
12
14
  const require_write = require('./commands/write.cjs');
13
15
  require('./commands/index.cjs');
14
16
  const require_errors = require('./errors.cjs');
17
+ const require_screen = require('./explorer/screen.cjs');
15
18
  let yargs = require("yargs");
16
19
  yargs = require_rolldown_runtime.__toESM(yargs);
17
20
  let yargs_helpers = require("yargs/helpers");
@@ -31,19 +34,58 @@ let yargs_helpers = require("yargs/helpers");
31
34
  * - afs write <path> Write file content (--content or stdin)
32
35
  * - afs exec <path> <action> Execute operation
33
36
  * - afs serve Start HTTP server to expose AFS
37
+ * - afs explore [path] Interactive TUI file explorer
34
38
  *
35
- * Output modes:
36
- * - Default: Machine Truth (LLM/script friendly)
39
+ * Output modes (default: human):
40
+ * - --view=human: Human friendly format with colors (default)
41
+ * - --view=llm: LLM optimized output (token-efficient)
42
+ * - --view=default: Machine truth (script/pipe friendly)
37
43
  * - --json: Structured JSON
38
- * - --view=llm: LLM optimized output
39
- * - --view=human: Human friendly format
40
44
  */
41
45
  function getViewType(argv) {
42
46
  if (argv.json) return "json";
43
47
  if (argv.view) return argv.view;
44
- return "default";
48
+ return "human";
49
+ }
50
+ let headerShown = false;
51
+ /**
52
+ * Show header if in human view mode and not already shown
53
+ */
54
+ async function maybeShowHeader(view) {
55
+ if (view !== "human" || headerShown || !require_index.shouldShowHeader()) return;
56
+ headerShown = true;
57
+ try {
58
+ require_index.printHeader({
59
+ version: require_version.VERSION,
60
+ mountCount: (await new require_loader.ConfigLoader().load(process.cwd())).mounts.length
61
+ });
62
+ } catch {
63
+ require_index.printHeader({
64
+ version: require_version.VERSION,
65
+ mountCount: 0
66
+ });
67
+ }
68
+ }
69
+ async function showHeaderIfNeeded() {
70
+ const args = process.argv.slice(2);
71
+ if ((args.length === 0 || args.includes("--help") || args.includes("-h") || args.length === 1 && [
72
+ "help",
73
+ "mount",
74
+ "explain"
75
+ ].includes(args[0])) && require_index.shouldShowHeader()) try {
76
+ require_index.printHeader({
77
+ version: require_version.VERSION,
78
+ mountCount: (await new require_loader.ConfigLoader().load(process.cwd())).mounts.length
79
+ });
80
+ } catch {
81
+ require_index.printHeader({
82
+ version: require_version.VERSION,
83
+ mountCount: 0
84
+ });
85
+ }
45
86
  }
46
87
  async function main() {
88
+ await showHeaderIfNeeded();
47
89
  const cli = (0, yargs.default)((0, yargs_helpers.hideBin)(process.argv)).scriptName("afs").version(require_version.VERSION).alias("version", "V").help("help").alias("help", "h").usage("$0 <command> [options]").option("json", {
48
90
  type: "boolean",
49
91
  description: "Output in JSON format",
@@ -55,7 +97,8 @@ async function main() {
55
97
  "llm",
56
98
  "human"
57
99
  ],
58
- description: "Output view format",
100
+ default: "human",
101
+ description: "Output format (llm for AI agents, default for scripts)",
59
102
  global: true
60
103
  }).command(["list [path]", "ls [path]"], "List directory contents", (yargs$2) => yargs$2.positional("path", {
61
104
  type: "string",
@@ -77,29 +120,32 @@ async function main() {
77
120
  type: "string",
78
121
  description: "Glob pattern to filter entries (e.g., *.ts, **/*.js)"
79
122
  }), async (argv) => {
123
+ const view = getViewType(argv);
124
+ await maybeShowHeader(view);
80
125
  const result = await require_ls.lsCommand(await require_runtime.createRuntime(), argv.path, {
81
126
  maxDepth: argv.depth,
82
127
  limit: argv.limit,
83
128
  maxChildren: argv["max-children"],
84
129
  pattern: argv.pattern
85
130
  });
86
- const view = getViewType(argv);
87
131
  console.log(require_ls.formatLsOutput(result, view));
88
132
  }).command("stat <path>", "Get file or directory info", (yargs$2) => yargs$2.positional("path", {
89
133
  type: "string",
90
134
  demandOption: true,
91
135
  description: "Path to stat"
92
136
  }), async (argv) => {
93
- const result = await require_stat.statCommand(await require_runtime.createRuntime(), argv.path);
94
137
  const view = getViewType(argv);
138
+ await maybeShowHeader(view);
139
+ const result = await require_stat.statCommand(await require_runtime.createRuntime(), argv.path);
95
140
  console.log(require_stat.formatStatOutput(result, view));
96
141
  }).command("read <path>", "Read file content", (yargs$2) => yargs$2.positional("path", {
97
142
  type: "string",
98
143
  demandOption: true,
99
144
  description: "Path to read"
100
145
  }), async (argv) => {
101
- const result = await require_read.readCommand(await require_runtime.createRuntime(), argv.path);
102
146
  const view = getViewType(argv);
147
+ await maybeShowHeader(view);
148
+ const result = await require_read.readCommand(await require_runtime.createRuntime(), argv.path);
103
149
  console.log(require_read.formatReadOutput(result, view));
104
150
  }).command("write <path>", "Write content to file (from --content or stdin)", (yargs$2) => yargs$2.positional("path", {
105
151
  type: "string",
@@ -113,6 +159,8 @@ async function main() {
113
159
  default: false,
114
160
  description: "Append to file instead of overwrite"
115
161
  }), async (argv) => {
162
+ const view = getViewType(argv);
163
+ await maybeShowHeader(view);
116
164
  let content;
117
165
  if (argv.content !== void 0) content = argv.content;
118
166
  else {
@@ -121,7 +169,6 @@ async function main() {
121
169
  content = Buffer.concat(chunks).toString("utf-8");
122
170
  }
123
171
  const result = await require_write.writeCommand(await require_runtime.createRuntime(), argv.path, content, { append: argv.append });
124
- const view = getViewType(argv);
125
172
  console.log(require_write.formatWriteOutput(result, view));
126
173
  if (!result.success) process.exit(require_errors.ExitCode.RUNTIME_ERROR);
127
174
  }).command("exec <path> [action]", "Execute operation on path", (yargs$2) => yargs$2.positional("path", {
@@ -136,14 +183,16 @@ async function main() {
136
183
  type: "string",
137
184
  description: "JSON parameters for the action"
138
185
  }), async (argv) => {
186
+ const view = getViewType(argv);
187
+ await maybeShowHeader(view);
139
188
  const params = argv.params ? JSON.parse(argv.params) : {};
140
189
  const result = await require_exec.execCommand(await require_runtime.createRuntime(), argv.path, argv.action, params);
141
- const view = getViewType(argv);
142
190
  console.log(require_exec.formatExecOutput(result, view));
143
191
  if (!result.success) process.exit(require_errors.ExitCode.RUNTIME_ERROR);
144
192
  }).command("mount", "Manage mount configurations", (yargs$2) => yargs$2.command(["list", "ls"], "List all mounts", () => {}, async (argv) => {
145
- const result = await require_mount.mountListCommand(process.cwd());
146
193
  const view = getViewType(argv);
194
+ await maybeShowHeader(view);
195
+ const result = await require_mount.mountListCommand(process.cwd());
147
196
  console.log(require_mount.formatMountListOutput(result.mounts, view));
148
197
  }).command("add <path> <uri>", "Add a new mount (path=virtual path, uri=data source)", (yargs$3) => yargs$3.positional("path", {
149
198
  type: "string",
@@ -156,12 +205,23 @@ async function main() {
156
205
  }).option("description", {
157
206
  type: "string",
158
207
  description: "Human-readable description for this mount"
208
+ }).option("token", {
209
+ type: "string",
210
+ description: "Bearer token for HTTP provider authorization"
159
211
  }), async (argv) => {
160
- const result = await require_mount.mountAddCommand(process.cwd(), argv.path, argv.uri, { description: argv.description });
161
- if (getViewType(argv) === "json") console.log(JSON.stringify(result, null, 2));
162
- else if (result.success) console.log(`Added mount ${argv.path}`);
163
- else {
164
- console.error(result.message);
212
+ const view = getViewType(argv);
213
+ await maybeShowHeader(view);
214
+ const result = await require_mount.mountAddCommand(process.cwd(), argv.path, argv.uri, {
215
+ description: argv.description,
216
+ token: argv.token
217
+ });
218
+ if (view === "json") console.log(JSON.stringify(result, null, 2));
219
+ else if (result.success) {
220
+ const msg = view === "human" ? `${require_index.colors.success("Added mount")} ${require_index.colors.cyan(result.normalizedPath)}` : `Added mount ${result.normalizedPath}`;
221
+ console.log(msg);
222
+ } else {
223
+ const msg = view === "human" ? require_index.colors.error(result.message) : result.message;
224
+ console.error(msg);
165
225
  process.exit(require_errors.ExitCode.RUNTIME_ERROR);
166
226
  }
167
227
  }).command(["remove <path>", "rm <path>"], "Remove a mount", (yargs$3) => yargs$3.positional("path", {
@@ -169,20 +229,33 @@ async function main() {
169
229
  demandOption: true,
170
230
  description: "Virtual path to remove (e.g., /src)"
171
231
  }), async (argv) => {
232
+ const view = getViewType(argv);
233
+ await maybeShowHeader(view);
172
234
  const result = await require_mount.mountRemoveCommand(process.cwd(), argv.path);
173
- if (getViewType(argv) === "json") console.log(JSON.stringify(result, null, 2));
174
- else if (result.success) console.log(`Removed mount ${argv.path}`);
175
- else {
176
- console.error(result.message);
235
+ if (view === "json") console.log(JSON.stringify(result, null, 2));
236
+ else if (result.success) {
237
+ const msg = view === "human" ? `${require_index.colors.success("Removed mount")} ${require_index.colors.cyan(argv.path)}` : `Removed mount ${argv.path}`;
238
+ console.log(msg);
239
+ } else {
240
+ const msg = view === "human" ? require_index.colors.error(result.message) : result.message;
241
+ console.error(msg);
177
242
  process.exit(require_errors.ExitCode.RUNTIME_ERROR);
178
243
  }
179
244
  }).command("validate", "Validate mount configuration", () => {}, async (argv) => {
245
+ const view = getViewType(argv);
246
+ await maybeShowHeader(view);
180
247
  const result = await require_mount.mountValidateCommand(process.cwd());
181
- if (getViewType(argv) === "json") console.log(JSON.stringify(result, null, 2));
182
- else if (result.valid) console.log("Configuration is valid");
183
- else {
184
- console.error("Configuration has errors:");
185
- for (const error of result.errors) console.error(` - ${error}`);
248
+ if (view === "json") console.log(JSON.stringify(result, null, 2));
249
+ else if (result.valid) {
250
+ const msg = view === "human" ? require_index.colors.success("Configuration is valid") : "Configuration is valid";
251
+ console.log(msg);
252
+ } else {
253
+ const titleMsg = view === "human" ? require_index.colors.error("Configuration has errors:") : "Configuration has errors:";
254
+ console.error(titleMsg);
255
+ for (const error of result.errors) {
256
+ const errMsg = view === "human" ? ` ${require_index.colors.dim("-")} ${require_index.colors.error(error)}` : ` - ${error}`;
257
+ console.error(errMsg);
258
+ }
186
259
  process.exit(require_errors.ExitCode.RUNTIME_ERROR);
187
260
  }
188
261
  }).demandCommand(1, "Please specify a mount subcommand"), () => {}).command("explain [topic]", "Explain AFS concepts or AFS object", (yargs$2) => yargs$2.positional("topic", {
@@ -190,6 +263,7 @@ async function main() {
190
263
  description: "Topic (mount, path, uri) or AFS path (e.g., /modules/src)"
191
264
  }), async (argv) => {
192
265
  const view = getViewType(argv);
266
+ await maybeShowHeader(view);
193
267
  const topic = argv.topic;
194
268
  if (topic?.startsWith("/")) {
195
269
  const result = await require_explain.explainPathCommand(await require_runtime.createRuntime(), topic);
@@ -221,17 +295,35 @@ async function main() {
221
295
  }).option("max-body", {
222
296
  type: "number",
223
297
  description: "Maximum request body size in bytes (default: 10MB)"
298
+ }).option("token", {
299
+ type: "string",
300
+ description: "Bearer token for authorization"
224
301
  }), async (argv) => {
302
+ await maybeShowHeader(getViewType(argv));
225
303
  const result = await require_serve.serveCommand({
226
304
  host: argv.host,
227
305
  port: argv.port,
228
306
  path: argv.path,
229
307
  readonly: argv.readonly,
230
308
  cors: argv.cors,
231
- maxBodySize: argv["max-body"]
309
+ maxBodySize: argv["max-body"],
310
+ token: argv.token
232
311
  });
233
312
  console.log(require_serve.formatServeOutput(result));
234
313
  await new Promise(() => {});
314
+ }).command("explore [path]", "Interactive TUI file explorer (PC Tools style)", (yargs$2) => yargs$2.positional("path", {
315
+ type: "string",
316
+ default: "/",
317
+ description: "Starting path to explore"
318
+ }), async (argv) => {
319
+ const configLoader = new require_loader.ConfigLoader();
320
+ const config = await configLoader.load(process.cwd());
321
+ await require_screen.createExplorerScreen({
322
+ runtime: await require_runtime.createRuntime(process.cwd(), { configLoader }),
323
+ startPath: argv.path,
324
+ version: require_version.VERSION,
325
+ mountCount: config.mounts.length
326
+ });
235
327
  }).demandCommand(1, "Please specify a command").strict();
236
328
  try {
237
329
  await cli.parse();