@aigne/afs-cli 1.11.0-beta.2 → 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 (75) hide show
  1. package/README.md +240 -12
  2. package/dist/cli.cjs +144 -26
  3. package/dist/cli.mjs +144 -26
  4. package/dist/cli.mjs.map +1 -1
  5. package/dist/commands/index.cjs +1 -0
  6. package/dist/commands/index.mjs +1 -0
  7. package/dist/commands/ls.cjs +12 -5
  8. package/dist/commands/ls.mjs +13 -5
  9. package/dist/commands/ls.mjs.map +1 -1
  10. package/dist/commands/mount.cjs +53 -17
  11. package/dist/commands/mount.mjs +53 -17
  12. package/dist/commands/mount.mjs.map +1 -1
  13. package/dist/commands/serve.cjs +142 -0
  14. package/dist/commands/serve.mjs +141 -0
  15. package/dist/commands/serve.mjs.map +1 -0
  16. package/dist/config/loader.cjs +28 -6
  17. package/dist/config/loader.mjs +28 -6
  18. package/dist/config/loader.mjs.map +1 -1
  19. package/dist/config/provider-factory.cjs +19 -1
  20. package/dist/config/provider-factory.mjs +19 -1
  21. package/dist/config/provider-factory.mjs.map +1 -1
  22. package/dist/config/schema.cjs +70 -3
  23. package/dist/config/schema.mjs +69 -3
  24. package/dist/config/schema.mjs.map +1 -1
  25. package/dist/config/uri-parser.cjs +17 -0
  26. package/dist/config/uri-parser.mjs +17 -0
  27. package/dist/config/uri-parser.mjs.map +1 -1
  28. package/dist/explorer/actions.cjs +246 -0
  29. package/dist/explorer/actions.mjs +240 -0
  30. package/dist/explorer/actions.mjs.map +1 -0
  31. package/dist/explorer/components/dialog.cjs +231 -0
  32. package/dist/explorer/components/dialog.mjs +232 -0
  33. package/dist/explorer/components/dialog.mjs.map +1 -0
  34. package/dist/explorer/components/file-list.cjs +107 -0
  35. package/dist/explorer/components/file-list.mjs +107 -0
  36. package/dist/explorer/components/file-list.mjs.map +1 -0
  37. package/dist/explorer/components/function-bar.cjs +55 -0
  38. package/dist/explorer/components/function-bar.mjs +55 -0
  39. package/dist/explorer/components/function-bar.mjs.map +1 -0
  40. package/dist/explorer/components/index.cjs +5 -0
  41. package/dist/explorer/components/index.mjs +7 -0
  42. package/dist/explorer/components/metadata-panel.cjs +122 -0
  43. package/dist/explorer/components/metadata-panel.mjs +122 -0
  44. package/dist/explorer/components/metadata-panel.mjs.map +1 -0
  45. package/dist/explorer/components/status-bar.cjs +53 -0
  46. package/dist/explorer/components/status-bar.mjs +54 -0
  47. package/dist/explorer/components/status-bar.mjs.map +1 -0
  48. package/dist/explorer/keybindings.cjs +214 -0
  49. package/dist/explorer/keybindings.mjs +213 -0
  50. package/dist/explorer/keybindings.mjs.map +1 -0
  51. package/dist/explorer/screen.cjs +200 -0
  52. package/dist/explorer/screen.mjs +199 -0
  53. package/dist/explorer/screen.mjs.map +1 -0
  54. package/dist/explorer/state.cjs +53 -0
  55. package/dist/explorer/state.mjs +53 -0
  56. package/dist/explorer/state.mjs.map +1 -0
  57. package/dist/explorer/theme.cjs +158 -0
  58. package/dist/explorer/theme.mjs +155 -0
  59. package/dist/explorer/theme.mjs.map +1 -0
  60. package/dist/path-utils.cjs +104 -0
  61. package/dist/path-utils.mjs +104 -0
  62. package/dist/path-utils.mjs.map +1 -0
  63. package/dist/runtime.cjs +47 -33
  64. package/dist/runtime.mjs +47 -33
  65. package/dist/runtime.mjs.map +1 -1
  66. package/dist/ui/header.cjs +60 -0
  67. package/dist/ui/header.mjs +59 -0
  68. package/dist/ui/header.mjs.map +1 -0
  69. package/dist/ui/index.cjs +17 -0
  70. package/dist/ui/index.mjs +15 -0
  71. package/dist/ui/index.mjs.map +1 -0
  72. package/dist/ui/terminal.cjs +97 -0
  73. package/dist/ui/terminal.mjs +95 -0
  74. package/dist/ui/terminal.mjs.map +1 -0
  75. package/package.json +9 -6
