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

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 (67) hide show
  1. package/README.md +209 -31
  2. package/dist/cli.cjs +107 -25
  3. package/dist/cli.mjs +107 -25
  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 +38 -15
  9. package/dist/commands/mount.mjs +38 -15
  10. package/dist/commands/mount.mjs.map +1 -1
  11. package/dist/commands/serve.cjs +6 -5
  12. package/dist/commands/serve.mjs +6 -5
  13. package/dist/commands/serve.mjs.map +1 -1
  14. package/dist/config/loader.cjs +14 -3
  15. package/dist/config/loader.mjs +14 -3
  16. package/dist/config/loader.mjs.map +1 -1
  17. package/dist/config/schema.cjs +55 -2
  18. package/dist/config/schema.mjs +54 -2
  19. package/dist/config/schema.mjs.map +1 -1
  20. package/dist/explorer/actions.cjs +246 -0
  21. package/dist/explorer/actions.mjs +240 -0
  22. package/dist/explorer/actions.mjs.map +1 -0
  23. package/dist/explorer/components/dialog.cjs +231 -0
  24. package/dist/explorer/components/dialog.mjs +232 -0
  25. package/dist/explorer/components/dialog.mjs.map +1 -0
  26. package/dist/explorer/components/file-list.cjs +107 -0
  27. package/dist/explorer/components/file-list.mjs +107 -0
  28. package/dist/explorer/components/file-list.mjs.map +1 -0
  29. package/dist/explorer/components/function-bar.cjs +55 -0
  30. package/dist/explorer/components/function-bar.mjs +55 -0
  31. package/dist/explorer/components/function-bar.mjs.map +1 -0
  32. package/dist/explorer/components/index.cjs +5 -0
  33. package/dist/explorer/components/index.mjs +7 -0
  34. package/dist/explorer/components/metadata-panel.cjs +122 -0
  35. package/dist/explorer/components/metadata-panel.mjs +122 -0
  36. package/dist/explorer/components/metadata-panel.mjs.map +1 -0
  37. package/dist/explorer/components/status-bar.cjs +53 -0
  38. package/dist/explorer/components/status-bar.mjs +54 -0
  39. package/dist/explorer/components/status-bar.mjs.map +1 -0
  40. package/dist/explorer/keybindings.cjs +214 -0
  41. package/dist/explorer/keybindings.mjs +213 -0
  42. package/dist/explorer/keybindings.mjs.map +1 -0
  43. package/dist/explorer/screen.cjs +200 -0
  44. package/dist/explorer/screen.mjs +199 -0
  45. package/dist/explorer/screen.mjs.map +1 -0
  46. package/dist/explorer/state.cjs +53 -0
  47. package/dist/explorer/state.mjs +53 -0
  48. package/dist/explorer/state.mjs.map +1 -0
  49. package/dist/explorer/theme.cjs +158 -0
  50. package/dist/explorer/theme.mjs +155 -0
  51. package/dist/explorer/theme.mjs.map +1 -0
  52. package/dist/path-utils.cjs +104 -0
  53. package/dist/path-utils.mjs +104 -0
  54. package/dist/path-utils.mjs.map +1 -0
  55. package/dist/runtime.cjs +47 -33
  56. package/dist/runtime.mjs +47 -33
  57. package/dist/runtime.mjs.map +1 -1
  58. package/dist/ui/header.cjs +60 -0
  59. package/dist/ui/header.mjs +59 -0
  60. package/dist/ui/header.mjs.map +1 -0
  61. package/dist/ui/index.cjs +17 -0
  62. package/dist/ui/index.mjs +15 -0
  63. package/dist/ui/index.mjs.map +1 -0
  64. package/dist/ui/terminal.cjs +97 -0
  65. package/dist/ui/terminal.mjs +95 -0
  66. package/dist/ui/terminal.mjs.map +1 -0
  67. package/package.json +9 -7
package/dist/cli.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { VERSION } from "./version.mjs";
3
3
  import { execCommand, formatExecOutput } from "./commands/exec.mjs";
4
+ import { ConfigLoader } from "./config/loader.mjs";
5
+ import { colors, printHeader, shouldShowHeader } from "./ui/index.mjs";
4
6
  import { formatMountListOutput, mountAddCommand, mountListCommand, mountRemoveCommand, mountValidateCommand } from "./commands/mount.mjs";
5
7
  import { explainCommand, explainPathCommand, formatExplainOutput, formatPathExplainOutput } from "./commands/explain.mjs";
6
8
  import { formatLsOutput, lsCommand } from "./commands/ls.mjs";
@@ -11,6 +13,7 @@ import { formatStatOutput, statCommand } from "./commands/stat.mjs";
11
13
  import { formatWriteOutput, writeCommand } from "./commands/write.mjs";
12
14
  import "./commands/index.mjs";
13
15
  import { CLIError, ExitCode } from "./errors.mjs";
16
+ import { createExplorerScreen } from "./explorer/screen.mjs";
14
17
  import yargs from "yargs";
15
18
  import { hideBin } from "yargs/helpers";
16
19
 
@@ -29,19 +32,58 @@ import { hideBin } from "yargs/helpers";
29
32
  * - afs write <path> Write file content (--content or stdin)
30
33
  * - afs exec <path> <action> Execute operation
31
34
  * - afs serve Start HTTP server to expose AFS
35
+ * - afs explore [path] Interactive TUI file explorer
32
36
  *
33
- * Output modes:
34
- * - Default: Machine Truth (LLM/script friendly)
37
+ * Output modes (default: human):
38
+ * - --view=human: Human friendly format with colors (default)
39
+ * - --view=llm: LLM optimized output (token-efficient)
40
+ * - --view=default: Machine truth (script/pipe friendly)
35
41
  * - --json: Structured JSON
36
- * - --view=llm: LLM optimized output
37
- * - --view=human: Human friendly format
38
42
  */
39
43
  function getViewType(argv) {
40
44
  if (argv.json) return "json";
41
45
  if (argv.view) return argv.view;
42
- return "default";
46
+ return "human";
47
+ }
48
+ let headerShown = false;
49
+ /**
50
+ * Show header if in human view mode and not already shown
51
+ */
52
+ async function maybeShowHeader(view) {
53
+ if (view !== "human" || headerShown || !shouldShowHeader()) return;
54
+ headerShown = true;
55
+ try {
56
+ printHeader({
57
+ version: VERSION,
58
+ mountCount: (await new ConfigLoader().load(process.cwd())).mounts.length
59
+ });
60
+ } catch {
61
+ printHeader({
62
+ version: VERSION,
63
+ mountCount: 0
64
+ });
65
+ }
66
+ }
67
+ async function showHeaderIfNeeded() {
68
+ const args = process.argv.slice(2);
69
+ if ((args.length === 0 || args.includes("--help") || args.includes("-h") || args.length === 1 && [
70
+ "help",
71
+ "mount",
72
+ "explain"
73
+ ].includes(args[0])) && shouldShowHeader()) try {
74
+ printHeader({
75
+ version: VERSION,
76
+ mountCount: (await new ConfigLoader().load(process.cwd())).mounts.length
77
+ });
78
+ } catch {
79
+ printHeader({
80
+ version: VERSION,
81
+ mountCount: 0
82
+ });
83
+ }
43
84
  }
