web-a2e 1.0.0

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 (295) hide show
  1. package/.clangd +5 -0
  2. package/.mcp.json +12 -0
  3. package/CLAUDE.md +362 -0
  4. package/CMakeLists.txt +774 -0
  5. package/LICENSE +21 -0
  6. package/README.md +392 -0
  7. package/build-wasm/generated/roms.cpp +2447 -0
  8. package/docker-compose.staging.yml +9 -0
  9. package/docs/basic-rom-disassembly.md +6663 -0
  10. package/docs/softswitch-comparison.md +273 -0
  11. package/docs/thunderclock-debug.md +89 -0
  12. package/examples/cube.bas +72 -0
  13. package/examples/hello.s +55 -0
  14. package/examples/scroll.s +140 -0
  15. package/package.json +18 -0
  16. package/public/assets/apple-logo-old.png +0 -0
  17. package/public/assets/apple-logo.png +0 -0
  18. package/public/assets/drive-closed-light-on.png +0 -0
  19. package/public/assets/drive-closed.png +0 -0
  20. package/public/assets/drive-open-light-on.png +0 -0
  21. package/public/assets/drive-open.png +0 -0
  22. package/public/audio-worklet.js +82 -0
  23. package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
  24. package/public/disks/ProDOS 2.4.3.po +0 -0
  25. package/public/disks/h32mb.2mg +0 -0
  26. package/public/disks/library.json +26 -0
  27. package/public/docs/llms/llm-assembler.txt +90 -0
  28. package/public/docs/llms/llm-basic-program.txt +256 -0
  29. package/public/docs/llms/llm-disk-drives.txt +72 -0
  30. package/public/docs/llms/llm-file-explorer.txt +50 -0
  31. package/public/docs/llms/llm-hard-drives.txt +80 -0
  32. package/public/docs/llms/llm-main.txt +51 -0
  33. package/public/docs/llms/llm-slot-configuration.txt +66 -0
  34. package/public/icons/icon-192.svg +4 -0
  35. package/public/icons/icon-512.svg +4 -0
  36. package/public/index.html +661 -0
  37. package/public/llms.txt +49 -0
  38. package/public/manifest.json +29 -0
  39. package/public/shaders/burnin.glsl +22 -0
  40. package/public/shaders/crt.glsl +706 -0
  41. package/public/shaders/edge.glsl +109 -0
  42. package/public/shaders/vertex.glsl +8 -0
  43. package/public/sw.js +186 -0
  44. package/roms/341-0027.bin +0 -0
  45. package/roms/341-0160-A-US-UK.bin +0 -0
  46. package/roms/341-0160-A.bin +0 -0
  47. package/roms/342-0273-A-US-UK.bin +0 -0
  48. package/roms/342-0349-B-C0-FF.bin +0 -0
  49. package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
  50. package/roms/Thunderclock Plus ROM.bin +0 -0
  51. package/scripts/generate_roms.sh +69 -0
  52. package/src/bindings/wasm_interface.cpp +1940 -0
  53. package/src/core/assembler/assembler.cpp +1239 -0
  54. package/src/core/assembler/assembler.hpp +115 -0
  55. package/src/core/audio/audio.cpp +160 -0
  56. package/src/core/audio/audio.hpp +81 -0
  57. package/src/core/basic/basic_detokenizer.cpp +436 -0
  58. package/src/core/basic/basic_detokenizer.hpp +41 -0
  59. package/src/core/basic/basic_tokenizer.cpp +286 -0
  60. package/src/core/basic/basic_tokenizer.hpp +26 -0
  61. package/src/core/basic/basic_tokens.hpp +295 -0
  62. package/src/core/cards/disk2_card.cpp +568 -0
  63. package/src/core/cards/disk2_card.hpp +316 -0
  64. package/src/core/cards/expansion_card.hpp +185 -0
  65. package/src/core/cards/mockingboard/ay8910.cpp +616 -0
  66. package/src/core/cards/mockingboard/ay8910.hpp +159 -0
  67. package/src/core/cards/mockingboard/via6522.cpp +530 -0
  68. package/src/core/cards/mockingboard/via6522.hpp +163 -0
  69. package/src/core/cards/mockingboard_card.cpp +312 -0
  70. package/src/core/cards/mockingboard_card.hpp +159 -0
  71. package/src/core/cards/mouse_card.cpp +654 -0
  72. package/src/core/cards/mouse_card.hpp +190 -0
  73. package/src/core/cards/smartport/block_device.cpp +202 -0
  74. package/src/core/cards/smartport/block_device.hpp +60 -0
  75. package/src/core/cards/smartport/smartport_card.cpp +603 -0
  76. package/src/core/cards/smartport/smartport_card.hpp +120 -0
  77. package/src/core/cards/thunderclock_card.cpp +237 -0
  78. package/src/core/cards/thunderclock_card.hpp +122 -0
  79. package/src/core/cpu/cpu6502.cpp +1609 -0
  80. package/src/core/cpu/cpu6502.hpp +203 -0
  81. package/src/core/debug/condition_evaluator.cpp +470 -0
  82. package/src/core/debug/condition_evaluator.hpp +87 -0
  83. package/src/core/disassembler/disassembler.cpp +552 -0
  84. package/src/core/disassembler/disassembler.hpp +171 -0
  85. package/src/core/disk-image/disk_image.hpp +267 -0
  86. package/src/core/disk-image/dsk_disk_image.cpp +827 -0
  87. package/src/core/disk-image/dsk_disk_image.hpp +204 -0
  88. package/src/core/disk-image/gcr_encoding.cpp +147 -0
  89. package/src/core/disk-image/gcr_encoding.hpp +78 -0
  90. package/src/core/disk-image/woz_disk_image.cpp +1049 -0
  91. package/src/core/disk-image/woz_disk_image.hpp +343 -0
  92. package/src/core/emulator.cpp +2126 -0
  93. package/src/core/emulator.hpp +434 -0
  94. package/src/core/filesystem/dos33.cpp +178 -0
  95. package/src/core/filesystem/dos33.hpp +66 -0
  96. package/src/core/filesystem/pascal.cpp +262 -0
  97. package/src/core/filesystem/pascal.hpp +87 -0
  98. package/src/core/filesystem/prodos.cpp +369 -0
  99. package/src/core/filesystem/prodos.hpp +119 -0
  100. package/src/core/input/keyboard.cpp +227 -0
  101. package/src/core/input/keyboard.hpp +111 -0
  102. package/src/core/mmu/mmu.cpp +1387 -0
  103. package/src/core/mmu/mmu.hpp +236 -0
  104. package/src/core/types.hpp +196 -0
  105. package/src/core/video/video.cpp +680 -0
  106. package/src/core/video/video.hpp +156 -0
  107. package/src/css/assembler-editor.css +1617 -0
  108. package/src/css/base.css +470 -0
  109. package/src/css/basic-debugger.css +791 -0
  110. package/src/css/basic-editor.css +792 -0
  111. package/src/css/controls.css +783 -0
  112. package/src/css/cpu-debugger.css +1413 -0
  113. package/src/css/debug-base.css +160 -0
  114. package/src/css/debug-windows.css +6455 -0
  115. package/src/css/disk-drives.css +406 -0
  116. package/src/css/documentation.css +392 -0
  117. package/src/css/file-explorer.css +867 -0
  118. package/src/css/hard-drive.css +180 -0
  119. package/src/css/layout.css +217 -0
  120. package/src/css/memory-windows.css +798 -0
  121. package/src/css/modals.css +510 -0
  122. package/src/css/monitor.css +425 -0
  123. package/src/css/release-notes.css +101 -0
  124. package/src/css/responsive.css +400 -0
  125. package/src/css/rule-builder.css +340 -0
  126. package/src/css/save-states.css +201 -0
  127. package/src/css/settings-windows.css +1231 -0
  128. package/src/css/window-switcher.css +150 -0
  129. package/src/js/agent/agent-manager.js +643 -0
  130. package/src/js/agent/agent-tools.js +293 -0
  131. package/src/js/agent/agent-version-tools.js +131 -0
  132. package/src/js/agent/assembler-tools.js +357 -0
  133. package/src/js/agent/basic-program-tools.js +894 -0
  134. package/src/js/agent/disk-tools.js +417 -0
  135. package/src/js/agent/file-explorer-tools.js +269 -0
  136. package/src/js/agent/index.js +13 -0
  137. package/src/js/agent/main-tools.js +222 -0
  138. package/src/js/agent/slot-tools.js +303 -0
  139. package/src/js/agent/smartport-tools.js +257 -0
  140. package/src/js/agent/window-tools.js +80 -0
  141. package/src/js/audio/audio-driver.js +417 -0
  142. package/src/js/audio/audio-worklet.js +85 -0
  143. package/src/js/audio/index.js +8 -0
  144. package/src/js/config/default-layout.js +34 -0
  145. package/src/js/config/version.js +8 -0
  146. package/src/js/data/apple2-rom-routines.js +577 -0
  147. package/src/js/debug/assembler-editor-window.js +2993 -0
  148. package/src/js/debug/basic-breakpoint-manager.js +529 -0
  149. package/src/js/debug/basic-program-parser.js +436 -0
  150. package/src/js/debug/basic-program-window.js +2594 -0
  151. package/src/js/debug/basic-variable-inspector.js +447 -0
  152. package/src/js/debug/breakpoint-manager.js +472 -0
  153. package/src/js/debug/cpu-debugger-window.js +2396 -0
  154. package/src/js/debug/index.js +22 -0
  155. package/src/js/debug/label-manager.js +238 -0
  156. package/src/js/debug/memory-browser-window.js +416 -0
  157. package/src/js/debug/memory-heat-map-window.js +481 -0
  158. package/src/js/debug/memory-map-window.js +206 -0
  159. package/src/js/debug/mockingboard-window.js +882 -0
  160. package/src/js/debug/mouse-card-window.js +355 -0
  161. package/src/js/debug/rule-builder-window.js +648 -0
  162. package/src/js/debug/soft-switch-window.js +458 -0
  163. package/src/js/debug/stack-viewer-window.js +221 -0
  164. package/src/js/debug/symbols.js +416 -0
  165. package/src/js/debug/trace-panel.js +291 -0
  166. package/src/js/debug/zero-page-watch-window.js +297 -0
  167. package/src/js/disk-manager/disk-drives-window.js +212 -0
  168. package/src/js/disk-manager/disk-operations.js +284 -0
  169. package/src/js/disk-manager/disk-persistence.js +301 -0
  170. package/src/js/disk-manager/disk-surface-renderer.js +388 -0
  171. package/src/js/disk-manager/drive-sounds.js +139 -0
  172. package/src/js/disk-manager/hard-drive-manager.js +481 -0
  173. package/src/js/disk-manager/hard-drive-persistence.js +187 -0
  174. package/src/js/disk-manager/hard-drive-window.js +57 -0
  175. package/src/js/disk-manager/index.js +890 -0
  176. package/src/js/display/display-settings-window.js +383 -0
  177. package/src/js/display/index.js +10 -0
  178. package/src/js/display/screen-window.js +342 -0
  179. package/src/js/display/webgl-renderer.js +705 -0
  180. package/src/js/file-explorer/disassembler.js +574 -0
  181. package/src/js/file-explorer/dos33.js +266 -0
  182. package/src/js/file-explorer/file-viewer.js +359 -0
  183. package/src/js/file-explorer/index.js +1261 -0
  184. package/src/js/file-explorer/prodos.js +549 -0
  185. package/src/js/file-explorer/utils.js +67 -0
  186. package/src/js/help/documentation-window.js +1096 -0
  187. package/src/js/help/index.js +10 -0
  188. package/src/js/help/release-notes-window.js +85 -0
  189. package/src/js/help/release-notes.js +612 -0
  190. package/src/js/input/gamepad-handler.js +176 -0
  191. package/src/js/input/index.js +12 -0
  192. package/src/js/input/input-handler.js +396 -0
  193. package/src/js/input/joystick-window.js +404 -0
  194. package/src/js/input/mouse-handler.js +99 -0
  195. package/src/js/input/text-selection.js +462 -0
  196. package/src/js/main.js +653 -0
  197. package/src/js/state/index.js +15 -0
  198. package/src/js/state/save-states-window.js +393 -0
  199. package/src/js/state/state-manager.js +409 -0
  200. package/src/js/state/state-persistence.js +218 -0
  201. package/src/js/ui/confirm.js +43 -0
  202. package/src/js/ui/disk-drive-positioner.js +347 -0
  203. package/src/js/ui/reminder-controller.js +129 -0
  204. package/src/js/ui/slot-configuration-window.js +560 -0
  205. package/src/js/ui/theme-manager.js +61 -0
  206. package/src/js/ui/toast.js +44 -0
  207. package/src/js/ui/ui-controller.js +897 -0
  208. package/src/js/ui/window-switcher.js +275 -0
  209. package/src/js/utils/basic-autocomplete.js +832 -0
  210. package/src/js/utils/basic-highlighting.js +473 -0
  211. package/src/js/utils/basic-tokenizer.js +153 -0
  212. package/src/js/utils/basic-tokens.js +117 -0
  213. package/src/js/utils/constants.js +28 -0
  214. package/src/js/utils/indexeddb-helper.js +225 -0
  215. package/src/js/utils/merlin-editor-support.js +905 -0
  216. package/src/js/utils/merlin-highlighting.js +551 -0
  217. package/src/js/utils/storage.js +125 -0
  218. package/src/js/utils/string-utils.js +19 -0
  219. package/src/js/utils/wasm-memory.js +54 -0
  220. package/src/js/windows/base-window.js +690 -0
  221. package/src/js/windows/index.js +9 -0
  222. package/src/js/windows/window-manager.js +375 -0
  223. package/tests/catch2/catch.hpp +17976 -0
  224. package/tests/common/basic_program_builder.cpp +119 -0
  225. package/tests/common/basic_program_builder.hpp +209 -0
  226. package/tests/common/disk_image_builder.cpp +444 -0
  227. package/tests/common/disk_image_builder.hpp +141 -0
  228. package/tests/common/test_helpers.hpp +118 -0
  229. package/tests/gcr/gcr-test.cpp +142 -0
  230. package/tests/integration/check-rom.js +70 -0
  231. package/tests/integration/compare-boot.js +239 -0
  232. package/tests/integration/crash-trace.js +102 -0
  233. package/tests/integration/disk-boot-test.js +264 -0
  234. package/tests/integration/memory-crash.js +108 -0
  235. package/tests/integration/nibble-read-test.js +249 -0
  236. package/tests/integration/phase-test.js +159 -0
  237. package/tests/integration/test_emulator.cpp +291 -0
  238. package/tests/integration/test_emulator_basic.cpp +91 -0
  239. package/tests/integration/test_emulator_debug.cpp +344 -0
  240. package/tests/integration/test_emulator_disk.cpp +153 -0
  241. package/tests/integration/test_emulator_state.cpp +163 -0
  242. package/tests/klaus/6502_functional_test.bin +0 -0
  243. package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
  244. package/tests/klaus/klaus_6502_test.cpp +184 -0
  245. package/tests/klaus/klaus_65c02_test.cpp +197 -0
  246. package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
  247. package/tests/thunderclock/thunderclock_test.cpp +550 -0
  248. package/tests/unit/test_assembler.cpp +521 -0
  249. package/tests/unit/test_audio.cpp +196 -0
  250. package/tests/unit/test_ay8910.cpp +311 -0
  251. package/tests/unit/test_basic_detokenizer.cpp +265 -0
  252. package/tests/unit/test_basic_tokenizer.cpp +382 -0
  253. package/tests/unit/test_block_device.cpp +259 -0
  254. package/tests/unit/test_condition_evaluator.cpp +219 -0
  255. package/tests/unit/test_cpu6502.cpp +1301 -0
  256. package/tests/unit/test_cpu_addressing.cpp +361 -0
  257. package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
  258. package/tests/unit/test_cpu_decimal.cpp +166 -0
  259. package/tests/unit/test_cpu_interrupts.cpp +285 -0
  260. package/tests/unit/test_disassembler.cpp +323 -0
  261. package/tests/unit/test_disk2_card.cpp +330 -0
  262. package/tests/unit/test_dos33.cpp +273 -0
  263. package/tests/unit/test_dsk_disk_image.cpp +315 -0
  264. package/tests/unit/test_expansion_card.cpp +178 -0
  265. package/tests/unit/test_gcr_encoding.cpp +232 -0
  266. package/tests/unit/test_keyboard.cpp +262 -0
  267. package/tests/unit/test_mmu.cpp +555 -0
  268. package/tests/unit/test_mmu_slots.cpp +323 -0
  269. package/tests/unit/test_mockingboard.cpp +352 -0
  270. package/tests/unit/test_mouse_card.cpp +386 -0
  271. package/tests/unit/test_pascal.cpp +248 -0
  272. package/tests/unit/test_prodos.cpp +259 -0
  273. package/tests/unit/test_smartport_card.cpp +321 -0
  274. package/tests/unit/test_thunderclock.cpp +354 -0
  275. package/tests/unit/test_via6522.cpp +323 -0
  276. package/tests/unit/test_video.cpp +319 -0
  277. package/tests/unit/test_woz_disk_image.cpp +257 -0
  278. package/vite.config.js +96 -0
  279. package/wiki/AI-Agent.md +372 -0
  280. package/wiki/Architecture-Overview.md +303 -0
  281. package/wiki/Audio-System.md +449 -0
  282. package/wiki/CPU-Emulation.md +477 -0
  283. package/wiki/Debugger.md +516 -0
  284. package/wiki/Disk-Drives.md +161 -0
  285. package/wiki/Disk-System-Internals.md +547 -0
  286. package/wiki/Display-Settings.md +88 -0
  287. package/wiki/Expansion-Slots.md +187 -0
  288. package/wiki/File-Explorer.md +259 -0
  289. package/wiki/Getting-Started.md +156 -0
  290. package/wiki/Home.md +69 -0
  291. package/wiki/Input-Devices.md +183 -0
  292. package/wiki/Keyboard-Shortcuts.md +158 -0
  293. package/wiki/Memory-System.md +364 -0
  294. package/wiki/Save-States.md +172 -0
  295. package/wiki/Video-Rendering.md +658 -0