@@ -0,0 +1,246 @@
1
+
2
+ //#region src/explorer/actions.ts
3
+ /**
4
+ * Convert AFS entry to explorer entry
5
+ */
6
+ function toExplorerEntry(entry, _basePath) {
7
+ const name = entry.path.split("/").pop() || entry.path;
8
+ const metadata = entry.metadata || {};
9
+ const entryType = entry.type || metadata.type;
10
+ let type = "file";
11
+ if (entryType === "directory" || entryType === "module") type = "directory";
12
+ else if (entryType === "exec") type = "exec";
13
+ else if (entryType === "link") type = "link";
14
+ else if (metadata.childrenCount !== void 0 || entry.path === "/modules" || entry.path.startsWith("/modules/") && !entry.path.includes(".")) type = "directory";
15
+ return {
16
+ name,
17
+ path: entry.path,
18
+ type,
19
+ size: metadata.size,
20
+ modified: entry.updatedAt instanceof Date ? entry.updatedAt : void 0,
21
+ childrenCount: metadata.childrenCount,
22
+ hash: metadata.hash,
23
+ description: metadata.description,
24
+ provider: metadata.provider
25
+ };
26
+ }
27
+ /**
28
+ * Create parent directory entry
29
+ */
30
+ function createUpEntry(parentPath) {
31
+ return {
32
+ name: "..",
33
+ path: parentPath,
34
+ type: "up"
35
+ };
36
+ }
37
+ /**
38
+ * Load directory entries from AFS
39
+ */
40
+ async function loadDirectory(runtime, path) {
41
+ try {
42
+ const result = await runtime.list(path, { maxDepth: 1 });
43
+ const entries = [];
44
+ if (path !== "/") {
45
+ const parentPath = path.split("/").slice(0, -1).join("/") || "/";
46
+ entries.push(createUpEntry(parentPath));
47
+ }
48
+ for (const entry of result.data) {
49
+ if (entry.path === path) continue;
50
+ entries.push(toExplorerEntry(entry, path));
51
+ }
52
+ entries.sort((a, b) => {
53
+ if (a.type === "up") return -1;
54
+ if (b.type === "up") return 1;
55
+ const aIsDir = a.type === "directory";
56
+ const bIsDir = b.type === "directory";
57
+ if (aIsDir && !bIsDir) return -1;
58
+ if (!aIsDir && bIsDir) return 1;
59
+ return a.name.localeCompare(b.name);
60
+ });
61
+ return { entries };
62
+ } catch (error) {
63
+ return {
64
+ entries: [],
65
+ error: error instanceof Error ? error.message : "Failed to load directory"
66
+ };
67
+ }
68
+ }
69
+ /**
70
+ * Load metadata for an entry
71
+ */
72
+ async function loadMetadata(runtime, entry) {
73
+ if (entry.type === "up") return;
74
+ try {
75
+ const afsEntry = (await runtime.list(entry.path, { maxDepth: 0 })).data[0];
76
+ if (!afsEntry) return {
77
+ path: entry.path,
78
+ type: entry.type,
79
+ size: entry.size,
80
+ modified: entry.modified
81
+ };
82
+ const metadata = afsEntry.metadata || {};
83
+ return {
84
+ path: entry.path,
85
+ type: entry.type,
86
+ size: metadata.size,
87
+ modified: afsEntry.updatedAt instanceof Date ? afsEntry.updatedAt : void 0,
88
+ childrenCount: metadata.childrenCount,
89
+ hash: metadata.hash,
90
+ description: metadata.description,
91
+ provider: metadata.provider,
92
+ mountPath: metadata.mountPath,
93
+ uri: metadata.uri,
94
+ permissions: metadata.permissions,
95
+ extra: metadata
96
+ };
97
+ } catch {
98
+ return {
99
+ path: entry.path,
100
+ type: entry.type,
101
+ size: entry.size,
102
+ modified: entry.modified
103
+ };
104
+ }
105
+ }
106
+ /**
107
+ * Get explain output for an entry
108
+ */
109
+ async function getExplain(runtime, path) {
110
+ try {
111
+ const entry = (await runtime.list(path, { maxDepth: 0 })).data[0];
112
+ if (!entry) return {
113
+ content: "",
114
+ error: "Entry not found"
115
+ };
116
+ const metadata = entry.metadata || {};
117
+ const lines = [];
118
+ lines.push(`OBJECT ${path}`);
119
+ lines.push("");
120
+ lines.push("TYPE");
121
+ lines.push(metadata.type || "unknown");
122
+ lines.push("");
123
+ if (metadata.description) {
124
+ lines.push("DESCRIPTION");
125
+ lines.push(metadata.description);
126
+ lines.push("");
127
+ }
128
+ if (metadata.size !== void 0) {
129
+ lines.push("SIZE");
130
+ lines.push(`${metadata.size} bytes`);
131
+ lines.push("");
132
+ }
133
+ if (metadata.childrenCount !== void 0) {
134
+ lines.push("CHILDREN");
135
+ lines.push(`${metadata.childrenCount} items`);
136
+ lines.push("");
137
+ }
138
+ if (metadata.provider) {
139
+ lines.push("PROVIDER");
140
+ lines.push(metadata.provider);
141
+ lines.push("");
142
+ }
143
+ if (metadata.hash) {
144
+ lines.push("HASH");
145
+ lines.push(metadata.hash);
146
+ lines.push("");
147
+ }
148
+ return { content: lines.join("\n") };
149
+ } catch (error) {
150
+ return {
151
+ content: "",
152
+ error: error instanceof Error ? error.message : "Failed to get explain"
153
+ };
154
+ }
155
+ }
156
+ /**
157
+ * Execute an action on an entry
158
+ *
159
+ * Note: The AFSRuntime doesn't currently expose exec functionality.
160
+ * This is a placeholder that returns a "not available" message.
161
+ */
162
+ async function executeAction(_runtime, path, action, _params) {
163
+ return {
164
+ success: false,
165
+ message: `Action '${action}' on '${path}' is not available in the explorer`
166
+ };
167
+ }
168
+ /**
169
+ * Read file content
170
+ */
171
+ async function readFileContent(runtime, path) {
172
+ try {
173
+ const entry = (await runtime.read(path)).data;
174
+ if (!entry) return {
175
+ content: "",
176
+ error: "File not found"
177
+ };
178
+ const rawContent = entry.content;
179
+ if (rawContent === void 0) return {
180
+ content: "",
181
+ error: "No content available"
182
+ };
183
+ return { content: typeof rawContent === "string" ? rawContent : Buffer.from(rawContent).toString("utf-8") };
184
+ } catch (error) {
185
+ return {
186
+ content: "",
187
+ error: error instanceof Error ? error.message : "Failed to read file"
188
+ };
189
+ }
190
+ }
191
+ /**
192
+ * Navigation helpers
193
+ */
194
+ const navigation = {
195
+ up(state) {
196
+ return { selectedIndex: Math.max(0, state.selectedIndex - 1) };
197
+ },
198
+ down(state) {
199
+ return { selectedIndex: Math.min(state.entries.length - 1, state.selectedIndex + 1) };
200
+ },
201
+ home(_state) {
202
+ return {
203
+ selectedIndex: 0,
204
+ scrollOffset: 0
205
+ };
206
+ },
207
+ end(state) {
208
+ return { selectedIndex: Math.max(0, state.entries.length - 1) };
209
+ },
210
+ pageUp(state, pageSize) {
211
+ return { selectedIndex: Math.max(0, state.selectedIndex - pageSize) };
212
+ },
213
+ pageDown(state, pageSize) {
214
+ return { selectedIndex: Math.min(state.entries.length - 1, state.selectedIndex + pageSize) };
215
+ },
216
+ getSelected(state) {
217
+ return state.entries[state.selectedIndex];
218
+ },
219
+ getParentPath(path) {
220
+ if (path === "/") return "/";
221
+ const parts = path.split("/").filter(Boolean);
222
+ parts.pop();
223
+ return `/${parts.join("/")}` || "/";
224
+ }
225
+ };
226
+ /**
227
+ * Create initial state
228
+ */
229
+ function createInitialState(startPath = "/") {
230
+ return {
231
+ currentPath: startPath,
232
+ entries: [],
233
+ selectedIndex: 0,
234
+ scrollOffset: 0,
235
+ loading: true
236
+ };
237
+ }
238
+
239
+ //#endregion
240
+ exports.createInitialState = createInitialState;
241
+ exports.executeAction = executeAction;
242
+ exports.getExplain = getExplain;
243
+ exports.loadDirectory = loadDirectory;
244
+ exports.loadMetadata = loadMetadata;
245
+ exports.navigation = navigation;
246
+ exports.readFileContent = readFileContent;
@@ -0,0 +1,240 @@
1
+ //#region src/explorer/actions.ts
2
+ /**
3
+ * Convert AFS entry to explorer entry
4
+ */
5
+ function toExplorerEntry(entry, _basePath) {
6
+ const name = entry.path.split("/").pop() || entry.path;
7
+ const metadata = entry.metadata || {};
8
+ const entryType = entry.type || metadata.type;
9
+ let type = "file";
10
+ if (entryType === "directory" || entryType === "module") type = "directory";
11
+ else if (entryType === "exec") type = "exec";
12
+ else if (entryType === "link") type = "link";
13
+ else if (metadata.childrenCount !== void 0 || entry.path === "/modules" || entry.path.startsWith("/modules/") && !entry.path.includes(".")) type = "directory";
14
+ return {
15
+ name,
16
+ path: entry.path,
17
+ type,
18
+ size: metadata.size,
19
+ modified: entry.updatedAt instanceof Date ? entry.updatedAt : void 0,
20
+ childrenCount: metadata.childrenCount,
21
+ hash: metadata.hash,
22
+ description: metadata.description,
23
+ provider: metadata.provider
24
+ };
25
+ }
26
+ /**
27
+ * Create parent directory entry
28
+ */
29
+ function createUpEntry(parentPath) {
30
+ return {
31
+ name: "..",
32
+ path: parentPath,
33
+ type: "up"
34
+ };
35
+ }
36
+ /**
37
+ * Load directory entries from AFS
38
+ */
39
+ async function loadDirectory(runtime, path) {
40
+ try {
41
+ const result = await runtime.list(path, { maxDepth: 1 });
42
+ const entries = [];
43
+ if (path !== "/") {
44
+ const parentPath = path.split("/").slice(0, -1).join("/") || "/";
45
+ entries.push(createUpEntry(parentPath));
46
+ }
47
+ for (const entry of result.data) {
48
+ if (entry.path === path) continue;
49
+ entries.push(toExplorerEntry(entry, path));
50
+ }
51
+ entries.sort((a, b) => {
52
+ if (a.type === "up") return -1;
53
+ if (b.type === "up") return 1;
54
+ const aIsDir = a.type === "directory";
55
+ const bIsDir = b.type === "directory";
56
+ if (aIsDir && !bIsDir) return -1;
57
+ if (!aIsDir && bIsDir) return 1;
58
+ return a.name.localeCompare(b.name);
59
+ });
60
+ return { entries };
61
+ } catch (error) {
62
+ return {
63
+ entries: [],
64
+ error: error instanceof Error ? error.message : "Failed to load directory"
65
+ };
66
+ }
67
+ }
68
+ /**
69
+ * Load metadata for an entry
70
+ */
71
+ async function loadMetadata(runtime, entry) {
72
+ if (entry.type === "up") return;
73
+ try {
74
+ const afsEntry = (await runtime.list(entry.path, { maxDepth: 0 })).data[0];
75
+ if (!afsEntry) return {
76
+ path: entry.path,
77
+ type: entry.type,
78
+ size: entry.size,
79
+ modified: entry.modified
80
+ };
81
+ const metadata = afsEntry.metadata || {};
82
+ return {
83
+ path: entry.path,
84
+ type: entry.type,
85
+ size: metadata.size,
86
+ modified: afsEntry.updatedAt instanceof Date ? afsEntry.updatedAt : void 0,
87
+ childrenCount: metadata.childrenCount,
88
+ hash: metadata.hash,
89
+ description: metadata.description,
90
+ provider: metadata.provider,
91
+ mountPath: metadata.mountPath,
92
+ uri: metadata.uri,
93
+ permissions: metadata.permissions,
94
+ extra: metadata
95
+ };
96
+ } catch {
97
+ return {
98
+ path: entry.path,
99
+ type: entry.type,
100
+ size: entry.size,
101
+ modified: entry.modified
102
+ };
103
+ }
104
+ }
105
+ /**
106
+ * Get explain output for an entry
107
+ */
108
+ async function getExplain(runtime, path) {
109
+ try {
110
+ const entry = (await runtime.list(path, { maxDepth: 0 })).data[0];
111
+ if (!entry) return {
112
+ content: "",
113
+ error: "Entry not found"
114
+ };
115
+ const metadata = entry.metadata || {};
116
+ const lines = [];
117
+ lines.push(`OBJECT ${path}`);
118
+ lines.push("");
119
+ lines.push("TYPE");
120
+ lines.push(metadata.type || "unknown");
121
+ lines.push("");
122
+ if (metadata.description) {
123
+ lines.push("DESCRIPTION");
124
+ lines.push(metadata.description);
125
+ lines.push("");
126
+ }
127
+ if (metadata.size !== void 0) {
128
+ lines.push("SIZE");
129
+ lines.push(`${metadata.size} bytes`);
130
+ lines.push("");
131
+ }
132
+ if (metadata.childrenCount !== void 0) {
133
+ lines.push("CHILDREN");
134
+ lines.push(`${metadata.childrenCount} items`);
135
+ lines.push("");
136
+ }
137
+ if (metadata.provider) {
138
+ lines.push("PROVIDER");
139
+ lines.push(metadata.provider);
140
+ lines.push("");
141
+ }
142
+ if (metadata.hash) {
143
+ lines.push("HASH");
144
+ lines.push(metadata.hash);
145
+ lines.push("");
146
+ }
147
+ return { content: lines.join("\n") };
148
+ } catch (error) {
149
+ return {
150
+ content: "",
151
+ error: error instanceof Error ? error.message : "Failed to get explain"
152
+ };
153
+ }
154
+ }
155
+ /**
156
+ * Execute an action on an entry
157
+ *
158
+ * Note: The AFSRuntime doesn't currently expose exec functionality.
159
+ * This is a placeholder that returns a "not available" message.
160
+ */
161
+ async function executeAction(_runtime, path, action, _params) {
162
+ return {
163
+ success: false,
164
+ message: `Action '${action}' on '${path}' is not available in the explorer`
165
+ };
166
+ }
167
+ /**
168
+ * Read file content
169
+ */
170
+ async function readFileContent(runtime, path) {
171
+ try {
172
+ const entry = (await runtime.read(path)).data;
173
+ if (!entry) return {
174
+ content: "",
175
+ error: "File not found"
176
+ };
177
+ const rawContent = entry.content;
178
+ if (rawContent === void 0) return {
179
+ content: "",
180
+ error: "No content available"
181
+ };
182
+ return { content: typeof rawContent === "string" ? rawContent : Buffer.from(rawContent).toString("utf-8") };
183
+ } catch (error) {
184
+ return {
185
+ content: "",
186
+ error: error instanceof Error ? error.message : "Failed to read file"
187
+ };
188
+ }
189
+ }
190
+ /**
191
+ * Navigation helpers
192
+ */
193
+ const navigation = {
194
+ up(state) {
195
+ return { selectedIndex: Math.max(0, state.selectedIndex - 1) };
196
+ },
197
+ down(state) {
198
+ return { selectedIndex: Math.min(state.entries.length - 1, state.selectedIndex + 1) };
199
+ },
200
+ home(_state) {
201
+ return {
202
+ selectedIndex: 0,
203
+ scrollOffset: 0
204
+ };
205
+ },
206
+ end(state) {
207
+ return { selectedIndex: Math.max(0, state.entries.length - 1) };
208
+ },
209
+ pageUp(state, pageSize) {
210
+ return { selectedIndex: Math.max(0, state.selectedIndex - pageSize) };
211
+ },
212
+ pageDown(state, pageSize) {
213
+ return { selectedIndex: Math.min(state.entries.length - 1, state.selectedIndex + pageSize) };
214
+ },
215
+ getSelected(state) {
216
+ return state.entries[state.selectedIndex];
217
+ },
218
+ getParentPath(path) {
219
+ if (path === "/") return "/";
220
+ const parts = path.split("/").filter(Boolean);
221
+ parts.pop();
222
+ return `/${parts.join("/")}` || "/";
223
+ }
224
+ };
225
+ /**
226
+ * Create initial state
227
+ */
228
+ function createInitialState(startPath = "/") {
229
+ return {
230
+ currentPath: startPath,
231
+ entries: [],
232
+ selectedIndex: 0,
233
+ scrollOffset: 0,
234
+ loading: true
235
+ };
236
+ }
237
+
238
+ //#endregion
239
+ export { createInitialState, executeAction, getExplain, loadDirectory, loadMetadata, navigation, readFileContent };
240
+ //# sourceMappingURL=actions.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.mjs","names":[],"sources":["../../src/explorer/actions.ts"],"sourcesContent":["/**\n * AFS Explorer Actions\n *\n * Core action handlers for the explorer.\n * These are separated from UI for testability.\n */\n\nimport type { AFSEntry } from \"@aigne/afs\";\nimport type { AFSRuntime } from \"../runtime.js\";\nimport type { ActionResult, EntryMetadata, ExplorerEntry, ExplorerState } from \"./types.js\";\n\n/**\n * Convert AFS entry to explorer entry\n */\nexport function toExplorerEntry(entry: AFSEntry, _basePath: string): ExplorerEntry {\n const name = entry.path.split(\"/\").pop() || entry.path;\n const metadata = entry.metadata || {};\n\n // Some providers (like HTTP) may return type at the top level\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const entryType = (entry as any).type || metadata.type;\n\n // Determine type\n let type: ExplorerEntry[\"type\"] = \"file\";\n if (entryType === \"directory\" || entryType === \"module\") {\n type = \"directory\";\n } else if (entryType === \"exec\") {\n type = \"exec\";\n } else if (entryType === \"link\") {\n type = \"link\";\n } else if (\n // Heuristics to detect directories when type is not set:\n // 1. Has children count (only directories have children)\n metadata.childrenCount !== undefined ||\n // 2. Known system directories (e.g., /modules)\n entry.path === \"/modules\" ||\n // 3. Path under /modules without extension is likely a module (directory)\n (entry.path.startsWith(\"/modules/\") && !entry.path.includes(\".\"))\n ) {\n type = \"directory\";\n }\n\n return {\n name,\n path: entry.path,\n type,\n size: metadata.size,\n modified: entry.updatedAt instanceof Date ? entry.updatedAt : undefined,\n childrenCount: metadata.childrenCount,\n hash: metadata.hash,\n description: metadata.description,\n provider: metadata.provider,\n };\n}\n\n/**\n * Create parent directory entry\n */\nexport function createUpEntry(parentPath: string): ExplorerEntry {\n return {\n name: \"..\",\n path: parentPath,\n type: \"up\",\n };\n}\n\n/**\n * Load directory entries from AFS\n */\nexport async function loadDirectory(\n runtime: AFSRuntime,\n path: string,\n): Promise<{ entries: ExplorerEntry[]; error?: string }> {\n try {\n const result = await runtime.list(path, { maxDepth: 1 });\n const entries: ExplorerEntry[] = [];\n\n // Add parent directory entry if not at root\n if (path !== \"/\") {\n const parentPath = path.split(\"/\").slice(0, -1).join(\"/\") || \"/\";\n entries.push(createUpEntry(parentPath));\n }\n\n // Convert AFS entries\n for (const entry of result.data) {\n // Skip the current directory itself\n if (entry.path === path) continue;\n entries.push(toExplorerEntry(entry, path));\n }\n\n // Sort: directories first, then by name\n entries.sort((a, b) => {\n // Up always first\n if (a.type === \"up\") return -1;\n if (b.type === \"up\") return 1;\n\n // Directories before files\n const aIsDir = a.type === \"directory\";\n const bIsDir = b.type === \"directory\";\n if (aIsDir && !bIsDir) return -1;\n if (!aIsDir && bIsDir) return 1;\n\n // Alphabetical\n return a.name.localeCompare(b.name);\n });\n\n return { entries };\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to load directory\";\n return { entries: [], error: message };\n }\n}\n\n/**\n * Load metadata for an entry\n */\nexport async function loadMetadata(\n runtime: AFSRuntime,\n entry: ExplorerEntry,\n): Promise<EntryMetadata | undefined> {\n if (entry.type === \"up\") {\n return undefined;\n }\n\n try {\n // Use stat to get detailed metadata\n const result = await runtime.list(entry.path, { maxDepth: 0 });\n const afsEntry = result.data[0];\n\n if (!afsEntry) {\n return {\n path: entry.path,\n type: entry.type,\n size: entry.size,\n modified: entry.modified,\n };\n }\n\n const metadata = afsEntry.metadata || {};\n\n return {\n path: entry.path,\n type: entry.type,\n size: metadata.size,\n modified: afsEntry.updatedAt instanceof Date ? afsEntry.updatedAt : undefined,\n childrenCount: metadata.childrenCount,\n hash: metadata.hash,\n description: metadata.description,\n provider: metadata.provider,\n mountPath: metadata.mountPath,\n uri: metadata.uri,\n permissions: metadata.permissions,\n extra: metadata,\n };\n } catch {\n // Return basic metadata if detailed load fails\n return {\n path: entry.path,\n type: entry.type,\n size: entry.size,\n modified: entry.modified,\n };\n }\n}\n\n/**\n * Get explain output for an entry\n */\nexport async function getExplain(\n runtime: AFSRuntime,\n path: string,\n): Promise<{ content: string; error?: string }> {\n try {\n // Try to get explain from runtime if available\n // For now, build explain from metadata\n const result = await runtime.list(path, { maxDepth: 0 });\n const entry = result.data[0];\n\n if (!entry) {\n return { content: \"\", error: \"Entry not found\" };\n }\n\n const metadata = entry.metadata || {};\n const lines: string[] = [];\n\n lines.push(`OBJECT ${path}`);\n lines.push(\"\");\n lines.push(\"TYPE\");\n lines.push(metadata.type || \"unknown\");\n lines.push(\"\");\n\n if (metadata.description) {\n lines.push(\"DESCRIPTION\");\n lines.push(metadata.description);\n lines.push(\"\");\n }\n\n if (metadata.size !== undefined) {\n lines.push(\"SIZE\");\n lines.push(`${metadata.size} bytes`);\n lines.push(\"\");\n }\n\n if (metadata.childrenCount !== undefined) {\n lines.push(\"CHILDREN\");\n lines.push(`${metadata.childrenCount} items`);\n lines.push(\"\");\n }\n\n if (metadata.provider) {\n lines.push(\"PROVIDER\");\n lines.push(metadata.provider);\n lines.push(\"\");\n }\n\n if (metadata.hash) {\n lines.push(\"HASH\");\n lines.push(metadata.hash);\n lines.push(\"\");\n }\n\n return { content: lines.join(\"\\n\") };\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to get explain\";\n return { content: \"\", error: message };\n }\n}\n\n/**\n * Get available actions for an entry\n */\nexport async function getAvailableActions(\n _runtime: AFSRuntime,\n _path: string,\n): Promise<{ actions: string[]; error?: string }> {\n // For now, return common actions\n // In the future, this could query the provider for available actions\n return {\n actions: [\"default\", \"info\"],\n };\n}\n\n/**\n * Execute an action on an entry\n *\n * Note: The AFSRuntime doesn't currently expose exec functionality.\n * This is a placeholder that returns a \"not available\" message.\n */\nexport async function executeAction(\n _runtime: AFSRuntime,\n path: string,\n action: string,\n _params?: Record<string, unknown>,\n): Promise<ActionResult> {\n // AFSRuntime currently doesn't implement exec\n // This is a placeholder for future implementation\n return {\n success: false,\n message: `Action '${action}' on '${path}' is not available in the explorer`,\n };\n}\n\n/**\n * Read file content\n */\nexport async function readFileContent(\n runtime: AFSRuntime,\n path: string,\n): Promise<{ content: string; error?: string }> {\n try {\n const result = await runtime.read(path);\n const entry = result.data;\n if (!entry) {\n return { content: \"\", error: \"File not found\" };\n }\n\n // Content can be string, Buffer, or undefined\n const rawContent = entry.content;\n if (rawContent === undefined) {\n return { content: \"\", error: \"No content available\" };\n }\n\n const content =\n typeof rawContent === \"string\" ? rawContent : Buffer.from(rawContent).toString(\"utf-8\");\n return { content };\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to read file\";\n return { content: \"\", error: message };\n }\n}\n\n/**\n * Navigation helpers\n */\nexport const navigation = {\n /**\n * Move selection up\n */\n up(state: ExplorerState): Partial<ExplorerState> {\n const newIndex = Math.max(0, state.selectedIndex - 1);\n return { selectedIndex: newIndex };\n },\n\n /**\n * Move selection down\n */\n down(state: ExplorerState): Partial<ExplorerState> {\n const newIndex = Math.min(state.entries.length - 1, state.selectedIndex + 1);\n return { selectedIndex: newIndex };\n },\n\n /**\n * Go to first item\n */\n home(_state: ExplorerState): Partial<ExplorerState> {\n return { selectedIndex: 0, scrollOffset: 0 };\n },\n\n /**\n * Go to last item\n */\n end(state: ExplorerState): Partial<ExplorerState> {\n return { selectedIndex: Math.max(0, state.entries.length - 1) };\n },\n\n /**\n * Page up (move by pageSize items)\n */\n pageUp(state: ExplorerState, pageSize: number): Partial<ExplorerState> {\n const newIndex = Math.max(0, state.selectedIndex - pageSize);\n return { selectedIndex: newIndex };\n },\n\n /**\n * Page down (move by pageSize items)\n */\n pageDown(state: ExplorerState, pageSize: number): Partial<ExplorerState> {\n const newIndex = Math.min(state.entries.length - 1, state.selectedIndex + pageSize);\n return { selectedIndex: newIndex };\n },\n\n /**\n * Get selected entry\n */\n getSelected(state: ExplorerState): ExplorerEntry | undefined {\n return state.entries[state.selectedIndex];\n },\n\n /**\n * Get parent path\n */\n getParentPath(path: string): string {\n if (path === \"/\") return \"/\";\n const parts = path.split(\"/\").filter(Boolean);\n parts.pop();\n return `/${parts.join(\"/\")}` || \"/\";\n },\n};\n\n/**\n * Filter entries by search text\n */\nexport function filterEntries(entries: ExplorerEntry[], filterText: string): ExplorerEntry[] {\n if (!filterText) return entries;\n\n const lower = filterText.toLowerCase();\n return entries.filter((e) => e.name.toLowerCase().includes(lower) || e.type === \"up\");\n}\n\n/**\n * Create initial state\n */\nexport function createInitialState(startPath: string = \"/\"): ExplorerState {\n return {\n currentPath: startPath,\n entries: [],\n selectedIndex: 0,\n scrollOffset: 0,\n loading: true,\n };\n}\n"],"mappings":"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"}