44
85
  async function main() {
86
+ await showHeaderIfNeeded();
45
87
  const cli = yargs(hideBin(process.argv)).scriptName("afs").version(VERSION).alias("version", "V").help("help").alias("help", "h").usage("$0 <command> [options]").option("json", {
46
88
  type: "boolean",
47
89
  description: "Output in JSON format",
@@ -53,7 +95,8 @@ async function main() {
53
95
  "llm",
54
96
  "human"
55
97
  ],
56
- description: "Output view format",
98
+ default: "human",
99
+ description: "Output format (llm for AI agents, default for scripts)",
57
100
  global: true
58
101
  }).command(["list [path]", "ls [path]"], "List directory contents", (yargs$1) => yargs$1.positional("path", {
59
102
  type: "string",
@@ -75,29 +118,32 @@ async function main() {
75
118
  type: "string",
76
119
  description: "Glob pattern to filter entries (e.g., *.ts, **/*.js)"
77
120
  }), async (argv) => {
121
+ const view = getViewType(argv);
122
+ await maybeShowHeader(view);
78
123
  const result = await lsCommand(await createRuntime(), argv.path, {
79
124
  maxDepth: argv.depth,
80
125
  limit: argv.limit,
81
126
  maxChildren: argv["max-children"],
82
127
  pattern: argv.pattern
83
128
  });
84
- const view = getViewType(argv);
85
129
  console.log(formatLsOutput(result, view));
86
130
  }).command("stat <path>", "Get file or directory info", (yargs$1) => yargs$1.positional("path", {
87
131
  type: "string",
88
132
  demandOption: true,
89
133
  description: "Path to stat"
90
134
  }), async (argv) => {
91
- const result = await statCommand(await createRuntime(), argv.path);
92
135
  const view = getViewType(argv);
136
+ await maybeShowHeader(view);
137
+ const result = await statCommand(await createRuntime(), argv.path);
93
138
  console.log(formatStatOutput(result, view));
94
139
  }).command("read <path>", "Read file content", (yargs$1) => yargs$1.positional("path", {
95
140
  type: "string",
96
141
  demandOption: true,
97
142
  description: "Path to read"
98
143
  }), async (argv) => {
99
- const result = await readCommand(await createRuntime(), argv.path);
100
144
  const view = getViewType(argv);
145
+ await maybeShowHeader(view);
146
+ const result = await readCommand(await createRuntime(), argv.path);
101
147
  console.log(formatReadOutput(result, view));
102
148
  }).command("write <path>", "Write content to file (from --content or stdin)", (yargs$1) => yargs$1.positional("path", {
103
149
  type: "string",
@@ -111,6 +157,8 @@ async function main() {
111
157
  default: false,
112
158
  description: "Append to file instead of overwrite"
113
159
  }), async (argv) => {
160
+ const view = getViewType(argv);
161
+ await maybeShowHeader(view);
114
162
  let content;
115
163
  if (argv.content !== void 0) content = argv.content;
116
164
  else {
@@ -119,7 +167,6 @@ async function main() {
119
167
  content = Buffer.concat(chunks).toString("utf-8");
120
168
  }
121
169
  const result = await writeCommand(await createRuntime(), argv.path, content, { append: argv.append });
122
- const view = getViewType(argv);
123
170
  console.log(formatWriteOutput(result, view));
124
171
  if (!result.success) process.exit(ExitCode.RUNTIME_ERROR);
125
172
  }).command("exec <path> [action]", "Execute operation on path", (yargs$1) => yargs$1.positional("path", {
@@ -134,14 +181,16 @@ async function main() {
134
181
  type: "string",
135
182
  description: "JSON parameters for the action"
136
183
  }), async (argv) => {
184
+ const view = getViewType(argv);
185
+ await maybeShowHeader(view);
137
186
  const params = argv.params ? JSON.parse(argv.params) : {};
138
187
  const result = await execCommand(await createRuntime(), argv.path, argv.action, params);
139
- const view = getViewType(argv);
140
188
  console.log(formatExecOutput(result, view));
141
189
  if (!result.success) process.exit(ExitCode.RUNTIME_ERROR);
142
190
  }).command("mount", "Manage mount configurations", (yargs$1) => yargs$1.command(["list", "ls"], "List all mounts", () => {}, async (argv) => {
143
- const result = await mountListCommand(process.cwd());
144
191
  const view = getViewType(argv);
192
+ await maybeShowHeader(view);
193
+ const result = await mountListCommand(process.cwd());
145
194
  console.log(formatMountListOutput(result.mounts, view));
146
195
  }).command("add <path> <uri>", "Add a new mount (path=virtual path, uri=data source)", (yargs$2) => yargs$2.positional("path", {
147
196
  type: "string",
@@ -155,11 +204,16 @@ async function main() {
155
204
  type: "string",
156
205
  description: "Human-readable description for this mount"
157
206
  }), async (argv) => {
207
+ const view = getViewType(argv);
208
+ await maybeShowHeader(view);
158
209
  const result = await mountAddCommand(process.cwd(), argv.path, argv.uri, { description: argv.description });
159
- if (getViewType(argv) === "json") console.log(JSON.stringify(result, null, 2));
160
- else if (result.success) console.log(`Added mount ${argv.path}`);
161
- else {
162
- console.error(result.message);
210
+ if (view === "json") console.log(JSON.stringify(result, null, 2));
211
+ else if (result.success) {
212
+ const msg = view === "human" ? `${colors.success("Added mount")} ${colors.cyan(result.normalizedPath)}` : `Added mount ${result.normalizedPath}`;
213
+ console.log(msg);
214
+ } else {
215
+ const msg = view === "human" ? colors.error(result.message) : result.message;
216
+ console.error(msg);
163
217
  process.exit(ExitCode.RUNTIME_ERROR);
164
218
  }
165
219
  }).command(["remove <path>", "rm <path>"], "Remove a mount", (yargs$2) => yargs$2.positional("path", {
@@ -167,20 +221,33 @@ async function main() {
167
221
  demandOption: true,
168
222
  description: "Virtual path to remove (e.g., /src)"
169
223
  }), async (argv) => {
224
+ const view = getViewType(argv);
225
+ await maybeShowHeader(view);
170
226
  const result = await mountRemoveCommand(process.cwd(), argv.path);
171
- if (getViewType(argv) === "json") console.log(JSON.stringify(result, null, 2));
172
- else if (result.success) console.log(`Removed mount ${argv.path}`);
173
- else {
174
- console.error(result.message);
227
+ if (view === "json") console.log(JSON.stringify(result, null, 2));
228
+ else if (result.success) {
229
+ const msg = view === "human" ? `${colors.success("Removed mount")} ${colors.cyan(argv.path)}` : `Removed mount ${argv.path}`;
230
+ console.log(msg);
231
+ } else {
232
+ const msg = view === "human" ? colors.error(result.message) : result.message;
233
+ console.error(msg);
175
234
  process.exit(ExitCode.RUNTIME_ERROR);
176
235
  }
177
236
  }).command("validate", "Validate mount configuration", () => {}, async (argv) => {
237
+ const view = getViewType(argv);
238
+ await maybeShowHeader(view);
178
239
  const result = await mountValidateCommand(process.cwd());
179
- if (getViewType(argv) === "json") console.log(JSON.stringify(result, null, 2));
180
- else if (result.valid) console.log("Configuration is valid");
181
- else {
182
- console.error("Configuration has errors:");
183
- for (const error of result.errors) console.error(` - ${error}`);
240
+ if (view === "json") console.log(JSON.stringify(result, null, 2));
241
+ else if (result.valid) {
242
+ const msg = view === "human" ? colors.success("Configuration is valid") : "Configuration is valid";
243
+ console.log(msg);
244
+ } else {
245
+ const titleMsg = view === "human" ? colors.error("Configuration has errors:") : "Configuration has errors:";
246
+ console.error(titleMsg);
247
+ for (const error of result.errors) {
248
+ const errMsg = view === "human" ? ` ${colors.dim("-")} ${colors.error(error)}` : ` - ${error}`;
249
+ console.error(errMsg);
250
+ }
184
251
  process.exit(ExitCode.RUNTIME_ERROR);
185
252
  }
186
253
  }).demandCommand(1, "Please specify a mount subcommand"), () => {}).command("explain [topic]", "Explain AFS concepts or AFS object", (yargs$1) => yargs$1.positional("topic", {
@@ -188,6 +255,7 @@ async function main() {
188
255
  description: "Topic (mount, path, uri) or AFS path (e.g., /modules/src)"
189
256
  }), async (argv) => {
190
257
  const view = getViewType(argv);
258
+ await maybeShowHeader(view);
191
259
  const topic = argv.topic;
192
260
  if (topic?.startsWith("/")) {
193
261
  const result = await explainPathCommand(await createRuntime(), topic);
@@ -220,6 +288,7 @@ async function main() {
220
288
  type: "number",
221
289
  description: "Maximum request body size in bytes (default: 10MB)"
222
290
  }), async (argv) => {
291
+ await maybeShowHeader(getViewType(argv));
223
292
  const result = await serveCommand({
224
293
  host: argv.host,
225
294
  port: argv.port,
@@ -230,6 +299,19 @@ async function main() {
230
299
  });
231
300
  console.log(formatServeOutput(result));
232
301
  await new Promise(() => {});
302
+ }).command("explore [path]", "Interactive TUI file explorer (PC Tools style)", (yargs$1) => yargs$1.positional("path", {
303
+ type: "string",
304
+ default: "/",
305
+ description: "Starting path to explore"
306
+ }), async (argv) => {
307
+ const configLoader = new ConfigLoader();
308
+ const config = await configLoader.load(process.cwd());
309
+ await createExplorerScreen({
310
+ runtime: await createRuntime(process.cwd(), { configLoader }),
311
+ startPath: argv.path,
312
+ version: VERSION,
313
+ mountCount: config.mounts.length
314
+ });
233
315
  }).demandCommand(1, "Please specify a command").strict();
234
316
  try {
235
317
  await cli.parse();
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":["yargs"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * AFS CLI - Command Line Interface\n *\n * Commands:\n * - afs mount list|ls List mount configurations\n * - afs mount add <path> <uri> Add a mount\n * - afs mount remove|rm <path> Remove a mount\n * - afs mount validate Validate mount configuration\n * - afs list|ls [path] List directory\n * - afs stat <path> Get file/directory info\n * - afs read <path> Read file content\n * - afs write <path> Write file content (--content or stdin)\n * - afs exec <path> <action> Execute operation\n * - afs serve Start HTTP server to expose AFS\n *\n * Output modes:\n * - Default: Machine Truth (LLM/script friendly)\n * - --json: Structured JSON\n * - --view=llm: LLM optimized output\n * - --view=human: Human friendly format\n */\n\nimport yargs from \"yargs\";\nimport { hideBin } from \"yargs/helpers\";\nimport {\n execCommand,\n explainCommand,\n explainPathCommand,\n formatExecOutput,\n formatExplainOutput,\n formatLsOutput,\n formatMountListOutput,\n formatPathExplainOutput,\n formatReadOutput,\n formatServeOutput,\n formatStatOutput,\n formatWriteOutput,\n lsCommand,\n mountAddCommand,\n mountListCommand,\n mountRemoveCommand,\n mountValidateCommand,\n readCommand,\n serveCommand,\n statCommand,\n type ViewType,\n writeCommand,\n} from \"./commands/index.js\";\nimport { CLIError, ExitCode } from \"./errors.js\";\nimport { createRuntime } from \"./runtime.js\";\nimport { VERSION } from \"./version.js\";\n\n// Global view type derived from args\nfunction getViewType(argv: { json?: boolean; view?: string }): ViewType {\n if (argv.json) return \"json\";\n if (argv.view) return argv.view as ViewType;\n return \"default\";\n}\n\n// Run the CLI\nasync function main() {\n const cli = yargs(hideBin(process.argv))\n .scriptName(\"afs\")\n .version(VERSION)\n .alias(\"version\", \"V\")\n .help(\"help\")\n .alias(\"help\", \"h\")\n .usage(\"$0 <command> [options]\")\n .option(\"json\", {\n type: \"boolean\",\n description: \"Output in JSON format\",\n global: true,\n })\n .option(\"view\", {\n type: \"string\",\n choices: [\"default\", \"llm\", \"human\"],\n description: \"Output view format\",\n global: true,\n })\n .command(\n [\"list [path]\", \"ls [path]\"],\n \"List directory contents\",\n (yargs) =>\n yargs\n .positional(\"path\", {\n type: \"string\",\n default: \"/\",\n description: \"Path to list\",\n })\n .option(\"depth\", {\n type: \"number\",\n default: 1,\n description: \"Maximum depth to list\",\n })\n .option(\"limit\", {\n alias: \"n\",\n type: \"number\",\n description: \"Maximum number of entries to return\",\n })\n .option(\"max-children\", {\n type: \"number\",\n description: \"Maximum children per directory\",\n })\n .option(\"pattern\", {\n alias: \"p\",\n type: \"string\",\n description: \"Glob pattern to filter entries (e.g., *.ts, **/*.js)\",\n }),\n async (argv) => {\n const runtime = await createRuntime();\n const result = await lsCommand(runtime, argv.path!, {\n maxDepth: argv.depth,\n limit: argv.limit,\n maxChildren: argv[\"max-children\"],\n pattern: argv.pattern,\n });\n const view = getViewType(argv);\n console.log(formatLsOutput(result, view));\n },\n )\n .command(\n \"stat <path>\",\n \"Get file or directory info\",\n (yargs) =>\n yargs.positional(\"path\", {\n type: \"string\",\n demandOption: true,\n description: \"Path to stat\",\n }),\n async (argv) => {\n const runtime = await createRuntime();\n const result = await statCommand(runtime, argv.path!);\n const view = getViewType(argv);\n console.log(formatStatOutput(result, view));\n },\n )\n .command(\n \"read <path>\",\n \"Read file content\",\n (yargs) =>\n yargs.positional(\"path\", {\n type: \"string\",\n demandOption: true,\n description: \"Path to read\",\n }),\n async (argv) => {\n const runtime = await createRuntime();\n const result = await readCommand(runtime, argv.path!);\n const view = getViewType(argv);\n console.log(formatReadOutput(result, view));\n },\n )\n .command(\n \"write <path>\",\n \"Write content to file (from --content or stdin)\",\n (yargs) =>\n yargs\n .positional(\"path\", {\n type: \"string\",\n demandOption: true,\n description: \"Path to write\",\n })\n .option(\"content\", {\n type: \"string\",\n description: \"Content to write (if not provided, reads from stdin)\",\n })\n .option(\"append\", {\n type: \"boolean\",\n default: false,\n description: \"Append to file instead of overwrite\",\n }),\n async (argv) => {\n let content: string;\n\n if (argv.content !== undefined) {\n // Use --content parameter\n content = argv.content;\n } else {\n // Read content from stdin\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n content = Buffer.concat(chunks).toString(\"utf-8\");\n }\n\n const runtime = await createRuntime();\n const result = await writeCommand(runtime, argv.path!, content, {\n append: argv.append,\n });\n const view = getViewType(argv);\n console.log(formatWriteOutput(result, view));\n\n if (!result.success) {\n process.exit(ExitCode.RUNTIME_ERROR);\n }\n },\n )\n .command(\n \"exec <path> [action]\",\n \"Execute operation on path\",\n (yargs) =>\n yargs\n .positional(\"path\", {\n type: \"string\",\n demandOption: true,\n description: \"Path to execute on\",\n })\n .positional(\"action\", {\n type: \"string\",\n default: \"default\",\n description: \"Action to execute\",\n })\n .option(\"params\", {\n type: \"string\",\n description: \"JSON parameters for the action\",\n }),\n async (argv) => {\n const params = argv.params ? JSON.parse(argv.params) : {};\n\n const runtime = await createRuntime();\n const result = await execCommand(runtime, argv.path!, argv.action!, params);\n const view = getViewType(argv);\n console.log(formatExecOutput(result, view));\n\n if (!result.success) {\n process.exit(ExitCode.RUNTIME_ERROR);\n }\n },\n )\n .command(\n \"mount\",\n \"Manage mount configurations\",\n (yargs) =>\n yargs\n .command(\n [\"list\", \"ls\"],\n \"List all mounts\",\n () => {},\n async (argv) => {\n const result = await mountListCommand(process.cwd());\n const view = getViewType(argv);\n console.log(formatMountListOutput(result.mounts, view));\n },\n )\n .command(\n \"add <path> <uri>\",\n \"Add a new mount (path=virtual path, uri=data source)\",\n (yargs) =>\n yargs\n .positional(\"path\", {\n type: \"string\",\n demandOption: true,\n description: \"Virtual path in AFS namespace (e.g., /src, /data)\",\n })\n .positional(\"uri\", {\n type: \"string\",\n demandOption: true,\n description: \"Data source URI: fs:///local/path, git://repo, sqlite:///db.sqlite\",\n })\n .option(\"description\", {\n type: \"string\",\n description: \"Human-readable description for this mount\",\n }),\n async (argv) => {\n const result = await mountAddCommand(process.cwd(), argv.path!, argv.uri!, {\n description: argv.description,\n });\n const view = getViewType(argv);\n\n if (view === \"json\") {\n console.log(JSON.stringify(result, null, 2));\n } else {\n if (result.success) {\n console.log(`Added mount ${argv.path}`);\n } else {\n console.error(result.message);\n process.exit(ExitCode.RUNTIME_ERROR);\n }\n }\n },\n )\n .command(\n [\"remove <path>\", \"rm <path>\"],\n \"Remove a mount\",\n (yargs) =>\n yargs.positional(\"path\", {\n type: \"string\",\n demandOption: true,\n description: \"Virtual path to remove (e.g., /src)\",\n }),\n async (argv) => {\n const result = await mountRemoveCommand(process.cwd(), argv.path!);\n const view = getViewType(argv);\n\n if (view === \"json\") {\n console.log(JSON.stringify(result, null, 2));\n } else {\n if (result.success) {\n console.log(`Removed mount ${argv.path}`);\n } else {\n console.error(result.message);\n process.exit(ExitCode.RUNTIME_ERROR);\n }\n }\n },\n )\n .command(\n \"validate\",\n \"Validate mount configuration\",\n () => {},\n async (argv) => {\n const result = await mountValidateCommand(process.cwd());\n const view = getViewType(argv);\n\n if (view === \"json\") {\n console.log(JSON.stringify(result, null, 2));\n } else {\n if (result.valid) {\n console.log(\"Configuration is valid\");\n } else {\n console.error(\"Configuration has errors:\");\n for (const error of result.errors) {\n console.error(` - ${error}`);\n }\n process.exit(ExitCode.RUNTIME_ERROR);\n }\n }\n },\n )\n .demandCommand(1, \"Please specify a mount subcommand\"),\n () => {},\n )\n .command(\n \"explain [topic]\",\n \"Explain AFS concepts or AFS object\",\n (yargs) =>\n yargs.positional(\"topic\", {\n type: \"string\",\n description: \"Topic (mount, path, uri) or AFS path (e.g., /modules/src)\",\n }),\n async (argv) => {\n const view = getViewType(argv);\n const topic = argv.topic;\n\n // If topic starts with /, treat as object path\n if (topic?.startsWith(\"/\")) {\n const runtime = await createRuntime();\n const result = await explainPathCommand(runtime, topic);\n console.log(formatPathExplainOutput(result, view));\n } else {\n const result = await explainCommand(process.cwd(), topic);\n console.log(formatExplainOutput(result, view));\n }\n },\n )\n .command(\n \"serve\",\n \"Start HTTP server to expose AFS providers\",\n (yargs) =>\n yargs\n .option(\"host\", {\n type: \"string\",\n default: \"localhost\",\n description: \"Host address to listen on\",\n })\n .option(\"port\", {\n type: \"number\",\n default: 3000,\n description: \"Port to listen on\",\n })\n .option(\"path\", {\n type: \"string\",\n default: \"/afs\",\n description: \"Base path for the server\",\n })\n .option(\"readonly\", {\n type: \"boolean\",\n default: false,\n description: \"Run in readonly mode (disable write operations)\",\n })\n .option(\"cors\", {\n type: \"boolean\",\n default: false,\n description: \"Enable CORS support\",\n })\n .option(\"max-body\", {\n type: \"number\",\n description: \"Maximum request body size in bytes (default: 10MB)\",\n }),\n async (argv) => {\n const result = await serveCommand({\n host: argv.host,\n port: argv.port,\n path: argv.path,\n readonly: argv.readonly,\n cors: argv.cors,\n maxBodySize: argv[\"max-body\"],\n });\n\n console.log(formatServeOutput(result));\n\n // Keep the process running\n await new Promise(() => {});\n },\n )\n .demandCommand(1, \"Please specify a command\")\n .strict();\n\n try {\n await cli.parse();\n } catch (error) {\n if (error instanceof CLIError) {\n console.error(error.message);\n process.exit(error.exitCode);\n }\n throw error;\n }\n}\n\nmain().catch((error) => {\n console.error(\"Fatal error:\", error.message);\n process.exit(ExitCode.RUNTIME_ERROR);\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":["yargs"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * AFS CLI - Command Line Interface\n *\n * Commands:\n * - afs mount list|ls List mount configurations\n * - afs mount add <path> <uri> Add a mount\n * - afs mount remove|rm <path> Remove a mount\n * - afs mount validate Validate mount configuration\n * - afs list|ls [path] List directory\n * - afs stat <path> Get file/directory info\n * - afs read <path> Read file content\n * - afs write <path> Write file content (--content or stdin)\n * - afs exec <path> <action> Execute operation\n * - afs serve Start HTTP server to expose AFS\n * - afs explore [path] Interactive TUI file explorer\n *\n * Output modes (default: human):\n * - --view=human: Human friendly format with colors (default)\n * - --view=llm: LLM optimized output (token-efficient)\n * - --view=default: Machine truth (script/pipe friendly)\n * - --json: Structured JSON\n */\n\nimport yargs from \"yargs\";\nimport { hideBin } from \"yargs/helpers\";\nimport {\n execCommand,\n explainCommand,\n explainPathCommand,\n formatExecOutput,\n formatExplainOutput,\n formatLsOutput,\n formatMountListOutput,\n formatPathExplainOutput,\n formatReadOutput,\n formatServeOutput,\n formatStatOutput,\n formatWriteOutput,\n lsCommand,\n mountAddCommand,\n mountListCommand,\n mountRemoveCommand,\n mountValidateCommand,\n readCommand,\n serveCommand,\n statCommand,\n type ViewType,\n writeCommand,\n} from \"./commands/index.js\";\nimport { ConfigLoader } from \"./config/loader.js\";\nimport { CLIError, ExitCode } from \"./errors.js\";\nimport { createExplorerScreen } from \"./explorer/screen.js\";\nimport { createRuntime } from \"./runtime.js\";\nimport { colors, printHeader, shouldShowHeader } from \"./ui/index.js\";\nimport { VERSION } from \"./version.js\";\n\n// Global view type derived from args\n// Default is \"human\" for interactive use; LLMs can use --view=llm or --view=default\nfunction getViewType(argv: { json?: boolean; view?: string }): ViewType {\n if (argv.json) return \"json\";\n if (argv.view) return argv.view as ViewType;\n return \"human\";\n}\n\n// Track if header has been shown (show only once per invocation)\nlet headerShown = false;\n\n/**\n * Show header if in human view mode and not already shown\n */\nasync function maybeShowHeader(view: ViewType): Promise<void> {\n if (view !== \"human\" || headerShown || !shouldShowHeader()) {\n return;\n }\n\n headerShown = true;\n\n // Load mount count for header\n try {\n const configLoader = new ConfigLoader();\n const config = await configLoader.load(process.cwd());\n printHeader({\n version: VERSION,\n mountCount: config.mounts.length,\n });\n } catch {\n // If config loading fails, just show header without mount count\n printHeader({\n version: VERSION,\n mountCount: 0,\n });\n }\n}\n\n// Show header for help/version in interactive mode\nasync function showHeaderIfNeeded(): Promise<void> {\n const args = process.argv.slice(2);\n const isHelp =\n args.length === 0 ||\n args.includes(\"--help\") ||\n args.includes(\"-h\") ||\n (args.length === 1 && [\"help\", \"mount\", \"explain\"].includes(args[0]!));\n\n if (isHelp && shouldShowHeader()) {\n try {\n const configLoader = new ConfigLoader();\n const config = await configLoader.load(process.cwd());\n printHeader({\n version: VERSION,\n mountCount: config.mounts.length,\n });\n } catch {\n printHeader({\n version: VERSION,\n mountCount: 0,\n });\n }\n }\n}\n\n// Run the CLI\nasync function main() {\n await showHeaderIfNeeded();\n\n const cli = yargs(hideBin(process.argv))\n .scriptName(\"afs\")\n .version(VERSION)\n .alias(\"version\", \"V\")\n .help(\"help\")\n .alias(\"help\", \"h\")\n .usage(\"$0 <command> [options]\")\n .option(\"json\", {\n type: \"boolean\",\n description: \"Output in JSON format\",\n global: true,\n })\n .option(\"view\", {\n type: \"string\",\n choices: [\"default\", \"llm\", \"human\"],\n default: \"human\",\n description: \"Output format (llm for AI agents, default for scripts)\",\n global: true,\n })\n .command(\n [\"list [path]\", \"ls [path]\"],\n \"List directory contents\",\n (yargs) =>\n yargs\n .positional(\"path\", {\n type: \"string\",\n default: \"/\",\n description: \"Path to list\",\n })\n .option(\"depth\", {\n type: \"number\",\n default: 1,\n description: \"Maximum depth to list\",\n })\n .option(\"limit\", {\n alias: \"n\",\n type: \"number\",\n description: \"Maximum number of entries to return\",\n })\n .option(\"max-children\", {\n type: \"number\",\n description: \"Maximum children per directory\",\n })\n .option(\"pattern\", {\n alias: \"p\",\n type: \"string\",\n description: \"Glob pattern to filter entries (e.g., *.ts, **/*.js)\",\n }),\n async (argv) => {\n const view = getViewType(argv);\n await maybeShowHeader(view);\n const runtime = await createRuntime();\n const result = await lsCommand(runtime, argv.path!, {\n maxDepth: argv.depth,\n limit: argv.limit,\n maxChildren: argv[\"max-children\"],\n pattern: argv.pattern,\n });\n console.log(formatLsOutput(result, view));\n },\n )\n .command(\n \"stat <path>\",\n \"Get file or directory info\",\n (yargs) =>\n yargs.positional(\"path\", {\n type: \"string\",\n demandOption: true,\n description: \"Path to stat\",\n }),\n async (argv) => {\n const view = getViewType(argv);\n await maybeShowHeader(view);\n const runtime = await createRuntime();\n const result = await statCommand(runtime, argv.path!);\n console.log(formatStatOutput(result, view));\n },\n )\n .command(\n \"read <path>\",\n \"Read file content\",\n (yargs) =>\n yargs.positional(\"path\", {\n type: \"string\",\n demandOption: true,\n description: \"Path to read\",\n }),\n async (argv) => {\n const view = getViewType(argv);\n await maybeShowHeader(view);\n const runtime = await createRuntime();\n const result = await readCommand(runtime, argv.path!);\n console.log(formatReadOutput(result, view));\n },\n )\n .command(\n \"write <path>\",\n \"Write content to file (from --content or stdin)\",\n (yargs) =>\n yargs\n .positional(\"path\", {\n type: \"string\",\n demandOption: true,\n description: \"Path to write\",\n })\n .option(\"content\", {\n type: \"string\",\n description: \"Content to write (if not provided, reads from stdin)\",\n })\n .option(\"append\", {\n type: \"boolean\",\n default: false,\n description: \"Append to file instead of overwrite\",\n }),\n async (argv) => {\n const view = getViewType(argv);\n await maybeShowHeader(view);\n\n let content: string;\n\n if (argv.content !== undefined) {\n // Use --content parameter\n content = argv.content;\n } else {\n // Read content from stdin\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n content = Buffer.concat(chunks).toString(\"utf-8\");\n }\n\n const runtime = await createRuntime();\n const result = await writeCommand(runtime, argv.path!, content, {\n append: argv.append,\n });\n console.log(formatWriteOutput(result, view));\n\n if (!result.success) {\n process.exit(ExitCode.RUNTIME_ERROR);\n }\n },\n )\n .command(\n \"exec <path> [action]\",\n \"Execute operation on path\",\n (yargs) =>\n yargs\n .positional(\"path\", {\n type: \"string\",\n demandOption: true,\n description: \"Path to execute on\",\n })\n .positional(\"action\", {\n type: \"string\",\n default: \"default\",\n description: \"Action to execute\",\n })\n .option(\"params\", {\n type: \"string\",\n description: \"JSON parameters for the action\",\n }),\n async (argv) => {\n const view = getViewType(argv);\n await maybeShowHeader(view);\n\n const params = argv.params ? JSON.parse(argv.params) : {};\n\n const runtime = await createRuntime();\n const result = await execCommand(runtime, argv.path!, argv.action!, params);\n console.log(formatExecOutput(result, view));\n\n if (!result.success) {\n process.exit(ExitCode.RUNTIME_ERROR);\n }\n },\n )\n .command(\n \"mount\",\n \"Manage mount configurations\",\n (yargs) =>\n yargs\n .command(\n [\"list\", \"ls\"],\n \"List all mounts\",\n () => {},\n async (argv) => {\n const view = getViewType(argv);\n await maybeShowHeader(view);\n const result = await mountListCommand(process.cwd());\n console.log(formatMountListOutput(result.mounts, view));\n },\n )\n .command(\n \"add <path> <uri>\",\n \"Add a new mount (path=virtual path, uri=data source)\",\n (yargs) =>\n yargs\n .positional(\"path\", {\n type: \"string\",\n demandOption: true,\n description: \"Virtual path in AFS namespace (e.g., /src, /data)\",\n })\n .positional(\"uri\", {\n type: \"string\",\n demandOption: true,\n description: \"Data source URI: fs:///local/path, git://repo, sqlite:///db.sqlite\",\n })\n .option(\"description\", {\n type: \"string\",\n description: \"Human-readable description for this mount\",\n }),\n async (argv) => {\n const view = getViewType(argv);\n await maybeShowHeader(view);\n\n const result = await mountAddCommand(process.cwd(), argv.path!, argv.uri!, {\n description: argv.description,\n });\n\n if (view === \"json\") {\n console.log(JSON.stringify(result, null, 2));\n } else {\n if (result.success) {\n const msg =\n view === \"human\"\n ? `${colors.success(\"Added mount\")} ${colors.cyan(result.normalizedPath!)}`\n : `Added mount ${result.normalizedPath}`;\n console.log(msg);\n } else {\n const msg = view === \"human\" ? colors.error(result.message!) : result.message;\n console.error(msg);\n process.exit(ExitCode.RUNTIME_ERROR);\n }\n }\n },\n )\n .command(\n [\"remove <path>\", \"rm <path>\"],\n \"Remove a mount\",\n (yargs) =>\n yargs.positional(\"path\", {\n type: \"string\",\n demandOption: true,\n description: \"Virtual path to remove (e.g., /src)\",\n }),\n async (argv) => {\n const view = getViewType(argv);\n await maybeShowHeader(view);\n\n const result = await mountRemoveCommand(process.cwd(), argv.path!);\n\n if (view === \"json\") {\n console.log(JSON.stringify(result, null, 2));\n } else {\n if (result.success) {\n const msg =\n view === \"human\"\n ? `${colors.success(\"Removed mount\")} ${colors.cyan(argv.path!)}`\n : `Removed mount ${argv.path}`;\n console.log(msg);\n } else {\n const msg = view === \"human\" ? colors.error(result.message!) : result.message;\n console.error(msg);\n process.exit(ExitCode.RUNTIME_ERROR);\n }\n }\n },\n )\n .command(\n \"validate\",\n \"Validate mount configuration\",\n () => {},\n async (argv) => {\n const view = getViewType(argv);\n await maybeShowHeader(view);\n\n const result = await mountValidateCommand(process.cwd());\n\n if (view === \"json\") {\n console.log(JSON.stringify(result, null, 2));\n } else {\n if (result.valid) {\n const msg =\n view === \"human\"\n ? colors.success(\"Configuration is valid\")\n : \"Configuration is valid\";\n console.log(msg);\n } else {\n const titleMsg =\n view === \"human\"\n ? colors.error(\"Configuration has errors:\")\n : \"Configuration has errors:\";\n console.error(titleMsg);\n for (const error of result.errors) {\n const errMsg =\n view === \"human\"\n ? ` ${colors.dim(\"-\")} ${colors.error(error)}`\n : ` - ${error}`;\n console.error(errMsg);\n }\n process.exit(ExitCode.RUNTIME_ERROR);\n }\n }\n },\n )\n .demandCommand(1, \"Please specify a mount subcommand\"),\n () => {},\n )\n .command(\n \"explain [topic]\",\n \"Explain AFS concepts or AFS object\",\n (yargs) =>\n yargs.positional(\"topic\", {\n type: \"string\",\n description: \"Topic (mount, path, uri) or AFS path (e.g., /modules/src)\",\n }),\n async (argv) => {\n const view = getViewType(argv);\n await maybeShowHeader(view);\n const topic = argv.topic;\n\n // If topic starts with /, treat as object path\n if (topic?.startsWith(\"/\")) {\n const runtime = await createRuntime();\n const result = await explainPathCommand(runtime, topic);\n console.log(formatPathExplainOutput(result, view));\n } else {\n const result = await explainCommand(process.cwd(), topic);\n console.log(formatExplainOutput(result, view));\n }\n },\n )\n .command(\n \"serve\",\n \"Start HTTP server to expose AFS providers\",\n (yargs) =>\n yargs\n .option(\"host\", {\n type: \"string\",\n default: \"localhost\",\n description: \"Host address to listen on\",\n })\n .option(\"port\", {\n type: \"number\",\n default: 3000,\n description: \"Port to listen on\",\n })\n .option(\"path\", {\n type: \"string\",\n default: \"/afs\",\n description: \"Base path for the server\",\n })\n .option(\"readonly\", {\n type: \"boolean\",\n default: false,\n description: \"Run in readonly mode (disable write operations)\",\n })\n .option(\"cors\", {\n type: \"boolean\",\n default: false,\n description: \"Enable CORS support\",\n })\n .option(\"max-body\", {\n type: \"number\",\n description: \"Maximum request body size in bytes (default: 10MB)\",\n }),\n async (argv) => {\n const view = getViewType(argv);\n await maybeShowHeader(view);\n\n const result = await serveCommand({\n host: argv.host,\n port: argv.port,\n path: argv.path,\n readonly: argv.readonly,\n cors: argv.cors,\n maxBodySize: argv[\"max-body\"],\n });\n\n console.log(formatServeOutput(result));\n\n // Keep the process running\n await new Promise(() => {});\n },\n )\n .command(\n \"explore [path]\",\n \"Interactive TUI file explorer (PC Tools style)\",\n (yargs) =>\n yargs.positional(\"path\", {\n type: \"string\",\n default: \"/\",\n description: \"Starting path to explore\",\n }),\n async (argv) => {\n const configLoader = new ConfigLoader();\n const config = await configLoader.load(process.cwd());\n const runtime = await createRuntime(process.cwd(), { configLoader });\n await createExplorerScreen({\n runtime,\n startPath: argv.path,\n version: VERSION,\n mountCount: config.mounts.length,\n });\n },\n )\n .demandCommand(1, \"Please specify a command\")\n .strict();\n\n try {\n await cli.parse();\n } catch (error) {\n if (error instanceof CLIError) {\n console.error(error.message);\n process.exit(error.exitCode);\n }\n throw error;\n }\n}\n\nmain().catch((error) => {\n console.error(\"Fatal error:\", error.message);\n process.exit(ExitCode.RUNTIME_ERROR);\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,SAAS,YAAY,MAAmD;AACtE,KAAI,KAAK,KAAM,QAAO;AACtB,KAAI,KAAK,KAAM,QAAO,KAAK;AAC3B,QAAO;;AAIT,IAAI,cAAc;;;;AAKlB,eAAe,gBAAgB,MAA+B;AAC5D,KAAI,SAAS,WAAW,eAAe,CAAC,kBAAkB,CACxD;AAGF,eAAc;AAGd,KAAI;AAGF,cAAY;GACV,SAAS;GACT,aAHa,MADM,IAAI,cAAc,CACL,KAAK,QAAQ,KAAK,CAAC,EAGhC,OAAO;GAC3B,CAAC;SACI;AAEN,cAAY;GACV,SAAS;GACT,YAAY;GACb,CAAC;;;AAKN,eAAe,qBAAoC;CACjD,MAAM,OAAO,QAAQ,KAAK,MAAM,EAAE;AAOlC,MALE,KAAK,WAAW,KAChB,KAAK,SAAS,SAAS,IACvB,KAAK,SAAS,KAAK,IAClB,KAAK,WAAW,KAAK;EAAC;EAAQ;EAAS;EAAU,CAAC,SAAS,KAAK,GAAI,KAEzD,kBAAkB,CAC9B,KAAI;AAGF,cAAY;GACV,SAAS;GACT,aAHa,MADM,IAAI,cAAc,CACL,KAAK,QAAQ,KAAK,CAAC,EAGhC,OAAO;GAC3B,CAAC;SACI;AACN,cAAY;GACV,SAAS;GACT,YAAY;GACb,CAAC;;;AAMR,eAAe,OAAO;AACpB,OAAM,oBAAoB;CAE1B,MAAM,MAAM,MAAM,QAAQ,QAAQ,KAAK,CAAC,CACrC,WAAW,MAAM,CACjB,QAAQ,QAAQ,CAChB,MAAM,WAAW,IAAI,CACrB,KAAK,OAAO,CACZ,MAAM,QAAQ,IAAI,CAClB,MAAM,yBAAyB,CAC/B,OAAO,QAAQ;EACd,MAAM;EACN,aAAa;EACb,QAAQ;EACT,CAAC,CACD,OAAO,QAAQ;EACd,MAAM;EACN,SAAS;GAAC;GAAW;GAAO;GAAQ;EACpC,SAAS;EACT,aAAa;EACb,QAAQ;EACT,CAAC,CACD,QACC,CAAC,eAAe,YAAY,EAC5B,4BACC,YACCA,QACG,WAAW,QAAQ;EAClB,MAAM;EACN,SAAS;EACT,aAAa;EACd,CAAC,CACD,OAAO,SAAS;EACf,MAAM;EACN,SAAS;EACT,aAAa;EACd,CAAC,CACD,OAAO,SAAS;EACf,OAAO;EACP,MAAM;EACN,aAAa;EACd,CAAC,CACD,OAAO,gBAAgB;EACtB,MAAM;EACN,aAAa;EACd,CAAC,CACD,OAAO,WAAW;EACjB,OAAO;EACP,MAAM;EACN,aAAa;EACd,CAAC,EACN,OAAO,SAAS;EACd,MAAM,OAAO,YAAY,KAAK;AAC9B,QAAM,gBAAgB,KAAK;EAE3B,MAAM,SAAS,MAAM,UADL,MAAM,eAAe,EACG,KAAK,MAAO;GAClD,UAAU,KAAK;GACf,OAAO,KAAK;GACZ,aAAa,KAAK;GAClB,SAAS,KAAK;GACf,CAAC;AACF,UAAQ,IAAI,eAAe,QAAQ,KAAK,CAAC;GAE5C,CACA,QACC,eACA,+BACC,YACCA,QAAM,WAAW,QAAQ;EACvB,MAAM;EACN,cAAc;EACd,aAAa;EACd,CAAC,EACJ,OAAO,SAAS;EACd,MAAM,OAAO,YAAY,KAAK;AAC9B,QAAM,gBAAgB,KAAK;EAE3B,MAAM,SAAS,MAAM,YADL,MAAM,eAAe,EACK,KAAK,KAAM;AACrD,UAAQ,IAAI,iBAAiB,QAAQ,KAAK,CAAC;GAE9C,CACA,QACC,eACA,sBACC,YACCA,QAAM,WAAW,QAAQ;EACvB,MAAM;EACN,cAAc;EACd,aAAa;EACd,CAAC,EACJ,OAAO,SAAS;EACd,MAAM,OAAO,YAAY,KAAK;AAC9B,QAAM,gBAAgB,KAAK;EAE3B,MAAM,SAAS,MAAM,YADL,MAAM,eAAe,EACK,KAAK,KAAM;AACrD,UAAQ,IAAI,iBAAiB,QAAQ,KAAK,CAAC;GAE9C,CACA,QACC,gBACA,oDACC,YACCA,QACG,WAAW,QAAQ;EAClB,MAAM;EACN,cAAc;EACd,aAAa;EACd,CAAC,CACD,OAAO,WAAW;EACjB,MAAM;EACN,aAAa;EACd,CAAC,CACD,OAAO,UAAU;EAChB,MAAM;EACN,SAAS;EACT,aAAa;EACd,CAAC,EACN,OAAO,SAAS;EACd,MAAM,OAAO,YAAY,KAAK;AAC9B,QAAM,gBAAgB,KAAK;EAE3B,IAAI;AAEJ,MAAI,KAAK,YAAY,OAEnB,WAAU,KAAK;OACV;GAEL,MAAM,SAAmB,EAAE;AAC3B,cAAW,MAAM,SAAS,QAAQ,MAChC,QAAO,KAAK,MAAM;AAEpB,aAAU,OAAO,OAAO,OAAO,CAAC,SAAS,QAAQ;;EAInD,MAAM,SAAS,MAAM,aADL,MAAM,eAAe,EACM,KAAK,MAAO,SAAS,EAC9D,QAAQ,KAAK,QACd,CAAC;AACF,UAAQ,IAAI,kBAAkB,QAAQ,KAAK,CAAC;AAE5C,MAAI,CAAC,OAAO,QACV,SAAQ,KAAK,SAAS,cAAc;GAGzC,CACA,QACC,wBACA,8BACC,YACCA,QACG,WAAW,QAAQ;EAClB,MAAM;EACN,cAAc;EACd,aAAa;EACd,CAAC,CACD,WAAW,UAAU;EACpB,MAAM;EACN,SAAS;EACT,aAAa;EACd,CAAC,CACD,OAAO,UAAU;EAChB,MAAM;EACN,aAAa;EACd,CAAC,EACN,OAAO,SAAS;EACd,MAAM,OAAO,YAAY,KAAK;AAC9B,QAAM,gBAAgB,KAAK;EAE3B,MAAM,SAAS,KAAK,SAAS,KAAK,MAAM,KAAK,OAAO,GAAG,EAAE;EAGzD,MAAM,SAAS,MAAM,YADL,MAAM,eAAe,EACK,KAAK,MAAO,KAAK,QAAS,OAAO;AAC3E,UAAQ,IAAI,iBAAiB,QAAQ,KAAK,CAAC;AAE3C,MAAI,CAAC,OAAO,QACV,SAAQ,KAAK,SAAS,cAAc;GAGzC,CACA,QACC,SACA,gCACC,YACCA,QACG,QACC,CAAC,QAAQ,KAAK,EACd,yBACM,IACN,OAAO,SAAS;EACd,MAAM,OAAO,YAAY,KAAK;AAC9B,QAAM,gBAAgB,KAAK;EAC3B,MAAM,SAAS,MAAM,iBAAiB,QAAQ,KAAK,CAAC;AACpD,UAAQ,IAAI,sBAAsB,OAAO,QAAQ,KAAK,CAAC;GAE1D,CACA,QACC,oBACA,yDACC,YACCA,QACG,WAAW,QAAQ;EAClB,MAAM;EACN,cAAc;EACd,aAAa;EACd,CAAC,CACD,WAAW,OAAO;EACjB,MAAM;EACN,cAAc;EACd,aAAa;EACd,CAAC,CACD,OAAO,eAAe;EACrB,MAAM;EACN,aAAa;EACd,CAAC,EACN,OAAO,SAAS;EACd,MAAM,OAAO,YAAY,KAAK;AAC9B,QAAM,gBAAgB,KAAK;EAE3B,MAAM,SAAS,MAAM,gBAAgB,QAAQ,KAAK,EAAE,KAAK,MAAO,KAAK,KAAM,EACzE,aAAa,KAAK,aACnB,CAAC;AAEF,MAAI,SAAS,OACX,SAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;WAExC,OAAO,SAAS;GAClB,MAAM,MACJ,SAAS,UACL,GAAG,OAAO,QAAQ,cAAc,CAAC,GAAG,OAAO,KAAK,OAAO,eAAgB,KACvE,eAAe,OAAO;AAC5B,WAAQ,IAAI,IAAI;SACX;GACL,MAAM,MAAM,SAAS,UAAU,OAAO,MAAM,OAAO,QAAS,GAAG,OAAO;AACtE,WAAQ,MAAM,IAAI;AAClB,WAAQ,KAAK,SAAS,cAAc;;GAI3C,CACA,QACC,CAAC,iBAAiB,YAAY,EAC9B,mBACC,YACCA,QAAM,WAAW,QAAQ;EACvB,MAAM;EACN,cAAc;EACd,aAAa;EACd,CAAC,EACJ,OAAO,SAAS;EACd,MAAM,OAAO,YAAY,KAAK;AAC9B,QAAM,gBAAgB,KAAK;EAE3B,MAAM,SAAS,MAAM,mBAAmB,QAAQ,KAAK,EAAE,KAAK,KAAM;AAElE,MAAI,SAAS,OACX,SAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;WAExC,OAAO,SAAS;GAClB,MAAM,MACJ,SAAS,UACL,GAAG,OAAO,QAAQ,gBAAgB,CAAC,GAAG,OAAO,KAAK,KAAK,KAAM,KAC7D,iBAAiB,KAAK;AAC5B,WAAQ,IAAI,IAAI;SACX;GACL,MAAM,MAAM,SAAS,UAAU,OAAO,MAAM,OAAO,QAAS,GAAG,OAAO;AACtE,WAAQ,MAAM,IAAI;AAClB,WAAQ,KAAK,SAAS,cAAc;;GAI3C,CACA,QACC,YACA,sCACM,IACN,OAAO,SAAS;EACd,MAAM,OAAO,YAAY,KAAK;AAC9B,QAAM,gBAAgB,KAAK;EAE3B,MAAM,SAAS,MAAM,qBAAqB,QAAQ,KAAK,CAAC;AAExD,MAAI,SAAS,OACX,SAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;WAExC,OAAO,OAAO;GAChB,MAAM,MACJ,SAAS,UACL,OAAO,QAAQ,yBAAyB,GACxC;AACN,WAAQ,IAAI,IAAI;SACX;GACL,MAAM,WACJ,SAAS,UACL,OAAO,MAAM,4BAA4B,GACzC;AACN,WAAQ,MAAM,SAAS;AACvB,QAAK,MAAM,SAAS,OAAO,QAAQ;IACjC,MAAM,SACJ,SAAS,UACL,KAAK,OAAO,IAAI,IAAI,CAAC,GAAG,OAAO,MAAM,MAAM,KAC3C,OAAO;AACb,YAAQ,MAAM,OAAO;;AAEvB,WAAQ,KAAK,SAAS,cAAc;;GAI3C,CACA,cAAc,GAAG,oCAAoC,QACpD,GACP,CACA,QACC,mBACA,uCACC,YACCA,QAAM,WAAW,SAAS;EACxB,MAAM;EACN,aAAa;EACd,CAAC,EACJ,OAAO,SAAS;EACd,MAAM,OAAO,YAAY,KAAK;AAC9B,QAAM,gBAAgB,KAAK;EAC3B,MAAM,QAAQ,KAAK;AAGnB,MAAI,OAAO,WAAW,IAAI,EAAE;GAE1B,MAAM,SAAS,MAAM,mBADL,MAAM,eAAe,EACY,MAAM;AACvD,WAAQ,IAAI,wBAAwB,QAAQ,KAAK,CAAC;SAC7C;GACL,MAAM,SAAS,MAAM,eAAe,QAAQ,KAAK,EAAE,MAAM;AACzD,WAAQ,IAAI,oBAAoB,QAAQ,KAAK,CAAC;;GAGnD,CACA,QACC,SACA,8CACC,YACCA,QACG,OAAO,QAAQ;EACd,MAAM;EACN,SAAS;EACT,aAAa;EACd,CAAC,CACD,OAAO,QAAQ;EACd,MAAM;EACN,SAAS;EACT,aAAa;EACd,CAAC,CACD,OAAO,QAAQ;EACd,MAAM;EACN,SAAS;EACT,aAAa;EACd,CAAC,CACD,OAAO,YAAY;EAClB,MAAM;EACN,SAAS;EACT,aAAa;EACd,CAAC,CACD,OAAO,QAAQ;EACd,MAAM;EACN,SAAS;EACT,aAAa;EACd,CAAC,CACD,OAAO,YAAY;EAClB,MAAM;EACN,aAAa;EACd,CAAC,EACN,OAAO,SAAS;AAEd,QAAM,gBADO,YAAY,KAAK,CACH;EAE3B,MAAM,SAAS,MAAM,aAAa;GAChC,MAAM,KAAK;GACX,MAAM,KAAK;GACX,MAAM,KAAK;GACX,UAAU,KAAK;GACf,MAAM,KAAK;GACX,aAAa,KAAK;GACnB,CAAC;AAEF,UAAQ,IAAI,kBAAkB,OAAO,CAAC;AAGtC,QAAM,IAAI,cAAc,GAAG;GAE9B,CACA,QACC,kBACA,mDACC,YACCA,QAAM,WAAW,QAAQ;EACvB,MAAM;EACN,SAAS;EACT,aAAa;EACd,CAAC,EACJ,OAAO,SAAS;EACd,MAAM,eAAe,IAAI,cAAc;EACvC,MAAM,SAAS,MAAM,aAAa,KAAK,QAAQ,KAAK,CAAC;AAErD,QAAM,qBAAqB;GACzB,SAFc,MAAM,cAAc,QAAQ,KAAK,EAAE,EAAE,cAAc,CAAC;GAGlE,WAAW,KAAK;GAChB,SAAS;GACT,YAAY,OAAO,OAAO;GAC3B,CAAC;GAEL,CACA,cAAc,GAAG,2BAA2B,CAC5C,QAAQ;AAEX,KAAI;AACF,QAAM,IAAI,OAAO;UACV,OAAO;AACd,MAAI,iBAAiB,UAAU;AAC7B,WAAQ,MAAM,MAAM,QAAQ;AAC5B,WAAQ,KAAK,MAAM,SAAS;;AAE9B,QAAM;;;AAIV,MAAM,CAAC,OAAO,UAAU;AACtB,SAAQ,MAAM,gBAAgB,MAAM,QAAQ;AAC5C,SAAQ,KAAK,SAAS,cAAc;EACpC"}
@@ -1,3 +1,4 @@
1
+ const require_index = require('../ui/index.cjs');
1
2
 
2
3
  //#region src/commands/ls.ts
3
4
  /**
@@ -107,7 +108,7 @@ function formatHuman(result) {
107
108
  renderTree(root, "", lines);
108
109
  if (result.truncated) {
109
110
  lines.push("");
110
- lines.push(`(Results truncated - ${result.total} entries shown)`);
111
+ lines.push(require_index.colors.yellow(`(Results truncated - ${result.total} entries shown)`));
111
112
  }
112
113
  return lines.join("\n");
113
114
  }
@@ -116,10 +117,11 @@ function renderTree(node, prefix, lines) {
116
117
  for (let i = 0; i < children.length; i++) {
117
118
  const child = children[i];
118
119
  const isLast = i === children.length - 1;
119
- const connector = isLast ? "└── " : "├── ";
120
+ const connector = require_index.colors.dim(isLast ? "└── " : "├── ");
120
121
  const icon = child.entry ? getTypeIcon(child.entry.type) : "📂";
121
- const sizeStr = child.entry?.size !== void 0 ? ` ${formatSize(child.entry.size)}` : "";
122
- lines.push(`${prefix}${connector}${icon} ${child.name}${sizeStr}`);
122
+ const name = child.entry?.type === "directory" ? require_index.colors.cyan(child.name) : child.name;
123
+ const sizeStr = child.entry?.size !== void 0 ? require_index.colors.dim(` ${formatSize(child.entry.size)}`) : "";
124
+ lines.push(`${require_index.colors.dim(prefix)}${connector}${icon} ${name}${sizeStr}`);
123
125
  renderTree(child, prefix + (isLast ? " " : "│ "), lines);
124
126
  }
125
127
  }
@@ -1,3 +1,5 @@
1
+ import { colors } from "../ui/index.mjs";
2
+
1
3
  //#region src/commands/ls.ts
2
4
  /**
3
5
  * List directory contents
@@ -106,7 +108,7 @@ function formatHuman(result) {
106
108
  renderTree(root, "", lines);
107
109
  if (result.truncated) {
108
110
  lines.push("");
109
- lines.push(`(Results truncated - ${result.total} entries shown)`);
111
+ lines.push(colors.yellow(`(Results truncated - ${result.total} entries shown)`));
110
112
  }
111
113
  return lines.join("\n");
112
114
  }
@@ -115,10 +117,11 @@ function renderTree(node, prefix, lines) {
115
117
  for (let i = 0; i < children.length; i++) {
116
118
  const child = children[i];
117
119
  const isLast = i === children.length - 1;
118
- const connector = isLast ? "└── " : "├── ";
120
+ const connector = colors.dim(isLast ? "└── " : "├── ");
119
121
  const icon = child.entry ? getTypeIcon(child.entry.type) : "📂";
120
- const sizeStr = child.entry?.size !== void 0 ? ` ${formatSize(child.entry.size)}` : "";
121
- lines.push(`${prefix}${connector}${icon} ${child.name}${sizeStr}`);
122
+ const name = child.entry?.type === "directory" ? colors.cyan(child.name) : child.name;
123
+ const sizeStr = child.entry?.size !== void 0 ? colors.dim(` ${formatSize(child.entry.size)}`) : "";
124
+ lines.push(`${colors.dim(prefix)}${connector}${icon} ${name}${sizeStr}`);
122
125
  renderTree(child, prefix + (isLast ? " " : "│ "), lines);
123
126
  }
124
127
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ls.mjs","names":[],"sources":["../../src/commands/ls.ts"],"sourcesContent":["import type { AFSEntry } from \"@aigne/afs\";\nimport type { AFSRuntime } from \"../runtime.js\";\n\nexport type ViewType = \"default\" | \"json\" | \"llm\" | \"human\";\n\nexport interface LsEntry {\n path: string;\n type: \"file\" | \"directory\";\n size?: number;\n modified?: string;\n hash?: string;\n childrenCount?: number;\n childrenTruncated?: boolean;\n}\n\nexport interface LsOptions {\n maxDepth?: number;\n limit?: number;\n maxChildren?: number;\n pattern?: string;\n}\n\nexport interface LsResult {\n entries: LsEntry[];\n total: number;\n truncated?: boolean;\n message?: string;\n}\n\n/**\n * List directory contents\n */\nexport async function lsCommand(\n runtime: AFSRuntime,\n path: string,\n options: LsOptions = {},\n): Promise<LsResult> {\n const result = await runtime.list(path, {\n maxDepth: options.maxDepth ?? 1,\n limit: options.limit,\n maxChildren: options.maxChildren,\n pattern: options.pattern,\n });\n\n const entries: LsEntry[] = result.data.map((entry: AFSEntry) => ({\n path: entry.path,\n type: mapEntryType(entry.metadata?.type),\n size: entry.metadata?.size,\n modified: formatDate(entry.updatedAt),\n childrenCount: entry.metadata?.childrenCount,\n childrenTruncated: entry.metadata?.childrenTruncated,\n }));\n\n // Check if any entry has truncated children\n const hasTruncatedChildren = entries.some((e) => e.childrenTruncated);\n\n // Determine if results were truncated\n const truncated =\n hasTruncatedChildren ||\n (options.limit !== undefined && entries.length >= options.limit) ||\n result.message?.toLowerCase().includes(\"truncat\");\n\n return {\n entries,\n total: entries.length,\n truncated,\n message: result.message,\n };\n}\n\nfunction mapEntryType(type?: string): LsEntry[\"type\"] {\n // Only \"file\" is a true file, all other types are treated as directories\n if (type === \"file\") {\n return \"file\";\n }\n return \"directory\";\n}\n\nfunction formatDate(date: Date | string | undefined): string | undefined {\n if (!date) return undefined;\n // If it's already a string (from HTTP JSON), return as-is\n if (typeof date === \"string\") return date;\n // If it's a Date object, convert to ISO string\n return date.toISOString();\n}\n\n/**\n * Format ls output for different views\n */\nexport function formatLsOutput(result: LsResult, view: ViewType): string {\n switch (view) {\n case \"json\":\n return formatJson(result);\n case \"llm\":\n return formatLlm(result);\n case \"human\":\n return formatHuman(result);\n default:\n return formatDefault(result);\n }\n}\n\n/**\n * Default format: Machine truth, one path per line\n */\nfunction formatDefault(result: LsResult): string {\n const lines = result.entries.map((entry) => entry.path);\n if (result.truncated) {\n lines.push(`# Results truncated (${result.total} shown)`);\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * JSON format: Structured output\n */\nfunction formatJson(result: LsResult): string {\n return JSON.stringify(\n {\n entries: result.entries,\n total: result.total,\n truncated: result.truncated,\n message: result.message,\n },\n null,\n 2,\n );\n}\n\n/**\n * LLM format: Token-efficient, semantic facts\n */\nfunction formatLlm(result: LsResult): string {\n const lines: string[] = [];\n\n for (const entry of result.entries) {\n const parts = [`ENTRY ${entry.path}`];\n parts.push(`TYPE=${entry.type}`);\n\n if (entry.size !== undefined) {\n parts.push(`SIZE=${entry.size}`);\n }\n\n if (entry.childrenCount !== undefined) {\n parts.push(`CHILDREN=${entry.childrenCount}`);\n }\n\n if (entry.childrenTruncated) {\n parts.push(\"TRUNCATED\");\n }\n\n lines.push(parts.join(\" \"));\n }\n\n lines.push(`TOTAL ${result.total}`);\n if (result.truncated) {\n lines.push(\"TRUNCATED true\");\n }\n return lines.join(\"\\n\");\n}\n\ninterface TreeNode {\n name: string;\n entry?: LsEntry;\n children: Map<string, TreeNode>;\n}\n\n/**\n * Human format: Tree structure\n */\nfunction formatHuman(result: LsResult): string {\n // Build tree structure from flat paths\n const root: TreeNode = { name: \"\", children: new Map() };\n\n for (const entry of result.entries) {\n const parts = entry.path.split(\"/\").filter(Boolean);\n let current = root;\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]!;\n if (!current.children.has(part)) {\n current.children.set(part, { name: part, children: new Map() });\n }\n current = current.children.get(part)!;\n\n // Attach entry to the leaf node\n if (i === parts.length - 1) {\n current.entry = entry;\n }\n }\n }\n\n // Render tree\n const lines: string[] = [];\n renderTree(root, \"\", lines);\n\n if (result.truncated) {\n lines.push(\"\");\n lines.push(`(Results truncated - ${result.total} entries shown)`);\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction renderTree(node: TreeNode, prefix: string, lines: string[]): void {\n const children = Array.from(node.children.values());\n\n for (let i = 0; i < children.length; i++) {\n const child = children[i]!;\n const isLast = i === children.length - 1;\n const connector = isLast ? \"└── \" : \"├── \";\n const icon = child.entry ? getTypeIcon(child.entry.type) : \"\\u{1F4C2}\"; // 📂\n const sizeStr = child.entry?.size !== undefined ? ` ${formatSize(child.entry.size)}` : \"\";\n\n lines.push(`${prefix}${connector}${icon} ${child.name}${sizeStr}`);\n\n // Recurse into children\n const childPrefix = prefix + (isLast ? \" \" : \"│ \");\n renderTree(child, childPrefix, lines);\n }\n}\n\nfunction getTypeIcon(type: LsEntry[\"type\"]): string {\n switch (type) {\n case \"directory\":\n return \"\\u{1F4C2}\"; // 📂\n case \"file\":\n return \"\\u{1F4C4}\"; // 📄\n default:\n return \"\\u{1F4C4}\"; // 📄\n }\n}\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes}B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ls.mjs","names":[],"sources":["../../src/commands/ls.ts"],"sourcesContent":["import type { AFSEntry } from \"@aigne/afs\";\nimport type { AFSRuntime } from \"../runtime.js\";\nimport { colors } from \"../ui/index.js\";\n\nexport type ViewType = \"default\" | \"json\" | \"llm\" | \"human\";\n\nexport interface LsEntry {\n path: string;\n type: \"file\" | \"directory\";\n size?: number;\n modified?: string;\n hash?: string;\n childrenCount?: number;\n childrenTruncated?: boolean;\n}\n\nexport interface LsOptions {\n maxDepth?: number;\n limit?: number;\n maxChildren?: number;\n pattern?: string;\n}\n\nexport interface LsResult {\n entries: LsEntry[];\n total: number;\n truncated?: boolean;\n message?: string;\n}\n\n/**\n * List directory contents\n */\nexport async function lsCommand(\n runtime: AFSRuntime,\n path: string,\n options: LsOptions = {},\n): Promise<LsResult> {\n const result = await runtime.list(path, {\n maxDepth: options.maxDepth ?? 1,\n limit: options.limit,\n maxChildren: options.maxChildren,\n pattern: options.pattern,\n });\n\n const entries: LsEntry[] = result.data.map((entry: AFSEntry) => ({\n path: entry.path,\n type: mapEntryType(entry.metadata?.type),\n size: entry.metadata?.size,\n modified: formatDate(entry.updatedAt),\n childrenCount: entry.metadata?.childrenCount,\n childrenTruncated: entry.metadata?.childrenTruncated,\n }));\n\n // Check if any entry has truncated children\n const hasTruncatedChildren = entries.some((e) => e.childrenTruncated);\n\n // Determine if results were truncated\n const truncated =\n hasTruncatedChildren ||\n (options.limit !== undefined && entries.length >= options.limit) ||\n result.message?.toLowerCase().includes(\"truncat\");\n\n return {\n entries,\n total: entries.length,\n truncated,\n message: result.message,\n };\n}\n\nfunction mapEntryType(type?: string): LsEntry[\"type\"] {\n // Only \"file\" is a true file, all other types are treated as directories\n if (type === \"file\") {\n return \"file\";\n }\n return \"directory\";\n}\n\nfunction formatDate(date: Date | string | undefined): string | undefined {\n if (!date) return undefined;\n // If it's already a string (from HTTP JSON), return as-is\n if (typeof date === \"string\") return date;\n // If it's a Date object, convert to ISO string\n return date.toISOString();\n}\n\n/**\n * Format ls output for different views\n */\nexport function formatLsOutput(result: LsResult, view: ViewType): string {\n switch (view) {\n case \"json\":\n return formatJson(result);\n case \"llm\":\n return formatLlm(result);\n case \"human\":\n return formatHuman(result);\n default:\n return formatDefault(result);\n }\n}\n\n/**\n * Default format: Machine truth, one path per line\n */\nfunction formatDefault(result: LsResult): string {\n const lines = result.entries.map((entry) => entry.path);\n if (result.truncated) {\n lines.push(`# Results truncated (${result.total} shown)`);\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * JSON format: Structured output\n */\nfunction formatJson(result: LsResult): string {\n return JSON.stringify(\n {\n entries: result.entries,\n total: result.total,\n truncated: result.truncated,\n message: result.message,\n },\n null,\n 2,\n );\n}\n\n/**\n * LLM format: Token-efficient, semantic facts\n */\nfunction formatLlm(result: LsResult): string {\n const lines: string[] = [];\n\n for (const entry of result.entries) {\n const parts = [`ENTRY ${entry.path}`];\n parts.push(`TYPE=${entry.type}`);\n\n if (entry.size !== undefined) {\n parts.push(`SIZE=${entry.size}`);\n }\n\n if (entry.childrenCount !== undefined) {\n parts.push(`CHILDREN=${entry.childrenCount}`);\n }\n\n if (entry.childrenTruncated) {\n parts.push(\"TRUNCATED\");\n }\n\n lines.push(parts.join(\" \"));\n }\n\n lines.push(`TOTAL ${result.total}`);\n if (result.truncated) {\n lines.push(\"TRUNCATED true\");\n }\n return lines.join(\"\\n\");\n}\n\ninterface TreeNode {\n name: string;\n entry?: LsEntry;\n children: Map<string, TreeNode>;\n}\n\n/**\n * Human format: Tree structure\n */\nfunction formatHuman(result: LsResult): string {\n // Build tree structure from flat paths\n const root: TreeNode = { name: \"\", children: new Map() };\n\n for (const entry of result.entries) {\n const parts = entry.path.split(\"/\").filter(Boolean);\n let current = root;\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]!;\n if (!current.children.has(part)) {\n current.children.set(part, { name: part, children: new Map() });\n }\n current = current.children.get(part)!;\n\n // Attach entry to the leaf node\n if (i === parts.length - 1) {\n current.entry = entry;\n }\n }\n }\n\n // Render tree\n const lines: string[] = [];\n renderTree(root, \"\", lines);\n\n if (result.truncated) {\n lines.push(\"\");\n lines.push(colors.yellow(`(Results truncated - ${result.total} entries shown)`));\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction renderTree(node: TreeNode, prefix: string, lines: string[]): void {\n const children = Array.from(node.children.values());\n\n for (let i = 0; i < children.length; i++) {\n const child = children[i]!;\n const isLast = i === children.length - 1;\n const connector = colors.dim(isLast ? \"└── \" : \"├── \");\n const icon = child.entry ? getTypeIcon(child.entry.type) : \"\\u{1F4C2}\"; // 📂\n const name = child.entry?.type === \"directory\" ? colors.cyan(child.name) : child.name;\n const sizeStr =\n child.entry?.size !== undefined ? colors.dim(` ${formatSize(child.entry.size)}`) : \"\";\n\n lines.push(`${colors.dim(prefix)}${connector}${icon} ${name}${sizeStr}`);\n\n // Recurse into children\n const childPrefix = prefix + (isLast ? \" \" : \"│ \");\n renderTree(child, childPrefix, lines);\n }\n}\n\nfunction getTypeIcon(type: LsEntry[\"type\"]): string {\n switch (type) {\n case \"directory\":\n return \"\\u{1F4C2}\"; // 📂\n case \"file\":\n return \"\\u{1F4C4}\"; // 📄\n default:\n return \"\\u{1F4C4}\"; // 📄\n }\n}\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes}B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;\n}\n"],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
2
+ const require_schema = require('../config/schema.cjs');
2
3
  const require_loader = require('../config/loader.cjs');
4
+ const require_index = require('../ui/index.cjs');
3
5
  let node_fs_promises = require("node:fs/promises");
4
6
  let node_path = require("node:path");
5
7
  let smol_toml = require("smol-toml");
@@ -47,27 +49,45 @@ async function mountListCommand(cwd) {
47
49
  * Add a mount to config
48
50
  */
49
51
  async function mountAddCommand(cwd, path, uri, options = {}) {
52
+ if (!uri || uri.trim() === "") return {
53
+ success: false,
54
+ message: "URI is required"
55
+ };
56
+ const resolvedUri = resolveUriPath(uri, cwd);
57
+ const validation = require_schema.MountSchema.safeParse({
58
+ path,
59
+ uri: resolvedUri,
60
+ ...options
61
+ });
62
+ if (!validation.success) return {
63
+ success: false,
64
+ message: validation.error.errors.map((e) => e.message).join("; ")
65
+ };
66
+ const newMount = {
67
+ path: validation.data.path,
68
+ uri: validation.data.uri,
69
+ ...options.description && { description: options.description }
70
+ };
50
71
  const configDir = (0, node_path.join)(cwd, require_loader.CONFIG_DIR_NAME);
51
72
  const configPath = (0, node_path.join)(configDir, require_loader.CONFIG_FILE_NAME);
52
73
  const config = { mounts: [] };
53
74
  try {
54
75
  config.mounts = (0, smol_toml.parse)(await (0, node_fs_promises.readFile)(configPath, "utf-8")).mounts ?? [];
55
76
  } catch {}
56
- if (config.mounts.some((m) => m.path === path)) return {
77
+ const normalizedPath = validation.data.path;
78
+ if (config.mounts.some((m) => m.path === normalizedPath)) return {
57
79
  success: false,
58
- message: `Mount path "${path}" already exists`
59
- };
60
- const newMount = {
61
- path,
62
- uri: resolveUriPath(uri, cwd),
63
- ...options.description && { description: options.description }
80
+ message: `Mount path "${normalizedPath}" already exists`
64
81
  };
65
82
  config.mounts.push(newMount);
66
83
  try {
67
84
  await (0, node_fs_promises.mkdir)(configDir, { recursive: true });
68
85
  } catch {}
69
86
  await (0, node_fs_promises.writeFile)(configPath, (0, smol_toml.stringify)(config), "utf-8");
70
- return { success: true };
87
+ return {
88
+ success: true,
89
+ normalizedPath
90
+ };
71
91
  }
72
92
  /**
73
93
  * Remove a mount from config
@@ -102,8 +122,11 @@ async function mountValidateCommand(cwd) {
102
122
  try {
103
123
  const mounts = (0, smol_toml.parse)(await (0, node_fs_promises.readFile)(configPath, "utf-8")).mounts ?? [];
104
124
  for (const mount of mounts) {
105
- if (!mount.path.startsWith("/")) errors.push(`Mount path "${mount.path}" must start with /`);
106
- if (!mount.uri || mount.uri.trim() === "") errors.push(`Mount at "${mount.path}" has empty URI`);
125
+ const validation = require_schema.MountSchema.safeParse(mount);
126
+ if (!validation.success) {
127
+ for (const err of validation.error.errors) errors.push(`Mount "${mount.path}": ${err.message}`);
128
+ continue;
129
+ }
107
130
  if (mount.uri.startsWith("fs://")) {
108
131
  const targetPath = mount.uri.replace("fs://", "");
109
132
  try {
@@ -151,12 +174,12 @@ function formatLlm(mounts) {
151
174
  }).join("\n\n");
152
175
  }
153
176
  function formatHuman(mounts) {
154
- if (mounts.length === 0) return "No mounts configured.";
155
- const lines = ["Configured Mounts:", ""];
177
+ if (mounts.length === 0) return require_index.colors.dim("No mounts configured.");
178
+ const lines = [require_index.colors.bold("Configured Mounts:"), ""];
156
179
  for (const m of mounts) {
157
- lines.push(` ${m.path}`);
158
- lines.push(` URI: ${m.uri}`);
159
- if (m.description) lines.push(` Description: ${m.description}`);
180
+ lines.push(` ${require_index.colors.cyan(m.path)}`);
181
+ lines.push(` ${require_index.colors.dim("URI:")} ${m.uri}`);
182
+ if (m.description) lines.push(` ${require_index.colors.dim("Description:")} ${m.description}`);
160
183
  lines.push("");
161
184
  }
162
185
  return lines.join("\n").trimEnd();