@aigne/afs-cli 1.11.0-beta.4 → 1.11.0-beta.6
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.
- package/README.md +16 -1
- package/dist/cli.cjs +53 -20
- package/dist/cli.mjs +54 -21
- package/dist/cli.mjs.map +1 -1
- package/dist/commands/exec.cjs +132 -14
- package/dist/commands/exec.mjs +129 -14
- package/dist/commands/exec.mjs.map +1 -1
- package/dist/commands/explain.cjs +1 -1
- package/dist/commands/explain.mjs +1 -1
- package/dist/commands/explain.mjs.map +1 -1
- package/dist/commands/index.mjs +1 -1
- package/dist/commands/ls.cjs +129 -30
- package/dist/commands/ls.mjs +129 -30
- package/dist/commands/ls.mjs.map +1 -1
- package/dist/commands/mount.cjs +2 -1
- package/dist/commands/mount.mjs +2 -1
- package/dist/commands/mount.mjs.map +1 -1
- package/dist/commands/read.cjs +213 -14
- package/dist/commands/read.mjs +213 -14
- package/dist/commands/read.mjs.map +1 -1
- package/dist/commands/serve.cjs +3 -1
- package/dist/commands/serve.mjs +3 -1
- package/dist/commands/serve.mjs.map +1 -1
- package/dist/commands/stat.cjs +116 -34
- package/dist/commands/stat.mjs +117 -34
- package/dist/commands/stat.mjs.map +1 -1
- package/dist/commands/write.cjs +37 -4
- package/dist/commands/write.mjs +38 -4
- package/dist/commands/write.mjs.map +1 -1
- package/dist/config/loader.cjs +33 -13
- package/dist/config/loader.mjs +33 -13
- package/dist/config/loader.mjs.map +1 -1
- package/dist/config/provider-factory.cjs +311 -3
- package/dist/config/provider-factory.mjs +311 -3
- package/dist/config/provider-factory.mjs.map +1 -1
- package/dist/config/schema.cjs +3 -1
- package/dist/config/schema.mjs +3 -1
- package/dist/config/schema.mjs.map +1 -1
- package/dist/config/uri-parser.cjs +195 -2
- package/dist/config/uri-parser.mjs +195 -2
- package/dist/config/uri-parser.mjs.map +1 -1
- package/dist/explorer/actions.cjs +53 -23
- package/dist/explorer/actions.mjs +54 -23
- package/dist/explorer/actions.mjs.map +1 -1
- package/dist/explorer/components/dialog.cjs +163 -10
- package/dist/explorer/components/dialog.mjs +163 -10
- package/dist/explorer/components/dialog.mjs.map +1 -1
- package/dist/explorer/components/file-list.mjs.map +1 -1
- package/dist/explorer/components/metadata-panel.cjs +39 -25
- package/dist/explorer/components/metadata-panel.mjs +39 -25
- package/dist/explorer/components/metadata-panel.mjs.map +1 -1
- package/dist/explorer/screen.cjs +23 -8
- package/dist/explorer/screen.mjs +24 -9
- package/dist/explorer/screen.mjs.map +1 -1
- package/dist/explorer/theme.cjs +3 -1
- package/dist/explorer/theme.mjs +3 -1
- package/dist/explorer/theme.mjs.map +1 -1
- package/dist/path-utils.cjs +2 -1
- package/dist/path-utils.mjs +1 -1
- package/dist/runtime.cjs +24 -0
- package/dist/runtime.mjs +24 -0
- package/dist/runtime.mjs.map +1 -1
- package/dist/ui/header.cjs +0 -9
- package/dist/ui/header.mjs +1 -9
- package/dist/ui/header.mjs.map +1 -1
- package/dist/ui/index.cjs +0 -2
- package/dist/ui/index.mjs +2 -3
- package/dist/ui/index.mjs.map +1 -1
- package/dist/utils/meta.cjs +51 -0
- package/dist/utils/meta.mjs +49 -0
- package/dist/utils/meta.mjs.map +1 -0
- package/package.json +19 -9
package/dist/commands/ls.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { colors } from "../ui/index.mjs";
|
|
2
|
+
import { isActionsPath } from "../utils/meta.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/commands/ls.ts
|
|
4
5
|
/**
|
|
@@ -11,15 +12,18 @@ async function lsCommand(runtime, path, options = {}) {
|
|
|
11
12
|
maxChildren: options.maxChildren,
|
|
12
13
|
pattern: options.pattern
|
|
13
14
|
});
|
|
14
|
-
const entries = result.data.map((entry) =>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
const entries = result.data.map((entry) => {
|
|
16
|
+
const lsEntry = {
|
|
17
|
+
path: entry.path,
|
|
18
|
+
size: entry.metadata?.size,
|
|
19
|
+
modified: formatDate(entry.updatedAt),
|
|
20
|
+
childrenCount: entry.metadata?.childrenCount
|
|
21
|
+
};
|
|
22
|
+
const meta = extractMeta(entry.metadata);
|
|
23
|
+
if (meta && Object.keys(meta).length > 0) lsEntry.meta = meta;
|
|
24
|
+
return lsEntry;
|
|
25
|
+
});
|
|
26
|
+
const truncated = options.limit !== void 0 && entries.length >= options.limit || result.message?.toLowerCase().includes("truncat");
|
|
23
27
|
return {
|
|
24
28
|
entries,
|
|
25
29
|
total: entries.length,
|
|
@@ -27,9 +31,20 @@ async function lsCommand(runtime, path, options = {}) {
|
|
|
27
31
|
message: result.message
|
|
28
32
|
};
|
|
29
33
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Extract meta fields from entry metadata
|
|
36
|
+
*/
|
|
37
|
+
function extractMeta(metadata) {
|
|
38
|
+
if (!metadata) return void 0;
|
|
39
|
+
const builtInFields = new Set([
|
|
40
|
+
"type",
|
|
41
|
+
"size",
|
|
42
|
+
"mimeType",
|
|
43
|
+
"childrenCount"
|
|
44
|
+
]);
|
|
45
|
+
const meta = {};
|
|
46
|
+
for (const [key, value] of Object.entries(metadata)) if (!builtInFields.has(key) && value !== void 0) meta[key] = value;
|
|
47
|
+
return Object.keys(meta).length > 0 ? meta : void 0;
|
|
33
48
|
}
|
|
34
49
|
function formatDate(date) {
|
|
35
50
|
if (!date) return void 0;
|
|
@@ -39,18 +54,20 @@ function formatDate(date) {
|
|
|
39
54
|
/**
|
|
40
55
|
* Format ls output for different views
|
|
41
56
|
*/
|
|
42
|
-
function formatLsOutput(result, view) {
|
|
57
|
+
function formatLsOutput(result, view, options) {
|
|
58
|
+
const isActions = options?.path && isActionsPath(options.path);
|
|
43
59
|
switch (view) {
|
|
44
|
-
case "json": return formatJson(result);
|
|
45
|
-
case "llm": return formatLlm(result);
|
|
46
|
-
case "human": return formatHuman(result);
|
|
47
|
-
default: return formatDefault(result);
|
|
60
|
+
case "json": return isActions ? formatActionsJson(result, options.path) : formatJson(result);
|
|
61
|
+
case "llm": return isActions ? formatActionsLlm(result, options.path) : formatLlm(result);
|
|
62
|
+
case "human": return isActions ? formatActionsHuman(result, options.path) : formatHuman(result);
|
|
63
|
+
default: return isActions ? formatActionsDefault(result) : formatDefault(result);
|
|
48
64
|
}
|
|
49
65
|
}
|
|
50
66
|
/**
|
|
51
67
|
* Default format: Machine truth, one path per line
|
|
52
68
|
*/
|
|
53
69
|
function formatDefault(result) {
|
|
70
|
+
if (result.entries.length === 0 && result.message) return `# ${result.message}`;
|
|
54
71
|
const lines = result.entries.map((entry) => entry.path);
|
|
55
72
|
if (result.truncated) lines.push(`# Results truncated (${result.total} shown)`);
|
|
56
73
|
return lines.join("\n");
|
|
@@ -67,16 +84,19 @@ function formatJson(result) {
|
|
|
67
84
|
}, null, 2);
|
|
68
85
|
}
|
|
69
86
|
/**
|
|
70
|
-
* LLM format: Token-efficient, semantic facts
|
|
87
|
+
* LLM format: Token-efficient, semantic facts (includes KIND if present)
|
|
71
88
|
*/
|
|
72
89
|
function formatLlm(result) {
|
|
73
90
|
const lines = [];
|
|
91
|
+
if (result.entries.length === 0 && result.message) {
|
|
92
|
+
lines.push(`ERROR ${result.message}`);
|
|
93
|
+
return lines.join("\n");
|
|
94
|
+
}
|
|
74
95
|
for (const entry of result.entries) {
|
|
75
96
|
const parts = [`ENTRY ${entry.path}`];
|
|
76
|
-
parts.push(`
|
|
97
|
+
if (entry.meta?.kind) parts.push(`KIND=${entry.meta.kind}`);
|
|
77
98
|
if (entry.size !== void 0) parts.push(`SIZE=${entry.size}`);
|
|
78
99
|
if (entry.childrenCount !== void 0) parts.push(`CHILDREN=${entry.childrenCount}`);
|
|
79
|
-
if (entry.childrenTruncated) parts.push("TRUNCATED");
|
|
80
100
|
lines.push(parts.join(" "));
|
|
81
101
|
}
|
|
82
102
|
lines.push(`TOTAL ${result.total}`);
|
|
@@ -87,6 +107,7 @@ function formatLlm(result) {
|
|
|
87
107
|
* Human format: Tree structure
|
|
88
108
|
*/
|
|
89
109
|
function formatHuman(result) {
|
|
110
|
+
if (result.entries.length === 0 && result.message) return colors.yellow(result.message);
|
|
90
111
|
const root = {
|
|
91
112
|
name: "",
|
|
92
113
|
children: /* @__PURE__ */ new Map()
|
|
@@ -118,25 +139,103 @@ function renderTree(node, prefix, lines) {
|
|
|
118
139
|
const child = children[i];
|
|
119
140
|
const isLast = i === children.length - 1;
|
|
120
141
|
const connector = colors.dim(isLast ? "└── " : "├── ");
|
|
121
|
-
const icon = child.entry
|
|
122
|
-
const name = child.entry?.type === "directory" ? colors.cyan(child.name) : child.name;
|
|
142
|
+
const icon = typeof child.entry?.childrenCount === "number" || child.children.size > 0 ? "📁" : "📄";
|
|
123
143
|
const sizeStr = child.entry?.size !== void 0 ? colors.dim(` ${formatSize(child.entry.size)}`) : "";
|
|
124
|
-
|
|
144
|
+
const kindStr = child.entry?.meta?.kind ? colors.dim(` (${child.entry.meta.kind})`) : "";
|
|
145
|
+
lines.push(`${colors.dim(prefix)}${connector}${icon} ${child.name}${kindStr}${sizeStr}`);
|
|
125
146
|
renderTree(child, prefix + (isLast ? " " : "│ "), lines);
|
|
126
147
|
}
|
|
127
148
|
}
|
|
128
|
-
function getTypeIcon(type) {
|
|
129
|
-
switch (type) {
|
|
130
|
-
case "directory": return "📂";
|
|
131
|
-
case "file": return "📄";
|
|
132
|
-
default: return "📄";
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
149
|
function formatSize(bytes) {
|
|
136
150
|
if (bytes < 1024) return `${bytes}B`;
|
|
137
151
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
138
152
|
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
139
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Extract action name from path (last segment after .actions/)
|
|
156
|
+
*/
|
|
157
|
+
function getActionName(entry) {
|
|
158
|
+
const parts = entry.path.split("/");
|
|
159
|
+
return parts[parts.length - 1] || entry.path;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get action description from entry metadata
|
|
163
|
+
*/
|
|
164
|
+
function getActionDescription(entry) {
|
|
165
|
+
return entry.meta?.description;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get the node path from an actions path (path before /.actions)
|
|
169
|
+
*/
|
|
170
|
+
function getNodePathFromActions(actionsPath) {
|
|
171
|
+
const idx = actionsPath.lastIndexOf("/.actions");
|
|
172
|
+
return idx > 0 ? actionsPath.substring(0, idx) : "/";
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Format actions list - Human view
|
|
176
|
+
* Shows: Available actions for /path:
|
|
177
|
+
* export Export table data to file
|
|
178
|
+
* truncate Delete all rows from table
|
|
179
|
+
*/
|
|
180
|
+
function formatActionsHuman(result, path) {
|
|
181
|
+
const lines = [];
|
|
182
|
+
const nodePath = getNodePathFromActions(path);
|
|
183
|
+
if (result.entries.length === 0) {
|
|
184
|
+
lines.push(`No actions available for ${nodePath}`);
|
|
185
|
+
return lines.join("\n");
|
|
186
|
+
}
|
|
187
|
+
lines.push(`Available actions for ${nodePath}:`);
|
|
188
|
+
lines.push("");
|
|
189
|
+
const maxNameLen = Math.max(...result.entries.map((e) => getActionName(e).length));
|
|
190
|
+
for (const entry of result.entries) {
|
|
191
|
+
const name = getActionName(entry);
|
|
192
|
+
const desc = getActionDescription(entry) || "";
|
|
193
|
+
lines.push(` ${name.padEnd(maxNameLen + 2)}${desc}`);
|
|
194
|
+
}
|
|
195
|
+
return lines.join("\n");
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Format actions list - LLM view
|
|
199
|
+
* Shows: ACTIONS /path/.actions
|
|
200
|
+
* ACTION export DESCRIPTION "Export table data to file"
|
|
201
|
+
*/
|
|
202
|
+
function formatActionsLlm(result, path) {
|
|
203
|
+
const lines = [];
|
|
204
|
+
lines.push(`ACTIONS ${path}`);
|
|
205
|
+
if (result.entries.length === 0) {
|
|
206
|
+
lines.push("ACTIONS_COUNT 0");
|
|
207
|
+
return lines.join("\n");
|
|
208
|
+
}
|
|
209
|
+
for (const entry of result.entries) {
|
|
210
|
+
const name = getActionName(entry);
|
|
211
|
+
const desc = getActionDescription(entry);
|
|
212
|
+
const descPart = desc ? ` DESCRIPTION "${desc}"` : "";
|
|
213
|
+
lines.push(`ACTION ${name}${descPart}`);
|
|
214
|
+
}
|
|
215
|
+
return lines.join("\n");
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Format actions list - JSON view
|
|
219
|
+
* Shows structured JSON with path and actions array
|
|
220
|
+
*/
|
|
221
|
+
function formatActionsJson(result, path) {
|
|
222
|
+
const actions = result.entries.map((entry) => ({
|
|
223
|
+
name: getActionName(entry),
|
|
224
|
+
description: getActionDescription(entry)
|
|
225
|
+
}));
|
|
226
|
+
return JSON.stringify({
|
|
227
|
+
path,
|
|
228
|
+
data: actions
|
|
229
|
+
}, null, 2);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Format actions list - Default view
|
|
233
|
+
* Shows action names, one per line
|
|
234
|
+
*/
|
|
235
|
+
function formatActionsDefault(result) {
|
|
236
|
+
if (result.entries.length === 0) return "";
|
|
237
|
+
return result.entries.map((entry) => getActionName(entry)).join("\n");
|
|
238
|
+
}
|
|
140
239
|
|
|
141
240
|
//#endregion
|
|
142
241
|
export { formatLsOutput, lsCommand };
|
package/dist/commands/ls.mjs.map
CHANGED
|
@@ -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\";\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
|
+
{"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\";\nimport { isActionsPath } from \"../utils/meta.js\";\n\nexport type ViewType = \"default\" | \"json\" | \"llm\" | \"human\";\n\nexport interface LsEntry {\n path: string;\n size?: number;\n modified?: string;\n hash?: string;\n childrenCount?: number;\n meta?: {\n kind?: string;\n icon?: string;\n label?: string;\n [key: string]: unknown;\n };\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 const lsEntry: LsEntry = {\n path: entry.path,\n size: entry.metadata?.size,\n modified: formatDate(entry.updatedAt),\n childrenCount: entry.metadata?.childrenCount,\n };\n\n // Add meta if present\n const meta = extractMeta(entry.metadata);\n if (meta && Object.keys(meta).length > 0) {\n lsEntry.meta = meta;\n }\n\n return lsEntry;\n });\n\n // Determine if results were truncated\n const truncated =\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\n/**\n * Extract meta fields from entry metadata\n */\nfunction extractMeta(\n metadata: Record<string, unknown> | null | undefined,\n): LsEntry[\"meta\"] | undefined {\n if (!metadata) return undefined;\n\n const builtInFields = new Set([\"type\", \"size\", \"mimeType\", \"childrenCount\"]);\n\n const meta: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(metadata)) {\n if (!builtInFields.has(key) && value !== undefined) {\n meta[key] = value;\n }\n }\n\n return Object.keys(meta).length > 0 ? meta : undefined;\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\nexport interface FormatLsOptions {\n /** The path being listed (used for special formatting of .actions paths) */\n path?: string;\n}\n\n/**\n * Format ls output for different views\n */\nexport function formatLsOutput(\n result: LsResult,\n view: ViewType,\n options?: FormatLsOptions,\n): string {\n // Check if we're listing an actions path for special formatting\n const isActions = options?.path && isActionsPath(options.path);\n\n switch (view) {\n case \"json\":\n return isActions ? formatActionsJson(result, options.path!) : formatJson(result);\n case \"llm\":\n return isActions ? formatActionsLlm(result, options.path!) : formatLlm(result);\n case \"human\":\n return isActions ? formatActionsHuman(result, options.path!) : formatHuman(result);\n default:\n return isActions ? formatActionsDefault(result) : formatDefault(result);\n }\n}\n\n/**\n * Default format: Machine truth, one path per line\n */\nfunction formatDefault(result: LsResult): string {\n // If there are no entries and there's an error message, show it\n if (result.entries.length === 0 && result.message) {\n return `# ${result.message}`;\n }\n\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 (includes KIND if present)\n */\nfunction formatLlm(result: LsResult): string {\n const lines: string[] = [];\n\n // If there are no entries and there's an error message, show it\n if (result.entries.length === 0 && result.message) {\n lines.push(`ERROR ${result.message}`);\n return lines.join(\"\\n\");\n }\n\n for (const entry of result.entries) {\n const parts = [`ENTRY ${entry.path}`];\n\n if (entry.meta?.kind) {\n parts.push(`KIND=${entry.meta.kind}`);\n }\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 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 // If there are no entries and there's an error message, show it prominently\n if (result.entries.length === 0 && result.message) {\n return colors.yellow(result.message);\n }\n\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 // Use folder icon if:\n // 1. childrenCount is defined (directory), OR\n // 2. This tree node has children (implies it's a directory/mount point)\n const isDirectory = typeof child.entry?.childrenCount === \"number\" || child.children.size > 0;\n const icon = isDirectory ? \"\\u{1F4C1}\" : \"\\u{1F4C4}\"; // 📁 or 📄\n const sizeStr =\n child.entry?.size !== undefined ? colors.dim(` ${formatSize(child.entry.size)}`) : \"\";\n\n // Add kind in parentheses if present\n const kindStr = child.entry?.meta?.kind ? colors.dim(` (${child.entry.meta.kind})`) : \"\";\n\n lines.push(`${colors.dim(prefix)}${connector}${icon} ${child.name}${kindStr}${sizeStr}`);\n\n // Recurse into children\n const childPrefix = prefix + (isLast ? \" \" : \"│ \");\n renderTree(child, childPrefix, lines);\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\n/**\n * Extract action name from path (last segment after .actions/)\n */\nfunction getActionName(entry: LsEntry): string {\n const parts = entry.path.split(\"/\");\n return parts[parts.length - 1] || entry.path;\n}\n\n/**\n * Get action description from entry metadata\n */\nfunction getActionDescription(entry: LsEntry): string | undefined {\n return entry.meta?.description as string | undefined;\n}\n\n/**\n * Get the node path from an actions path (path before /.actions)\n */\nfunction getNodePathFromActions(actionsPath: string): string {\n const idx = actionsPath.lastIndexOf(\"/.actions\");\n return idx > 0 ? actionsPath.substring(0, idx) : \"/\";\n}\n\n/**\n * Format actions list - Human view\n * Shows: Available actions for /path:\n * export Export table data to file\n * truncate Delete all rows from table\n */\nfunction formatActionsHuman(result: LsResult, path: string): string {\n const lines: string[] = [];\n const nodePath = getNodePathFromActions(path);\n\n if (result.entries.length === 0) {\n lines.push(`No actions available for ${nodePath}`);\n return lines.join(\"\\n\");\n }\n\n lines.push(`Available actions for ${nodePath}:`);\n lines.push(\"\");\n\n // Find max action name length for padding\n const maxNameLen = Math.max(...result.entries.map((e) => getActionName(e).length));\n\n for (const entry of result.entries) {\n const name = getActionName(entry);\n const desc = getActionDescription(entry) || \"\";\n lines.push(` ${name.padEnd(maxNameLen + 2)}${desc}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Format actions list - LLM view\n * Shows: ACTIONS /path/.actions\n * ACTION export DESCRIPTION \"Export table data to file\"\n */\nfunction formatActionsLlm(result: LsResult, path: string): string {\n const lines: string[] = [];\n\n lines.push(`ACTIONS ${path}`);\n\n if (result.entries.length === 0) {\n lines.push(\"ACTIONS_COUNT 0\");\n return lines.join(\"\\n\");\n }\n\n for (const entry of result.entries) {\n const name = getActionName(entry);\n const desc = getActionDescription(entry);\n const descPart = desc ? ` DESCRIPTION \"${desc}\"` : \"\";\n lines.push(`ACTION ${name}${descPart}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Format actions list - JSON view\n * Shows structured JSON with path and actions array\n */\nfunction formatActionsJson(result: LsResult, path: string): string {\n const actions = result.entries.map((entry) => ({\n name: getActionName(entry),\n description: getActionDescription(entry),\n }));\n\n return JSON.stringify(\n {\n path,\n data: actions,\n },\n null,\n 2,\n );\n}\n\n/**\n * Format actions list - Default view\n * Shows action names, one per line\n */\nfunction formatActionsDefault(result: LsResult): string {\n if (result.entries.length === 0) {\n return \"\";\n }\n return result.entries.map((entry) => getActionName(entry)).join(\"\\n\");\n}\n"],"mappings":"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"}
|
package/dist/commands/mount.cjs
CHANGED
|
@@ -66,7 +66,8 @@ async function mountAddCommand(cwd, path, uri, options = {}) {
|
|
|
66
66
|
const newMount = {
|
|
67
67
|
path: validation.data.path,
|
|
68
68
|
uri: validation.data.uri,
|
|
69
|
-
...options.description && { description: options.description }
|
|
69
|
+
...options.description && { description: options.description },
|
|
70
|
+
...options.token && { token: options.token }
|
|
70
71
|
};
|
|
71
72
|
const configDir = (0, node_path.join)(cwd, require_loader.CONFIG_DIR_NAME);
|
|
72
73
|
const configPath = (0, node_path.join)(configDir, require_loader.CONFIG_FILE_NAME);
|
package/dist/commands/mount.mjs
CHANGED
|
@@ -65,7 +65,8 @@ async function mountAddCommand(cwd, path, uri, options = {}) {
|
|
|
65
65
|
const newMount = {
|
|
66
66
|
path: validation.data.path,
|
|
67
67
|
uri: validation.data.uri,
|
|
68
|
-
...options.description && { description: options.description }
|
|
68
|
+
...options.description && { description: options.description },
|
|
69
|
+
...options.token && { token: options.token }
|
|
69
70
|
};
|
|
70
71
|
const configDir = join(cwd, CONFIG_DIR_NAME);
|
|
71
72
|
const configPath = join(configDir, CONFIG_FILE_NAME);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount.mjs","names":[],"sources":["../../src/commands/mount.ts"],"sourcesContent":["import { access, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { parse, stringify } from \"smol-toml\";\nimport { CONFIG_DIR_NAME, CONFIG_FILE_NAME, ConfigLoader } from \"../config/loader.js\";\nimport { MountSchema } from \"../config/schema.js\";\nimport { colors } from \"../ui/index.js\";\nimport type { ViewType } from \"./ls.js\";\n\nexport interface MountEntry {\n path: string;\n uri: string;\n description?: string;\n access_mode?: \"readonly\" | \"readwrite\";\n auth?: string;\n options?: Record<string, unknown>;\n}\n\nexport interface MountListResult {\n mounts: MountEntry[];\n}\n\nexport interface MountCommandResult {\n success: boolean;\n message?: string;\n /** The normalized path (for display after successful add) */\n normalizedPath?: string;\n}\n\nexport interface MountValidateResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Check if a path looks like a remote Git URL\n * Matches SSH format (git@host:path) or embedded protocols (https://, http://, ssh://)\n */\nfunction isRemoteGitUrl(path: string): boolean {\n // SSH format: git@github.com:user/repo.git or user@host:path\n if (/^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+:/.test(path)) {\n return true;\n }\n // Embedded protocol: https://github.com/user/repo.git\n if (/^(https?|ssh|git):\\/\\//.test(path)) {\n return true;\n }\n return false;\n}\n\n/**\n * Resolve relative paths in URI to absolute paths\n * Supports fs://, git://, sqlite://, json:// schemes\n * Paths without protocol prefix are treated as fs:// paths (unless it's a remote git URL)\n */\nfunction resolveUriPath(uri: string, cwd: string): string {\n const schemeMatch = uri.match(/^([a-z]+):\\/\\//);\n\n // No protocol prefix\n if (!schemeMatch) {\n // Check if it's a remote git URL (SSH format like git@github.com:user/repo.git)\n if (isRemoteGitUrl(uri)) {\n return `git://${uri}`;\n }\n // Treat as local filesystem path\n const absolutePath = isAbsolute(uri) ? uri : resolve(cwd, uri);\n return `fs://${absolutePath}`;\n }\n\n const scheme = schemeMatch[1];\n const pathPart = uri.slice(schemeMatch[0].length);\n\n // Only resolve for local file-based schemes\n if (![\"fs\", \"git\", \"sqlite\", \"json\"].includes(scheme!)) {\n return uri;\n }\n\n // For git:// scheme, check if the path is a remote URL (not a local path)\n if (scheme === \"git\" && isRemoteGitUrl(pathPart)) {\n return uri;\n }\n\n // If path is already absolute, return as-is\n if (isAbsolute(pathPart)) {\n return uri;\n }\n\n // Resolve relative path against cwd\n const absolutePath = resolve(cwd, pathPart);\n return `${scheme}://${absolutePath}`;\n}\n\n/**\n * List all mounts from config (merged from all config layers)\n */\nexport async function mountListCommand(cwd: string): Promise<MountListResult> {\n const loader = new ConfigLoader();\n const config = await loader.load(cwd);\n return {\n mounts: config.mounts as MountEntry[],\n };\n}\n\n/**\n * Add a mount to config\n */\nexport async function mountAddCommand(\n cwd: string,\n path: string,\n uri: string,\n options: { description?: string } = {},\n): Promise<MountCommandResult> {\n // Check for empty URI before resolving (resolveUriPath would transform empty string)\n if (!uri || uri.trim() === \"\") {\n return {\n success: false,\n message: \"URI is required\",\n };\n }\n\n // Resolve relative paths in URI to absolute paths\n const resolvedUri = resolveUriPath(uri, cwd);\n\n // Validate and normalize inputs using schema\n // The schema transforms path (normalizes it) and validates all fields\n const validation = MountSchema.safeParse({\n path,\n uri: resolvedUri,\n ...options,\n });\n if (!validation.success) {\n const errors = validation.error.errors.map((e) => e.message).join(\"; \");\n return {\n success: false,\n message: errors,\n };\n }\n\n // Build mount entry with validated/normalized path and resolved URI\n const newMount: MountEntry = {\n path: validation.data.path, // Use normalized path from schema\n uri: validation.data.uri,\n ...(options.description && { description: options.description }),\n };\n\n const configDir = join(cwd, CONFIG_DIR_NAME);\n const configPath = join(configDir, CONFIG_FILE_NAME);\n\n // Load existing config or create new\n const config: { mounts: MountEntry[] } = { mounts: [] };\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const parsed = parse(content) as { mounts?: MountEntry[] };\n config.mounts = parsed.mounts ?? [];\n } catch {\n // Config doesn't exist, will create\n }\n\n // Check for duplicate path (use normalized path for comparison)\n const normalizedPath = validation.data.path;\n if (config.mounts.some((m) => m.path === normalizedPath)) {\n return {\n success: false,\n message: `Mount path \"${normalizedPath}\" already exists`,\n };\n }\n\n // Add validated mount\n config.mounts.push(newMount);\n\n // Ensure config directory exists\n try {\n await mkdir(configDir, { recursive: true });\n } catch {\n // Directory might already exist\n }\n\n // Write config\n await writeFile(configPath, stringify(config), \"utf-8\");\n\n return {\n success: true,\n normalizedPath,\n };\n}\n\n/**\n * Remove a mount from config\n */\nexport async function mountRemoveCommand(cwd: string, path: string): Promise<MountCommandResult> {\n const configPath = join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const config = parse(content) as { mounts?: MountEntry[] };\n const mounts = config.mounts ?? [];\n\n const index = mounts.findIndex((m) => m.path === path);\n if (index === -1) {\n return {\n success: false,\n message: `Mount path \"${path}\" not found`,\n };\n }\n\n mounts.splice(index, 1);\n config.mounts = mounts;\n\n await writeFile(configPath, stringify(config), \"utf-8\");\n\n return {\n success: true,\n };\n } catch {\n return {\n success: false,\n message: `Mount path \"${path}\" not found`,\n };\n }\n}\n\n/**\n * Validate mount configuration\n */\nexport async function mountValidateCommand(cwd: string): Promise<MountValidateResult> {\n const configPath = join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n const errors: string[] = [];\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const config = parse(content) as { mounts?: MountEntry[] };\n const mounts = config.mounts ?? [];\n\n for (const mount of mounts) {\n // Validate mount using schema\n const validation = MountSchema.safeParse(mount);\n if (!validation.success) {\n for (const err of validation.error.errors) {\n errors.push(`Mount \"${mount.path}\": ${err.message}`);\n }\n continue;\n }\n\n // For fs:// URIs, check if target exists\n if (mount.uri.startsWith(\"fs://\")) {\n const targetPath = mount.uri.replace(\"fs://\", \"\");\n try {\n await access(targetPath);\n } catch {\n errors.push(`Mount target \"${targetPath}\" does not exist`);\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n } catch {\n return {\n valid: true,\n errors: [],\n };\n }\n}\n\n/**\n * Format mount list output for different views\n */\nexport function formatMountListOutput(mounts: MountEntry[], view: ViewType): string {\n switch (view) {\n case \"json\":\n return JSON.stringify({ mounts }, null, 2);\n case \"llm\":\n return formatLlm(mounts);\n case \"human\":\n return formatHuman(mounts);\n default:\n return formatDefault(mounts);\n }\n}\n\nfunction formatDefault(mounts: MountEntry[]): string {\n if (mounts.length === 0) {\n return \"No mounts configured\";\n }\n\n return mounts\n .map((m) => {\n const desc = m.description ? ` (${m.description})` : \"\";\n return `${m.path} → ${m.uri}${desc}`;\n })\n .join(\"\\n\");\n}\n\nfunction formatLlm(mounts: MountEntry[]): string {\n if (mounts.length === 0) {\n return \"NO_MOUNTS\";\n }\n\n return mounts\n .map((m) => {\n const lines = [`MOUNT ${m.path}`, `URI=${m.uri}`];\n if (m.description) {\n lines.push(`DESC=${m.description}`);\n }\n return lines.join(\"\\n\");\n })\n .join(\"\\n\\n\");\n}\n\nfunction formatHuman(mounts: MountEntry[]): string {\n if (mounts.length === 0) {\n return colors.dim(\"No mounts configured.\");\n }\n\n const lines = [colors.bold(\"Configured Mounts:\"), \"\"];\n for (const m of mounts) {\n lines.push(` ${colors.cyan(m.path)}`);\n lines.push(` ${colors.dim(\"URI:\")} ${m.uri}`);\n if (m.description) {\n lines.push(` ${colors.dim(\"Description:\")} ${m.description}`);\n }\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\").trimEnd();\n}\n"],"mappings":";;;;;;;;;;;;AAqCA,SAAS,eAAe,MAAuB;AAE7C,KAAI,oCAAoC,KAAK,KAAK,CAChD,QAAO;AAGT,KAAI,yBAAyB,KAAK,KAAK,CACrC,QAAO;AAET,QAAO;;;;;;;AAQT,SAAS,eAAe,KAAa,KAAqB;CACxD,MAAM,cAAc,IAAI,MAAM,iBAAiB;AAG/C,KAAI,CAAC,aAAa;AAEhB,MAAI,eAAe,IAAI,CACrB,QAAO,SAAS;AAIlB,SAAO,QADc,WAAW,IAAI,GAAG,MAAM,QAAQ,KAAK,IAAI;;CAIhE,MAAM,SAAS,YAAY;CAC3B,MAAM,WAAW,IAAI,MAAM,YAAY,GAAG,OAAO;AAGjD,KAAI,CAAC;EAAC;EAAM;EAAO;EAAU;EAAO,CAAC,SAAS,OAAQ,CACpD,QAAO;AAIT,KAAI,WAAW,SAAS,eAAe,SAAS,CAC9C,QAAO;AAIT,KAAI,WAAW,SAAS,CACtB,QAAO;AAKT,QAAO,GAAG,OAAO,KADI,QAAQ,KAAK,SAAS;;;;;AAO7C,eAAsB,iBAAiB,KAAuC;AAG5E,QAAO,EACL,SAFa,MADA,IAAI,cAAc,CACL,KAAK,IAAI,EAEpB,QAChB;;;;;AAMH,eAAsB,gBACpB,KACA,MACA,KACA,UAAoC,EAAE,EACT;AAE7B,KAAI,CAAC,OAAO,IAAI,MAAM,KAAK,GACzB,QAAO;EACL,SAAS;EACT,SAAS;EACV;CAIH,MAAM,cAAc,eAAe,KAAK,IAAI;CAI5C,MAAM,aAAa,YAAY,UAAU;EACvC;EACA,KAAK;EACL,GAAG;EACJ,CAAC;AACF,KAAI,CAAC,WAAW,QAEd,QAAO;EACL,SAAS;EACT,SAHa,WAAW,MAAM,OAAO,KAAK,MAAM,EAAE,QAAQ,CAAC,KAAK,KAAK;EAItE;CAIH,MAAM,WAAuB;EAC3B,MAAM,WAAW,KAAK;EACtB,KAAK,WAAW,KAAK;EACrB,GAAI,QAAQ,eAAe,EAAE,aAAa,QAAQ,aAAa;EAChE;CAED,MAAM,YAAY,KAAK,KAAK,gBAAgB;CAC5C,MAAM,aAAa,KAAK,WAAW,iBAAiB;CAGpD,MAAM,SAAmC,EAAE,QAAQ,EAAE,EAAE;AAEvD,KAAI;AAGF,SAAO,SADQ,MADC,MAAM,SAAS,YAAY,QAAQ,CACtB,CACN,UAAU,EAAE;SAC7B;CAKR,MAAM,iBAAiB,WAAW,KAAK;AACvC,KAAI,OAAO,OAAO,MAAM,MAAM,EAAE,SAAS,eAAe,CACtD,QAAO;EACL,SAAS;EACT,SAAS,eAAe,eAAe;EACxC;AAIH,QAAO,OAAO,KAAK,SAAS;AAG5B,KAAI;AACF,QAAM,MAAM,WAAW,EAAE,WAAW,MAAM,CAAC;SACrC;AAKR,OAAM,UAAU,YAAY,UAAU,OAAO,EAAE,QAAQ;AAEvD,QAAO;EACL,SAAS;EACT;EACD;;;;;AAMH,eAAsB,mBAAmB,KAAa,MAA2C;CAC/F,MAAM,aAAa,KAAK,KAAK,iBAAiB,iBAAiB;AAE/D,KAAI;EAEF,MAAM,SAAS,MADC,MAAM,SAAS,YAAY,QAAQ,CACtB;EAC7B,MAAM,SAAS,OAAO,UAAU,EAAE;EAElC,MAAM,QAAQ,OAAO,WAAW,MAAM,EAAE,SAAS,KAAK;AACtD,MAAI,UAAU,GACZ,QAAO;GACL,SAAS;GACT,SAAS,eAAe,KAAK;GAC9B;AAGH,SAAO,OAAO,OAAO,EAAE;AACvB,SAAO,SAAS;AAEhB,QAAM,UAAU,YAAY,UAAU,OAAO,EAAE,QAAQ;AAEvD,SAAO,EACL,SAAS,MACV;SACK;AACN,SAAO;GACL,SAAS;GACT,SAAS,eAAe,KAAK;GAC9B;;;;;;AAOL,eAAsB,qBAAqB,KAA2C;CACpF,MAAM,aAAa,KAAK,KAAK,iBAAiB,iBAAiB;CAC/D,MAAM,SAAmB,EAAE;AAE3B,KAAI;EAGF,MAAM,SADS,MADC,MAAM,SAAS,YAAY,QAAQ,CACtB,CACP,UAAU,EAAE;AAElC,OAAK,MAAM,SAAS,QAAQ;GAE1B,MAAM,aAAa,YAAY,UAAU,MAAM;AAC/C,OAAI,CAAC,WAAW,SAAS;AACvB,SAAK,MAAM,OAAO,WAAW,MAAM,OACjC,QAAO,KAAK,UAAU,MAAM,KAAK,KAAK,IAAI,UAAU;AAEtD;;AAIF,OAAI,MAAM,IAAI,WAAW,QAAQ,EAAE;IACjC,MAAM,aAAa,MAAM,IAAI,QAAQ,SAAS,GAAG;AACjD,QAAI;AACF,WAAM,OAAO,WAAW;YAClB;AACN,YAAO,KAAK,iBAAiB,WAAW,kBAAkB;;;;AAKhE,SAAO;GACL,OAAO,OAAO,WAAW;GACzB;GACD;SACK;AACN,SAAO;GACL,OAAO;GACP,QAAQ,EAAE;GACX;;;;;;AAOL,SAAgB,sBAAsB,QAAsB,MAAwB;AAClF,SAAQ,MAAR;EACE,KAAK,OACH,QAAO,KAAK,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE;EAC5C,KAAK,MACH,QAAO,UAAU,OAAO;EAC1B,KAAK,QACH,QAAO,YAAY,OAAO;EAC5B,QACE,QAAO,cAAc,OAAO;;;AAIlC,SAAS,cAAc,QAA8B;AACnD,KAAI,OAAO,WAAW,EACpB,QAAO;AAGT,QAAO,OACJ,KAAK,MAAM;EACV,MAAM,OAAO,EAAE,cAAc,KAAK,EAAE,YAAY,KAAK;AACrD,SAAO,GAAG,EAAE,KAAK,KAAK,EAAE,MAAM;GAC9B,CACD,KAAK,KAAK;;AAGf,SAAS,UAAU,QAA8B;AAC/C,KAAI,OAAO,WAAW,EACpB,QAAO;AAGT,QAAO,OACJ,KAAK,MAAM;EACV,MAAM,QAAQ,CAAC,SAAS,EAAE,QAAQ,OAAO,EAAE,MAAM;AACjD,MAAI,EAAE,YACJ,OAAM,KAAK,QAAQ,EAAE,cAAc;AAErC,SAAO,MAAM,KAAK,KAAK;GACvB,CACD,KAAK,OAAO;;AAGjB,SAAS,YAAY,QAA8B;AACjD,KAAI,OAAO,WAAW,EACpB,QAAO,OAAO,IAAI,wBAAwB;CAG5C,MAAM,QAAQ,CAAC,OAAO,KAAK,qBAAqB,EAAE,GAAG;AACrD,MAAK,MAAM,KAAK,QAAQ;AACtB,QAAM,KAAK,KAAK,OAAO,KAAK,EAAE,KAAK,GAAG;AACtC,QAAM,KAAK,OAAO,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,MAAM;AAChD,MAAI,EAAE,YACJ,OAAM,KAAK,OAAO,OAAO,IAAI,eAAe,CAAC,GAAG,EAAE,cAAc;AAElE,QAAM,KAAK,GAAG;;AAGhB,QAAO,MAAM,KAAK,KAAK,CAAC,SAAS"}
|
|
1
|
+
{"version":3,"file":"mount.mjs","names":[],"sources":["../../src/commands/mount.ts"],"sourcesContent":["import { access, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { parse, stringify } from \"smol-toml\";\nimport { CONFIG_DIR_NAME, CONFIG_FILE_NAME, ConfigLoader } from \"../config/loader.js\";\nimport { MountSchema } from \"../config/schema.js\";\nimport { colors } from \"../ui/index.js\";\nimport type { ViewType } from \"./ls.js\";\n\nexport interface MountEntry {\n path: string;\n uri: string;\n description?: string;\n access_mode?: \"readonly\" | \"readwrite\";\n auth?: string;\n token?: string;\n options?: Record<string, unknown>;\n}\n\nexport interface MountListResult {\n mounts: MountEntry[];\n}\n\nexport interface MountCommandResult {\n success: boolean;\n message?: string;\n /** The normalized path (for display after successful add) */\n normalizedPath?: string;\n}\n\nexport interface MountValidateResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Check if a path looks like a remote Git URL\n * Matches SSH format (git@host:path) or embedded protocols (https://, http://, ssh://)\n */\nfunction isRemoteGitUrl(path: string): boolean {\n // SSH format: git@github.com:user/repo.git or user@host:path\n if (/^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+:/.test(path)) {\n return true;\n }\n // Embedded protocol: https://github.com/user/repo.git\n if (/^(https?|ssh|git):\\/\\//.test(path)) {\n return true;\n }\n return false;\n}\n\n/**\n * Resolve relative paths in URI to absolute paths\n * Supports fs://, git://, sqlite://, json:// schemes\n * Paths without protocol prefix are treated as fs:// paths (unless it's a remote git URL)\n */\nfunction resolveUriPath(uri: string, cwd: string): string {\n const schemeMatch = uri.match(/^([a-z]+):\\/\\//);\n\n // No protocol prefix\n if (!schemeMatch) {\n // Check if it's a remote git URL (SSH format like git@github.com:user/repo.git)\n if (isRemoteGitUrl(uri)) {\n return `git://${uri}`;\n }\n // Treat as local filesystem path\n const absolutePath = isAbsolute(uri) ? uri : resolve(cwd, uri);\n return `fs://${absolutePath}`;\n }\n\n const scheme = schemeMatch[1];\n const pathPart = uri.slice(schemeMatch[0].length);\n\n // Only resolve for local file-based schemes\n if (![\"fs\", \"git\", \"sqlite\", \"json\"].includes(scheme!)) {\n return uri;\n }\n\n // For git:// scheme, check if the path is a remote URL (not a local path)\n if (scheme === \"git\" && isRemoteGitUrl(pathPart)) {\n return uri;\n }\n\n // If path is already absolute, return as-is\n if (isAbsolute(pathPart)) {\n return uri;\n }\n\n // Resolve relative path against cwd\n const absolutePath = resolve(cwd, pathPart);\n return `${scheme}://${absolutePath}`;\n}\n\n/**\n * List all mounts from config (merged from all config layers)\n */\nexport async function mountListCommand(cwd: string): Promise<MountListResult> {\n const loader = new ConfigLoader();\n const config = await loader.load(cwd);\n return {\n mounts: config.mounts as MountEntry[],\n };\n}\n\n/**\n * Add a mount to config\n */\nexport async function mountAddCommand(\n cwd: string,\n path: string,\n uri: string,\n options: { description?: string; token?: string } = {},\n): Promise<MountCommandResult> {\n // Check for empty URI before resolving (resolveUriPath would transform empty string)\n if (!uri || uri.trim() === \"\") {\n return {\n success: false,\n message: \"URI is required\",\n };\n }\n\n // Resolve relative paths in URI to absolute paths\n const resolvedUri = resolveUriPath(uri, cwd);\n\n // Validate and normalize inputs using schema\n // The schema transforms path (normalizes it) and validates all fields\n const validation = MountSchema.safeParse({\n path,\n uri: resolvedUri,\n ...options,\n });\n if (!validation.success) {\n const errors = validation.error.errors.map((e) => e.message).join(\"; \");\n return {\n success: false,\n message: errors,\n };\n }\n\n // Build mount entry with validated/normalized path and resolved URI\n const newMount: MountEntry = {\n path: validation.data.path, // Use normalized path from schema\n uri: validation.data.uri,\n ...(options.description && { description: options.description }),\n ...(options.token && { token: options.token }),\n };\n\n const configDir = join(cwd, CONFIG_DIR_NAME);\n const configPath = join(configDir, CONFIG_FILE_NAME);\n\n // Load existing config or create new\n const config: { mounts: MountEntry[] } = { mounts: [] };\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const parsed = parse(content) as { mounts?: MountEntry[] };\n config.mounts = parsed.mounts ?? [];\n } catch {\n // Config doesn't exist, will create\n }\n\n // Check for duplicate path (use normalized path for comparison)\n const normalizedPath = validation.data.path;\n if (config.mounts.some((m) => m.path === normalizedPath)) {\n return {\n success: false,\n message: `Mount path \"${normalizedPath}\" already exists`,\n };\n }\n\n // Add validated mount\n config.mounts.push(newMount);\n\n // Ensure config directory exists\n try {\n await mkdir(configDir, { recursive: true });\n } catch {\n // Directory might already exist\n }\n\n // Write config\n await writeFile(configPath, stringify(config), \"utf-8\");\n\n return {\n success: true,\n normalizedPath,\n };\n}\n\n/**\n * Remove a mount from config\n */\nexport async function mountRemoveCommand(cwd: string, path: string): Promise<MountCommandResult> {\n const configPath = join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const config = parse(content) as { mounts?: MountEntry[] };\n const mounts = config.mounts ?? [];\n\n const index = mounts.findIndex((m) => m.path === path);\n if (index === -1) {\n return {\n success: false,\n message: `Mount path \"${path}\" not found`,\n };\n }\n\n mounts.splice(index, 1);\n config.mounts = mounts;\n\n await writeFile(configPath, stringify(config), \"utf-8\");\n\n return {\n success: true,\n };\n } catch {\n return {\n success: false,\n message: `Mount path \"${path}\" not found`,\n };\n }\n}\n\n/**\n * Validate mount configuration\n */\nexport async function mountValidateCommand(cwd: string): Promise<MountValidateResult> {\n const configPath = join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n const errors: string[] = [];\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const config = parse(content) as { mounts?: MountEntry[] };\n const mounts = config.mounts ?? [];\n\n for (const mount of mounts) {\n // Validate mount using schema\n const validation = MountSchema.safeParse(mount);\n if (!validation.success) {\n for (const err of validation.error.errors) {\n errors.push(`Mount \"${mount.path}\": ${err.message}`);\n }\n continue;\n }\n\n // For fs:// URIs, check if target exists\n if (mount.uri.startsWith(\"fs://\")) {\n const targetPath = mount.uri.replace(\"fs://\", \"\");\n try {\n await access(targetPath);\n } catch {\n errors.push(`Mount target \"${targetPath}\" does not exist`);\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n } catch {\n return {\n valid: true,\n errors: [],\n };\n }\n}\n\n/**\n * Format mount list output for different views\n */\nexport function formatMountListOutput(mounts: MountEntry[], view: ViewType): string {\n switch (view) {\n case \"json\":\n return JSON.stringify({ mounts }, null, 2);\n case \"llm\":\n return formatLlm(mounts);\n case \"human\":\n return formatHuman(mounts);\n default:\n return formatDefault(mounts);\n }\n}\n\nfunction formatDefault(mounts: MountEntry[]): string {\n if (mounts.length === 0) {\n return \"No mounts configured\";\n }\n\n return mounts\n .map((m) => {\n const desc = m.description ? ` (${m.description})` : \"\";\n return `${m.path} → ${m.uri}${desc}`;\n })\n .join(\"\\n\");\n}\n\nfunction formatLlm(mounts: MountEntry[]): string {\n if (mounts.length === 0) {\n return \"NO_MOUNTS\";\n }\n\n return mounts\n .map((m) => {\n const lines = [`MOUNT ${m.path}`, `URI=${m.uri}`];\n if (m.description) {\n lines.push(`DESC=${m.description}`);\n }\n return lines.join(\"\\n\");\n })\n .join(\"\\n\\n\");\n}\n\nfunction formatHuman(mounts: MountEntry[]): string {\n if (mounts.length === 0) {\n return colors.dim(\"No mounts configured.\");\n }\n\n const lines = [colors.bold(\"Configured Mounts:\"), \"\"];\n for (const m of mounts) {\n lines.push(` ${colors.cyan(m.path)}`);\n lines.push(` ${colors.dim(\"URI:\")} ${m.uri}`);\n if (m.description) {\n lines.push(` ${colors.dim(\"Description:\")} ${m.description}`);\n }\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\").trimEnd();\n}\n"],"mappings":"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"}
|
package/dist/commands/read.cjs
CHANGED
|
@@ -1,22 +1,92 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_meta = require('../utils/meta.cjs');
|
|
3
|
+
let _aigne_afs = require("@aigne/afs");
|
|
1
4
|
|
|
2
5
|
//#region src/commands/read.ts
|
|
3
6
|
/**
|
|
4
7
|
* Read file content
|
|
5
8
|
*/
|
|
6
9
|
async function readCommand(runtime, path) {
|
|
7
|
-
|
|
10
|
+
let result;
|
|
11
|
+
try {
|
|
12
|
+
result = await runtime.read(path);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
if (error instanceof _aigne_afs.AFSNotFoundError) return {
|
|
15
|
+
path,
|
|
16
|
+
content: void 0
|
|
17
|
+
};
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
8
20
|
if (!result.data) return {
|
|
9
21
|
path,
|
|
10
22
|
content: void 0
|
|
11
23
|
};
|
|
12
24
|
const entry = result.data;
|
|
13
|
-
|
|
25
|
+
const isMeta = require_meta.isMetaPath(path);
|
|
26
|
+
const isAction = require_meta.isActionsPath(path);
|
|
27
|
+
const content = isMeta ? entry.content : typeof entry.content === "string" ? entry.content : entry.content !== void 0 ? JSON.stringify(entry.content) : void 0;
|
|
28
|
+
const readResult = {
|
|
14
29
|
path: entry.path,
|
|
15
|
-
content
|
|
16
|
-
|
|
30
|
+
content,
|
|
31
|
+
isMeta,
|
|
32
|
+
isAction,
|
|
17
33
|
size: entry.metadata?.size,
|
|
18
|
-
mimeType: entry.metadata?.mimeType
|
|
34
|
+
mimeType: entry.metadata?.mimeType,
|
|
35
|
+
childrenCount: entry.metadata?.childrenCount
|
|
19
36
|
};
|
|
37
|
+
if (!isMeta && !isAction) try {
|
|
38
|
+
const statResult = await runtime.stat(path);
|
|
39
|
+
if (statResult.data?.actions && statResult.data.actions.length > 0) readResult.actions = statResult.data.actions;
|
|
40
|
+
} catch {}
|
|
41
|
+
const meta = extractMeta(entry.metadata);
|
|
42
|
+
if (meta && Object.keys(meta).length > 0) readResult.meta = meta;
|
|
43
|
+
return readResult;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Extract meta fields from entry metadata
|
|
47
|
+
* Excludes built-in fields that are exposed separately
|
|
48
|
+
*/
|
|
49
|
+
function extractMeta(metadata) {
|
|
50
|
+
if (!metadata) return void 0;
|
|
51
|
+
const builtInFields = new Set([
|
|
52
|
+
"type",
|
|
53
|
+
"size",
|
|
54
|
+
"mimeType",
|
|
55
|
+
"childrenCount"
|
|
56
|
+
]);
|
|
57
|
+
const meta = {};
|
|
58
|
+
for (const [key, value] of Object.entries(metadata)) if (!builtInFields.has(key) && value !== void 0) meta[key] = value;
|
|
59
|
+
return Object.keys(meta).length > 0 ? meta : void 0;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Extract user-defined metadata (excludes system fields)
|
|
63
|
+
*/
|
|
64
|
+
function extractUserMetadata(meta) {
|
|
65
|
+
if (!meta) return {};
|
|
66
|
+
const systemFields = new Set([
|
|
67
|
+
"kind",
|
|
68
|
+
"kinds",
|
|
69
|
+
"childrenCount",
|
|
70
|
+
"inputSchema",
|
|
71
|
+
"required",
|
|
72
|
+
"description"
|
|
73
|
+
]);
|
|
74
|
+
return Object.fromEntries(Object.entries(meta).filter(([k]) => !systemFields.has(k)));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Format params summary: format*,path,limit (* = required)
|
|
78
|
+
*/
|
|
79
|
+
function formatParamsSummary(schema, required = []) {
|
|
80
|
+
if (!schema) return "";
|
|
81
|
+
const props = schema.properties || schema;
|
|
82
|
+
return Object.keys(props).map((name) => required.includes(name) ? `${name}*` : name).join(",");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Check if entry is executable (has afs:executable in kinds)
|
|
86
|
+
*/
|
|
87
|
+
function isExecutable(meta) {
|
|
88
|
+
if (!meta?.kinds) return false;
|
|
89
|
+
return meta.kinds.includes("afs:executable");
|
|
20
90
|
}
|
|
21
91
|
/**
|
|
22
92
|
* Format read output for different views
|
|
@@ -30,34 +100,163 @@ function formatReadOutput(read, view) {
|
|
|
30
100
|
}
|
|
31
101
|
}
|
|
32
102
|
/**
|
|
33
|
-
* Default format: Raw content
|
|
103
|
+
* Default format: Raw content or one-liner summary
|
|
104
|
+
* - If content exists: show raw content
|
|
105
|
+
* - If no content (directories): show one-liner summary /path kind children=N actions=a,b,c
|
|
106
|
+
* - For executables: /path afs:executable params=format*,path,limit
|
|
107
|
+
* - For meta paths: raw JSON
|
|
34
108
|
*/
|
|
35
109
|
function formatDefault(read) {
|
|
36
|
-
|
|
110
|
+
if (read.isMeta && typeof read.content === "object") return JSON.stringify(read.content, null, 2);
|
|
111
|
+
if (read.isAction && isExecutable(read.meta)) {
|
|
112
|
+
if (read.content !== void 0 && read.content !== null && read.content !== "") return typeof read.content === "string" ? read.content : JSON.stringify(read.content, null, 2);
|
|
113
|
+
const parts$1 = [read.path, "afs:executable"];
|
|
114
|
+
if (read.meta?.inputSchema) {
|
|
115
|
+
const params = formatParamsSummary(read.meta.inputSchema, read.meta.required);
|
|
116
|
+
if (params) parts$1.push(`params=${params}`);
|
|
117
|
+
}
|
|
118
|
+
return parts$1.join(" ");
|
|
119
|
+
}
|
|
120
|
+
if (read.content !== void 0 && read.content !== null && read.content !== "") return typeof read.content === "string" ? read.content : JSON.stringify(read.content, null, 2);
|
|
121
|
+
const parts = [read.path];
|
|
122
|
+
if (read.meta?.kind) parts.push(read.meta.kind);
|
|
123
|
+
if (read.childrenCount !== void 0) parts.push(`children=${read.childrenCount}`);
|
|
124
|
+
if (read.actions && read.actions.length > 0) parts.push(`actions=${read.actions.map((a) => a.name).join(",")}`);
|
|
125
|
+
return parts.join(" ");
|
|
37
126
|
}
|
|
38
127
|
/**
|
|
39
|
-
* JSON format
|
|
128
|
+
* JSON format (includes meta field for regular files if present)
|
|
40
129
|
*/
|
|
41
130
|
function formatJson(read) {
|
|
42
131
|
return JSON.stringify(read, null, 2);
|
|
43
132
|
}
|
|
44
133
|
/**
|
|
45
|
-
* LLM format:
|
|
134
|
+
* LLM format: Structured text for AI parsing
|
|
46
135
|
*/
|
|
47
136
|
function formatLlm(read) {
|
|
48
137
|
const lines = [];
|
|
49
|
-
|
|
138
|
+
if (read.isMeta) {
|
|
139
|
+
lines.push(`META ${read.path}`);
|
|
140
|
+
const content = read.content;
|
|
141
|
+
if (content?.kind) lines.push(`KIND ${content.kind}`);
|
|
142
|
+
lines.push(`CONTENT`);
|
|
143
|
+
lines.push(JSON.stringify(content));
|
|
144
|
+
return lines.join("\n");
|
|
145
|
+
}
|
|
146
|
+
if (read.isAction && isExecutable(read.meta)) {
|
|
147
|
+
lines.push(`EXECUTABLE ${read.path}`);
|
|
148
|
+
if (read.meta?.description) lines.push(`DESCRIPTION "${read.meta.description}"`);
|
|
149
|
+
if (read.meta?.inputSchema) {
|
|
150
|
+
const props = read.meta.inputSchema.properties || {};
|
|
151
|
+
const required = read.meta.required || [];
|
|
152
|
+
for (const [name, propDef] of Object.entries(props)) {
|
|
153
|
+
const prop = propDef;
|
|
154
|
+
const isReq = required.includes(name);
|
|
155
|
+
let paramLine = `PARAM ${name} TYPE ${prop.type || "any"}`;
|
|
156
|
+
paramLine += isReq ? " REQUIRED" : " OPTIONAL";
|
|
157
|
+
if (prop.enum) paramLine += ` CHOICES ${prop.enum.join(",")}`;
|
|
158
|
+
if (prop.default !== void 0) paramLine += ` DEFAULT ${prop.default}`;
|
|
159
|
+
lines.push(paramLine);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return lines.join("\n");
|
|
163
|
+
}
|
|
164
|
+
lines.push(`NODE ${read.path}`);
|
|
165
|
+
if (read.meta?.kind) lines.push(`KIND ${read.meta.kind}`);
|
|
166
|
+
if (read.meta?.kinds && Array.isArray(read.meta.kinds) && read.meta.kinds.length > 0) lines.push(`KINDS ${read.meta.kinds.join(" ")}`);
|
|
167
|
+
if (read.childrenCount !== void 0) lines.push(`CHILDREN ${read.childrenCount}`);
|
|
50
168
|
if (read.mimeType) lines.push(`MIME ${read.mimeType}`);
|
|
51
169
|
if (read.size !== void 0) lines.push(`SIZE ${read.size}`);
|
|
52
|
-
|
|
53
|
-
|
|
170
|
+
const userMeta = extractUserMetadata(read.meta);
|
|
171
|
+
if (Object.keys(userMeta).length > 0) {
|
|
172
|
+
const metaStr = Object.entries(userMeta).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
|
|
173
|
+
lines.push(`META ${metaStr}`);
|
|
174
|
+
}
|
|
175
|
+
if (read.actions && read.actions.length > 0) lines.push(`ACTIONS ${read.actions.map((a) => a.name).join(" ")}`);
|
|
176
|
+
if (read.content !== void 0 && read.content !== null && read.content !== "") lines.push(`CONTENT ${JSON.stringify(read.content)}`);
|
|
54
177
|
return lines.join("\n");
|
|
55
178
|
}
|
|
56
179
|
/**
|
|
57
|
-
* Human format:
|
|
180
|
+
* Human format: Formatted display with metadata and actions
|
|
58
181
|
*/
|
|
59
182
|
function formatHuman(read) {
|
|
60
|
-
|
|
183
|
+
const lines = [];
|
|
184
|
+
if (read.isMeta && typeof read.content === "object") {
|
|
185
|
+
lines.push(`Metadata for ${read.path.replace(/\/.meta$/, "")}`);
|
|
186
|
+
lines.push("");
|
|
187
|
+
lines.push(JSON.stringify(read.content, null, 2));
|
|
188
|
+
return lines.join("\n");
|
|
189
|
+
}
|
|
190
|
+
if (read.isAction && isExecutable(read.meta)) {
|
|
191
|
+
const actionName = read.path.split("/").pop() || "action";
|
|
192
|
+
lines.push(`Action: ${actionName}`);
|
|
193
|
+
lines.push(`Path: ${read.path}`);
|
|
194
|
+
if (read.meta?.description) {
|
|
195
|
+
lines.push("");
|
|
196
|
+
lines.push("Description:");
|
|
197
|
+
lines.push(` ${read.meta.description}`);
|
|
198
|
+
}
|
|
199
|
+
if (read.meta?.inputSchema) {
|
|
200
|
+
const props = read.meta.inputSchema.properties || {};
|
|
201
|
+
const required = read.meta.required || [];
|
|
202
|
+
if (Object.keys(props).length > 0) {
|
|
203
|
+
lines.push("");
|
|
204
|
+
lines.push("Parameters:");
|
|
205
|
+
for (const [name, propDef] of Object.entries(props)) {
|
|
206
|
+
const prop = propDef;
|
|
207
|
+
const isReq = required.includes(name);
|
|
208
|
+
const typeStr = `<${prop.type || "any"}>`;
|
|
209
|
+
const reqLabel = isReq ? "(required)" : "(optional)";
|
|
210
|
+
let line = ` --${name} ${typeStr}`.padEnd(24);
|
|
211
|
+
line += prop.description || "";
|
|
212
|
+
line += ` ${reqLabel}`;
|
|
213
|
+
lines.push(line);
|
|
214
|
+
if (prop.enum) lines.push(`${"".padEnd(24)}Choices: ${prop.enum.join(", ")}`);
|
|
215
|
+
if (prop.default !== void 0) lines.push(`${"".padEnd(24)}Default: ${prop.default}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
lines.push("");
|
|
220
|
+
lines.push("Usage:");
|
|
221
|
+
lines.push(` afs exec ${read.path}`);
|
|
222
|
+
return lines.join("\n");
|
|
223
|
+
}
|
|
224
|
+
lines.push(read.path);
|
|
225
|
+
lines.push("");
|
|
226
|
+
if (read.meta?.kind) lines.push(`Kind: ${read.meta.kind}`);
|
|
227
|
+
if (read.meta?.kinds && Array.isArray(read.meta.kinds) && read.meta.kinds.length > 0) lines.push(`Kinds: ${read.meta.kinds.join(" → ")}`);
|
|
228
|
+
if (read.childrenCount !== void 0) lines.push(`Children: ${read.childrenCount}`);
|
|
229
|
+
const userMeta = extractUserMetadata(read.meta);
|
|
230
|
+
if (Object.keys(userMeta).length > 0) {
|
|
231
|
+
lines.push("");
|
|
232
|
+
lines.push("Metadata:");
|
|
233
|
+
for (const [key, value] of Object.entries(userMeta)) lines.push(` ${key.padEnd(12)} ${formatValue(value)}`);
|
|
234
|
+
}
|
|
235
|
+
if (read.actions && read.actions.length > 0) {
|
|
236
|
+
lines.push("");
|
|
237
|
+
lines.push(`Actions: ${read.actions.map((a) => a.name).join(", ")}`);
|
|
238
|
+
}
|
|
239
|
+
if (read.content !== void 0 && read.content !== null && read.content !== "") {
|
|
240
|
+
lines.push("");
|
|
241
|
+
lines.push("Content:");
|
|
242
|
+
lines.push(formatContent(read.content));
|
|
243
|
+
} else if (read.childrenCount !== void 0 && read.childrenCount > 0) {
|
|
244
|
+
lines.push("");
|
|
245
|
+
lines.push("Content:");
|
|
246
|
+
lines.push(" (directory - use 'afs ls' to list children)");
|
|
247
|
+
}
|
|
248
|
+
return lines.join("\n");
|
|
249
|
+
}
|
|
250
|
+
function formatValue(value) {
|
|
251
|
+
if (value === null || value === void 0) return "";
|
|
252
|
+
if (typeof value === "string") return value;
|
|
253
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
254
|
+
if (Array.isArray(value)) return value.join(", ");
|
|
255
|
+
return JSON.stringify(value);
|
|
256
|
+
}
|
|
257
|
+
function formatContent(content) {
|
|
258
|
+
if (typeof content === "string") return content.split("\n").map((line) => ` ${line}`).join("\n");
|
|
259
|
+
return ` ${JSON.stringify(content, null, 2).split("\n").join("\n ")}`;
|
|
61
260
|
}
|
|
62
261
|
|
|
63
262
|
//#endregion
|