@@ -0,0 +1,417 @@
1
+ /*
2
+ * disk-tools.js - Disk drive operation tools
3
+ *
4
+ * Written by
5
+ * Shawn Bullock <shawn@agenticexpert.ai>
6
+ */
7
+
8
+ import { loadDiskFromData } from "../disk-manager/disk-operations.js";
9
+ import { getRecentDisks, loadRecentDisk, clearRecentDisks } from "../disk-manager/disk-persistence.js";
10
+
11
+ export const diskTools = {
12
+ /**
13
+ * Insert a disk image into a drive by path
14
+ */
15
+ driveInsertDisc: async (args) => {
16
+ const { driveNum = 1, path } = args;
17
+
18
+ if (!path) {
19
+ throw new Error("path parameter is required");
20
+ }
21
+
22
+ // Validate driveNum (1 or 2)
23
+ if (driveNum !== 1 && driveNum !== 2) {
24
+ throw new Error("driveNum must be 1 or 2");
25
+ }
26
+
27
+ // Validate disk format
28
+ const supportedFormats = ['.dsk', '.do', '.po', '.nib', '.woz'];
29
+ const extension = path.toLowerCase().match(/\.(dsk|do|po|nib|woz)$/);
30
+
31
+ if (!extension) {
32
+ throw new Error(
33
+ `Unsupported disk format. Supported formats: ${supportedFormats.join(', ')}`
34
+ );
35
+ }
36
+
37
+ // Convert to 0-based index
38
+ const driveIndex = driveNum - 1;
39
+
40
+ // Get disk manager and WASM module
41
+ const diskManager = window.emulator?.diskManager;
42
+ const wasmModule = window.emulator?.wasmModule;
43
+
44
+ if (!diskManager) {
45
+ throw new Error("Disk manager not available");
46
+ }
47
+
48
+ if (!wasmModule) {
49
+ throw new Error("WASM module not available");
50
+ }
51
+
52
+ // Get drive state
53
+ const drive = diskManager.drives[driveIndex];
54
+ if (!drive) {
55
+ throw new Error(`Drive ${driveNum} not found`);
56
+ }
57
+
58
+ try {
59
+ // Call MCP tool to load disk image from filesystem
60
+ const agentManager = window.emulator?.agentManager;
61
+ if (!agentManager) {
62
+ throw new Error("Agent manager not available");
63
+ }
64
+
65
+ const result = await agentManager.callMCPTool("load_disk_image", { path });
66
+
67
+ if (!result || !result.success) {
68
+ throw new Error(result?.error || "Failed to load disk image");
69
+ }
70
+
71
+ // Decode base64 to Uint8Array
72
+ const binaryString = atob(result.data);
73
+ const data = new Uint8Array(binaryString.length);
74
+ for (let i = 0; i < binaryString.length; i++) {
75
+ data[i] = binaryString.charCodeAt(i);
76
+ }
77
+
78
+ // Use filename from MCP result
79
+ const filename = result.filename;
80
+
81
+ // Load disk using the disk operations helper
82
+ return new Promise((resolve, reject) => {
83
+ loadDiskFromData({
84
+ wasmModule,
85
+ drive,
86
+ driveNum: driveIndex,
87
+ filename,
88
+ data,
89
+ onSuccess: (loadedFilename) => {
90
+ // Update UI
91
+ diskManager.setDiskName(driveIndex, loadedFilename);
92
+
93
+ // Notify if callback exists
94
+ if (diskManager.onDiskLoaded) {
95
+ diskManager.onDiskLoaded(driveIndex, loadedFilename);
96
+ }
97
+
98
+ resolve({
99
+ success: true,
100
+ drive: driveNum,
101
+ filename: loadedFilename,
102
+ size: data.length,
103
+ message: `Disk inserted into drive ${driveNum}: ${loadedFilename}`,
104
+ });
105
+ },
106
+ onError: (error) => {
107
+ reject(new Error(`Failed to insert disk: ${error}`));
108
+ },
109
+ });
110
+ });
111
+ } catch (error) {
112
+ throw new Error(`Error loading disk image: ${error.message}`);
113
+ }
114
+ },
115
+
116
+ /**
117
+ * Get list of recent disks for a drive
118
+ */
119
+ driveRecentsList: async (args) => {
120
+ const { driveNum = 1 } = args;
121
+
122
+ // Validate driveNum (1 or 2)
123
+ if (driveNum !== 1 && driveNum !== 2) {
124
+ throw new Error("driveNum must be 1 or 2");
125
+ }
126
+
127
+ // Convert to 0-based index
128
+ const driveIndex = driveNum - 1;
129
+
130
+ try {
131
+ // Get recent disks from IndexedDB
132
+ const recentDisks = await getRecentDisks(driveIndex);
133
+
134
+ // Return list of filenames with access times
135
+ const diskList = recentDisks.map(disk => ({
136
+ filename: disk.filename,
137
+ accessedAt: disk.accessedAt,
138
+ id: disk.id,
139
+ }));
140
+
141
+ return {
142
+ success: true,
143
+ drive: driveNum,
144
+ count: diskList.length,
145
+ disks: diskList,
146
+ message: `Found ${diskList.length} recent disk(s) for drive ${driveNum}`,
147
+ };
148
+
149
+ } catch (error) {
150
+ throw new Error(`Error getting recent disks: ${error.message}`);
151
+ }
152
+ },
153
+
154
+ /**
155
+ * Insert a recent disk by name
156
+ */
157
+ driveInsertRecent: async (args) => {
158
+ const { driveNum = 1, name } = args;
159
+
160
+ if (!name) {
161
+ throw new Error("name parameter is required");
162
+ }
163
+
164
+ // Validate driveNum (1 or 2)
165
+ if (driveNum !== 1 && driveNum !== 2) {
166
+ throw new Error("driveNum must be 1 or 2");
167
+ }
168
+
169
+ // Convert to 0-based index
170
+ const driveIndex = driveNum - 1;
171
+
172
+ // Get disk manager and WASM module
173
+ const diskManager = window.emulator?.diskManager;
174
+ const wasmModule = window.emulator?.wasmModule;
175
+
176
+ if (!diskManager) {
177
+ throw new Error("Disk manager not available");
178
+ }
179
+
180
+ if (!wasmModule) {
181
+ throw new Error("WASM module not available");
182
+ }
183
+
184
+ // Get drive state
185
+ const drive = diskManager.drives[driveIndex];
186
+ if (!drive) {
187
+ throw new Error(`Drive ${driveNum} not found`);
188
+ }
189
+
190
+ try {
191
+ // Get recent disks list
192
+ const recentDisks = await getRecentDisks(driveIndex);
193
+
194
+ // Find the disk by filename
195
+ const diskEntry = recentDisks.find(d => d.filename === name);
196
+ if (!diskEntry) {
197
+ throw new Error(`Disk "${name}" not found in recent list for drive ${driveNum}`);
198
+ }
199
+
200
+ // Load the disk data from IndexedDB
201
+ const diskData = await loadRecentDisk(diskEntry.id);
202
+ if (!diskData) {
203
+ throw new Error(`Failed to load disk data for "${name}"`);
204
+ }
205
+
206
+ // Load disk using the disk operations helper
207
+ return new Promise((resolve, reject) => {
208
+ loadDiskFromData({
209
+ wasmModule,
210
+ drive,
211
+ driveNum: driveIndex,
212
+ filename: diskData.filename,
213
+ data: diskData.data,
214
+ onSuccess: (loadedFilename) => {
215
+ // Update UI
216
+ diskManager.setDiskName(driveIndex, loadedFilename);
217
+
218
+ // Notify if callback exists
219
+ if (diskManager.onDiskLoaded) {
220
+ diskManager.onDiskLoaded(driveIndex, loadedFilename);
221
+ }
222
+
223
+ resolve({
224
+ success: true,
225
+ drive: driveNum,
226
+ filename: loadedFilename,
227
+ size: diskData.data.length,
228
+ message: `Recent disk inserted into drive ${driveNum}: ${loadedFilename}`,
229
+ });
230
+ },
231
+ onError: (error) => {
232
+ reject(new Error(`Failed to insert disk: ${error}`));
233
+ },
234
+ });
235
+ });
236
+
237
+ } catch (error) {
238
+ throw new Error(`Error inserting recent disk: ${error.message}`);
239
+ }
240
+ },
241
+
242
+ /**
243
+ * Load a recent disk by filename
244
+ */
245
+ driveLoadRecent: async (args) => {
246
+ const { driveNum = 1, file } = args;
247
+
248
+ if (!file) {
249
+ throw new Error("file parameter is required");
250
+ }
251
+
252
+ // Validate driveNum (1 or 2)
253
+ if (driveNum !== 1 && driveNum !== 2) {
254
+ throw new Error("driveNum must be 1 or 2");
255
+ }
256
+
257
+ // Convert to 0-based index
258
+ const driveIndex = driveNum - 1;
259
+
260
+ // Get disk manager and WASM module
261
+ const diskManager = window.emulator?.diskManager;
262
+ const wasmModule = window.emulator?.wasmModule;
263
+
264
+ if (!diskManager) {
265
+ throw new Error("Disk manager not available");
266
+ }
267
+
268
+ if (!wasmModule) {
269
+ throw new Error("WASM module not available");
270
+ }
271
+
272
+ // Get drive state
273
+ const drive = diskManager.drives[driveIndex];
274
+ if (!drive) {
275
+ throw new Error(`Drive ${driveNum} not found`);
276
+ }
277
+
278
+ try {
279
+ // Get recent disks list
280
+ const recentDisks = await getRecentDisks(driveIndex);
281
+
282
+ // Find the disk by filename
283
+ const diskEntry = recentDisks.find(d => d.filename === file);
284
+ if (!diskEntry) {
285
+ throw new Error(`Disk "${file}" not found in recent list for drive ${driveNum}`);
286
+ }
287
+
288
+ // Load the disk data from IndexedDB
289
+ const diskData = await loadRecentDisk(diskEntry.id);
290
+ if (!diskData) {
291
+ throw new Error(`Failed to load disk data for "${file}"`);
292
+ }
293
+
294
+ // Load disk using the disk operations helper
295
+ return new Promise((resolve, reject) => {
296
+ loadDiskFromData({
297
+ wasmModule,
298
+ drive,
299
+ driveNum: driveIndex,
300
+ filename: diskData.filename,
301
+ data: diskData.data,
302
+ onSuccess: (loadedFilename) => {
303
+ // Update UI
304
+ diskManager.setDiskName(driveIndex, loadedFilename);
305
+
306
+ // Notify if callback exists
307
+ if (diskManager.onDiskLoaded) {
308
+ diskManager.onDiskLoaded(driveIndex, loadedFilename);
309
+ }
310
+
311
+ resolve({
312
+ success: true,
313
+ drive: driveNum,
314
+ filename: loadedFilename,
315
+ size: diskData.data.length,
316
+ message: `Recent disk loaded into drive ${driveNum}: ${loadedFilename}`,
317
+ });
318
+ },
319
+ onError: (error) => {
320
+ reject(new Error(`Failed to insert disk: ${error}`));
321
+ },
322
+ });
323
+ });
324
+
325
+ } catch (error) {
326
+ throw new Error(`Error loading recent disk: ${error.message}`);
327
+ }
328
+ },
329
+
330
+ /**
331
+ * Clear all recent disks for a drive
332
+ */
333
+ drivesClearRecent: async (args) => {
334
+ const { driveNum = 1 } = args;
335
+
336
+ // Validate driveNum (1 or 2)
337
+ if (driveNum !== 1 && driveNum !== 2) {
338
+ throw new Error("driveNum must be 1 or 2");
339
+ }
340
+
341
+ // Convert to 0-based index
342
+ const driveIndex = driveNum - 1;
343
+
344
+ try {
345
+ // Clear recent disks from IndexedDB
346
+ await clearRecentDisks(driveIndex);
347
+
348
+ return {
349
+ success: true,
350
+ drive: driveNum,
351
+ message: `Cleared all recent disks for drive ${driveNum}`,
352
+ };
353
+
354
+ } catch (error) {
355
+ throw new Error(`Error clearing recent disks: ${error.message}`);
356
+ }
357
+ },
358
+
359
+ /**
360
+ * Eject a disk from a drive
361
+ */
362
+ diskDriveEject: async (args) => {
363
+ const { driveNum = 1 } = args;
364
+
365
+ // Validate driveNum (1 or 2)
366
+ if (driveNum !== 1 && driveNum !== 2) {
367
+ throw new Error("driveNum must be 1 or 2");
368
+ }
369
+
370
+ // Convert to 0-based index
371
+ const driveIndex = driveNum - 1;
372
+
373
+ // Get disk manager and WASM module
374
+ const diskManager = window.emulator?.diskManager;
375
+ const wasmModule = window.emulator?.wasmModule;
376
+
377
+ if (!diskManager) {
378
+ throw new Error("Disk manager not available");
379
+ }
380
+
381
+ if (!wasmModule) {
382
+ throw new Error("WASM module not available");
383
+ }
384
+
385
+ // Check if a disk is inserted
386
+ const isDiskInserted = wasmModule._isDiskInserted(driveIndex);
387
+ if (!isDiskInserted) {
388
+ return {
389
+ success: true,
390
+ drive: driveNum,
391
+ message: `No disk in drive ${driveNum}`,
392
+ };
393
+ }
394
+
395
+ try {
396
+ // Eject disk via WASM
397
+ wasmModule._ejectDisk(driveIndex);
398
+
399
+ // Update UI
400
+ diskManager.setDiskName(driveIndex, null);
401
+
402
+ // Notify if callback exists
403
+ if (diskManager.onDiskEjected) {
404
+ diskManager.onDiskEjected(driveIndex);
405
+ }
406
+
407
+ return {
408
+ success: true,
409
+ drive: driveNum,
410
+ message: `Disk ejected from drive ${driveNum}`,
411
+ };
412
+
413
+ } catch (error) {
414
+ throw new Error(`Error ejecting disk: ${error.message}`);
415
+ }
416
+ },
417
+ };
@@ -0,0 +1,269 @@
1
+ /*
2
+ * file-explorer-tools.js - File Explorer window tools
3
+ *
4
+ * Written by
5
+ * Shawn Bullock <shawn@agenticexpert.ai>
6
+ */
7
+
8
+ export const fileExplorerTools = {
9
+ /**
10
+ * List files in a disk drive
11
+ */
12
+ listDiskFiles: async (args) => {
13
+ const { drive = 0 } = args;
14
+
15
+ if (drive !== 0 && drive !== 1) {
16
+ throw new Error("drive must be 0 or 1");
17
+ }
18
+
19
+ const windowManager = window.emulator?.windowManager;
20
+ if (!windowManager) {
21
+ throw new Error("Window manager not available");
22
+ }
23
+
24
+ const feWindow = windowManager.getWindow("file-explorer-window");
25
+ if (!feWindow) {
26
+ throw new Error("File explorer window not found");
27
+ }
28
+
29
+ const wasmModule = window.emulator?.wasmModule;
30
+ if (!wasmModule) {
31
+ throw new Error("WASM module not available");
32
+ }
33
+
34
+ // Check if disk is inserted
35
+ if (!wasmModule._isDiskInserted(drive)) {
36
+ return {
37
+ success: false,
38
+ drive: drive,
39
+ format: null,
40
+ files: [],
41
+ message: `No disk in drive ${drive + 1}`,
42
+ };
43
+ }
44
+
45
+ // Get disk data
46
+ const sizePtr = wasmModule._malloc(4);
47
+ const dataPtr = wasmModule._getDiskSectorData(drive, sizePtr);
48
+ const size = new Uint32Array(wasmModule.HEAPU8.buffer, sizePtr, 1)[0];
49
+ wasmModule._free(sizePtr);
50
+
51
+ if (!dataPtr || size === 0) {
52
+ return {
53
+ success: false,
54
+ drive: drive,
55
+ format: null,
56
+ files: [],
57
+ message: "Cannot read disk data",
58
+ };
59
+ }
60
+
61
+ // Detect format and read catalog
62
+ let format = null;
63
+ let files = [];
64
+ let volumeName = null;
65
+
66
+ if (wasmModule._isProDOSFormat(dataPtr, size)) {
67
+ format = "prodos";
68
+ wasmModule._getProDOSVolumeInfo(dataPtr, size);
69
+ volumeName = wasmModule.UTF8ToString(wasmModule._getProDOSVolumeName());
70
+
71
+ const count = wasmModule._getProDOSCatalog(dataPtr, size);
72
+ for (let i = 0; i < count; i++) {
73
+ const fileType = wasmModule._getProDOSEntryFileType(i);
74
+ const isDirectory = wasmModule._getProDOSEntryIsDirectory(i);
75
+
76
+ files.push({
77
+ filename: wasmModule.UTF8ToString(
78
+ wasmModule._getProDOSEntryFilename(i),
79
+ ),
80
+ path: wasmModule.UTF8ToString(wasmModule._getProDOSEntryPath(i)),
81
+ type: fileType,
82
+ typeName: wasmModule.UTF8ToString(
83
+ wasmModule._getProDOSEntryFileTypeName(i),
84
+ ),
85
+ isDirectory: isDirectory,
86
+ isLocked: wasmModule._getProDOSEntryIsLocked(i),
87
+ size: wasmModule._getProDOSEntryEOF(i),
88
+ blocks: wasmModule._getProDOSEntryBlocksUsed(i),
89
+ index: i,
90
+ });
91
+ }
92
+ } else if (wasmModule._isDOS33Format(dataPtr, size)) {
93
+ format = "dos33";
94
+
95
+ const count = wasmModule._getDOS33Catalog(dataPtr, size);
96
+ for (let i = 0; i < count; i++) {
97
+ const fileType = wasmModule._getDOS33EntryFileType(i);
98
+
99
+ files.push({
100
+ filename: wasmModule.UTF8ToString(
101
+ wasmModule._getDOS33EntryFilename(i),
102
+ ),
103
+ type: fileType,
104
+ typeName: wasmModule.UTF8ToString(
105
+ wasmModule._getDOS33EntryFileTypeName(i),
106
+ ),
107
+ isLocked: wasmModule._getDOS33EntryIsLocked(i),
108
+ sectors: wasmModule._getDOS33EntrySectorCount(i),
109
+ index: i,
110
+ });
111
+ }
112
+ }
113
+
114
+ return {
115
+ success: true,
116
+ drive: drive,
117
+ format: format,
118
+ volumeName: volumeName,
119
+ fileCount: files.length,
120
+ files: files,
121
+ message: `Found ${files.length} file(s) on ${format ? format.toUpperCase() : "Unknown"} disk in drive ${drive + 1}`,
122
+ };
123
+ },
124
+
125
+ /**
126
+ * Get file content from disk
127
+ */
128
+ getDiskFileContent: async (args) => {
129
+ const { drive = 0, filename, isBinary = true } = args;
130
+
131
+ if (!filename) {
132
+ throw new Error("filename parameter is required");
133
+ }
134
+
135
+ if (drive !== 0 && drive !== 1) {
136
+ throw new Error("drive must be 0 or 1");
137
+ }
138
+
139
+ const wasmModule = window.emulator?.wasmModule;
140
+ if (!wasmModule) {
141
+ throw new Error("WASM module not available");
142
+ }
143
+
144
+ // Check if disk is inserted
145
+ if (!wasmModule._isDiskInserted(drive)) {
146
+ throw new Error(`No disk in drive ${drive + 1}`);
147
+ }
148
+
149
+ // Get disk data
150
+ const sizePtr = wasmModule._malloc(4);
151
+ const dataPtr = wasmModule._getDiskSectorData(drive, sizePtr);
152
+ const size = new Uint32Array(wasmModule.HEAPU8.buffer, sizePtr, 1)[0];
153
+ wasmModule._free(sizePtr);
154
+
155
+ if (!dataPtr || size === 0) {
156
+ throw new Error("Cannot read disk data");
157
+ }
158
+
159
+ // Detect format and find file
160
+ let format = null;
161
+ let fileIndex = -1;
162
+ let fileData = null;
163
+
164
+ if (wasmModule._isProDOSFormat(dataPtr, size)) {
165
+ format = "prodos";
166
+ const count = wasmModule._getProDOSCatalog(dataPtr, size);
167
+
168
+ // Find file by name or path
169
+ for (let i = 0; i < count; i++) {
170
+ const name = wasmModule.UTF8ToString(
171
+ wasmModule._getProDOSEntryFilename(i),
172
+ );
173
+ const path = wasmModule.UTF8ToString(
174
+ wasmModule._getProDOSEntryPath(i),
175
+ );
176
+
177
+ if (name === filename || path === filename) {
178
+ fileIndex = i;
179
+ break;
180
+ }
181
+ }
182
+
183
+ if (fileIndex === -1) {
184
+ throw new Error(`File not found: ${filename}`);
185
+ }
186
+
187
+ // Read file
188
+ const bytesRead = wasmModule._readProDOSFile(dataPtr, size, fileIndex);
189
+ if (bytesRead === 0) {
190
+ throw new Error("Failed to read file");
191
+ }
192
+
193
+ const bufPtr = wasmModule._getProDOSFileBuffer();
194
+ fileData = new Uint8Array(bytesRead);
195
+ fileData.set(new Uint8Array(wasmModule.HEAPU8.buffer, bufPtr, bytesRead));
196
+ } else if (wasmModule._isDOS33Format(dataPtr, size)) {
197
+ format = "dos33";
198
+ const count = wasmModule._getDOS33Catalog(dataPtr, size);
199
+
200
+ // Find file by name
201
+ for (let i = 0; i < count; i++) {
202
+ const name = wasmModule.UTF8ToString(
203
+ wasmModule._getDOS33EntryFilename(i),
204
+ );
205
+ if (name === filename) {
206
+ fileIndex = i;
207
+ break;
208
+ }
209
+ }
210
+
211
+ if (fileIndex === -1) {
212
+ throw new Error(`File not found: ${filename}`);
213
+ }
214
+
215
+ // Read file
216
+ const bytesRead = wasmModule._readDOS33File(dataPtr, size, fileIndex);
217
+ if (bytesRead === 0) {
218
+ throw new Error("Failed to read file");
219
+ }
220
+
221
+ const bufPtr = wasmModule._getDOS33FileBuffer();
222
+ fileData = new Uint8Array(bytesRead);
223
+ fileData.set(new Uint8Array(wasmModule.HEAPU8.buffer, bufPtr, bytesRead));
224
+ } else {
225
+ throw new Error("Unknown disk format");
226
+ }
227
+
228
+ // Return either base64 (binary) or plain text
229
+ if (isBinary) {
230
+ const base64 = btoa(String.fromCharCode(...fileData));
231
+ return {
232
+ success: true,
233
+ filename: filename,
234
+ drive: drive,
235
+ format: format,
236
+ size: fileData.length,
237
+ isBinary: true,
238
+ contentBase64: base64,
239
+ message: `Read ${fileData.length} bytes from ${filename}`,
240
+ };
241
+ } else {
242
+ // Decode as text (assuming ASCII/high-bit ASCII)
243
+ let text = "";
244
+ for (let i = 0; i < fileData.length; i++) {
245
+ const byte = fileData[i];
246
+ // Handle carriage return (0x0D) as newline
247
+ if (byte === 0x0d) {
248
+ text += "\n";
249
+ } else if (byte === 0x00) {
250
+ // Skip null bytes
251
+ continue;
252
+ } else {
253
+ // Strip high bit for high-bit ASCII
254
+ text += String.fromCharCode(byte & 0x7f);
255
+ }
256
+ }
257
+ return {
258
+ success: true,
259
+ filename: filename,
260
+ drive: drive,
261
+ format: format,
262
+ size: fileData.length,
263
+ isBinary: false,
264
+ content: text,
265
+ message: `Read ${fileData.length} bytes from ${filename}`,
266
+ };
267
+ }
268
+ },
269
+ };
@@ -0,0 +1,13 @@
1
+ /*
2
+ * index.js - Agent module exports
3
+ *
4
+ * Written by
5
+ * Shawn Bullock <shawn@agenticexpert.ai>
6
+ */
7
+
8
+ export { AgentManager } from "./agent-manager.js";
9
+ export { executeAgentTool, getAvailableTools } from "./agent-tools.js";
10
+ export { windowTools } from "./window-tools.js";
11
+ export { basicProgramTools } from "./basic-program-tools.js";
12
+ export { mainTools } from "./main-tools.js";
13
+ export { diskTools } from "./disk-tools.js";