@adhdev/daemon-standalone 0.1.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.
- package/README.md +36 -0
- package/dist/index.js +685 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
- package/public/assets/index-BkM6DpoZ.js +186 -0
- package/public/assets/index-CAdAUHJD.js +186 -0
- package/public/assets/index-DaTd7tHO.css +32 -0
- package/public/index.html +16 -0
- package/public/otter-logo.png +0 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/index.ts
|
|
27
|
+
var import_http = require("http");
|
|
28
|
+
var import_ws = require("ws");
|
|
29
|
+
var path = __toESM(require("path"));
|
|
30
|
+
var fs = __toESM(require("fs"));
|
|
31
|
+
var os = __toESM(require("os"));
|
|
32
|
+
var import_daemon_core = require("@adhdev/daemon-core");
|
|
33
|
+
var DEFAULT_PORT = 3847;
|
|
34
|
+
var STATUS_INTERVAL = 2e3;
|
|
35
|
+
var StandaloneServer = class {
|
|
36
|
+
httpServer = null;
|
|
37
|
+
wss = null;
|
|
38
|
+
clients = /* @__PURE__ */ new Set();
|
|
39
|
+
authToken = null;
|
|
40
|
+
statusTimer = null;
|
|
41
|
+
running = false;
|
|
42
|
+
// daemon-core components
|
|
43
|
+
providerLoader;
|
|
44
|
+
instanceManager;
|
|
45
|
+
cliManager;
|
|
46
|
+
commandHandler = null;
|
|
47
|
+
agentStreamManager = null;
|
|
48
|
+
statusReporter = null;
|
|
49
|
+
cdpManagers = /* @__PURE__ */ new Map();
|
|
50
|
+
instanceIdMap = /* @__PURE__ */ new Map();
|
|
51
|
+
detectedIdes = [];
|
|
52
|
+
devServer = null;
|
|
53
|
+
constructor() {
|
|
54
|
+
this.providerLoader = new import_daemon_core.ProviderLoader({
|
|
55
|
+
builtinDir: path.join(__dirname, "..", "..", "daemon-cloud", "providers", "_builtin")
|
|
56
|
+
});
|
|
57
|
+
this.providerLoader.loadAll();
|
|
58
|
+
this.providerLoader.registerToDetector();
|
|
59
|
+
this.instanceManager = new import_daemon_core.ProviderInstanceManager();
|
|
60
|
+
this.cliManager = new import_daemon_core.DaemonCliManager({
|
|
61
|
+
getServerConn: () => null,
|
|
62
|
+
getP2p: () => null,
|
|
63
|
+
onStatusChange: () => this.broadcastStatus(),
|
|
64
|
+
removeAgentTracking: () => {
|
|
65
|
+
},
|
|
66
|
+
getInstanceManager: () => this.instanceManager
|
|
67
|
+
}, this.providerLoader);
|
|
68
|
+
}
|
|
69
|
+
async start(options = {}) {
|
|
70
|
+
const port = options.port || DEFAULT_PORT;
|
|
71
|
+
const host = options.host || "127.0.0.1";
|
|
72
|
+
this.authToken = options.token || process.env.ADHDEV_TOKEN || null;
|
|
73
|
+
(0, import_daemon_core.installGlobalInterceptor)();
|
|
74
|
+
console.log("\u{1F50D} Detecting IDEs...");
|
|
75
|
+
this.detectedIdes = await (0, import_daemon_core.detectIDEs)();
|
|
76
|
+
const installed = this.detectedIdes.filter((i) => i.installed);
|
|
77
|
+
console.log(` Found ${installed.length} IDE(s): ${installed.map((i) => i.id).join(", ") || "none"}`);
|
|
78
|
+
await this.initCdp();
|
|
79
|
+
if (options.dev) {
|
|
80
|
+
this.devServer = new import_daemon_core.DevServer({
|
|
81
|
+
providerLoader: this.providerLoader,
|
|
82
|
+
cdpManagers: this.cdpManagers,
|
|
83
|
+
logFn: (msg) => console.log(msg)
|
|
84
|
+
});
|
|
85
|
+
await this.devServer.start();
|
|
86
|
+
}
|
|
87
|
+
for (const ide of this.detectedIdes) {
|
|
88
|
+
if (!ide.installed) continue;
|
|
89
|
+
const cdp = this.cdpManagers.get(ide.id);
|
|
90
|
+
if (!cdp) continue;
|
|
91
|
+
const provider = this.providerLoader.resolve(ide.id);
|
|
92
|
+
if (!provider) continue;
|
|
93
|
+
const instance = new import_daemon_core.IdeProviderInstance(provider, ide.id);
|
|
94
|
+
const context = {
|
|
95
|
+
cdp: cdp || void 0,
|
|
96
|
+
serverConn: void 0,
|
|
97
|
+
settings: {}
|
|
98
|
+
};
|
|
99
|
+
await this.instanceManager.addInstance(ide.id, instance, context);
|
|
100
|
+
this.instanceIdMap.set(instance.getInstanceId(), ide.id);
|
|
101
|
+
}
|
|
102
|
+
this.instanceManager.startTicking(3e3);
|
|
103
|
+
this.commandHandler = new import_daemon_core.DaemonCommandHandler({
|
|
104
|
+
cdpManagers: this.cdpManagers,
|
|
105
|
+
ideType: "unknown",
|
|
106
|
+
adapters: this.cliManager.adapters,
|
|
107
|
+
providerLoader: this.providerLoader,
|
|
108
|
+
instanceIdMap: this.instanceIdMap
|
|
109
|
+
});
|
|
110
|
+
this.agentStreamManager = new import_daemon_core.DaemonAgentStreamManager(
|
|
111
|
+
console.log,
|
|
112
|
+
this.providerLoader
|
|
113
|
+
);
|
|
114
|
+
this.commandHandler.setAgentStreamManager(this.agentStreamManager);
|
|
115
|
+
this.httpServer = (0, import_http.createServer)((req, res) => {
|
|
116
|
+
this.handleHttp(req, res, options.publicDir);
|
|
117
|
+
});
|
|
118
|
+
this.wss = new import_ws.WebSocketServer({ noServer: true });
|
|
119
|
+
this.httpServer.on("upgrade", (req, socket, head) => {
|
|
120
|
+
const wsUrl = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
121
|
+
if (wsUrl.pathname === "/ws") {
|
|
122
|
+
if (this.authToken) {
|
|
123
|
+
const urlToken = wsUrl.searchParams.get("token");
|
|
124
|
+
if (urlToken !== this.authToken) {
|
|
125
|
+
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
|
126
|
+
socket.destroy();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
this.wss.handleUpgrade(req, socket, head, (ws) => {
|
|
131
|
+
this.handleWsConnection(ws);
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
socket.destroy();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
this.statusTimer = setInterval(() => {
|
|
138
|
+
this.broadcastStatus();
|
|
139
|
+
}, STATUS_INTERVAL);
|
|
140
|
+
this.running = true;
|
|
141
|
+
await new Promise((resolve) => {
|
|
142
|
+
this.httpServer.listen(port, host, () => {
|
|
143
|
+
resolve();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
console.log("");
|
|
147
|
+
console.log("\u{1F680} ADHDev Standalone Server");
|
|
148
|
+
console.log(` http://${host === "0.0.0.0" ? "localhost" : host}:${port}`);
|
|
149
|
+
console.log(` ws://${host === "0.0.0.0" ? "localhost" : host}:${port}/ws`);
|
|
150
|
+
if (host === "0.0.0.0") {
|
|
151
|
+
const lanIps = this.getLanIPs();
|
|
152
|
+
for (const ip of lanIps) {
|
|
153
|
+
console.log(` http://${ip}:${port} (LAN)`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (this.authToken) {
|
|
157
|
+
console.log(` \u{1F511} Token: ${this.authToken}`);
|
|
158
|
+
}
|
|
159
|
+
console.log("");
|
|
160
|
+
const cdpCount = [...this.cdpManagers.values()].filter((m) => m.isConnected).length;
|
|
161
|
+
console.log(` CDP: ${cdpCount > 0 ? `\u2705 ${cdpCount} connected` : "\u274C none"}`);
|
|
162
|
+
console.log(` Providers: ${this.providerLoader.getAll().length} loaded`);
|
|
163
|
+
if (options.dev) {
|
|
164
|
+
console.log(` \u{1F6E0}\uFE0F DevConsole: http://127.0.0.1:19280`);
|
|
165
|
+
}
|
|
166
|
+
console.log("");
|
|
167
|
+
console.log(" Press Ctrl+C to stop.");
|
|
168
|
+
console.log("");
|
|
169
|
+
if (options.open !== false) {
|
|
170
|
+
try {
|
|
171
|
+
const open = (await import("open")).default;
|
|
172
|
+
await open(`http://localhost:${port}`);
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
process.on("SIGINT", () => this.stop());
|
|
177
|
+
process.on("SIGTERM", () => this.stop());
|
|
178
|
+
}
|
|
179
|
+
// ─── HTTP Handler ───
|
|
180
|
+
handleHttp(req, res, publicDir) {
|
|
181
|
+
const url = req.url || "/";
|
|
182
|
+
const method = req.method || "GET";
|
|
183
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
184
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
185
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
186
|
+
if (method === "OPTIONS") {
|
|
187
|
+
res.writeHead(204);
|
|
188
|
+
res.end();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (this.authToken && url.startsWith("/api/")) {
|
|
192
|
+
const authHeader = req.headers["authorization"];
|
|
193
|
+
const bearerToken = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
194
|
+
const queryToken = new URL(url, `http://${req.headers.host || "localhost"}`).searchParams.get("token");
|
|
195
|
+
if (bearerToken !== this.authToken && queryToken !== this.authToken) {
|
|
196
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
197
|
+
res.end(JSON.stringify({ error: "Unauthorized. Provide token via Authorization header or ?token= query." }));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const apiPath = url.startsWith("/api/v1/") ? url.slice(7) : null;
|
|
202
|
+
if (apiPath === "/status" && method === "GET") {
|
|
203
|
+
const status = this.getStatus();
|
|
204
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
205
|
+
res.end(JSON.stringify(status));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (apiPath === "/ides" && method === "GET") {
|
|
209
|
+
const ides = this.getIdes();
|
|
210
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
211
|
+
res.end(JSON.stringify({ ides }));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (apiPath === "/clis" && method === "GET") {
|
|
215
|
+
const clis = this.getClis();
|
|
216
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
217
|
+
res.end(JSON.stringify({ clis }));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (apiPath === "/agents" && method === "GET") {
|
|
221
|
+
const ides = this.getIdes();
|
|
222
|
+
const agents = [];
|
|
223
|
+
for (const ide of ides) {
|
|
224
|
+
const chat = ide.activeChat;
|
|
225
|
+
if (chat) {
|
|
226
|
+
agents.push({
|
|
227
|
+
ideId: ide.id,
|
|
228
|
+
type: ide.type,
|
|
229
|
+
name: ide.name || ide.type,
|
|
230
|
+
status: chat.status || "idle",
|
|
231
|
+
source: "native"
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
for (const stream of ide.agentStreams || []) {
|
|
235
|
+
agents.push({
|
|
236
|
+
ideId: ide.id,
|
|
237
|
+
type: stream.agentType,
|
|
238
|
+
name: stream.agentName,
|
|
239
|
+
status: stream.status || "idle",
|
|
240
|
+
source: "extension"
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
245
|
+
res.end(JSON.stringify({ agents }));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (apiPath === "/command" && method === "POST") {
|
|
249
|
+
let body = "";
|
|
250
|
+
req.on("data", (chunk) => {
|
|
251
|
+
body += chunk;
|
|
252
|
+
});
|
|
253
|
+
req.on("end", async () => {
|
|
254
|
+
try {
|
|
255
|
+
const { type, payload, target } = JSON.parse(body);
|
|
256
|
+
const args = { ...payload, _targetInstance: target };
|
|
257
|
+
const result = await this.executeCommand(type, args);
|
|
258
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
259
|
+
res.end(JSON.stringify(result));
|
|
260
|
+
} catch (e) {
|
|
261
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
262
|
+
res.end(JSON.stringify({ success: false, error: e.message }));
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (publicDir) {
|
|
268
|
+
const filePath = url === "/" ? "/index.html" : url;
|
|
269
|
+
const fullPath = path.join(publicDir, filePath);
|
|
270
|
+
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
|
|
271
|
+
const ext = path.extname(fullPath);
|
|
272
|
+
const mimeTypes = {
|
|
273
|
+
".html": "text/html",
|
|
274
|
+
".js": "application/javascript",
|
|
275
|
+
".css": "text/css",
|
|
276
|
+
".json": "application/json",
|
|
277
|
+
".png": "image/png",
|
|
278
|
+
".svg": "image/svg+xml",
|
|
279
|
+
".ico": "image/x-icon",
|
|
280
|
+
".woff2": "font/woff2"
|
|
281
|
+
};
|
|
282
|
+
res.writeHead(200, { "Content-Type": mimeTypes[ext] || "application/octet-stream" });
|
|
283
|
+
fs.createReadStream(fullPath).pipe(res);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const indexPath = path.join(publicDir, "index.html");
|
|
287
|
+
if (fs.existsSync(indexPath) && !url.startsWith("/api/")) {
|
|
288
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
289
|
+
fs.createReadStream(indexPath).pipe(res);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
294
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
295
|
+
}
|
|
296
|
+
// ─── WebSocket Handler ───
|
|
297
|
+
handleWsConnection(ws) {
|
|
298
|
+
const MAX_WS_CLIENTS = 10;
|
|
299
|
+
if (this.clients.size >= MAX_WS_CLIENTS) {
|
|
300
|
+
const oldest = this.clients.values().next().value;
|
|
301
|
+
if (oldest) {
|
|
302
|
+
try {
|
|
303
|
+
oldest.close(1e3, "Too many connections");
|
|
304
|
+
} catch {
|
|
305
|
+
}
|
|
306
|
+
this.clients.delete(oldest);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
this.clients.add(ws);
|
|
310
|
+
console.log(`[WS] Client connected (total: ${this.clients.size})`);
|
|
311
|
+
const status = this.getStatus();
|
|
312
|
+
ws.send(JSON.stringify({ type: "status", data: status }));
|
|
313
|
+
ws.on("message", async (raw) => {
|
|
314
|
+
try {
|
|
315
|
+
const msg = JSON.parse(raw.toString());
|
|
316
|
+
if (msg.type === "command" && msg.data) {
|
|
317
|
+
const { type, payload, target } = msg.data;
|
|
318
|
+
const requestId = msg.requestId;
|
|
319
|
+
const args = { ...payload, _targetInstance: target };
|
|
320
|
+
const result = await this.executeCommand(type, args);
|
|
321
|
+
ws.send(JSON.stringify({ type: "command_result", requestId, data: result }));
|
|
322
|
+
}
|
|
323
|
+
} catch (e) {
|
|
324
|
+
const requestId = (() => {
|
|
325
|
+
try {
|
|
326
|
+
return JSON.parse(raw.toString()).requestId;
|
|
327
|
+
} catch {
|
|
328
|
+
return void 0;
|
|
329
|
+
}
|
|
330
|
+
})();
|
|
331
|
+
ws.send(JSON.stringify({ type: "error", requestId, data: { message: e.message } }));
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
ws.on("close", () => {
|
|
335
|
+
this.clients.delete(ws);
|
|
336
|
+
console.log(`[WS] Client disconnected (total: ${this.clients.size})`);
|
|
337
|
+
});
|
|
338
|
+
ws.on("error", () => {
|
|
339
|
+
this.clients.delete(ws);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
// ─── Core Logic ───
|
|
343
|
+
getStatus() {
|
|
344
|
+
const machineId = os.hostname().replace(/[^a-zA-Z0-9]/g, "_");
|
|
345
|
+
const ides = this.getIdes();
|
|
346
|
+
const clis = this.getClis();
|
|
347
|
+
const cpus2 = os.cpus();
|
|
348
|
+
const totalMem = os.totalmem();
|
|
349
|
+
const freeMem = os.freemem();
|
|
350
|
+
const loadavg2 = os.loadavg();
|
|
351
|
+
return {
|
|
352
|
+
id: `standalone_${machineId}`,
|
|
353
|
+
type: "standalone",
|
|
354
|
+
platform: os.platform(),
|
|
355
|
+
hostname: os.hostname(),
|
|
356
|
+
timestamp: Date.now(),
|
|
357
|
+
ides,
|
|
358
|
+
clis,
|
|
359
|
+
cdpConnected: [...this.cdpManagers.values()].some((m) => m.isConnected),
|
|
360
|
+
detectedIdes: this.detectedIdes.filter((i) => i.installed).map((i) => ({
|
|
361
|
+
id: i.id,
|
|
362
|
+
type: i.id,
|
|
363
|
+
name: i.displayName || i.name,
|
|
364
|
+
installed: true,
|
|
365
|
+
running: this.cdpManagers.has(i.id) && this.cdpManagers.get(i.id).isConnected
|
|
366
|
+
})),
|
|
367
|
+
availableProviders: this.providerLoader.getAll().map((p) => ({
|
|
368
|
+
type: p.type,
|
|
369
|
+
icon: p.icon || "\u{1F4BB}",
|
|
370
|
+
displayName: p.displayName || p.type,
|
|
371
|
+
category: p.category
|
|
372
|
+
})),
|
|
373
|
+
system: {
|
|
374
|
+
cpus: cpus2.length,
|
|
375
|
+
totalMem,
|
|
376
|
+
freeMem,
|
|
377
|
+
loadavg: loadavg2,
|
|
378
|
+
uptime: os.uptime(),
|
|
379
|
+
arch: os.arch()
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
getIdes() {
|
|
384
|
+
const result = [];
|
|
385
|
+
const allStates = this.instanceManager.collectAllStates();
|
|
386
|
+
const ideStates = allStates.filter((s) => s.category === "ide");
|
|
387
|
+
for (const state of ideStates) {
|
|
388
|
+
const cdp = this.cdpManagers.get(state.type);
|
|
389
|
+
result.push({
|
|
390
|
+
id: `standalone:ide:${state.instanceId || state.type}`,
|
|
391
|
+
instanceId: state.instanceId || state.type,
|
|
392
|
+
type: state.type,
|
|
393
|
+
name: state.name || state.type,
|
|
394
|
+
cdpConnected: cdp?.isConnected || false,
|
|
395
|
+
cdpPort: cdp?.getPort?.(),
|
|
396
|
+
chatHistory: state.activeChat?.messages || [],
|
|
397
|
+
activeChat: state.activeChat || null,
|
|
398
|
+
agents: [],
|
|
399
|
+
agentStreams: (state.extensions || []).map((ext) => ({
|
|
400
|
+
agentType: ext.type,
|
|
401
|
+
agentName: ext.name,
|
|
402
|
+
extensionId: ext.type,
|
|
403
|
+
status: ext.status || "idle",
|
|
404
|
+
messages: ext.activeChat?.messages || [],
|
|
405
|
+
inputContent: ext.activeChat?.inputContent || "",
|
|
406
|
+
activeModal: ext.activeChat?.activeModal || null
|
|
407
|
+
})),
|
|
408
|
+
workspaceFolders: state.workspaceFolders || [],
|
|
409
|
+
activeFile: state.activeFile || null,
|
|
410
|
+
currentModel: state.currentModel,
|
|
411
|
+
currentPlan: state.currentPlan
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
const coveredTypes = new Set(ideStates.map((s) => s.type));
|
|
415
|
+
for (const ide of this.detectedIdes) {
|
|
416
|
+
if (!ide.installed || coveredTypes.has(ide.id)) continue;
|
|
417
|
+
const cdp = this.cdpManagers.get(ide.id);
|
|
418
|
+
if (!cdp?.isConnected) continue;
|
|
419
|
+
result.push({
|
|
420
|
+
id: `standalone:ide:${ide.id}`,
|
|
421
|
+
instanceId: ide.id,
|
|
422
|
+
type: ide.id,
|
|
423
|
+
name: ide.displayName || ide.id,
|
|
424
|
+
cdpConnected: true,
|
|
425
|
+
cdpPort: cdp?.getPort?.(),
|
|
426
|
+
chatHistory: [],
|
|
427
|
+
activeChat: null,
|
|
428
|
+
agents: [],
|
|
429
|
+
agentStreams: []
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
return result;
|
|
433
|
+
}
|
|
434
|
+
getClis() {
|
|
435
|
+
const result = [];
|
|
436
|
+
for (const [key, adapter] of this.cliManager.adapters.entries()) {
|
|
437
|
+
result.push({
|
|
438
|
+
id: `standalone:cli:${key}`,
|
|
439
|
+
cliId: key,
|
|
440
|
+
type: adapter.cliType || key,
|
|
441
|
+
isRunning: true
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
return result;
|
|
445
|
+
}
|
|
446
|
+
async executeCommand(type, args) {
|
|
447
|
+
if (!this.commandHandler) {
|
|
448
|
+
return { success: false, error: "Command handler not initialized" };
|
|
449
|
+
}
|
|
450
|
+
switch (type) {
|
|
451
|
+
case "launch_ide": {
|
|
452
|
+
const result2 = await (0, import_daemon_core.launchWithCdp)(args);
|
|
453
|
+
if (result2.success && result2.port && result2.ideId && !this.cdpManagers.has(result2.ideId)) {
|
|
454
|
+
const manager = new import_daemon_core.DaemonCdpManager(result2.port, (msg) => {
|
|
455
|
+
console.log(`[CDP:${result2.ideId}] ${msg}`);
|
|
456
|
+
});
|
|
457
|
+
const connected = await manager.connect();
|
|
458
|
+
if (connected) {
|
|
459
|
+
this.cdpManagers.set(result2.ideId, manager);
|
|
460
|
+
console.log(`[CDP] Connected: ${result2.ideId} (port ${result2.port})`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return { success: result2.success, ...result2 };
|
|
464
|
+
}
|
|
465
|
+
case "detect_ides": {
|
|
466
|
+
this.detectedIdes = await (0, import_daemon_core.detectIDEs)();
|
|
467
|
+
return { success: true, ides: this.detectedIdes };
|
|
468
|
+
}
|
|
469
|
+
case "launch_cli":
|
|
470
|
+
case "stop_cli": {
|
|
471
|
+
return this.cliManager.handleCliCommand(type, args);
|
|
472
|
+
}
|
|
473
|
+
case "get_logs": {
|
|
474
|
+
const count = args?.count || 100;
|
|
475
|
+
const minLevel = args?.minLevel || "info";
|
|
476
|
+
const sinceTs = args?.since || 0;
|
|
477
|
+
let logs = (0, import_daemon_core.getRecentLogs)(count, minLevel);
|
|
478
|
+
if (sinceTs > 0) {
|
|
479
|
+
logs = logs.filter((l) => l.ts > sinceTs);
|
|
480
|
+
}
|
|
481
|
+
return { success: true, logs };
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const result = await this.commandHandler.handle(type, args);
|
|
485
|
+
(0, import_daemon_core.logCommand)({ ts: (/* @__PURE__ */ new Date()).toISOString(), cmd: type, source: "standalone", args, success: result.success, durationMs: 0 });
|
|
486
|
+
return result;
|
|
487
|
+
}
|
|
488
|
+
broadcastStatus() {
|
|
489
|
+
if (this.clients.size === 0) return;
|
|
490
|
+
const status = this.getStatus();
|
|
491
|
+
const msg = JSON.stringify({ type: "status", data: status });
|
|
492
|
+
const cdpCount = [...this.cdpManagers.values()].filter((m) => m.isConnected).length;
|
|
493
|
+
import_daemon_core.LOG.debug("Broadcast", `status \u2192 ${this.clients.size} client(s), ${status.ides?.length || 0} IDE(s), ${cdpCount} CDP`);
|
|
494
|
+
for (const client of this.clients) {
|
|
495
|
+
if (client.readyState === import_ws.WebSocket.OPEN) {
|
|
496
|
+
client.send(msg);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
// ─── CDP Init ───
|
|
501
|
+
async initCdp() {
|
|
502
|
+
const cdpPortMap = this.providerLoader.getCdpPortMap?.() || {};
|
|
503
|
+
for (const ide of this.detectedIdes) {
|
|
504
|
+
if (!ide.installed) continue;
|
|
505
|
+
const candidates = [];
|
|
506
|
+
if (ide.cdpPort) {
|
|
507
|
+
candidates.push(ide.cdpPort);
|
|
508
|
+
}
|
|
509
|
+
const providerPorts = cdpPortMap[ide.id];
|
|
510
|
+
if (providerPorts) {
|
|
511
|
+
for (let p = providerPorts[0]; p <= providerPorts[1]; p++) {
|
|
512
|
+
if (!candidates.includes(p)) candidates.push(p);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (candidates.length === 0) continue;
|
|
516
|
+
for (const port of candidates) {
|
|
517
|
+
try {
|
|
518
|
+
const probe = await fetch(`http://localhost:${port}/json/version`, {
|
|
519
|
+
signal: AbortSignal.timeout(1e3)
|
|
520
|
+
}).then((r) => r.json()).catch(() => null);
|
|
521
|
+
if (!probe) continue;
|
|
522
|
+
const manager = new import_daemon_core.DaemonCdpManager(port, (msg) => {
|
|
523
|
+
console.log(`[CDP:${ide.id}] ${msg}`);
|
|
524
|
+
});
|
|
525
|
+
const connected = await manager.connect();
|
|
526
|
+
if (connected) {
|
|
527
|
+
const enabledExtProviders = this.providerLoader.getEnabledExtensionProviders(ide.id).map((p) => ({
|
|
528
|
+
agentType: p.type,
|
|
529
|
+
extensionId: p.extensionId || "",
|
|
530
|
+
extensionIdPattern: p.extensionIdPattern
|
|
531
|
+
}));
|
|
532
|
+
manager.setExtensionProviders(enabledExtProviders);
|
|
533
|
+
this.cdpManagers.set(ide.id, manager);
|
|
534
|
+
console.log(` \u2705 CDP connected: ${ide.id} (port ${port})`);
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
} catch {
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
setInterval(async () => {
|
|
542
|
+
const portMap = this.providerLoader.getCdpPortMap?.() || {};
|
|
543
|
+
for (const [ide, ports] of Object.entries(portMap)) {
|
|
544
|
+
const alreadyConnected = [...this.cdpManagers.entries()].some(
|
|
545
|
+
([key, m]) => m.isConnected && (key === ide || key.startsWith(ide + "_"))
|
|
546
|
+
);
|
|
547
|
+
if (alreadyConnected) continue;
|
|
548
|
+
const primaryPort = ports[0];
|
|
549
|
+
try {
|
|
550
|
+
const probe = await fetch(`http://localhost:${primaryPort}/json/version`, {
|
|
551
|
+
signal: AbortSignal.timeout(1e3)
|
|
552
|
+
}).then((r) => r.json()).catch(() => null);
|
|
553
|
+
if (!probe) continue;
|
|
554
|
+
const manager = new import_daemon_core.DaemonCdpManager(primaryPort, (msg) => {
|
|
555
|
+
console.log(`[CDP:${ide}] ${msg}`);
|
|
556
|
+
});
|
|
557
|
+
const connected = await manager.connect();
|
|
558
|
+
if (connected) {
|
|
559
|
+
const enabledExtProviders = this.providerLoader.getEnabledExtensionProviders(ide).map((p) => ({
|
|
560
|
+
agentType: p.type,
|
|
561
|
+
extensionId: p.extensionId || "",
|
|
562
|
+
extensionIdPattern: p.extensionIdPattern
|
|
563
|
+
}));
|
|
564
|
+
manager.setExtensionProviders(enabledExtProviders);
|
|
565
|
+
this.cdpManagers.set(ide, manager);
|
|
566
|
+
console.log(`[CDP-Scan] \u2705 Auto-connected: ${ide} (port ${primaryPort})`);
|
|
567
|
+
const provider = this.providerLoader.resolve(ide);
|
|
568
|
+
if (provider) {
|
|
569
|
+
const instance = new import_daemon_core.IdeProviderInstance(provider, ide);
|
|
570
|
+
await this.instanceManager.addInstance(ide, instance, {
|
|
571
|
+
cdp: manager,
|
|
572
|
+
serverConn: void 0,
|
|
573
|
+
settings: {}
|
|
574
|
+
});
|
|
575
|
+
this.instanceIdMap.set(instance.getInstanceId(), ide);
|
|
576
|
+
}
|
|
577
|
+
this.broadcastStatus();
|
|
578
|
+
}
|
|
579
|
+
} catch {
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}, 15e3);
|
|
583
|
+
}
|
|
584
|
+
// ─── Network ───
|
|
585
|
+
getLanIPs() {
|
|
586
|
+
const interfaces = os.networkInterfaces();
|
|
587
|
+
const ips = [];
|
|
588
|
+
for (const iface of Object.values(interfaces)) {
|
|
589
|
+
if (!iface) continue;
|
|
590
|
+
for (const info of iface) {
|
|
591
|
+
if (info.family === "IPv4" && !info.internal) {
|
|
592
|
+
ips.push(info.address);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return ips;
|
|
597
|
+
}
|
|
598
|
+
// ─── Lifecycle ───
|
|
599
|
+
async stop() {
|
|
600
|
+
if (!this.running) return;
|
|
601
|
+
this.running = false;
|
|
602
|
+
console.log("\n Shutting down...");
|
|
603
|
+
if (this.statusTimer) {
|
|
604
|
+
clearInterval(this.statusTimer);
|
|
605
|
+
this.statusTimer = null;
|
|
606
|
+
}
|
|
607
|
+
for (const ws of this.clients) {
|
|
608
|
+
try {
|
|
609
|
+
ws.close();
|
|
610
|
+
} catch {
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
this.clients.clear();
|
|
614
|
+
if (this.wss) {
|
|
615
|
+
this.wss.close();
|
|
616
|
+
this.wss = null;
|
|
617
|
+
}
|
|
618
|
+
for (const m of this.cdpManagers.values()) {
|
|
619
|
+
try {
|
|
620
|
+
m.disconnect();
|
|
621
|
+
} catch {
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
this.cdpManagers.clear();
|
|
625
|
+
try {
|
|
626
|
+
await this.cliManager.shutdownAll();
|
|
627
|
+
} catch {
|
|
628
|
+
}
|
|
629
|
+
if (this.httpServer) {
|
|
630
|
+
this.httpServer.close();
|
|
631
|
+
this.httpServer = null;
|
|
632
|
+
}
|
|
633
|
+
console.log(" \u2713 ADHDev Standalone stopped.\n");
|
|
634
|
+
process.exit(0);
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
async function main() {
|
|
638
|
+
const args = process.argv.slice(2);
|
|
639
|
+
const options = {};
|
|
640
|
+
for (let i = 0; i < args.length; i++) {
|
|
641
|
+
if ((args[i] === "--port" || args[i] === "-p") && args[i + 1]) {
|
|
642
|
+
options.port = parseInt(args[i + 1]);
|
|
643
|
+
i++;
|
|
644
|
+
}
|
|
645
|
+
if (args[i] === "--host" || args[i] === "-H") {
|
|
646
|
+
options.host = "0.0.0.0";
|
|
647
|
+
}
|
|
648
|
+
if (args[i] === "--public" && args[i + 1]) {
|
|
649
|
+
options.publicDir = args[i + 1];
|
|
650
|
+
i++;
|
|
651
|
+
}
|
|
652
|
+
if (args[i] === "--no-open") {
|
|
653
|
+
options.open = false;
|
|
654
|
+
}
|
|
655
|
+
if (args[i] === "--dev") {
|
|
656
|
+
options.dev = true;
|
|
657
|
+
}
|
|
658
|
+
if (args[i] === "--token" && args[i + 1]) {
|
|
659
|
+
options.token = args[i + 1];
|
|
660
|
+
i++;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (!options.publicDir) {
|
|
664
|
+
const candidates = [
|
|
665
|
+
path.join(__dirname, "../../web-standalone/dist"),
|
|
666
|
+
path.join(__dirname, "../public"),
|
|
667
|
+
path.join(process.cwd(), "public")
|
|
668
|
+
];
|
|
669
|
+
for (const candidate of candidates) {
|
|
670
|
+
if (fs.existsSync(path.join(candidate, "index.html"))) {
|
|
671
|
+
options.publicDir = candidate;
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
const server = new StandaloneServer();
|
|
677
|
+
await server.start(options);
|
|
678
|
+
await new Promise(() => {
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
main().catch((e) => {
|
|
682
|
+
console.error("Fatal error:", e);
|
|
683
|
+
process.exit(1);
|
|
684
|
+
});
|
|
685
|
+
//# sourceMappingURL=index.js.map
|