@adhdev/daemon-core 0.5.3

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 (217) hide show
  1. package/dist/index.d.ts +2662 -0
  2. package/dist/index.js +11341 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +48 -0
  5. package/providers/_builtin/.github/workflows/generate-registry.yml +57 -0
  6. package/providers/_builtin/COMPATIBILITY.md +217 -0
  7. package/providers/_builtin/CONTRIBUTING.md +200 -0
  8. package/providers/_builtin/README.md +119 -0
  9. package/providers/_builtin/_helpers/index.js +188 -0
  10. package/providers/_builtin/acp/agentpool/provider.json +54 -0
  11. package/providers/_builtin/acp/amp/provider.json +52 -0
  12. package/providers/_builtin/acp/auggie/provider.json +57 -0
  13. package/providers/_builtin/acp/autodev/provider.json +54 -0
  14. package/providers/_builtin/acp/autohand/provider.json +52 -0
  15. package/providers/_builtin/acp/blackbox-ai/provider.json +54 -0
  16. package/providers/_builtin/acp/claude-agent/provider.json +57 -0
  17. package/providers/_builtin/acp/cline-acp/provider.json +54 -0
  18. package/providers/_builtin/acp/codebuddy/provider.json +54 -0
  19. package/providers/_builtin/acp/codex-cli/provider.json +57 -0
  20. package/providers/_builtin/acp/corust-agent/provider.json +52 -0
  21. package/providers/_builtin/acp/crow-cli/provider.json +54 -0
  22. package/providers/_builtin/acp/cursor-acp/provider.json +54 -0
  23. package/providers/_builtin/acp/deepagents/provider.json +52 -0
  24. package/providers/_builtin/acp/dimcode/provider.json +54 -0
  25. package/providers/_builtin/acp/docker-cagent/provider.json +57 -0
  26. package/providers/_builtin/acp/factory-droid/provider.json +60 -0
  27. package/providers/_builtin/acp/fast-agent/provider.json +52 -0
  28. package/providers/_builtin/acp/gemini-cli/provider.json +114 -0
  29. package/providers/_builtin/acp/github-copilot/provider.json +54 -0
  30. package/providers/_builtin/acp/goose/provider.json +57 -0
  31. package/providers/_builtin/acp/junie/provider.json +52 -0
  32. package/providers/_builtin/acp/kilo/provider.json +54 -0
  33. package/providers/_builtin/acp/kimi-cli/provider.json +57 -0
  34. package/providers/_builtin/acp/minion-code/provider.json +52 -0
  35. package/providers/_builtin/acp/mistral-vibe/provider.json +57 -0
  36. package/providers/_builtin/acp/nova/provider.json +54 -0
  37. package/providers/_builtin/acp/openclaw/provider.json +54 -0
  38. package/providers/_builtin/acp/opencode/provider.json +52 -0
  39. package/providers/_builtin/acp/openhands/provider.json +54 -0
  40. package/providers/_builtin/acp/pi-acp/provider.json +52 -0
  41. package/providers/_builtin/acp/qoder/provider.json +54 -0
  42. package/providers/_builtin/acp/qwen-code/provider.json +60 -0
  43. package/providers/_builtin/acp/stakpak/provider.json +54 -0
  44. package/providers/_builtin/acp/vtcode/provider.json +54 -0
  45. package/providers/_builtin/cli/claude-cli/provider.json +100 -0
  46. package/providers/_builtin/cli/codex-cli/provider.json +89 -0
  47. package/providers/_builtin/cli/gemini-cli/provider.json +93 -0
  48. package/providers/_builtin/docs/CDP_SELECTOR_GUIDE.md +370 -0
  49. package/providers/_builtin/docs/PROVIDER_GUIDE.md +916 -0
  50. package/providers/_builtin/extension/cline/provider.json +35 -0
  51. package/providers/_builtin/extension/cline/scripts/focus_editor.js +48 -0
  52. package/providers/_builtin/extension/cline/scripts/list_chats.js +100 -0
  53. package/providers/_builtin/extension/cline/scripts/list_models.js +43 -0
  54. package/providers/_builtin/extension/cline/scripts/list_modes.js +35 -0
  55. package/providers/_builtin/extension/cline/scripts/new_session.js +85 -0
  56. package/providers/_builtin/extension/cline/scripts/open_panel.js +25 -0
  57. package/providers/_builtin/extension/cline/scripts/read_chat.js +257 -0
  58. package/providers/_builtin/extension/cline/scripts/resolve_action.js +83 -0
  59. package/providers/_builtin/extension/cline/scripts/send_message.js +95 -0
  60. package/providers/_builtin/extension/cline/scripts/set_mode.js +36 -0
  61. package/providers/_builtin/extension/cline/scripts/set_model.js +36 -0
  62. package/providers/_builtin/extension/cline/scripts/switch_session.js +206 -0
  63. package/providers/_builtin/extension/cline/scripts.js +73 -0
  64. package/providers/_builtin/extension/roo-code/provider.json +35 -0
  65. package/providers/_builtin/extension/roo-code/scripts.js +659 -0
  66. package/providers/_builtin/ide/antigravity/provider.json +68 -0
  67. package/providers/_builtin/ide/antigravity/scripts/1.106/focus_editor.js +20 -0
  68. package/providers/_builtin/ide/antigravity/scripts/1.106/list_chats.js +137 -0
  69. package/providers/_builtin/ide/antigravity/scripts/1.106/list_models.js +38 -0
  70. package/providers/_builtin/ide/antigravity/scripts/1.106/list_modes.js +48 -0
  71. package/providers/_builtin/ide/antigravity/scripts/1.106/new_session.js +75 -0
  72. package/providers/_builtin/ide/antigravity/scripts/1.106/read_chat.js +262 -0
  73. package/providers/_builtin/ide/antigravity/scripts/1.106/resolve_action.js +68 -0
  74. package/providers/_builtin/ide/antigravity/scripts/1.106/scripts.js +57 -0
  75. package/providers/_builtin/ide/antigravity/scripts/1.106/send_message.js +56 -0
  76. package/providers/_builtin/ide/antigravity/scripts/1.106/set_mode.js +34 -0
  77. package/providers/_builtin/ide/antigravity/scripts/1.106/set_model.js +47 -0
  78. package/providers/_builtin/ide/antigravity/scripts/1.106/switch_session.js +114 -0
  79. package/providers/_builtin/ide/antigravity/scripts/1.107/focus_editor.js +20 -0
  80. package/providers/_builtin/ide/antigravity/scripts/1.107/list_chats.js +137 -0
  81. package/providers/_builtin/ide/antigravity/scripts/1.107/list_models.js +61 -0
  82. package/providers/_builtin/ide/antigravity/scripts/1.107/list_modes.js +72 -0
  83. package/providers/_builtin/ide/antigravity/scripts/1.107/new_session.js +75 -0
  84. package/providers/_builtin/ide/antigravity/scripts/1.107/read_chat.js +262 -0
  85. package/providers/_builtin/ide/antigravity/scripts/1.107/resolve_action.js +68 -0
  86. package/providers/_builtin/ide/antigravity/scripts/1.107/scripts.js +67 -0
  87. package/providers/_builtin/ide/antigravity/scripts/1.107/send_message.js +56 -0
  88. package/providers/_builtin/ide/antigravity/scripts/1.107/set_mode.js +67 -0
  89. package/providers/_builtin/ide/antigravity/scripts/1.107/set_model.js +72 -0
  90. package/providers/_builtin/ide/antigravity/scripts/1.107/switch_session.js +114 -0
  91. package/providers/_builtin/ide/cursor/provider.json +70 -0
  92. package/providers/_builtin/ide/cursor/scripts/0.49/dismiss_notification.js +30 -0
  93. package/providers/_builtin/ide/cursor/scripts/0.49/focus_editor.js +13 -0
  94. package/providers/_builtin/ide/cursor/scripts/0.49/list_models.js +78 -0
  95. package/providers/_builtin/ide/cursor/scripts/0.49/list_modes.js +40 -0
  96. package/providers/_builtin/ide/cursor/scripts/0.49/list_notifications.js +23 -0
  97. package/providers/_builtin/ide/cursor/scripts/0.49/list_sessions.js +42 -0
  98. package/providers/_builtin/ide/cursor/scripts/0.49/new_session.js +20 -0
  99. package/providers/_builtin/ide/cursor/scripts/0.49/open_panel.js +23 -0
  100. package/providers/_builtin/ide/cursor/scripts/0.49/read_chat.js +75 -0
  101. package/providers/_builtin/ide/cursor/scripts/0.49/resolve_action.js +19 -0
  102. package/providers/_builtin/ide/cursor/scripts/0.49/scripts.js +78 -0
  103. package/providers/_builtin/ide/cursor/scripts/0.49/send_message.js +23 -0
  104. package/providers/_builtin/ide/cursor/scripts/0.49/set_mode.js +38 -0
  105. package/providers/_builtin/ide/cursor/scripts/0.49/set_model.js +81 -0
  106. package/providers/_builtin/ide/cursor/scripts/0.49/switch_session.js +28 -0
  107. package/providers/_builtin/ide/kiro/provider.json +67 -0
  108. package/providers/_builtin/ide/kiro/scripts/focus_editor.js +20 -0
  109. package/providers/_builtin/ide/kiro/scripts/open_panel.js +47 -0
  110. package/providers/_builtin/ide/kiro/scripts/resolve_action.js +54 -0
  111. package/providers/_builtin/ide/kiro/scripts/send_message.js +29 -0
  112. package/providers/_builtin/ide/kiro/scripts/webview_list_models.js +39 -0
  113. package/providers/_builtin/ide/kiro/scripts/webview_list_modes.js +39 -0
  114. package/providers/_builtin/ide/kiro/scripts/webview_list_sessions.js +21 -0
  115. package/providers/_builtin/ide/kiro/scripts/webview_new_session.js +34 -0
  116. package/providers/_builtin/ide/kiro/scripts/webview_read_chat.js +68 -0
  117. package/providers/_builtin/ide/kiro/scripts/webview_send_message.js +72 -0
  118. package/providers/_builtin/ide/kiro/scripts/webview_set_mode.js +15 -0
  119. package/providers/_builtin/ide/kiro/scripts/webview_set_model.js +15 -0
  120. package/providers/_builtin/ide/kiro/scripts/webview_switch_session.js +26 -0
  121. package/providers/_builtin/ide/kiro/scripts.js +62 -0
  122. package/providers/_builtin/ide/pearai/provider.json +67 -0
  123. package/providers/_builtin/ide/pearai/scripts/focus_editor.js +20 -0
  124. package/providers/_builtin/ide/pearai/scripts/list_sessions.js +38 -0
  125. package/providers/_builtin/ide/pearai/scripts/new_session.js +55 -0
  126. package/providers/_builtin/ide/pearai/scripts/open_panel.js +46 -0
  127. package/providers/_builtin/ide/pearai/scripts/resolve_action.js +54 -0
  128. package/providers/_builtin/ide/pearai/scripts/send_message.js +29 -0
  129. package/providers/_builtin/ide/pearai/scripts/webview_list_models.js +43 -0
  130. package/providers/_builtin/ide/pearai/scripts/webview_list_modes.js +35 -0
  131. package/providers/_builtin/ide/pearai/scripts/webview_list_sessions.js +62 -0
  132. package/providers/_builtin/ide/pearai/scripts/webview_new_session.js +49 -0
  133. package/providers/_builtin/ide/pearai/scripts/webview_read_chat.js +92 -0
  134. package/providers/_builtin/ide/pearai/scripts/webview_resolve_action.js +59 -0
  135. package/providers/_builtin/ide/pearai/scripts/webview_send_message.js +72 -0
  136. package/providers/_builtin/ide/pearai/scripts/webview_set_mode.js +36 -0
  137. package/providers/_builtin/ide/pearai/scripts/webview_set_model.js +36 -0
  138. package/providers/_builtin/ide/pearai/scripts/webview_switch_session.js +34 -0
  139. package/providers/_builtin/ide/pearai/scripts.js +74 -0
  140. package/providers/_builtin/ide/trae/provider.json +66 -0
  141. package/providers/_builtin/ide/trae/scripts/focus_editor.js +20 -0
  142. package/providers/_builtin/ide/trae/scripts/list_chats.js +24 -0
  143. package/providers/_builtin/ide/trae/scripts/list_models.js +39 -0
  144. package/providers/_builtin/ide/trae/scripts/list_modes.js +39 -0
  145. package/providers/_builtin/ide/trae/scripts/new_session.js +30 -0
  146. package/providers/_builtin/ide/trae/scripts/open_panel.js +44 -0
  147. package/providers/_builtin/ide/trae/scripts/read_chat.js +113 -0
  148. package/providers/_builtin/ide/trae/scripts/resolve_action.js +54 -0
  149. package/providers/_builtin/ide/trae/scripts/send_message.js +69 -0
  150. package/providers/_builtin/ide/trae/scripts/set_mode.js +15 -0
  151. package/providers/_builtin/ide/trae/scripts/set_model.js +15 -0
  152. package/providers/_builtin/ide/trae/scripts/switch_session.js +23 -0
  153. package/providers/_builtin/ide/trae/scripts.js +57 -0
  154. package/providers/_builtin/ide/vscode/provider.json +64 -0
  155. package/providers/_builtin/ide/vscode-insiders/provider.json +62 -0
  156. package/providers/_builtin/ide/vscodium/provider.json +63 -0
  157. package/providers/_builtin/ide/windsurf/provider.json +53 -0
  158. package/providers/_builtin/ide/windsurf/scripts/focus_editor.js +30 -0
  159. package/providers/_builtin/ide/windsurf/scripts/list_chats.js +117 -0
  160. package/providers/_builtin/ide/windsurf/scripts/list_models.js +39 -0
  161. package/providers/_builtin/ide/windsurf/scripts/list_modes.js +39 -0
  162. package/providers/_builtin/ide/windsurf/scripts/new_session.js +69 -0
  163. package/providers/_builtin/ide/windsurf/scripts/open_panel.js +58 -0
  164. package/providers/_builtin/ide/windsurf/scripts/read_chat.js +297 -0
  165. package/providers/_builtin/ide/windsurf/scripts/resolve_action.js +68 -0
  166. package/providers/_builtin/ide/windsurf/scripts/send_message.js +87 -0
  167. package/providers/_builtin/ide/windsurf/scripts/set_mode.js +15 -0
  168. package/providers/_builtin/ide/windsurf/scripts/set_model.js +15 -0
  169. package/providers/_builtin/ide/windsurf/scripts/switch_session.js +58 -0
  170. package/providers/_builtin/ide/windsurf/scripts.js +57 -0
  171. package/providers/_builtin/registry.json +266 -0
  172. package/providers/_builtin/validate.js +156 -0
  173. package/src/agent-stream/index.ts +6 -0
  174. package/src/agent-stream/manager.ts +286 -0
  175. package/src/agent-stream/poller.ts +154 -0
  176. package/src/agent-stream/provider-adapter.ts +138 -0
  177. package/src/agent-stream/types.ts +61 -0
  178. package/src/boot/daemon-lifecycle.ts +252 -0
  179. package/src/cdp/devtools.ts +335 -0
  180. package/src/cdp/initializer.ts +191 -0
  181. package/src/cdp/manager.ts +897 -0
  182. package/src/cdp/scanner.ts +185 -0
  183. package/src/cdp/setup.ts +150 -0
  184. package/src/cli-adapter-types.ts +25 -0
  185. package/src/cli-adapters/provider-cli-adapter.ts +448 -0
  186. package/src/commands/cdp-commands.ts +208 -0
  187. package/src/commands/chat-commands.ts +675 -0
  188. package/src/commands/cli-manager.ts +353 -0
  189. package/src/commands/handler.ts +328 -0
  190. package/src/commands/router.ts +258 -0
  191. package/src/commands/stream-commands.ts +325 -0
  192. package/src/config/chat-history.ts +211 -0
  193. package/src/config/config.ts +219 -0
  194. package/src/daemon/dev-server.ts +2378 -0
  195. package/src/daemon/scaffold-template.ts +394 -0
  196. package/src/daemon-core.ts +50 -0
  197. package/src/detection/cli-detector.ts +89 -0
  198. package/src/detection/ide-detector.ts +157 -0
  199. package/src/index.ts +103 -0
  200. package/src/installer.ts +263 -0
  201. package/src/ipc-protocol.ts +133 -0
  202. package/src/launch.ts +433 -0
  203. package/src/logging/command-log.ts +180 -0
  204. package/src/logging/logger.ts +316 -0
  205. package/src/providers/acp-provider-instance.ts +1140 -0
  206. package/src/providers/cli-provider-instance.ts +207 -0
  207. package/src/providers/contracts.ts +524 -0
  208. package/src/providers/extension-provider-instance.ts +156 -0
  209. package/src/providers/ide-provider-instance.ts +377 -0
  210. package/src/providers/index.ts +18 -0
  211. package/src/providers/provider-instance-manager.ts +182 -0
  212. package/src/providers/provider-instance.ts +112 -0
  213. package/src/providers/provider-loader.ts +1031 -0
  214. package/src/providers/status-monitor.ts +125 -0
  215. package/src/providers/version-archive.ts +266 -0
  216. package/src/status/reporter.ts +294 -0
  217. package/src/types.ts +206 -0
