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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +224 -31
  2. package/dist/cli.cjs +119 -27
  3. package/dist/cli.mjs +119 -27
  4. package/dist/cli.mjs.map +1 -1
  5. package/dist/commands/ls.cjs +6 -4
  6. package/dist/commands/ls.mjs +7 -4
  7. package/dist/commands/ls.mjs.map +1 -1
  8. package/dist/commands/mount.cjs +39 -15
  9. package/dist/commands/mount.mjs +39 -15
  10. package/dist/commands/mount.mjs.map +1 -1
  11. package/dist/commands/serve.cjs +9 -6
  12. package/dist/commands/serve.mjs +9 -6
  13. package/dist/commands/serve.mjs.map +1 -1
  14. package/dist/config/loader.cjs +30 -10
  15. package/dist/config/loader.mjs +30 -10
  16. package/dist/config/loader.mjs.map +1 -1
  17. package/dist/config/provider-factory.cjs +1 -0
  18. package/dist/config/provider-factory.mjs +1 -0
  19. package/dist/config/provider-factory.mjs.map +1 -1
  20. package/dist/config/schema.cjs +58 -3
  21. package/dist/config/schema.mjs +57 -3
  22. package/dist/config/schema.mjs.map +1 -1
  23. package/dist/explorer/actions.cjs +246 -0
  24. package/dist/explorer/actions.mjs +240 -0
  25. package/dist/explorer/actions.mjs.map +1 -0
  26. package/dist/explorer/components/dialog.cjs +231 -0
  27. package/dist/explorer/components/dialog.mjs +232 -0
  28. package/dist/explorer/components/dialog.mjs.map +1 -0
  29. package/dist/explorer/components/file-list.cjs +107 -0
  30. package/dist/explorer/components/file-list.mjs +107 -0
  31. package/dist/explorer/components/file-list.mjs.map +1 -0
  32. package/dist/explorer/components/function-bar.cjs +55 -0
  33. package/dist/explorer/components/function-bar.mjs +55 -0
  34. package/dist/explorer/components/function-bar.mjs.map +1 -0
  35. package/dist/explorer/components/index.cjs +5 -0
  36. package/dist/explorer/components/index.mjs +7 -0
  37. package/dist/explorer/components/metadata-panel.cjs +122 -0
  38. package/dist/explorer/components/metadata-panel.mjs +122 -0
  39. package/dist/explorer/components/metadata-panel.mjs.map +1 -0
  40. package/dist/explorer/components/status-bar.cjs +53 -0
  41. package/dist/explorer/components/status-bar.mjs +54 -0
  42. package/dist/explorer/components/status-bar.mjs.map +1 -0
  43. package/dist/explorer/keybindings.cjs +214 -0
  44. package/dist/explorer/keybindings.mjs +213 -0
  45. package/dist/explorer/keybindings.mjs.map +1 -0
  46. package/dist/explorer/screen.cjs +200 -0
  47. package/dist/explorer/screen.mjs +199 -0
  48. package/dist/explorer/screen.mjs.map +1 -0
  49. package/dist/explorer/state.cjs +53 -0
  50. package/dist/explorer/state.mjs +53 -0
  51. package/dist/explorer/state.mjs.map +1 -0
  52. package/dist/explorer/theme.cjs +158 -0
  53. package/dist/explorer/theme.mjs +155 -0
  54. package/dist/explorer/theme.mjs.map +1 -0
  55. package/dist/path-utils.cjs +104 -0
  56. package/dist/path-utils.mjs +104 -0
  57. package/dist/path-utils.mjs.map +1 -0
  58. package/dist/runtime.cjs +47 -33
  59. package/dist/runtime.mjs +47 -33
  60. package/dist/runtime.mjs.map +1 -1
  61. package/dist/ui/header.cjs +60 -0
  62. package/dist/ui/header.mjs +59 -0
  63. package/dist/ui/header.mjs.map +1 -0
  64. package/dist/ui/index.cjs +17 -0
  65. package/dist/ui/index.mjs +15 -0
  66. package/dist/ui/index.mjs.map +1 -0
  67. package/dist/ui/terminal.cjs +97 -0
  68. package/dist/ui/terminal.mjs +95 -0
  69. package/dist/ui/terminal.mjs.map +1 -0
  70. package/package.json +9 -7