package/src/launch.ts ADDED
@@ -0,0 +1,433 @@
1
+ /**
2
+ * ADHDev Launcher — IDE Launch/Relaunch with CDP
3
+ *
4
+ * Launches IDE with Chrome DevTools Protocol (remote-debugging-port).
5
+ * If IDE is already running, terminates it and restarts with CDP option.
6
+ *
7
+ * Pipeline:
8
+ * 1. IDE process detection (already running?)
9
+ * 2. If already running with CDP → reuse as-is
10
+ * 3. If running without CDP → kill process → wait → restart with CDP
11
+ * 4. Not running → start fresh with CDP
12
+ *
13
+ * Usage:
14
+ * adhdev launch — Launch configured IDE with CDP port
15
+ * adhdev launch cursor — Launch Cursor with CDP port
16
+ * adhdev launch --workspace /path — Open specific workspace
17
+ */
18
+
19
+ import { execSync, spawn, spawnSync } from 'child_process';
20
+ import * as net from 'net';
21
+ import * as os from 'os';
22
+ import * as path from 'path';
23
+ import { detectIDEs } from './detection/ide-detector.js';
24
+ import { IDEInfo } from './detection/ide-detector.js';
25
+ import { ProviderLoader } from './providers/provider-loader.js';
26
+
27
+ // ─── Provider-based dynamic IDE infrastructure ────────────────
28
+ // Reads cdpPorts, processNames from provider.js — only create provider.js to add new IDE
29
+
30
+ let _providerLoader: ProviderLoader | null = null;
31
+
32
+ function getProviderLoader(): ProviderLoader {
33
+ if (!_providerLoader) {
34
+ _providerLoader = new ProviderLoader({ logFn: () => {} }); // Suppress logs during launch
35
+ _providerLoader.loadAll();
36
+ _providerLoader.registerToDetector(); // IDE provider → detector registry
37
+ }
38
+ return _providerLoader;
39
+ }
40
+
41
+ function getCdpPorts(): Record<string, [number, number]> {
42
+ return getProviderLoader().getCdpPortMap();
43
+ }
44
+
45
+ function getMacAppIdentifiers(): Record<string, string> {
46
+ return getProviderLoader().getMacAppIdentifiers();
47
+ }
48
+
49
+ function getWinProcessNames(): Record<string, string[]> {
50
+ return getProviderLoader().getWinProcessNames();
51
+ }
52
+
53
+ // ─── Helpers ────────────────────────────────────
54
+
55
+ /** Find available port (primary → secondary → sequential after) */
56
+ async function findFreePort(ports: [number, number]): Promise<number> {
57
+ for (const port of ports) {
58
+ const free = await checkPortFree(port);
59
+ if (free) return port;
60
+ }
61
+ // If both ports in use, scan from secondary+1
62
+ let port = ports[1] + 1;
63
+ while (port < ports[1] + 10) {
64
+ if (await checkPortFree(port)) return port;
65
+ port++;
66
+ }
67
+ throw new Error('No free port found');
68
+ }
69
+
70
+ function checkPortFree(port: number): Promise<boolean> {
71
+ return new Promise((resolve) => {
72
+ const server = net.createServer();
73
+ server.unref();
74
+ server.on('error', () => resolve(false));
75
+ server.listen(port, '127.0.0.1', () => {
76
+ server.close(() => resolve(true));
77
+ });
78
+ });
79
+ }
80
+
81
+ /** Check if CDP responds on port */
82
+ async function isCdpActive(port: number): Promise<boolean> {
83
+ return new Promise((resolve) => {
84
+ const req = require('http').get(`http://127.0.0.1:${port}/json/version`, {
85
+ timeout: 2000,
86
+ }, (res: any) => {
87
+ let data = '';
88
+ res.on('data', (c: string) => data += c);
89
+ res.on('end', () => {
90
+ try {
91
+ const info = JSON.parse(data);
92
+ resolve(!!info['WebKit-Version'] || !!info['Browser']);
93
+ } catch {
94
+ resolve(false);
95
+ }
96
+ });
97
+ });
98
+ req.on('error', () => resolve(false));
99
+ req.on('timeout', () => { req.destroy(); resolve(false); });
100
+ });
101
+ }
102
+
103
+ /** Kill IDE process (graceful → force) */
104
+ async function killIdeProcess(ideId: string): Promise<boolean> {
105
+ const plat = os.platform();
106
+ const appName = getMacAppIdentifiers()[ideId];
107
+ const winProcesses = getWinProcessNames()[ideId];
108
+
109
+ try {
110
+ if (plat === 'darwin' && appName) {
111
+ // macOS: graceful quit via osascript
112
+ try {
113
+ execSync(`osascript -e 'tell application "${appName}" to quit' 2>/dev/null`, {
114
+ timeout: 5000,
115
+ });
116
+ } catch {
117
+ try { execSync(`pkill -f "${appName}" 2>/dev/null`); } catch { }
118
+ }
119
+ } else if (plat === 'win32' && winProcesses) {
120
+ // Windows: taskkill for each process name
121
+ for (const proc of winProcesses) {
122
+ try {
123
+ execSync(`taskkill /IM "${proc}" /F 2>nul`, { timeout: 5000 });
124
+ } catch { }
125
+ }
126
+ // Process name may differ, so also try via WMIC
127
+ try {
128
+ const exeName = winProcesses[0].replace('.exe', '');
129
+ execSync(`powershell -Command "Get-Process -Name '${exeName}' -ErrorAction SilentlyContinue | Stop-Process -Force"`, {
130
+ timeout: 10000,
131
+ });
132
+ } catch { }
133
+ } else {
134
+ try { execSync(`pkill -f "${ideId}" 2>/dev/null`); } catch { }
135
+ }
136
+
137
+ // Wait for process kill (max 15 seconds)
138
+ for (let i = 0; i < 30; i++) {
139
+ await new Promise(r => setTimeout(r, 500));
140
+ if (!isIdeRunning(ideId)) return true;
141
+ }
142
+
143
+ // Force terminate retry
144
+ if (plat === 'darwin' && appName) {
145
+ try { execSync(`pkill -9 -f "${appName}" 2>/dev/null`); } catch { }
146
+ } else if (plat === 'win32' && winProcesses) {
147
+ for (const proc of winProcesses) {
148
+ try { execSync(`taskkill /IM "${proc}" /F 2>nul`); } catch { }
149
+ }
150
+ }
151
+
152
+ await new Promise(r => setTimeout(r, 2000));
153
+ return !isIdeRunning(ideId);
154
+
155
+ } catch {
156
+ return false;
157
+ }
158
+ }
159
+
160
+ /** Check if IDE process is running */
161
+ function isIdeRunning(ideId: string): boolean {
162
+ const plat = os.platform();
163
+
164
+ try {
165
+ if (plat === 'darwin') {
166
+ const appName = getMacAppIdentifiers()[ideId];
167
+ if (!appName) return false;
168
+ const result = execSync(`pgrep -f "${appName}" 2>/dev/null`, { encoding: 'utf-8' });
169
+ return result.trim().length > 0;
170
+ } else if (plat === 'win32') {
171
+ const winProcesses = getWinProcessNames()[ideId];
172
+ if (!winProcesses) return false;
173
+ // Check each process name
174
+ for (const proc of winProcesses) {
175
+ try {
176
+ const result = execSync(`tasklist /FI "IMAGENAME eq ${proc}" /NH 2>nul`, { encoding: 'utf-8' });
177
+ if (result.includes(proc)) return true;
178
+ } catch { }
179
+ }
180
+ // Also check via PowerShell (when tasklist cannot find)
181
+ try {
182
+ const exeName = winProcesses[0].replace('.exe', '');
183
+ const result = execSync(
184
+ `powershell -Command "(Get-Process -Name '${exeName}' -ErrorAction SilentlyContinue).Count"`,
185
+ { encoding: 'utf-8', timeout: 5000 }
186
+ );
187
+ return parseInt(result.trim()) > 0;
188
+ } catch { }
189
+ return false;
190
+ } else {
191
+ const result = execSync(`pgrep -f "${ideId}" 2>/dev/null`, { encoding: 'utf-8' });
192
+ return result.trim().length > 0;
193
+ }
194
+ } catch {
195
+ return false;
196
+ }
197
+ }
198
+
199
+ /** Detect currently open workspace path */
200
+ function detectCurrentWorkspace(ideId: string): string | undefined {
201
+ const plat = os.platform();
202
+
203
+ if (plat === 'darwin') {
204
+ try {
205
+ const appName = getMacAppIdentifiers()[ideId];
206
+ if (!appName) return undefined;
207
+ const result = execSync(
208
+ `lsof -c "${appName}" 2>/dev/null | grep cwd | head -1 | awk '{print $NF}'`,
209
+ { encoding: 'utf-8', timeout: 3000 }
210
+ );
211
+ const dir = result.trim();
212
+ if (dir && dir !== '/') return dir;
213
+ } catch { }
214
+ } else if (plat === 'win32') {
215
+ // Windows: read IDE recent workspaces from storage.json
216
+ try {
217
+ const fs = require('fs');
218
+ const appNameMap = getMacAppIdentifiers(); // Provider-based dynamic mapping
219
+ const appName = appNameMap[ideId];
220
+ if (appName) {
221
+ const storagePath = path.join(
222
+ process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
223
+ appName, 'storage.json'
224
+ );
225
+ if (fs.existsSync(storagePath)) {
226
+ const data = JSON.parse(fs.readFileSync(storagePath, 'utf-8'));
227
+ // openedPathsList.workspaces3 has recent folders
228
+ const workspaces = data?.openedPathsList?.workspaces3 || data?.openedPathsList?.entries || [];
229
+ if (workspaces.length > 0) {
230
+ const recent = workspaces[0];
231
+ // Can be object { folderUri: 'file:///...' } or string
232
+ const uri = typeof recent === 'string' ? recent : recent?.folderUri;
233
+ if (uri?.startsWith('file:///')) {
234
+ return decodeURIComponent(uri.replace('file:///', ''));
235
+ }
236
+ }
237
+ }
238
+ }
239
+ } catch { }
240
+ }
241
+
242
+ return undefined;
243
+ }
244
+
245
+ // ─── Launch Logic ───────────────────────────────
246
+
247
+ export interface LaunchOptions {
248
+ ideId?: string;
249
+ workspace?: string;
250
+ newWindow?: boolean;
251
+ }
252
+
253
+ export interface LaunchResult {
254
+ success: boolean;
255
+ ideId: string;
256
+ ideName: string;
257
+ port: number;
258
+ action: 'started' | 'restarted' | 'reused' | 'failed';
259
+ message: string;
260
+ error?: string;
261
+ }
262
+
263
+ /**
264
+ * Execute IDE with CDP port (relaunch pipeline)
265
+ *
266
+ * 1. IDE detect
267
+ * 2. per-fixed IDE CDP port determine
268
+ * 3. CDP not active → reuse
269
+ * 4. IDE execute during but CDP none → terminate → restart with CDP
270
+ * 5. IDE not running → start fresh with CDP
271
+ */
272
+ export async function launchWithCdp(options: LaunchOptions = {}): Promise<LaunchResult> {
273
+ const platform = os.platform();
274
+
275
+ // 1. IDE determine
276
+ let targetIde: IDEInfo | undefined;
277
+ const ides = await detectIDEs();
278
+
279
+ if (options.ideId) {
280
+ targetIde = ides.find(i => i.id === options.ideId && i.installed);
281
+ if (!targetIde) {
282
+ return {
283
+ success: false, ideId: options.ideId, ideName: options.ideId,
284
+ port: 0, action: 'failed',
285
+ message: '', error: `IDE '${options.ideId}' not found or not installed`,
286
+ };
287
+ }
288
+ } else {
289
+ const { loadConfig } = await import('./config/config.js');
290
+ const config = loadConfig();
291
+ if (config.selectedIde) {
292
+ targetIde = ides.find(i => i.id === config.selectedIde && i.installed);
293
+ }
294
+ if (!targetIde) {
295
+ targetIde = ides.find(i => i.installed);
296
+ }
297
+ if (!targetIde) {
298
+ return {
299
+ success: false, ideId: 'unknown', ideName: 'Unknown',
300
+ port: 0, action: 'failed',
301
+ message: '', error: 'No IDE found. Install VS Code, Cursor, or Antigravity first.',
302
+ };
303
+ }
304
+ }
305
+
306
+ // 2. per-fixed IDE CDP port determine
307
+ const portPair = getCdpPorts()[targetIde.id] || [9333, 9334];
308
+
309
+ // 3. Check if CDP is not yet enabled
310
+ for (const port of portPair) {
311
+ if (await isCdpActive(port)) {
312
+ return {
313
+ success: true, ideId: targetIde.id, ideName: targetIde.displayName,
314
+ port, action: 'reused',
315
+ message: `CDP already active on port ${port}`,
316
+ };
317
+ }
318
+ }
319
+
320
+ // 4. Check if IDE is currently running
321
+ const alreadyRunning = isIdeRunning(targetIde.id);
322
+ const workspace = options.workspace || (alreadyRunning ? detectCurrentWorkspace(targetIde.id) : undefined);
323
+
324
+ // 5. If IDE is running, terminate it
325
+ if (alreadyRunning) {
326
+ const killed = await killIdeProcess(targetIde.id);
327
+ if (!killed) {
328
+ return {
329
+ success: false, ideId: targetIde.id, ideName: targetIde.displayName,
330
+ port: 0, action: 'failed',
331
+ message: '', error: `Could not stop ${targetIde.displayName}. Close it manually and try again.`,
332
+ };
333
+ }
334
+ // Wait for process full termination
335
+ await new Promise(r => setTimeout(r, 3000));
336
+ }
337
+
338
+ // 6. Find available port
339
+ const port = await findFreePort(portPair);
340
+
341
+ // 7. Execute with CDP
342
+ try {
343
+ if (platform === 'darwin') {
344
+ await launchMacOS(targetIde, port, workspace, options.newWindow);
345
+ } else if (platform === 'win32') {
346
+ await launchWindows(targetIde, port, workspace, options.newWindow);
347
+ } else {
348
+ await launchLinux(targetIde, port, workspace, options.newWindow);
349
+ }
350
+
351
+ // Wait for CDP to enable (max 15 seconds)
352
+ let cdpReady = false;
353
+ for (let i = 0; i < 30; i++) {
354
+ await new Promise(r => setTimeout(r, 500));
355
+ if (await isCdpActive(port)) {
356
+ cdpReady = true;
357
+ break;
358
+ }
359
+ }
360
+
361
+ return {
362
+ success: true, ideId: targetIde.id, ideName: targetIde.displayName,
363
+ port, action: alreadyRunning ? 'restarted' : 'started',
364
+ message: cdpReady
365
+ ? `${targetIde.displayName} launched with CDP on port ${port}`
366
+ : `${targetIde.displayName} launched (CDP may take a moment to initialize)`,
367
+ };
368
+ } catch (e: any) {
369
+ return {
370
+ success: false, ideId: targetIde.id, ideName: targetIde.displayName,
371
+ port, action: 'failed',
372
+ message: '', error: e?.message || String(e),
373
+ };
374
+ }
375
+ }
376
+
377
+ // ─── Platform Launch ────────────────────────────
378
+
379
+ async function launchMacOS(ide: IDEInfo, port: number, workspace?: string, newWindow?: boolean): Promise<void> {
380
+ const appName = getMacAppIdentifiers()[ide.id];
381
+
382
+ const args = ['--remote-debugging-port=' + port];
383
+ if (newWindow) args.push('--new-window');
384
+ if (workspace) args.push(workspace);
385
+
386
+ if (appName) {
387
+ // 'open -a' execution (ensures GUI session)
388
+ const openArgs = ['-a', appName, '--args', ...args];
389
+ spawn('open', openArgs, { detached: true, stdio: 'ignore' }).unref();
390
+ } else if (ide.cliCommand) {
391
+ // CLI based execute
392
+ spawn(ide.cliCommand, args, { detached: true, stdio: 'ignore' }).unref();
393
+ } else {
394
+ throw new Error(`No app identifier or CLI for ${ide.displayName}`);
395
+ }
396
+ }
397
+
398
+ async function launchWindows(ide: IDEInfo, port: number, workspace?: string, newWindow?: boolean): Promise<void> {
399
+ const cli = ide.cliCommand;
400
+ if (!cli) {
401
+ throw new Error(`No CLI command for ${ide.displayName}. Please add it to PATH.`);
402
+ }
403
+
404
+ // Compose arguments for CLI command — IDE CLI wrapper (.cmd) handles Electron execution
405
+ const parts = [`"${cli}"`, `--remote-debugging-port=${port}`];
406
+ if (newWindow) parts.push('--new-window');
407
+ if (workspace) parts.push(`"${workspace}"`);
408
+
409
+ const fullCmd = parts.join(' ');
410
+
411
+ // exec fire-and-forget: delegate to CLI to properly start IDE process
412
+ const { exec: execCmd } = require('child_process');
413
+ execCmd(fullCmd, { windowsHide: true }, () => {
414
+ // IDE process runs independently even after CLI per-terminates wrap
415
+ });
416
+ }
417
+
418
+ async function launchLinux(ide: IDEInfo, port: number, workspace?: string, newWindow?: boolean): Promise<void> {
419
+ const cli = ide.cliCommand;
420
+ if (!cli) {
421
+ throw new Error(`No CLI command for ${ide.displayName}. Make sure it's in PATH.`);
422
+ }
423
+
424
+ const args = ['--remote-debugging-port=' + port];
425
+ if (newWindow) args.push('--new-window');
426
+ if (workspace) args.push(workspace);
427
+
428
+ spawn(cli, args, { detached: true, stdio: 'ignore' }).unref();
429
+ }
430
+
431
+ export function getAvailableIdeIds(): string[] {
432
+ return getProviderLoader().getAvailableIdeTypes();
433
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * ADHDev Daemon — Command History Logger
3
+ *
4
+ * Record all commands from dashboard/WS/P2P/Extension/API to local file.
5
+ * Per-date JSONL file, 7-day retention, 5MB limit.
6
+ *
7
+ * Purpose:
8
+ * - Debugging: track what command came and when
9
+ * - Audit: record all commands executed from remote
10
+ * - Stats: identify frequently used features
11
+ */
12
+
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ import * as os from 'os';
16
+
17
+ // ─── Config ──────────────────────────────────
18
+ const LOG_DIR = process.platform === 'darwin'
19
+ ? path.join(os.homedir(), 'Library', 'Logs', 'adhdev')
20
+ : path.join(os.homedir(), '.local', 'share', 'adhdev', 'logs');
21
+
22
+ const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
23
+ const MAX_DAYS = 7;
24
+
25
+ try { fs.mkdirSync(LOG_DIR, { recursive: true }); } catch { }
26
+
27
+ // ─── Types ───────────────────────────────────
28
+ export interface CommandLogEntry {
29
+ ts: string; // ISO timestamp
30
+ cmd: string; // command name
31
+ source: 'ws' | 'p2p' | 'ext' | 'api' | 'standalone' | 'unknown'; // where it came from
32
+ args?: Record<string, unknown>; // command arguments (sensitive values masked)
33
+ success?: boolean; // result
34
+ error?: string; // error message if failed
35
+ durationMs?: number; // execution time
36
+ }
37
+
38
+ // ─── Sensitive field masking ─────────────────
39
+ const SENSITIVE_KEYS = new Set([
40
+ 'token', 'password', 'secret', 'apiKey', 'api_key',
41
+ 'connectionToken', 'content', 'message', 'text',
42
+ ]);
43
+
44
+ function maskArgs(args: any): Record<string, unknown> | undefined {
45
+ if (!args || typeof args !== 'object') return undefined;
46
+ const masked: Record<string, unknown> = {};
47
+ for (const [key, value] of Object.entries(args)) {
48
+ if (SENSITIVE_KEYS.has(key)) {
49
+ masked[key] = typeof value === 'string'
50
+ ? `[${value.length} chars]`
51
+ : '[masked]';
52
+ } else if (key.startsWith('_')) {
53
+ // internal fields: keep as-is (e.g. _targetType, _targetInstance)
54
+ masked[key] = value;
55
+ } else if (typeof value === 'object' && value !== null) {
56
+ // Don't recurse deeply — just note the type
57
+ masked[key] = Array.isArray(value)
58
+ ? `[Array(${value.length})]`
59
+ : `[Object]`;
60
+ } else {
61
+ masked[key] = value;
62
+ }
63
+ }
64
+ return masked;
65
+ }
66
+
67
+ // ─── File management ─────────────────────────
68
+ function getDateStr(): string {
69
+ return new Date().toISOString().slice(0, 10);
70
+ }
71
+
72
+ let currentDate = getDateStr();
73
+ let currentFile = path.join(LOG_DIR, `commands-${currentDate}.jsonl`);
74
+ let writeCount = 0;
75
+
76
+ function checkRotation(): void {
77
+ const today = getDateStr();
78
+ if (today !== currentDate) {
79
+ currentDate = today;
80
+ currentFile = path.join(LOG_DIR, `commands-${currentDate}.jsonl`);
81
+ cleanOldFiles();
82
+ }
83
+ }
84
+
85
+ function cleanOldFiles(): void {
86
+ try {
87
+ const files = fs.readdirSync(LOG_DIR).filter(f => f.startsWith('commands-') && f.endsWith('.jsonl'));
88
+ const cutoff = new Date();
89
+ cutoff.setDate(cutoff.getDate() - MAX_DAYS);
90
+ const cutoffStr = cutoff.toISOString().slice(0, 10);
91
+ for (const file of files) {
92
+ const dateMatch = file.match(/commands-(\d{4}-\d{2}-\d{2})/);
93
+ if (dateMatch && dateMatch[1] < cutoffStr) {
94
+ try { fs.unlinkSync(path.join(LOG_DIR, file)); } catch { }
95
+ }
96
+ }
97
+ } catch { }
98
+ }
99
+
100
+ function checkSize(): void {
101
+ try {
102
+ const stat = fs.statSync(currentFile);
103
+ if (stat.size > MAX_FILE_SIZE) {
104
+ const backup = currentFile.replace('.jsonl', '.1.jsonl');
105
+ try { fs.unlinkSync(backup); } catch { }
106
+ fs.renameSync(currentFile, backup);
107
+ }
108
+ } catch { /* file doesn't exist yet */ }
109
+ }
110
+
111
+ // ─── Noise filter ────────────────────────────
112
+ // These commands are too frequent / low-value to log
113
+ const SKIP_COMMANDS = new Set([
114
+ 'heartbeat',
115
+ 'status_report',
116
+ ]);
117
+
118
+ // ─── Public API ──────────────────────────────
119
+
120
+ /**
121
+ * Log a command received from the dashboard/WS/P2P/extension/API.
122
+ * Call this at the entry point of command handling.
123
+ */
124
+ export function logCommand(entry: CommandLogEntry): void {
125
+ if (SKIP_COMMANDS.has(entry.cmd)) return;
126
+
127
+ try {
128
+ if (++writeCount % 500 === 0) {
129
+ checkRotation();
130
+ checkSize();
131
+ }
132
+
133
+ const line = JSON.stringify({
134
+ ts: entry.ts,
135
+ cmd: entry.cmd,
136
+ src: entry.source,
137
+ ...(entry.args ? { args: maskArgs(entry.args) } : {}),
138
+ ...(entry.success !== undefined ? { ok: entry.success } : {}),
139
+ ...(entry.error ? { err: entry.error } : {}),
140
+ ...(entry.durationMs !== undefined ? { ms: entry.durationMs } : {}),
141
+ });
142
+
143
+ fs.appendFileSync(currentFile, line + '\n');
144
+ } catch { /* never crash the daemon for logging */ }
145
+ }
146
+
147
+ /**
148
+ * Read recent command history (for dashboard display / debugging)
149
+ */
150
+ export function getRecentCommands(count = 50): CommandLogEntry[] {
151
+ try {
152
+ if (!fs.existsSync(currentFile)) return [];
153
+ const content = fs.readFileSync(currentFile, 'utf-8');
154
+ const lines = content.trim().split('\n').filter(Boolean);
155
+ return lines.slice(-count).map(line => {
156
+ try {
157
+ const parsed = JSON.parse(line);
158
+ return {
159
+ ts: parsed.ts,
160
+ cmd: parsed.cmd,
161
+ source: parsed.src,
162
+ args: parsed.args,
163
+ success: parsed.ok,
164
+ error: parsed.err,
165
+ durationMs: parsed.ms,
166
+ };
167
+ } catch {
168
+ return { ts: '', cmd: 'parse_error', source: 'unknown' as const };
169
+ }
170
+ });
171
+ } catch {
172
+ return [];
173
+ }
174
+ }
175
+
176
+ /** Current command log file path */
177
+ export function getCommandLogPath(): string { return currentFile; }
178
+
179
+ // Initial cleanup
180
+ cleanOldFiles();