@@ -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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"}
@@ -0,0 +1,231 @@
1
+ const require_keybindings = require('../keybindings.cjs');
2
+ const require_theme = require('../theme.cjs');
3
+
4
+ //#region src/explorer/components/dialog.ts
5
+ /**
6
+ * Create dialog manager
7
+ */
8
+ function createDialogManager(blessed, options) {
9
+ const { parent } = options;
10
+ let currentDialog = null;
11
+ let currentOverlay = null;
12
+ /**
13
+ * Create a modal overlay to capture mouse events behind the dialog
14
+ */
15
+ function createOverlay() {
16
+ const overlay = blessed.box({
17
+ parent,
18
+ top: 0,
19
+ left: 0,
20
+ width: "100%",
21
+ height: "100%",
22
+ mouse: true,
23
+ style: { transparent: true }
24
+ });
25
+ overlay.on("wheeldown", () => {});
26
+ overlay.on("wheelup", () => {});
27
+ overlay.on("click", () => {});
28
+ return overlay;
29
+ }
30
+ /**
31
+ * Create a basic dialog box
32
+ */
33
+ function createDialog(title, width, height, options$1) {
34
+ currentOverlay = createOverlay();
35
+ const dialog = blessed.box({
36
+ parent,
37
+ top: "center",
38
+ left: "center",
39
+ width,
40
+ height,
41
+ tags: true,
42
+ keys: true,
43
+ vi: true,
44
+ mouse: true,
45
+ scrollable: true,
46
+ alwaysScroll: true,
47
+ scrollbar: {
48
+ ch: " ",
49
+ track: { bg: require_theme.Colors.bg.main },
50
+ style: { inverse: true }
51
+ },
52
+ style: {
53
+ fg: require_theme.Colors.fg.normal,
54
+ bg: require_theme.Colors.bg.main,
55
+ border: { fg: require_theme.Colors.fg.border }
56
+ },
57
+ border: { type: "line" },
58
+ label: ` ${title} `,
59
+ shadow: true
60
+ });
61
+ dialog.on("wheeldown", () => {
62
+ dialog.scroll(1);
63
+ parent.render();
64
+ });
65
+ dialog.on("wheelup", () => {
66
+ dialog.scroll(-1);
67
+ parent.render();
68
+ });
69
+ if (!options$1?.skipDefaultKeys) dialog.key([
70
+ "escape",
71
+ "q",
72
+ "enter"
73
+ ], () => {
74
+ close();
75
+ });
76
+ return dialog;
77
+ }
78
+ /**
79
+ * Close current dialog
80
+ */
81
+ function close() {
82
+ if (currentOverlay) {
83
+ currentOverlay.destroy();
84
+ currentOverlay = null;
85
+ }
86
+ if (currentDialog) {
87
+ currentDialog.destroy();
88
+ currentDialog = null;
89
+ parent.render();
90
+ }
91
+ }
92
+ return {
93
+ showHelp(registry) {
94
+ close();
95
+ const dialog = createDialog("Help - AFS Explorer", "60%", "70%");
96
+ currentDialog = dialog;
97
+ const lines = [
98
+ " AFS Explorer - Navigate your Agentic File System",
99
+ "",
100
+ " {bold}Commands:{/bold}",
101
+ ""
102
+ ];
103
+ const bindings = registry.getFunctionBarBindings();
104
+ for (const binding of bindings) {
105
+ const key = require_keybindings.formatKeyName(binding.key).padEnd(6);
106
+ lines.push(` ${key} ${binding.description}`);
107
+ }
108
+ lines.push("");
109
+ lines.push(" {bold}Navigation:{/bold}");
110
+ lines.push("");
111
+ lines.push(" ↑/k Move up");
112
+ lines.push(" ↓/j Move down");
113
+ lines.push(" Enter/l Enter directory or view file");
114
+ lines.push(" Bksp/h Go to parent directory");
115
+ lines.push(" g/Home Go to first item");
116
+ lines.push(" G/End Go to last item");
117
+ lines.push(" ^U/PgUp Page up");
118
+ lines.push(" ^D/PgDn Page down");
119
+ lines.push("");
120
+ lines.push(" {bold}Other:{/bold}");
121
+ lines.push("");
122
+ lines.push(" / Filter entries");
123
+ lines.push(" ? Show this help");
124
+ lines.push("");
125
+ lines.push(" {gray-fg}Press Esc, Enter, or Q to close{/gray-fg}");
126
+ dialog.setContent(lines.join("\n"));
127
+ dialog.focus();
128
+ parent.render();
129
+ },
130
+ showExplain(path, content) {
131
+ close();
132
+ const dialog = createDialog(`Explain: ${path}`, "80%", "80%");
133
+ currentDialog = dialog;
134
+ dialog.setContent(` ${content.split("\n").join("\n ")}`);
135
+ dialog.focus();
136
+ parent.render();
137
+ },
138
+ showFileView(path, content) {
139
+ close();
140
+ const dialog = createDialog(`View: ${path}`, "90%", "90%");
141
+ currentDialog = dialog;
142
+ const numbered = content.split("\n").map((line, i) => {
143
+ return `{gray-fg}${(i + 1).toString().padStart(4)}{/gray-fg} │ ${line.replace(/\{/g, "\\{").replace(/\}/g, "\\}")}`;
144
+ });
145
+ dialog.setContent(numbered.join("\n"));
146
+ dialog.focus();
147
+ parent.render();
148
+ },
149
+ showActionResult(action, success, message, data) {
150
+ close();
151
+ const dialog = createDialog(`Action: ${action}`, "60%", "50%");
152
+ currentDialog = dialog;
153
+ const lines = [];
154
+ if (success) lines.push(" {green-fg}✓ Action completed successfully{/green-fg}");
155
+ else lines.push(" {red-fg}✗ Action failed{/red-fg}");
156
+ if (message) {
157
+ lines.push("");
158
+ lines.push(` ${message}`);
159
+ }
160
+ if (data !== void 0) {
161
+ lines.push("");
162
+ lines.push(" {bold}Result:{/bold}");
163
+ lines.push("");
164
+ const formatted = JSON.stringify(data, null, 2);
165
+ for (const line of formatted.split("\n")) lines.push(` ${line}`);
166
+ }
167
+ lines.push("");
168
+ lines.push(" {gray-fg}Press Esc, Enter, or Q to close{/gray-fg}");
169
+ dialog.setContent(lines.join("\n"));
170
+ dialog.focus();
171
+ parent.render();
172
+ },
173
+ showError(title, error) {
174
+ close();
175
+ const dialog = createDialog(title, "50%", "30%");
176
+ currentDialog = dialog;
177
+ const lines = [
178
+ " {red-fg}Error:{/red-fg}",
179
+ "",
180
+ ` ${error}`,
181
+ "",
182
+ " {gray-fg}Press Esc to close{/gray-fg}"
183
+ ];
184
+ dialog.setContent(lines.join("\n"));
185
+ dialog.focus();
186
+ parent.render();
187
+ },
188
+ showLoading(message) {
189
+ close();
190
+ const dialog = createDialog("Loading", "40%", "20%");
191
+ currentDialog = dialog;
192
+ dialog.setContent(`\n ${message}...`);
193
+ parent.render();
194
+ },
195
+ showConfirm(message, onConfirm) {
196
+ close();
197
+ const dialog = createDialog("Confirm", "50%", "25%", { skipDefaultKeys: true });
198
+ currentDialog = dialog;
199
+ const lines = [
200
+ "",
201
+ ` ${message}`,
202
+ "",
203
+ " {gray-fg}Press Y to confirm, N or Esc to cancel{/gray-fg}"
204
+ ];
205
+ dialog.setContent(lines.join("\n"));
206
+ dialog.key(["y", "Y"], () => {
207
+ close();
208
+ onConfirm();
209
+ });
210
+ dialog.key([
211
+ "n",
212
+ "N",
213
+ "escape"
214
+ ], () => {
215
+ close();
216
+ });
217
+ dialog.focus();
218
+ parent.render();
219
+ },
220
+ close,
221
+ isOpen() {
222
+ return currentDialog !== null;
223
+ },
224
+ destroy() {
225
+ close();
226
+ }
227
+ };
228
+ }
229
+
230
+ //#endregion
231
+ exports.createDialogManager = createDialogManager;
@@ -0,0 +1,232 @@
1
+ import { formatKeyName } from "../keybindings.mjs";
2
+ import { Colors } from "../theme.mjs";
3
+
4
+ //#region src/explorer/components/dialog.ts
5
+ /**
6
+ * Create dialog manager
7
+ */
8
+ function createDialogManager(blessed, options) {
9
+ const { parent } = options;
10
+ let currentDialog = null;
11
+ let currentOverlay = null;
12
+ /**
13
+ * Create a modal overlay to capture mouse events behind the dialog
14
+ */
15
+ function createOverlay() {
16
+ const overlay = blessed.box({
17
+ parent,
18
+ top: 0,
19
+ left: 0,
20
+ width: "100%",
21
+ height: "100%",
22
+ mouse: true,
23
+ style: { transparent: true }
24
+ });
25
+ overlay.on("wheeldown", () => {});
26
+ overlay.on("wheelup", () => {});
27
+ overlay.on("click", () => {});
28
+ return overlay;
29
+ }
30
+ /**
31
+ * Create a basic dialog box
32
+ */
33
+ function createDialog(title, width, height, options$1) {
34
+ currentOverlay = createOverlay();
35
+ const dialog = blessed.box({
36
+ parent,
37
+ top: "center",
38
+ left: "center",
39
+ width,
40
+ height,
41
+ tags: true,
42
+ keys: true,
43
+ vi: true,
44
+ mouse: true,
45
+ scrollable: true,
46
+ alwaysScroll: true,
47
+ scrollbar: {
48
+ ch: " ",
49
+ track: { bg: Colors.bg.main },
50
+ style: { inverse: true }
51
+ },
52
+ style: {
53
+ fg: Colors.fg.normal,
54
+ bg: Colors.bg.main,
55
+ border: { fg: Colors.fg.border }
56
+ },
57
+ border: { type: "line" },
58
+ label: ` ${title} `,
59
+ shadow: true
60
+ });
61
+ dialog.on("wheeldown", () => {
62
+ dialog.scroll(1);
63
+ parent.render();
64
+ });
65
+ dialog.on("wheelup", () => {
66
+ dialog.scroll(-1);
67
+ parent.render();
68
+ });
69
+ if (!options$1?.skipDefaultKeys) dialog.key([
70
+ "escape",
71
+ "q",
72
+ "enter"
73
+ ], () => {
74
+ close();
75
+ });
76
+ return dialog;
77
+ }
78
+ /**
79
+ * Close current dialog
80
+ */
81
+ function close() {
82
+ if (currentOverlay) {
83
+ currentOverlay.destroy();
84
+ currentOverlay = null;
85
+ }
86
+ if (currentDialog) {
87
+ currentDialog.destroy();
88
+ currentDialog = null;
89
+ parent.render();
90
+ }
91
+ }
92
+ return {
93
+ showHelp(registry) {
94
+ close();
95
+ const dialog = createDialog("Help - AFS Explorer", "60%", "70%");
96
+ currentDialog = dialog;
97
+ const lines = [
98
+ " AFS Explorer - Navigate your Agentic File System",
99
+ "",
100
+ " {bold}Commands:{/bold}",
101
+ ""
102
+ ];
103
+ const bindings = registry.getFunctionBarBindings();
104
+ for (const binding of bindings) {
105
+ const key = formatKeyName(binding.key).padEnd(6);
106
+ lines.push(` ${key} ${binding.description}`);
107
+ }
108
+ lines.push("");
109
+ lines.push(" {bold}Navigation:{/bold}");
110
+ lines.push("");
111
+ lines.push(" ↑/k Move up");
112
+ lines.push(" ↓/j Move down");
113
+ lines.push(" Enter/l Enter directory or view file");
114
+ lines.push(" Bksp/h Go to parent directory");
115
+ lines.push(" g/Home Go to first item");
116
+ lines.push(" G/End Go to last item");
117
+ lines.push(" ^U/PgUp Page up");
118
+ lines.push(" ^D/PgDn Page down");
119
+ lines.push("");
120
+ lines.push(" {bold}Other:{/bold}");
121
+ lines.push("");
122
+ lines.push(" / Filter entries");
123
+ lines.push(" ? Show this help");
124
+ lines.push("");
125
+ lines.push(" {gray-fg}Press Esc, Enter, or Q to close{/gray-fg}");
126
+ dialog.setContent(lines.join("\n"));
127
+ dialog.focus();
128
+ parent.render();
129
+ },
130
+ showExplain(path, content) {
131
+ close();
132
+ const dialog = createDialog(`Explain: ${path}`, "80%", "80%");
133
+ currentDialog = dialog;
134
+ dialog.setContent(` ${content.split("\n").join("\n ")}`);
135
+ dialog.focus();
136
+ parent.render();
137
+ },
138
+ showFileView(path, content) {
139
+ close();
140
+ const dialog = createDialog(`View: ${path}`, "90%", "90%");
141
+ currentDialog = dialog;
142
+ const numbered = content.split("\n").map((line, i) => {
143
+ return `{gray-fg}${(i + 1).toString().padStart(4)}{/gray-fg} │ ${line.replace(/\{/g, "\\{").replace(/\}/g, "\\}")}`;
144
+ });
145
+ dialog.setContent(numbered.join("\n"));
146
+ dialog.focus();
147
+ parent.render();
148
+ },
149
+ showActionResult(action, success, message, data) {
150
+ close();
151
+ const dialog = createDialog(`Action: ${action}`, "60%", "50%");
152
+ currentDialog = dialog;
153
+ const lines = [];
154
+ if (success) lines.push(" {green-fg}✓ Action completed successfully{/green-fg}");
155
+ else lines.push(" {red-fg}✗ Action failed{/red-fg}");
156
+ if (message) {
157
+ lines.push("");
158
+ lines.push(` ${message}`);
159
+ }
160
+ if (data !== void 0) {
161
+ lines.push("");
162
+ lines.push(" {bold}Result:{/bold}");
163
+ lines.push("");
164
+ const formatted = JSON.stringify(data, null, 2);
165
+ for (const line of formatted.split("\n")) lines.push(` ${line}`);
166
+ }
167
+ lines.push("");
168
+ lines.push(" {gray-fg}Press Esc, Enter, or Q to close{/gray-fg}");
169
+ dialog.setContent(lines.join("\n"));
170
+ dialog.focus();
171
+ parent.render();
172
+ },
173
+ showError(title, error) {
174
+ close();
175
+ const dialog = createDialog(title, "50%", "30%");
176
+ currentDialog = dialog;
177
+ const lines = [
178
+ " {red-fg}Error:{/red-fg}",
179
+ "",
180
+ ` ${error}`,
181
+ "",
182
+ " {gray-fg}Press Esc to close{/gray-fg}"
183
+ ];
184
+ dialog.setContent(lines.join("\n"));
185
+ dialog.focus();
186
+ parent.render();
187
+ },
188
+ showLoading(message) {
189
+ close();
190
+ const dialog = createDialog("Loading", "40%", "20%");
191
+ currentDialog = dialog;
192
+ dialog.setContent(`\n ${message}...`);
193
+ parent.render();
194
+ },
195
+ showConfirm(message, onConfirm) {
196
+ close();
197
+ const dialog = createDialog("Confirm", "50%", "25%", { skipDefaultKeys: true });
198
+ currentDialog = dialog;
199
+ const lines = [
200
+ "",
201
+ ` ${message}`,
202
+ "",
203
+ " {gray-fg}Press Y to confirm, N or Esc to cancel{/gray-fg}"
204
+ ];
205
+ dialog.setContent(lines.join("\n"));
206
+ dialog.key(["y", "Y"], () => {
207
+ close();
208
+ onConfirm();
209
+ });
210
+ dialog.key([
211
+ "n",
212
+ "N",
213
+ "escape"
214
+ ], () => {
215
+ close();
216
+ });
217
+ dialog.focus();
218
+ parent.render();
219
+ },
220
+ close,
221
+ isOpen() {
222
+ return currentDialog !== null;
223
+ },
224
+ destroy() {
225
+ close();
226
+ }
227
+ };
228
+ }
229
+
230
+ //#endregion
231
+ export { createDialogManager };
232
+ //# sourceMappingURL=dialog.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog.mjs","names":["options"],"sources":["../../../src/explorer/components/dialog.ts"],"sourcesContent":["/**\n * AFS Explorer Dialog Component\n *\n * Modal dialogs for help, explain output, file view, etc.\n */\n\nimport type Blessed from \"blessed\";\nimport { formatKeyName, type KeyBindingRegistry } from \"../keybindings.js\";\nimport { Colors } from \"../theme.js\";\n\nexport interface DialogOptions {\n parent: Blessed.Widgets.Screen;\n}\n\n/**\n * Create dialog manager\n */\nexport function createDialogManager(blessed: typeof Blessed, options: DialogOptions) {\n const { parent } = options;\n let currentDialog: Blessed.Widgets.BoxElement | null = null;\n let currentOverlay: Blessed.Widgets.BoxElement | null = null;\n\n /**\n * Create a modal overlay to capture mouse events behind the dialog\n */\n function createOverlay() {\n const overlay = blessed.box({\n parent,\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n mouse: true,\n style: {\n transparent: true,\n },\n });\n\n // Capture and ignore all mouse events on the overlay\n overlay.on(\"wheeldown\", () => {});\n overlay.on(\"wheelup\", () => {});\n overlay.on(\"click\", () => {});\n\n return overlay;\n }\n\n /**\n * Create a basic dialog box\n */\n function createDialog(\n title: string,\n width: string | number,\n height: string | number,\n options?: { skipDefaultKeys?: boolean },\n ) {\n // Create overlay first to capture events behind dialog\n currentOverlay = createOverlay();\n\n const dialog = blessed.box({\n parent,\n top: \"center\",\n left: \"center\",\n width,\n height,\n tags: true,\n keys: true,\n vi: true,\n mouse: true,\n scrollable: true,\n alwaysScroll: true,\n scrollbar: {\n ch: \" \",\n track: {\n bg: Colors.bg.main,\n },\n style: {\n inverse: true,\n },\n },\n style: {\n fg: Colors.fg.normal,\n bg: Colors.bg.main,\n border: {\n fg: Colors.fg.border,\n },\n },\n border: {\n type: \"line\",\n },\n label: ` ${title} `,\n shadow: true,\n });\n\n // Capture mouse events to prevent them from reaching elements behind the dialog\n dialog.on(\"wheeldown\", () => {\n dialog.scroll(1);\n parent.render();\n });\n dialog.on(\"wheelup\", () => {\n dialog.scroll(-1);\n parent.render();\n });\n\n // Close on escape or q (unless skipped)\n if (!options?.skipDefaultKeys) {\n dialog.key([\"escape\", \"q\", \"enter\"], () => {\n close();\n });\n }\n\n return dialog;\n }\n\n /**\n * Close current dialog\n */\n function close(): void {\n if (currentOverlay) {\n currentOverlay.destroy();\n currentOverlay = null;\n }\n if (currentDialog) {\n currentDialog.destroy();\n currentDialog = null;\n parent.render();\n }\n }\n\n return {\n /**\n * Show help dialog\n */\n showHelp(registry: KeyBindingRegistry): void {\n close();\n\n const dialog = createDialog(\"Help - AFS Explorer\", \"60%\", \"70%\");\n currentDialog = dialog;\n\n const lines: string[] = [\n \" AFS Explorer - Navigate your Agentic File System\",\n \"\",\n \" {bold}Commands:{/bold}\",\n \"\",\n ];\n\n // Get function bar bindings\n const bindings = registry.getFunctionBarBindings();\n for (const binding of bindings) {\n const key = formatKeyName(binding.key).padEnd(6);\n lines.push(` ${key} ${binding.description}`);\n }\n\n lines.push(\"\");\n lines.push(\" {bold}Navigation:{/bold}\");\n lines.push(\"\");\n lines.push(\" ↑/k Move up\");\n lines.push(\" ↓/j Move down\");\n lines.push(\" Enter/l Enter directory or view file\");\n lines.push(\" Bksp/h Go to parent directory\");\n lines.push(\" g/Home Go to first item\");\n lines.push(\" G/End Go to last item\");\n lines.push(\" ^U/PgUp Page up\");\n lines.push(\" ^D/PgDn Page down\");\n lines.push(\"\");\n lines.push(\" {bold}Other:{/bold}\");\n lines.push(\"\");\n lines.push(\" / Filter entries\");\n lines.push(\" ? Show this help\");\n lines.push(\"\");\n lines.push(\" {gray-fg}Press Esc, Enter, or Q to close{/gray-fg}\");\n\n dialog.setContent(lines.join(\"\\n\"));\n dialog.focus();\n parent.render();\n },\n\n /**\n * Show explain output dialog\n */\n showExplain(path: string, content: string): void {\n close();\n\n const dialog = createDialog(`Explain: ${path}`, \"80%\", \"80%\");\n currentDialog = dialog;\n\n dialog.setContent(` ${content.split(\"\\n\").join(\"\\n \")}`);\n dialog.focus();\n parent.render();\n },\n\n /**\n * Show file view dialog\n */\n showFileView(path: string, content: string): void {\n close();\n\n const dialog = createDialog(`View: ${path}`, \"90%\", \"90%\");\n currentDialog = dialog;\n\n // Add line numbers\n // Escape curly braces in content to prevent blessed tag parsing issues\n const lines = content.split(\"\\n\");\n const numbered = lines.map((line, i) => {\n const num = (i + 1).toString().padStart(4);\n // Escape { and } in line content to prevent tag interpretation\n const escaped = line.replace(/\\{/g, \"\\\\{\").replace(/\\}/g, \"\\\\}\");\n return `{gray-fg}${num}{/gray-fg} │ ${escaped}`;\n });\n\n dialog.setContent(numbered.join(\"\\n\"));\n dialog.focus();\n parent.render();\n },\n\n /**\n * Show action result dialog\n */\n showActionResult(action: string, success: boolean, message?: string, data?: unknown): void {\n close();\n\n const dialog = createDialog(`Action: ${action}`, \"60%\", \"50%\");\n currentDialog = dialog;\n\n const lines: string[] = [];\n\n if (success) {\n lines.push(\" {green-fg}✓ Action completed successfully{/green-fg}\");\n } else {\n lines.push(\" {red-fg}✗ Action failed{/red-fg}\");\n }\n\n if (message) {\n lines.push(\"\");\n lines.push(` ${message}`);\n }\n\n if (data !== undefined) {\n lines.push(\"\");\n lines.push(\" {bold}Result:{/bold}\");\n lines.push(\"\");\n const formatted = JSON.stringify(data, null, 2);\n for (const line of formatted.split(\"\\n\")) {\n lines.push(` ${line}`);\n }\n }\n\n lines.push(\"\");\n lines.push(\" {gray-fg}Press Esc, Enter, or Q to close{/gray-fg}\");\n\n dialog.setContent(lines.join(\"\\n\"));\n dialog.focus();\n parent.render();\n },\n\n /**\n * Show error dialog\n */\n showError(title: string, error: string): void {\n close();\n\n const dialog = createDialog(title, \"50%\", \"30%\");\n currentDialog = dialog;\n\n const lines = [\n \" {red-fg}Error:{/red-fg}\",\n \"\",\n ` ${error}`,\n \"\",\n \" {gray-fg}Press Esc to close{/gray-fg}\",\n ];\n\n dialog.setContent(lines.join(\"\\n\"));\n dialog.focus();\n parent.render();\n },\n\n /**\n * Show loading indicator\n */\n showLoading(message: string): void {\n close();\n\n const dialog = createDialog(\"Loading\", \"40%\", \"20%\");\n currentDialog = dialog;\n\n dialog.setContent(`\\n ${message}...`);\n parent.render();\n },\n\n /**\n * Show confirmation dialog\n */\n showConfirm(message: string, onConfirm: () => void): void {\n close();\n\n const dialog = createDialog(\"Confirm\", \"50%\", \"25%\", { skipDefaultKeys: true });\n currentDialog = dialog;\n\n const lines = [\n \"\",\n ` ${message}`,\n \"\",\n \" {gray-fg}Press Y to confirm, N or Esc to cancel{/gray-fg}\",\n ];\n\n dialog.setContent(lines.join(\"\\n\"));\n\n // Custom key handling for confirmation\n dialog.key([\"y\", \"Y\"], () => {\n close();\n onConfirm();\n });\n dialog.key([\"n\", \"N\", \"escape\"], () => {\n close();\n });\n\n dialog.focus();\n parent.render();\n },\n\n /**\n * Close current dialog\n */\n close,\n\n /**\n * Check if a dialog is open\n */\n isOpen(): boolean {\n return currentDialog !== null;\n },\n\n /**\n * Destroy dialog manager\n */\n destroy(): void {\n close();\n },\n };\n}\n\nexport type DialogManager = ReturnType<typeof createDialogManager>;\n"],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ const require_actions = require('../actions.cjs');
2
+ const require_theme = require('../theme.cjs');
3
+
4
+ //#region src/explorer/components/file-list.ts
5
+ /**
6
+ * Format an entry for display in the list
7
+ */
8
+ function formatEntry(entry, maxNameWidth) {
9
+ return `${require_theme.Icons[entry.type] || require_theme.Icons.file} ${entry.name.padEnd(maxNameWidth)} ${entry.size !== void 0 ? require_theme.formatSize(entry.size).padStart(8) : " "} ${entry.modified ? formatDate(entry.modified) : " "}`;
10
+ }
11
+ /**
12
+ * Format date for display
13
+ */
14
+ function formatDate(date) {
15
+ const now = /* @__PURE__ */ new Date();
16
+ const isThisYear = date.getFullYear() === now.getFullYear();
17
+ const month = date.toLocaleString("en", { month: "short" });
18
+ const day = date.getDate().toString().padStart(2, " ");
19
+ if (isThisYear) return `${month} ${day} ${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}`;
20
+ return `${month} ${day} ${date.getFullYear()}`;
21
+ }
22
+ /**
23
+ * Create file list component
24
+ */
25
+ function createFileList(blessed, options) {
26
+ const { parent, store, width, height, top = 0, left = 0 } = options;
27
+ const list = blessed.list({
28
+ parent,
29
+ top,
30
+ left,
31
+ width,
32
+ height,
33
+ tags: true,
34
+ keys: false,
35
+ mouse: true,
36
+ scrollable: true,
37
+ alwaysScroll: true,
38
+ scrollbar: {
39
+ ch: require_theme.Symbols.scrollbar,
40
+ track: { bg: require_theme.Colors.bg.main },
41
+ style: { inverse: true }
42
+ },
43
+ style: {
44
+ fg: require_theme.Colors.fg.normal,
45
+ bg: require_theme.Colors.bg.main,
46
+ selected: {
47
+ fg: require_theme.Colors.fg.selected,
48
+ bg: require_theme.Colors.bg.selected
49
+ },
50
+ item: {
51
+ fg: require_theme.Colors.fg.normal,
52
+ bg: require_theme.Colors.bg.main
53
+ }
54
+ },
55
+ border: { type: "line" }
56
+ });
57
+ function getMaxNameWidth(entries) {
58
+ if (entries.length === 0) return 20;
59
+ const maxLen = Math.max(...entries.map((e) => e.name.length));
60
+ return Math.min(Math.max(maxLen, 10), 40);
61
+ }
62
+ function updateContent(state) {
63
+ const maxNameWidth = getMaxNameWidth(state.entries);
64
+ const items = state.entries.map((entry) => {
65
+ const formatted = formatEntry(entry, maxNameWidth);
66
+ const color = getEntryColor(entry.type);
67
+ return `{${color}-fg}${formatted}{/${color}-fg}`;
68
+ });
69
+ list.setItems(items);
70
+ list.select(state.selectedIndex);
71
+ list.scrollTo(state.selectedIndex);
72
+ }
73
+ function getEntryColor(type) {
74
+ switch (type) {
75
+ case "directory": return require_theme.Colors.fg.directory;
76
+ case "exec": return require_theme.Colors.fg.exec;
77
+ case "link": return require_theme.Colors.fg.link;
78
+ case "up": return require_theme.Colors.fg.up;
79
+ default: return require_theme.Colors.fg.file;
80
+ }
81
+ }
82
+ store.subscribe((state) => {
83
+ updateContent(state);
84
+ list.screen?.render();
85
+ });
86
+ updateContent(store.getState());
87
+ return {
88
+ element: list,
89
+ focus() {
90
+ list.focus();
91
+ },
92
+ getVisibleHeight() {
93
+ const h = typeof list.height === "number" ? list.height : 20;
94
+ return Math.max(1, h - 2);
95
+ },
96
+ getSelected() {
97
+ return require_actions.navigation.getSelected(store.getState());
98
+ },
99
+ destroy() {
100
+ list.destroy();
101
+ }
102
+ };
103
+ }
104
+
105
+ //#endregion
106
+ exports.createFileList = createFileList;
107
+ exports.formatEntry = formatEntry;