adhdev 0.1.22 → 0.1.24

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 (2) hide show
  1. package/dist/index.js +491 -191
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -74,10 +74,10 @@ function getIdeVersion(cliCommand) {
74
74
  function checkPathExists(paths) {
75
75
  for (const p of paths) {
76
76
  if (p.includes("*")) {
77
- const home2 = (0, import_os.homedir)();
77
+ const home = (0, import_os.homedir)();
78
78
  const starIdx = p.indexOf("*");
79
79
  const suffix = p.substring(starIdx + 1);
80
- const homeNormalized = home2.replace(/\//g, "\\\\");
80
+ const homeNormalized = home.replace(/\//g, "\\\\");
81
81
  const resolved = homeNormalized + suffix;
82
82
  if ((0, import_fs.existsSync)(resolved)) return resolved;
83
83
  } else {
@@ -325,11 +325,62 @@ var init_config = __esm({
325
325
  userName: null,
326
326
  setupCompleted: false,
327
327
  setupDate: null,
328
- configuredCLIs: []
328
+ configuredCLIs: [],
329
+ enabledIdes: [],
330
+ recentCliWorkspaces: []
329
331
  };
330
332
  }
331
333
  });
332
334
 
335
+ // src/cli-detector.ts
336
+ async function detectCLIs() {
337
+ const platform9 = os.platform();
338
+ const whichCmd = platform9 === "win32" ? "where" : "which";
339
+ const results = [];
340
+ for (const cli of KNOWN_CLIS) {
341
+ try {
342
+ const pathResult = (0, import_child_process3.execSync)(`${whichCmd} ${cli.command} 2>/dev/null`, {
343
+ encoding: "utf-8",
344
+ timeout: 5e3,
345
+ stdio: ["pipe", "pipe", "pipe"]
346
+ }).trim().split("\n")[0];
347
+ if (!pathResult) throw new Error("Not found");
348
+ let version;
349
+ try {
350
+ const versionResult = (0, import_child_process3.execSync)(`${cli.command} --version 2>/dev/null`, {
351
+ encoding: "utf-8",
352
+ timeout: 5e3,
353
+ stdio: ["pipe", "pipe", "pipe"]
354
+ }).trim();
355
+ const match = versionResult.match(/(\d+\.\d+[\.\d]*)/);
356
+ version = match ? match[1] : versionResult.split("\n")[0].slice(0, 30);
357
+ } catch {
358
+ }
359
+ results.push({ ...cli, installed: true, version, path: pathResult });
360
+ } catch {
361
+ results.push({ ...cli, installed: false });
362
+ }
363
+ }
364
+ return results;
365
+ }
366
+ async function detectCLI(cliId) {
367
+ const all = await detectCLIs();
368
+ return all.find((c) => c.id === cliId && c.installed) || null;
369
+ }
370
+ var import_child_process3, os, KNOWN_CLIS;
371
+ var init_cli_detector = __esm({
372
+ "src/cli-detector.ts"() {
373
+ "use strict";
374
+ import_child_process3 = require("child_process");
375
+ os = __toESM(require("os"));
376
+ KNOWN_CLIS = [
377
+ { id: "gemini-cli", displayName: "Gemini CLI", icon: "\u264A", command: "gemini" },
378
+ { id: "claude-code", displayName: "Claude Code", icon: "\u{1F916}", command: "claude" },
379
+ { id: "codex-cli", displayName: "Codex CLI", icon: "\u{1F9E0}", command: "codex" }
380
+ ];
381
+ }
382
+ });
383
+
333
384
  // src/launch.ts
334
385
  async function findFreePort(ports) {
335
386
  for (const port2 of ports) {
@@ -377,38 +428,38 @@ async function isCdpActive(port) {
377
428
  });
378
429
  }
379
430
  async function killIdeProcess(ideId) {
380
- const plat = os.platform();
431
+ const plat = os2.platform();
381
432
  const appName = MAC_APP_IDENTIFIERS[ideId];
382
433
  const winProcesses = WIN_PROCESS_NAMES[ideId];
383
434
  try {
384
435
  if (plat === "darwin" && appName) {
385
436
  try {
386
- (0, import_child_process3.execSync)(`osascript -e 'tell application "${appName}" to quit' 2>/dev/null`, {
437
+ (0, import_child_process4.execSync)(`osascript -e 'tell application "${appName}" to quit' 2>/dev/null`, {
387
438
  timeout: 5e3
388
439
  });
389
440
  } catch {
390
441
  try {
391
- (0, import_child_process3.execSync)(`pkill -f "${appName}" 2>/dev/null`);
442
+ (0, import_child_process4.execSync)(`pkill -f "${appName}" 2>/dev/null`);
392
443
  } catch {
393
444
  }
394
445
  }
395
446
  } else if (plat === "win32" && winProcesses) {
396
447
  for (const proc of winProcesses) {
397
448
  try {
398
- (0, import_child_process3.execSync)(`taskkill /IM "${proc}" /F 2>nul`, { timeout: 5e3 });
449
+ (0, import_child_process4.execSync)(`taskkill /IM "${proc}" /F 2>nul`, { timeout: 5e3 });
399
450
  } catch {
400
451
  }
401
452
  }
402
453
  try {
403
454
  const exeName = winProcesses[0].replace(".exe", "");
404
- (0, import_child_process3.execSync)(`powershell -Command "Get-Process -Name '${exeName}' -ErrorAction SilentlyContinue | Stop-Process -Force"`, {
455
+ (0, import_child_process4.execSync)(`powershell -Command "Get-Process -Name '${exeName}' -ErrorAction SilentlyContinue | Stop-Process -Force"`, {
405
456
  timeout: 1e4
406
457
  });
407
458
  } catch {
408
459
  }
409
460
  } else {
410
461
  try {
411
- (0, import_child_process3.execSync)(`pkill -f "${ideId}" 2>/dev/null`);
462
+ (0, import_child_process4.execSync)(`pkill -f "${ideId}" 2>/dev/null`);
412
463
  } catch {
413
464
  }
414
465
  }
@@ -418,13 +469,13 @@ async function killIdeProcess(ideId) {
418
469
  }
419
470
  if (plat === "darwin" && appName) {
420
471
  try {
421
- (0, import_child_process3.execSync)(`pkill -9 -f "${appName}" 2>/dev/null`);
472
+ (0, import_child_process4.execSync)(`pkill -9 -f "${appName}" 2>/dev/null`);
422
473
  } catch {
423
474
  }
424
475
  } else if (plat === "win32" && winProcesses) {
425
476
  for (const proc of winProcesses) {
426
477
  try {
427
- (0, import_child_process3.execSync)(`taskkill /IM "${proc}" /F 2>nul`);
478
+ (0, import_child_process4.execSync)(`taskkill /IM "${proc}" /F 2>nul`);
428
479
  } catch {
429
480
  }
430
481
  }
@@ -436,26 +487,26 @@ async function killIdeProcess(ideId) {
436
487
  }
437
488
  }
438
489
  function isIdeRunning(ideId) {
439
- const plat = os.platform();
490
+ const plat = os2.platform();
440
491
  try {
441
492
  if (plat === "darwin") {
442
493
  const appName = MAC_APP_IDENTIFIERS[ideId];
443
494
  if (!appName) return false;
444
- const result = (0, import_child_process3.execSync)(`pgrep -f "${appName}" 2>/dev/null`, { encoding: "utf-8" });
495
+ const result = (0, import_child_process4.execSync)(`pgrep -f "${appName}" 2>/dev/null`, { encoding: "utf-8" });
445
496
  return result.trim().length > 0;
446
497
  } else if (plat === "win32") {
447
498
  const winProcesses = WIN_PROCESS_NAMES[ideId];
448
499
  if (!winProcesses) return false;
449
500
  for (const proc of winProcesses) {
450
501
  try {
451
- const result = (0, import_child_process3.execSync)(`tasklist /FI "IMAGENAME eq ${proc}" /NH 2>nul`, { encoding: "utf-8" });
502
+ const result = (0, import_child_process4.execSync)(`tasklist /FI "IMAGENAME eq ${proc}" /NH 2>nul`, { encoding: "utf-8" });
452
503
  if (result.includes(proc)) return true;
453
504
  } catch {
454
505
  }
455
506
  }
456
507
  try {
457
508
  const exeName = winProcesses[0].replace(".exe", "");
458
- const result = (0, import_child_process3.execSync)(
509
+ const result = (0, import_child_process4.execSync)(
459
510
  `powershell -Command "(Get-Process -Name '${exeName}' -ErrorAction SilentlyContinue).Count"`,
460
511
  { encoding: "utf-8", timeout: 5e3 }
461
512
  );
@@ -464,7 +515,7 @@ function isIdeRunning(ideId) {
464
515
  }
465
516
  return false;
466
517
  } else {
467
- const result = (0, import_child_process3.execSync)(`pgrep -f "${ideId}" 2>/dev/null`, { encoding: "utf-8" });
518
+ const result = (0, import_child_process4.execSync)(`pgrep -f "${ideId}" 2>/dev/null`, { encoding: "utf-8" });
468
519
  return result.trim().length > 0;
469
520
  }
470
521
  } catch {
@@ -472,12 +523,12 @@ function isIdeRunning(ideId) {
472
523
  }
473
524
  }
474
525
  function detectCurrentWorkspace(ideId) {
475
- const plat = os.platform();
526
+ const plat = os2.platform();
476
527
  if (plat === "darwin") {
477
528
  try {
478
529
  const appName = MAC_APP_IDENTIFIERS[ideId];
479
530
  if (!appName) return void 0;
480
- const result = (0, import_child_process3.execSync)(
531
+ const result = (0, import_child_process4.execSync)(
481
532
  `lsof -c "${appName}" 2>/dev/null | grep cwd | head -1 | awk '{print $NF}'`,
482
533
  { encoding: "utf-8", timeout: 3e3 }
483
534
  );
@@ -498,7 +549,7 @@ function detectCurrentWorkspace(ideId) {
498
549
  const appName = appNameMap[ideId];
499
550
  if (appName) {
500
551
  const storagePath = path.join(
501
- process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"),
552
+ process.env.APPDATA || path.join(os2.homedir(), "AppData", "Roaming"),
502
553
  appName,
503
554
  "storage.json"
504
555
  );
@@ -520,7 +571,7 @@ function detectCurrentWorkspace(ideId) {
520
571
  return void 0;
521
572
  }
522
573
  async function launchWithCdp(options = {}) {
523
- const platform9 = os.platform();
574
+ const platform9 = os2.platform();
524
575
  let targetIde;
525
576
  const ides = await detectIDEs();
526
577
  if (options.ideId) {
@@ -630,9 +681,9 @@ async function launchMacOS(ide, port, workspace, newWindow) {
630
681
  if (workspace) args.push(workspace);
631
682
  if (appName) {
632
683
  const openArgs = ["-a", appName, "--args", ...args];
633
- (0, import_child_process3.spawn)("open", openArgs, { detached: true, stdio: "ignore" }).unref();
684
+ (0, import_child_process4.spawn)("open", openArgs, { detached: true, stdio: "ignore" }).unref();
634
685
  } else if (ide.cliCommand) {
635
- (0, import_child_process3.spawn)(ide.cliCommand, args, { detached: true, stdio: "ignore" }).unref();
686
+ (0, import_child_process4.spawn)(ide.cliCommand, args, { detached: true, stdio: "ignore" }).unref();
636
687
  } else {
637
688
  throw new Error(`No app identifier or CLI for ${ide.displayName}`);
638
689
  }
@@ -658,15 +709,15 @@ async function launchLinux(ide, port, workspace, newWindow) {
658
709
  const args = ["--remote-debugging-port=" + port];
659
710
  if (newWindow) args.push("--new-window");
660
711
  if (workspace) args.push(workspace);
661
- (0, import_child_process3.spawn)(cli, args, { detached: true, stdio: "ignore" }).unref();
712
+ (0, import_child_process4.spawn)(cli, args, { detached: true, stdio: "ignore" }).unref();
662
713
  }
663
- var import_child_process3, net, os, path, CDP_PORTS, MAC_APP_IDENTIFIERS, WIN_PROCESS_NAMES;
714
+ var import_child_process4, net, os2, path, CDP_PORTS, MAC_APP_IDENTIFIERS, WIN_PROCESS_NAMES;
664
715
  var init_launch = __esm({
665
716
  "src/launch.ts"() {
666
717
  "use strict";
667
- import_child_process3 = require("child_process");
718
+ import_child_process4 = require("child_process");
668
719
  net = __toESM(require("net"));
669
- os = __toESM(require("os"));
720
+ os2 = __toESM(require("os"));
670
721
  path = __toESM(require("path"));
671
722
  init_detector();
672
723
  init_config();
@@ -821,7 +872,7 @@ var init_cli_bridge = __esm({
821
872
  const handlers = this.messageHandlers.get(message.type);
822
873
  if (handlers) {
823
874
  handlers.forEach((h) => h(message));
824
- } else if (message.type !== "auth_ok" && message.type !== "auth_error") {
875
+ } else if (message.type !== "auth_ok") {
825
876
  console.log(`[CliBridge] Unhandled message type: ${message.type}`);
826
877
  }
827
878
  } catch (error) {
@@ -883,21 +934,21 @@ function stripAnsi(str) {
883
934
  return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "").replace(/\x1B\][^\x07]*\x07/g, "").replace(/\x1B\][^\x1B]*\x1B\\/g, "");
884
935
  }
885
936
  function findGeminiBinary() {
886
- const isWin = os2.platform() === "win32";
937
+ const isWin = os3.platform() === "win32";
887
938
  const cmd = isWin ? "where gemini" : "which gemini";
888
939
  try {
889
- const result = (0, import_child_process4.execSync)(cmd, { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
940
+ const result = (0, import_child_process5.execSync)(cmd, { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
890
941
  return result.split("\n")[0].trim();
891
942
  } catch {
892
943
  return isWin ? "gemini.cmd" : "gemini";
893
944
  }
894
945
  }
895
- var os2, import_child_process4, pty, PROMPT_PATTERNS, STARTUP_DIALOG_PATTERNS, GeminiCliAdapter;
946
+ var os3, import_child_process5, pty, PROMPT_PATTERNS, STARTUP_DIALOG_PATTERNS, GeminiCliAdapter;
896
947
  var init_gemini_cli = __esm({
897
948
  "src/cli-adapters/gemini-cli.ts"() {
898
949
  "use strict";
899
- os2 = __toESM(require("os"));
900
- import_child_process4 = require("child_process");
950
+ os3 = __toESM(require("os"));
951
+ import_child_process5 = require("child_process");
901
952
  try {
902
953
  pty = require("node-pty");
903
954
  } catch {
@@ -1149,21 +1200,21 @@ function stripAnsi2(str) {
1149
1200
  return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "").replace(/\x1B\][^\x07]*\x07/g, "").replace(/\x1B\][^\x1B]*\x1B\\/g, "");
1150
1201
  }
1151
1202
  function findClaudeBinary() {
1152
- const isWin = os3.platform() === "win32";
1203
+ const isWin = os4.platform() === "win32";
1153
1204
  const cmd = isWin ? "where claude" : "which claude";
1154
1205
  try {
1155
- const result = (0, import_child_process5.execSync)(cmd, { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
1206
+ const result = (0, import_child_process6.execSync)(cmd, { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
1156
1207
  return result.split("\n")[0].trim();
1157
1208
  } catch {
1158
1209
  return isWin ? "claude.cmd" : "claude";
1159
1210
  }
1160
1211
  }
1161
- var os3, import_child_process5, pty2, ClaudeCliAdapter;
1212
+ var os4, import_child_process6, pty2, ClaudeCliAdapter;
1162
1213
  var init_claude_cli = __esm({
1163
1214
  "src/cli-adapters/claude-cli.ts"() {
1164
1215
  "use strict";
1165
- os3 = __toESM(require("os"));
1166
- import_child_process5 = require("child_process");
1216
+ os4 = __toESM(require("os"));
1217
+ import_child_process6 = require("child_process");
1167
1218
  try {
1168
1219
  pty2 = require("node-pty");
1169
1220
  } catch {
@@ -1361,21 +1412,21 @@ function stripAnsi3(str) {
1361
1412
  return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "").replace(/\x1B\][^\x07]*\x07/g, "").replace(/\x1B\][^\x1B]*\x1B\\/g, "");
1362
1413
  }
1363
1414
  function findCodexBinary() {
1364
- const isWin = os4.platform() === "win32";
1415
+ const isWin = os5.platform() === "win32";
1365
1416
  const cmd = isWin ? "where codex" : "which codex";
1366
1417
  try {
1367
- const result = (0, import_child_process6.execSync)(cmd, { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
1418
+ const result = (0, import_child_process7.execSync)(cmd, { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
1368
1419
  return result.split("\n")[0].trim();
1369
1420
  } catch {
1370
1421
  return isWin ? "codex.cmd" : "codex";
1371
1422
  }
1372
1423
  }
1373
- var os4, import_child_process6, pty3, CodexCliAdapter;
1424
+ var os5, import_child_process7, pty3, CodexCliAdapter;
1374
1425
  var init_codex_cli = __esm({
1375
1426
  "src/cli-adapters/codex-cli.ts"() {
1376
1427
  "use strict";
1377
- os4 = __toESM(require("os"));
1378
- import_child_process6 = require("child_process");
1428
+ os5 = __toESM(require("os"));
1429
+ import_child_process7 = require("child_process");
1379
1430
  try {
1380
1431
  pty3 = require("node-pty");
1381
1432
  } catch {
@@ -1716,6 +1767,14 @@ var init_local_server = __esm({
1716
1767
  }
1717
1768
  break;
1718
1769
  }
1770
+ case "ext:command": {
1771
+ const ext = this.extensions.get(ws);
1772
+ if (ext) {
1773
+ ext.lastSeen = Date.now();
1774
+ this.options.onCommand?.(ext, msg.payload.command, msg.payload.args, msg.id);
1775
+ }
1776
+ break;
1777
+ }
1719
1778
  }
1720
1779
  }
1721
1780
  /**
@@ -1828,13 +1887,16 @@ var init_local_server = __esm({
1828
1887
  /**
1829
1888
  * 가장 최근 Extension 상태 데이터 (통합)
1830
1889
  */
1831
- getLatestExtensionData() {
1890
+ getLatestExtensionData(enabledIdes) {
1832
1891
  let activeFile = null;
1833
1892
  const allWorkspaceFolders = [];
1834
1893
  let totalTerminals = 0;
1835
1894
  const allAgents = [];
1836
1895
  const connectedIdes = [];
1837
1896
  for (const ext of this.extensions.values()) {
1897
+ if (enabledIdes && enabledIdes.length > 0 && !enabledIdes.includes(ext.ideType.toLowerCase())) {
1898
+ continue;
1899
+ }
1838
1900
  connectedIdes.push({ ideType: ext.ideType, instanceId: ext.instanceId });
1839
1901
  if (ext.lastStatus) {
1840
1902
  if (!activeFile && ext.lastStatus.activeFile) {
@@ -1857,55 +1919,6 @@ var init_local_server = __esm({
1857
1919
  }
1858
1920
  });
1859
1921
 
1860
- // src/cli-detector.ts
1861
- async function detectCLIs() {
1862
- const platform9 = os5.platform();
1863
- const whichCmd = platform9 === "win32" ? "where" : "which";
1864
- const results = [];
1865
- for (const cli of KNOWN_CLIS) {
1866
- try {
1867
- const pathResult = (0, import_child_process7.execSync)(`${whichCmd} ${cli.command} 2>/dev/null`, {
1868
- encoding: "utf-8",
1869
- timeout: 5e3,
1870
- stdio: ["pipe", "pipe", "pipe"]
1871
- }).trim().split("\n")[0];
1872
- if (!pathResult) throw new Error("Not found");
1873
- let version;
1874
- try {
1875
- const versionResult = (0, import_child_process7.execSync)(`${cli.command} --version 2>/dev/null`, {
1876
- encoding: "utf-8",
1877
- timeout: 5e3,
1878
- stdio: ["pipe", "pipe", "pipe"]
1879
- }).trim();
1880
- const match = versionResult.match(/(\d+\.\d+[\.\d]*)/);
1881
- version = match ? match[1] : versionResult.split("\n")[0].slice(0, 30);
1882
- } catch {
1883
- }
1884
- results.push({ ...cli, installed: true, version, path: pathResult });
1885
- } catch {
1886
- results.push({ ...cli, installed: false });
1887
- }
1888
- }
1889
- return results;
1890
- }
1891
- async function detectCLI(cliId) {
1892
- const all = await detectCLIs();
1893
- return all.find((c) => c.id === cliId && c.installed) || null;
1894
- }
1895
- var import_child_process7, os5, KNOWN_CLIS;
1896
- var init_cli_detector = __esm({
1897
- "src/cli-detector.ts"() {
1898
- "use strict";
1899
- import_child_process7 = require("child_process");
1900
- os5 = __toESM(require("os"));
1901
- KNOWN_CLIS = [
1902
- { id: "gemini-cli", displayName: "Gemini CLI", icon: "\u264A", command: "gemini" },
1903
- { id: "claude-code", displayName: "Claude Code", icon: "\u{1F916}", command: "claude" },
1904
- { id: "codex-cli", displayName: "Codex CLI", icon: "\u{1F9E0}", command: "codex" }
1905
- ];
1906
- }
1907
- });
1908
-
1909
1922
  // src/daemon-cdp.ts
1910
1923
  var import_ws3, http, fs, KNOWN_AGENTS, DaemonCdpManager;
1911
1924
  var init_daemon_cdp = __esm({
@@ -1921,10 +1934,15 @@ var init_daemon_cdp = __esm({
1921
1934
  ];
1922
1935
  DaemonCdpManager = class {
1923
1936
  ws = null;
1937
+ browserWs = null;
1938
+ // browser-level WS for Target discovery
1939
+ browserMsgId = 1e4;
1940
+ browserPending = /* @__PURE__ */ new Map();
1924
1941
  msgId = 1;
1925
1942
  pending = /* @__PURE__ */ new Map();
1926
1943
  port;
1927
1944
  _connected = false;
1945
+ _browserConnected = false;
1928
1946
  targetUrl = "";
1929
1947
  reconnectTimer = null;
1930
1948
  contexts = /* @__PURE__ */ new Set();
@@ -2036,6 +2054,8 @@ var init_daemon_cdp = __esm({
2036
2054
  await this.sendInternal("Runtime.enable");
2037
2055
  } catch {
2038
2056
  }
2057
+ this.connectBrowserWs().catch(() => {
2058
+ });
2039
2059
  resolve3(true);
2040
2060
  });
2041
2061
  this.ws.on("message", (data) => {
@@ -2060,6 +2080,9 @@ var init_daemon_cdp = __esm({
2060
2080
  this.ws.on("close", () => {
2061
2081
  this.log("[CDP] WebSocket closed \u2014 scheduling reconnect");
2062
2082
  this._connected = false;
2083
+ this._browserConnected = false;
2084
+ this.browserWs?.close();
2085
+ this.browserWs = null;
2063
2086
  this.connectPromise = null;
2064
2087
  this.scheduleReconnect();
2065
2088
  });
@@ -2070,6 +2093,89 @@ var init_daemon_cdp = __esm({
2070
2093
  });
2071
2094
  });
2072
2095
  }
2096
+ /** Browser-level CDP 연결 — Target discovery에 필요 */
2097
+ async connectBrowserWs() {
2098
+ if (this._browserConnected && this.browserWs?.readyState === import_ws3.default.OPEN) return;
2099
+ try {
2100
+ const browserWsUrl = await this.getBrowserWsUrl();
2101
+ if (!browserWsUrl) {
2102
+ this.log("[CDP] No browser WS URL found");
2103
+ return;
2104
+ }
2105
+ this.log(`[CDP] Connecting browser WS for target discovery...`);
2106
+ await new Promise((resolve3, reject) => {
2107
+ this.browserWs = new import_ws3.default(browserWsUrl);
2108
+ this.browserWs.on("open", async () => {
2109
+ this._browserConnected = true;
2110
+ this.log("[CDP] \u2705 Browser WS connected \u2014 enabling target discovery");
2111
+ try {
2112
+ await this.sendBrowser("Target.setDiscoverTargets", { discover: true });
2113
+ } catch (e) {
2114
+ this.log(`[CDP] setDiscoverTargets failed: ${e.message}`);
2115
+ }
2116
+ resolve3();
2117
+ });
2118
+ this.browserWs.on("message", (data) => {
2119
+ try {
2120
+ const msg = JSON.parse(data.toString());
2121
+ if (msg.id && this.browserPending.has(msg.id)) {
2122
+ const { resolve: resolve4, reject: reject2 } = this.browserPending.get(msg.id);
2123
+ this.browserPending.delete(msg.id);
2124
+ if (msg.error) reject2(new Error(msg.error.message));
2125
+ else resolve4(msg.result);
2126
+ }
2127
+ } catch {
2128
+ }
2129
+ });
2130
+ this.browserWs.on("close", () => {
2131
+ this._browserConnected = false;
2132
+ this.browserWs = null;
2133
+ });
2134
+ this.browserWs.on("error", (err) => {
2135
+ this.log(`[CDP] Browser WS error: ${err.message}`);
2136
+ this._browserConnected = false;
2137
+ reject(err);
2138
+ });
2139
+ });
2140
+ } catch (e) {
2141
+ this.log(`[CDP] Browser WS connect failed: ${e.message}`);
2142
+ }
2143
+ }
2144
+ getBrowserWsUrl() {
2145
+ return new Promise((resolve3) => {
2146
+ const req = http.get(`http://127.0.0.1:${this.port}/json/version`, (res) => {
2147
+ let data = "";
2148
+ res.on("data", (chunk) => data += chunk.toString());
2149
+ res.on("end", () => {
2150
+ try {
2151
+ const info = JSON.parse(data);
2152
+ resolve3(info.webSocketDebuggerUrl || null);
2153
+ } catch {
2154
+ resolve3(null);
2155
+ }
2156
+ });
2157
+ });
2158
+ req.on("error", () => resolve3(null));
2159
+ req.setTimeout(3e3, () => {
2160
+ req.destroy();
2161
+ resolve3(null);
2162
+ });
2163
+ });
2164
+ }
2165
+ sendBrowser(method, params = {}, timeoutMs = 15e3) {
2166
+ return new Promise((resolve3, reject) => {
2167
+ if (!this.browserWs || !this._browserConnected) return reject(new Error("Browser WS not connected"));
2168
+ const id = this.browserMsgId++;
2169
+ this.browserPending.set(id, { resolve: resolve3, reject });
2170
+ this.browserWs.send(JSON.stringify({ id, method, params }));
2171
+ setTimeout(() => {
2172
+ if (this.browserPending.has(id)) {
2173
+ this.browserPending.delete(id);
2174
+ reject(new Error(`Browser CDP timeout: ${method}`));
2175
+ }
2176
+ }, timeoutMs);
2177
+ });
2178
+ }
2073
2179
  scheduleReconnect() {
2074
2180
  if (this.reconnectTimer) return;
2075
2181
  this.reconnectTimer = setTimeout(async () => {
@@ -2090,6 +2196,9 @@ var init_daemon_cdp = __esm({
2090
2196
  this.ws?.close();
2091
2197
  this.ws = null;
2092
2198
  this._connected = false;
2199
+ this.browserWs?.close();
2200
+ this.browserWs = null;
2201
+ this._browserConnected = false;
2093
2202
  this.failureCount = 0;
2094
2203
  }
2095
2204
  get isConnected() {
@@ -2163,23 +2272,47 @@ var init_daemon_cdp = __esm({
2163
2272
  // ─── Agent Webview Multi-Session ─────────────────────────
2164
2273
  async discoverAgentWebviews() {
2165
2274
  if (!this.isConnected) return [];
2275
+ if (!this._browserConnected) {
2276
+ await this.connectBrowserWs().catch(() => {
2277
+ });
2278
+ }
2166
2279
  try {
2167
- const result = await this.sendInternal("Target.getTargets");
2168
- const allTargets = result?.targetInfos || [];
2280
+ let allTargets = [];
2281
+ if (this._browserConnected) {
2282
+ const result = await this.sendBrowser("Target.getTargets");
2283
+ allTargets = result?.targetInfos || [];
2284
+ } else {
2285
+ const result = await this.sendInternal("Target.getTargets");
2286
+ allTargets = result?.targetInfos || [];
2287
+ }
2169
2288
  const iframes = allTargets.filter((t) => t.type === "iframe");
2170
- this.log(`[CDP] discoverAgentWebviews: ${allTargets.length} total, ${iframes.length} iframes`);
2289
+ const typeMap = /* @__PURE__ */ new Map();
2290
+ for (const t of allTargets) {
2291
+ typeMap.set(t.type, (typeMap.get(t.type) || 0) + 1);
2292
+ }
2293
+ const typeSummary = [...typeMap.entries()].map(([k, v]) => `${k}:${v}`).join(",");
2294
+ this.log(`[CDP] discoverAgentWebviews: ${allTargets.length} total [${typeSummary}], ${iframes.length} iframes (browser=${this._browserConnected})`);
2295
+ for (const t of allTargets) {
2296
+ if (t.type !== "page" && t.type !== "worker" && t.type !== "service_worker") {
2297
+ this.log(`[CDP] target: type=${t.type} url=${(t.url || "").substring(0, 120)}`);
2298
+ }
2299
+ if ((t.url || "").includes("vscode-webview")) {
2300
+ this.log(`[CDP] webview: type=${t.type} url=${(t.url || "").substring(0, 150)}`);
2301
+ }
2302
+ }
2171
2303
  const agents = [];
2172
2304
  for (const target of allTargets) {
2173
2305
  if (target.type !== "iframe") continue;
2174
- const hasWebview = target.url?.includes("vscode-webview");
2306
+ const url = target.url || "";
2307
+ const hasWebview = url.includes("vscode-webview");
2175
2308
  if (!hasWebview) continue;
2176
2309
  for (const known of KNOWN_AGENTS) {
2177
- if (known.extensionIdPattern.test(target.url)) {
2310
+ if (known.extensionIdPattern.test(url)) {
2178
2311
  agents.push({
2179
2312
  targetId: target.targetId,
2180
2313
  extensionId: known.extensionId,
2181
2314
  agentType: known.agentType,
2182
- url: target.url
2315
+ url
2183
2316
  });
2184
2317
  this.log(`[CDP] Found agent: ${known.agentType} (${target.targetId})`);
2185
2318
  break;
@@ -2198,7 +2331,8 @@ var init_daemon_cdp = __esm({
2198
2331
  if (t.agentType === target.agentType) return sid;
2199
2332
  }
2200
2333
  try {
2201
- const result = await this.sendInternal("Target.attachToTarget", {
2334
+ const sendFn = this._browserConnected ? this.sendBrowser.bind(this) : this.sendInternal.bind(this);
2335
+ const result = await sendFn("Target.attachToTarget", {
2202
2336
  targetId: target.targetId,
2203
2337
  flatten: true
2204
2338
  });
@@ -2214,12 +2348,15 @@ var init_daemon_cdp = __esm({
2214
2348
  }
2215
2349
  }
2216
2350
  async evaluateInSession(sessionId, expression, timeoutMs = 15e3) {
2217
- if (!this.isConnected || !this.ws || this.ws.readyState !== import_ws3.default.OPEN) {
2351
+ const ws = this._browserConnected ? this.browserWs : this.ws;
2352
+ const pendingMap = this._browserConnected ? this.browserPending : this.pending;
2353
+ const getNextId = () => this._browserConnected ? this.browserMsgId++ : this.msgId++;
2354
+ if (!ws || ws.readyState !== import_ws3.default.OPEN) {
2218
2355
  throw new Error("CDP not connected");
2219
2356
  }
2220
2357
  return new Promise((resolve3, reject) => {
2221
- const id = this.msgId++;
2222
- this.pending.set(id, {
2358
+ const id = getNextId();
2359
+ pendingMap.set(id, {
2223
2360
  resolve: (result) => {
2224
2361
  if (result?.result?.subtype === "error") {
2225
2362
  reject(new Error(result.result.description));
@@ -2229,15 +2366,15 @@ var init_daemon_cdp = __esm({
2229
2366
  },
2230
2367
  reject
2231
2368
  });
2232
- this.ws.send(JSON.stringify({
2369
+ ws.send(JSON.stringify({
2233
2370
  id,
2234
2371
  sessionId,
2235
2372
  method: "Runtime.evaluate",
2236
2373
  params: { expression, returnByValue: true, awaitPromise: true }
2237
2374
  }));
2238
2375
  setTimeout(() => {
2239
- if (this.pending.has(id)) {
2240
- this.pending.delete(id);
2376
+ if (pendingMap.has(id)) {
2377
+ pendingMap.delete(id);
2241
2378
  reject(new Error(`CDP agent timeout: ${sessionId.substring(0, 12)}...`));
2242
2379
  }
2243
2380
  }, timeoutMs);
@@ -2245,7 +2382,8 @@ var init_daemon_cdp = __esm({
2245
2382
  }
2246
2383
  async detachAgent(sessionId) {
2247
2384
  try {
2248
- await this.sendInternal("Target.detachFromTarget", { sessionId });
2385
+ const sendFn = this._browserConnected ? this.sendBrowser.bind(this) : this.sendInternal.bind(this);
2386
+ await sendFn("Target.detachFromTarget", { sessionId });
2249
2387
  } catch {
2250
2388
  }
2251
2389
  this.agentSessions.delete(sessionId);
@@ -2611,7 +2749,8 @@ var init_daemon_p2p = __esm({
2611
2749
  tryLoadNodeDatachannel() {
2612
2750
  try {
2613
2751
  this.nodeDatachannel = require_lib();
2614
- log(`node-datachannel loaded \u2705`);
2752
+ const keys = Object.keys(this.nodeDatachannel).join(",");
2753
+ log(`node-datachannel loaded \u2705 (keys: ${keys.substring(0, 100)})`);
2615
2754
  return;
2616
2755
  } catch (e) {
2617
2756
  log(`node-datachannel not found: ${e?.message}`);
@@ -2745,7 +2884,12 @@ var init_daemon_p2p = __esm({
2745
2884
  if (existing?.state === "connected") return;
2746
2885
  if (existing?.state === "connecting") this.disconnectPeer(pid);
2747
2886
  log(`initiateConnection() for peer ${pid}...`);
2748
- const { PeerConnection } = this.nodeDatachannel;
2887
+ const mod = this.nodeDatachannel;
2888
+ const PeerConnectionCtor = mod.PeerConnection || mod.default?.PeerConnection || mod.default || mod;
2889
+ if (!PeerConnectionCtor || typeof PeerConnectionCtor !== "function") {
2890
+ log(`PeerConnection constructor not found in node-datachannel module. Keys: ${Object.keys(mod).join(",")}`);
2891
+ return;
2892
+ }
2749
2893
  let iceServers = [
2750
2894
  "stun:stun.cloudflare.com:3478",
2751
2895
  "stun:stun.l.google.com:19302"
@@ -2758,7 +2902,7 @@ var init_daemon_p2p = __esm({
2758
2902
  }
2759
2903
  } catch {
2760
2904
  }
2761
- const pc = new PeerConnection(`ADHDev-Daemon-${pid.substring(0, 8)}`, {
2905
+ const pc = new PeerConnectionCtor(`ADHDev-Daemon-${pid.substring(0, 8)}`, {
2762
2906
  iceServers
2763
2907
  });
2764
2908
  const entry = {
@@ -3195,9 +3339,6 @@ var init_daemon_script_loader = __esm({
3195
3339
  });
3196
3340
 
3197
3341
  // src/daemon-commands.ts
3198
- function home() {
3199
- return os7.homedir();
3200
- }
3201
3342
  var fs4, path3, os7, DaemonCommandHandler;
3202
3343
  var init_daemon_commands = __esm({
3203
3344
  "src/daemon-commands.ts"() {
@@ -3205,6 +3346,7 @@ var init_daemon_commands = __esm({
3205
3346
  fs4 = __toESM(require("fs"));
3206
3347
  path3 = __toESM(require("path"));
3207
3348
  os7 = __toESM(require("os"));
3349
+ init_config();
3208
3350
  DaemonCommandHandler = class {
3209
3351
  ctx;
3210
3352
  agentStream = null;
@@ -3352,6 +3494,21 @@ var init_daemon_commands = __esm({
3352
3494
  async handleSendChat(args) {
3353
3495
  const text = args?.text || args?.message;
3354
3496
  if (!text) return { success: false, error: "text required" };
3497
+ const targetAgent = args?.agentType || args?.cliType || this._currentIdeType;
3498
+ if (targetAgent) {
3499
+ for (const [key, adapter] of this.ctx.adapters.entries()) {
3500
+ if (adapter.cliType === targetAgent || key.startsWith(targetAgent)) {
3501
+ console.log(`[send_chat] Routing to CLI adapter: ${adapter.cliType} (${key})`);
3502
+ try {
3503
+ await adapter.sendMessage(text);
3504
+ return { success: true, sent: true, targetAgent: adapter.cliType };
3505
+ } catch (e) {
3506
+ console.log(`[send_chat] CLI adapter failed: ${e.message}`);
3507
+ return { success: false, error: `CLI send failed: ${e.message}` };
3508
+ }
3509
+ }
3510
+ }
3511
+ }
3355
3512
  const targetCdp = this.getCdp();
3356
3513
  const targetLoader = this.getScriptLoader();
3357
3514
  if (targetCdp?.isConnected && targetLoader) {
@@ -3381,7 +3538,7 @@ var init_daemon_commands = __esm({
3381
3538
  console.log(`[send_chat] Failed on ${ideType}: ${e.message}`);
3382
3539
  }
3383
3540
  }
3384
- return { success: false, error: "No CDP could send the message" };
3541
+ return { success: false, error: "No CDP or CLI adapter could send the message" };
3385
3542
  }
3386
3543
  async handleListChats(args) {
3387
3544
  const cdp = this.getCdp();
@@ -3657,10 +3814,10 @@ var init_daemon_commands = __esm({
3657
3814
  return this.handleFileList(args);
3658
3815
  }
3659
3816
  resolveSafePath(requestedPath) {
3660
- const home2 = os7.homedir();
3817
+ const home = os7.homedir();
3661
3818
  let resolved;
3662
3819
  if (requestedPath.startsWith("~")) {
3663
- resolved = path3.join(home2, requestedPath.slice(1));
3820
+ resolved = path3.join(home, requestedPath.slice(1));
3664
3821
  } else if (path3.isAbsolute(requestedPath)) {
3665
3822
  resolved = requestedPath;
3666
3823
  } else {
@@ -3677,18 +3834,24 @@ var init_daemon_commands = __esm({
3677
3834
  }
3678
3835
  // ─── 기타 ───────────────────────────────────
3679
3836
  async handleGetRecentWorkspaces(args) {
3837
+ const config = loadConfig();
3838
+ const cliRecent = config.recentCliWorkspaces || [];
3680
3839
  try {
3681
- const storageDir = path3.join(home(), "Library", "Application Support");
3840
+ const storageDir = path3.join(os7.homedir(), "Library", "Application Support");
3682
3841
  const candidates = ["Cursor", "Code", "VSCodium"];
3683
3842
  for (const app of candidates) {
3684
3843
  const stateFile = path3.join(storageDir, app, "User", "globalStorage", "state.vscdb");
3685
3844
  if (fs4.existsSync(stateFile)) {
3686
- return this.delegateToExtension("adhdev.getRecentWorkspaces", []);
3845
+ const result = await this.delegateToExtension("adhdev.getRecentWorkspaces", []);
3846
+ if (result.success && Array.isArray(result.result)) {
3847
+ const merged = Array.from(/* @__PURE__ */ new Set([...cliRecent, ...result.result])).slice(0, 20);
3848
+ return { success: true, result: merged };
3849
+ }
3687
3850
  }
3688
3851
  }
3689
- return this.delegateToExtension("adhdev.getRecentWorkspaces", []);
3852
+ return { success: true, result: cliRecent };
3690
3853
  } catch {
3691
- return this.delegateToExtension("adhdev.getRecentWorkspaces", []);
3854
+ return { success: true, result: cliRecent };
3692
3855
  }
3693
3856
  }
3694
3857
  async handleRefreshScripts(args) {
@@ -4352,7 +4515,7 @@ var init_adhdev_daemon = __esm({
4352
4515
  AdhdevDaemon = class {
4353
4516
  localServer = null;
4354
4517
  bridge = null;
4355
- adapter = null;
4518
+ adapters = /* @__PURE__ */ new Map();
4356
4519
  cdpManagers = /* @__PURE__ */ new Map();
4357
4520
  cdpDiscoveryTimer = null;
4358
4521
  p2p = null;
@@ -4363,8 +4526,12 @@ var init_adhdev_daemon = __esm({
4363
4526
  agentStreamTimer = null;
4364
4527
  running = false;
4365
4528
  statusTimer = null;
4366
- lastAgentStatus = "idle";
4367
- generatingStartedAt = 0;
4529
+ lastAgentStatus = /* @__PURE__ */ new Map();
4530
+ generatingStartedAt = /* @__PURE__ */ new Map();
4531
+ getCliKey(cliType, dir) {
4532
+ const hash = require("crypto").createHash("md5").update(require("path").resolve(dir)).digest("hex").slice(0, 8);
4533
+ return `${cliType}_${hash}`;
4534
+ }
4368
4535
  detectedIdes = [];
4369
4536
  localPort;
4370
4537
  ideType = "unknown";
@@ -4421,6 +4588,17 @@ var init_adhdev_daemon = __esm({
4421
4588
  });
4422
4589
  }
4423
4590
  }
4591
+ },
4592
+ onCommand: async (ext, command, args, messageId) => {
4593
+ const mockMsg = {
4594
+ id: messageId || `ext_${Date.now()}`,
4595
+ type: "ext:command",
4596
+ payload: args,
4597
+ metadata: { ideType: ext.ideType, instanceId: ext.instanceId },
4598
+ ipcWs: ext.ws
4599
+ // 응답을 위해 저장
4600
+ };
4601
+ await this.handleCommand(mockMsg, command, args);
4424
4602
  }
4425
4603
  });
4426
4604
  try {
@@ -4455,7 +4633,8 @@ var init_adhdev_daemon = __esm({
4455
4633
  cdpManagers: this.cdpManagers,
4456
4634
  scriptLoaders: this.scriptLoaders,
4457
4635
  localServer: this.localServer,
4458
- ideType: this.ideType
4636
+ ideType: this.ideType,
4637
+ adapters: this.adapters
4459
4638
  });
4460
4639
  this.agentStreamManager = new DaemonAgentStreamManager(
4461
4640
  console.log,
@@ -4466,15 +4645,9 @@ var init_adhdev_daemon = __esm({
4466
4645
  this.commandHandler.setAgentStreamManager(this.agentStreamManager);
4467
4646
  this.startAgentStreamPolling();
4468
4647
  if (options.cliType) {
4469
- const cliInfo = await detectCLI(options.cliType);
4470
- if (cliInfo) {
4471
- this.adapter = this.createAdapter(options.cliType, workingDir);
4472
- await this.adapter.spawn();
4473
- this.adapter.setOnStatusChange(() => this.sendUnifiedStatusReport());
4474
- console.log(import_chalk2.default.green(` \u{1F916} CLI Agent started: ${cliInfo.displayName} v${cliInfo.version || "unknown"}`));
4475
- } else {
4476
- console.log(import_chalk2.default.yellow(` \u26A0 CLI ${options.cliType} not found \u2014 running without CLI agent`));
4477
- }
4648
+ await this.startCliSession(options.cliType, workingDir).catch((e) => {
4649
+ console.log(import_chalk2.default.yellow(` \u26A0 Failed to start CLI ${options.cliType}: ${e.message}`));
4650
+ });
4478
4651
  }
4479
4652
  const machineId = os8.hostname().replace(/[^a-zA-Z0-9]/g, "_");
4480
4653
  const machineHash = crypto2.createHash("md5").update(os8.hostname() + os8.homedir()).digest("hex").slice(0, 8);
@@ -4489,9 +4662,6 @@ var init_adhdev_daemon = __esm({
4489
4662
  instanceId
4490
4663
  }
4491
4664
  });
4492
- if (this.adapter && typeof this.adapter.setBridge === "function") {
4493
- this.adapter.setBridge(this.bridge);
4494
- }
4495
4665
  this.p2p = new DaemonP2PSender(this.bridge);
4496
4666
  if (this.p2p.isAvailable) {
4497
4667
  console.log(import_chalk2.default.green(" \u{1F517} P2P available (node-datachannel)"));
@@ -4624,9 +4794,11 @@ var init_adhdev_daemon = __esm({
4624
4794
  "launch_ide",
4625
4795
  "detect_ides",
4626
4796
  "restart_session",
4627
- "switch_cli",
4628
- "change_dir",
4629
4797
  "exec_command",
4798
+ "launch_cli",
4799
+ "stop_cli",
4800
+ "agent_command",
4801
+ // Added agent_command here
4630
4802
  // Extension-delegated commands (must be registered to receive WS messages)
4631
4803
  "vscode_command_exec",
4632
4804
  "execute_vscode_command",
@@ -4662,21 +4834,71 @@ var init_adhdev_daemon = __esm({
4662
4834
  console.log(import_chalk2.default.magenta(` \u2699 Command: ${cmd}`));
4663
4835
  try {
4664
4836
  switch (cmd) {
4665
- case "switch_cli": {
4666
- const newCli = args?.cli || "gemini-cli";
4667
- const newDir = args?.dir || this.adapter?.workingDir || process.cwd();
4668
- await this.switchCliSession(newCli, newDir);
4669
- this.sendResult(msg, true, { cli: newCli, dir: newDir });
4837
+ case "launch_cli": {
4838
+ const cliType = args?.cliType;
4839
+ const dir = args?.dir || process.cwd();
4840
+ if (!cliType) throw new Error("cliType required");
4841
+ const key = this.getCliKey(cliType, dir);
4842
+ if (!this.adapters.has(key)) {
4843
+ await this.startCliSession(cliType, dir);
4844
+ try {
4845
+ const config = loadConfig();
4846
+ console.log(import_chalk2.default.cyan(` \u{1F4C2} Saving recent workspace: ${dir}`));
4847
+ const recent = config.recentCliWorkspaces || [];
4848
+ if (!recent.includes(dir)) {
4849
+ const updated = [dir, ...recent].slice(0, 10);
4850
+ saveConfig({ ...config, recentCliWorkspaces: updated });
4851
+ console.log(import_chalk2.default.green(` \u2713 Recent workspace saved: ${dir}`));
4852
+ }
4853
+ } catch (e) {
4854
+ console.error(import_chalk2.default.red(` \u2717 Failed to save recent workspace: ${e}`));
4855
+ }
4856
+ }
4857
+ this.sendResult(msg, true, { cliType, dir, id: key });
4858
+ return;
4859
+ }
4860
+ case "agent_command": {
4861
+ const agentType = args?.agentType || args?.cliType;
4862
+ const dir = args?.dir || process.cwd();
4863
+ const action = args?.action;
4864
+ if (!agentType || !action) throw new Error("agentType and action required");
4865
+ const key = this.getCliKey(agentType, dir);
4866
+ const adapter = this.adapters.get(key);
4867
+ if (!adapter) throw new Error(`CLI agent not running for ${key}`);
4868
+ if (action === "send_chat") {
4869
+ const message = args.message || args.text;
4870
+ if (!message) throw new Error("message required for send_chat");
4871
+ await adapter.sendMessage(message);
4872
+ this.sendResult(msg, true, { status: "generating" });
4873
+ } else if (action === "clear_history") {
4874
+ if (typeof adapter.clearHistory === "function") {
4875
+ adapter.clearHistory();
4876
+ }
4877
+ this.sendResult(msg, true, { cleared: true });
4878
+ } else if (action === "stop") {
4879
+ await this.stopCliSession(key);
4880
+ this.sendResult(msg, true, { stopped: true });
4881
+ } else {
4882
+ throw new Error(`Unknown action: ${action}`);
4883
+ }
4670
4884
  return;
4671
4885
  }
4672
- case "change_dir": {
4673
- if (!args?.dir) throw new Error("Directory path required");
4674
- if (this.adapter) await this.switchCliSession(this.adapter.cliType, args.dir);
4675
- this.sendResult(msg, true, { dir: args.dir });
4886
+ case "stop_cli": {
4887
+ const cliType = args?.cliType;
4888
+ const dir = args?.dir || process.cwd();
4889
+ if (!cliType) throw new Error("cliType required");
4890
+ const key = this.getCliKey(cliType, dir);
4891
+ await this.stopCliSession(key);
4892
+ this.sendResult(msg, true, { cliType, dir });
4676
4893
  return;
4677
4894
  }
4678
4895
  case "restart_session": {
4679
- if (this.adapter) await this.switchCliSession(this.adapter.cliType, this.adapter.workingDir);
4896
+ const cliType = args?.cliType || args?.agentType;
4897
+ const dir = args?.dir || process.cwd();
4898
+ if (!cliType) throw new Error("cliType required");
4899
+ const key = this.getCliKey(cliType, dir);
4900
+ await this.stopCliSession(key);
4901
+ await this.startCliSession(cliType, dir);
4680
4902
  this.sendResult(msg, true, { restarted: true });
4681
4903
  return;
4682
4904
  }
@@ -4687,7 +4909,7 @@ var init_adhdev_daemon = __esm({
4687
4909
  this.sendResult(msg, false, { error: `Blocked: "${cmdStr}"` });
4688
4910
  return;
4689
4911
  }
4690
- const cwd = this.adapter?.workingDir || process.cwd();
4912
+ const cwd = args?.dir || process.cwd();
4691
4913
  const { exec: exec2 } = require("child_process");
4692
4914
  exec2(cmdStr, { cwd, timeout: 6e4 }, (err, stdout, stderr) => {
4693
4915
  if (!this.bridge) return;
@@ -4772,23 +4994,41 @@ var init_adhdev_daemon = __esm({
4772
4994
  return new GeminiCliAdapter(workingDir);
4773
4995
  }
4774
4996
  }
4775
- async switchCliSession(cliType, workingDir) {
4776
- console.log(import_chalk2.default.yellow(` \u26A1 Switching CLI to ${cliType} in ${workingDir}...`));
4777
- if (this.adapter) this.adapter.shutdown();
4997
+ async startCliSession(cliType, workingDir) {
4778
4998
  const cliInfo = await detectCLI(cliType);
4779
4999
  if (!cliInfo) throw new Error(`${cliType} not found`);
4780
- this.adapter = this.createAdapter(cliType, workingDir);
4781
- await this.adapter.spawn();
4782
- if (this.bridge && typeof this.adapter.setBridge === "function") {
4783
- this.adapter.setBridge(this.bridge);
5000
+ const key = this.getCliKey(cliType, workingDir);
5001
+ if (this.adapters.has(key)) {
5002
+ console.log(import_chalk2.default.yellow(` \u26A1 CLI ${cliType} already running in ${workingDir}`));
5003
+ return;
5004
+ }
5005
+ console.log(import_chalk2.default.yellow(` \u26A1 Starting CLI ${cliType} in ${workingDir}...`));
5006
+ const adapter = this.createAdapter(cliType, workingDir);
5007
+ await adapter.spawn();
5008
+ if (this.bridge && typeof adapter.setBridge === "function") {
5009
+ adapter.setBridge(this.bridge);
5010
+ }
5011
+ adapter.setOnStatusChange(() => this.sendUnifiedStatusReport());
5012
+ this.adapters.set(key, adapter);
5013
+ this.lastAgentStatus.set(key, "idle");
5014
+ console.log(import_chalk2.default.green(` \u2713 CLI started: ${cliInfo.displayName} v${cliInfo.version || "unknown"} in ${workingDir}`));
5015
+ this.sendUnifiedStatusReport();
5016
+ }
5017
+ async stopCliSession(key) {
5018
+ const adapter = this.adapters.get(key);
5019
+ if (adapter) {
5020
+ adapter.shutdown();
5021
+ this.adapters.delete(key);
5022
+ this.lastAgentStatus.delete(key);
5023
+ this.generatingStartedAt.delete(key);
5024
+ console.log(import_chalk2.default.yellow(` \u{1F6D1} CLI Agent stopped: ${adapter.cliType} in ${adapter.workingDir}`));
5025
+ this.sendUnifiedStatusReport();
4784
5026
  }
4785
- this.adapter.setOnStatusChange(() => this.sendUnifiedStatusReport());
4786
- console.log(import_chalk2.default.green(` \u2713 CLI switched to ${cliType}`));
4787
5027
  }
4788
5028
  // ─── 통합 상태 보고 ─────────────────────────────
4789
5029
  startStatusReporting() {
4790
5030
  const scheduleNext = () => {
4791
- const isProcessing = this.adapter?.isProcessing();
5031
+ const isProcessing = Array.from(this.adapters.values()).some((a) => a.isProcessing());
4792
5032
  const interval = isProcessing ? 2e3 : 15e3;
4793
5033
  this.statusTimer = setTimeout(() => {
4794
5034
  this.sendUnifiedStatusReport().catch(() => {
@@ -4830,20 +5070,35 @@ var init_adhdev_daemon = __esm({
4830
5070
  }
4831
5071
  const now = Date.now();
4832
5072
  const perExtData = this.localServer?.getPerExtensionData() || [];
4833
- const extSummary = this.localServer?.getLatestExtensionData() || {
5073
+ const enabledIdesFilter = loadConfig().enabledIdes || [];
5074
+ const filteredExtData = enabledIdesFilter.length > 0 ? perExtData.filter((ext) => enabledIdesFilter.includes(ext.ideType.toLowerCase())) : perExtData;
5075
+ const extSummary = this.localServer?.getLatestExtensionData(enabledIdesFilter) || {
4834
5076
  activeFile: null,
4835
5077
  workspaceFolders: [],
4836
5078
  terminals: 0,
4837
5079
  aiAgents: [],
4838
5080
  connectedIdes: []
4839
5081
  };
4840
- const managedIdes = perExtData.map((ext) => {
5082
+ const managedIdes = filteredExtData.map((ext) => {
4841
5083
  const ideKey = ext.ideType.toLowerCase();
4842
5084
  const isMainCdpIde = this.cdpManagers.has(ideKey);
4843
5085
  const cdpStreamsForIde = this._cachedAgentStreamsMap.get(ideKey) || [];
5086
+ const extAgentsForIde = (ext.aiAgents || []).filter((a) => {
5087
+ const existsInCdp = cdpStreamsForIde.some((s) => s.agentType === a.id);
5088
+ const existsInExt = ext.agentStreams.some((s) => s.agentType === a.id);
5089
+ return !existsInCdp && !existsInExt;
5090
+ }).map((a) => ({
5091
+ agentType: a.id,
5092
+ agentName: a.name,
5093
+ extensionId: a.id,
5094
+ status: a.status === "active" || a.status === "installed" ? "connected" : "idle",
5095
+ messages: [],
5096
+ inputContent: ""
5097
+ }));
4844
5098
  const ideAgentStreams = [
4845
5099
  ...ext.agentStreams,
4846
- ...isMainCdpIde ? cdpStreamsForIde : []
5100
+ ...isMainCdpIde ? cdpStreamsForIde : [],
5101
+ ...extAgentsForIde
4847
5102
  ];
4848
5103
  return {
4849
5104
  ideType: ext.ideType,
@@ -4860,47 +5115,50 @@ var init_adhdev_daemon = __esm({
4860
5115
  };
4861
5116
  });
4862
5117
  const managedClis = [];
4863
- if (this.adapter) {
4864
- const adapterStatus = this.adapter.getStatus();
5118
+ for (const [key, adapter] of this.adapters.entries()) {
5119
+ const adapterStatus = adapter.getStatus();
4865
5120
  const cliStatus = adapterStatus.status;
4866
- if (cliStatus !== this.lastAgentStatus) {
4867
- if (this.lastAgentStatus === "idle" && cliStatus === "generating") {
4868
- this.generatingStartedAt = now;
4869
- this.bridge.sendMessage("status_event", {
5121
+ let lastStatus = this.lastAgentStatus.get(key) || "idle";
5122
+ if (cliStatus !== lastStatus) {
5123
+ if (lastStatus === "idle" && cliStatus === "generating") {
5124
+ this.generatingStartedAt.set(key, now);
5125
+ this.bridge?.sendMessage("status_event", {
4870
5126
  event: "agent:generating_started",
4871
- chatTitle: `${this.adapter.cliName} Session`,
5127
+ chatTitle: `${adapter.cliName} Session`,
4872
5128
  timestamp: now
4873
5129
  });
4874
- } else if (this.lastAgentStatus === "generating" && cliStatus === "idle") {
4875
- const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
4876
- this.bridge.sendMessage("status_event", {
5130
+ } else if (lastStatus === "generating" && cliStatus === "idle") {
5131
+ const startedAt = this.generatingStartedAt.get(key);
5132
+ const duration = startedAt ? Math.round((now - startedAt) / 1e3) : 0;
5133
+ this.bridge?.sendMessage("status_event", {
4877
5134
  event: "agent:generating_completed",
4878
- chatTitle: `${this.adapter.cliName} Session`,
5135
+ chatTitle: `${adapter.cliName} Session`,
4879
5136
  duration,
4880
5137
  timestamp: now
4881
5138
  });
4882
- this.generatingStartedAt = 0;
5139
+ this.generatingStartedAt.delete(key);
4883
5140
  }
4884
- this.lastAgentStatus = cliStatus;
5141
+ this.lastAgentStatus.set(key, cliStatus);
4885
5142
  }
4886
5143
  const recentMessages = adapterStatus.messages.slice(-50);
4887
5144
  const cliMessages = recentMessages.map((m) => ({
4888
5145
  role: m.role,
4889
5146
  content: m.content.length > 2e3 ? m.content.slice(0, 2e3) + "\n... (truncated)" : m.content
4890
5147
  }));
4891
- const partial = this.adapter.getPartialResponse();
5148
+ const partial = adapter.getPartialResponse();
4892
5149
  if (cliStatus === "generating" && partial) {
4893
- cliMessages.push({ role: "assistant", content: partial.slice(0, 2e3) + "..." });
5150
+ cliMessages.push({ role: "assistant", content: partial.length > 2e3 ? partial.slice(0, 2e3) + "..." : partial + "..." });
4894
5151
  }
4895
5152
  managedClis.push({
4896
- cliType: this.adapter.cliType,
4897
- cliName: this.adapter.cliName,
4898
- workingDir: this.adapter.workingDir,
5153
+ id: key,
5154
+ cliType: adapter.cliType,
5155
+ cliName: adapter.cliName,
4899
5156
  status: cliStatus,
5157
+ workingDir: adapter.workingDir,
4900
5158
  activeChat: {
4901
- id: `cli-${this.adapter.cliType}`,
5159
+ id: key,
4902
5160
  status: cliStatus,
4903
- title: `${this.adapter.cliName} Session`,
5161
+ title: `${adapter.cliName} Session`,
4904
5162
  messages: cliMessages,
4905
5163
  inputContent: "",
4906
5164
  activeModal: adapterStatus.activeModal
@@ -4942,13 +5200,20 @@ var init_adhdev_daemon = __esm({
4942
5200
  timestamp: now,
4943
5201
  // ─── Legacy compat (서버 기존 로직용, 점진적 제거 예정) ───
4944
5202
  activeFile: extSummary.activeFile,
4945
- workspaceFolders: extSummary.workspaceFolders.length > 0 ? extSummary.workspaceFolders : this.adapter ? [{ name: path4.basename(this.adapter.workingDir), path: this.adapter.workingDir }] : [],
5203
+ workspaceFolders: extSummary.workspaceFolders.length > 0 ? extSummary.workspaceFolders : Array.from(this.adapters.values()).map((a) => ({ name: path4.basename(a.workingDir), path: a.workingDir })),
4946
5204
  terminals: extSummary.terminals,
5205
+ // Legacy aiAgents: managedIdes에서 각 IDE의 aiAgents를 통합
4947
5206
  aiAgents: [
4948
- ...extSummary.aiAgents,
4949
- ...this.adapter ? [{ id: this.adapter.cliType, name: this.adapter.cliName, status: this.lastAgentStatus }] : []
5207
+ ...managedIdes.flatMap((ide) => (ide.aiAgents || []).map((a) => ({
5208
+ id: a.id,
5209
+ name: a.name,
5210
+ status: a.status,
5211
+ ideType: ide.ideType
5212
+ }))),
5213
+ ...Array.from(this.adapters.entries()).map(([k, a]) => ({ id: a.cliType, name: a.cliName, status: this.lastAgentStatus.get(k) || "idle" }))
4950
5214
  ],
4951
5215
  activeChat: managedClis[0]?.activeChat || managedIdes[0]?.activeChat || null,
5216
+ // Legacy agentStreams: managedIdes의 agentStreams를 통합 (이미 IDE별로 올바르게 구성됨)
4952
5217
  agentStreams: [
4953
5218
  ...managedClis.map((c) => ({
4954
5219
  agentType: c.cliType,
@@ -4958,7 +5223,7 @@ var init_adhdev_daemon = __esm({
4958
5223
  messages: c.activeChat?.messages || [],
4959
5224
  inputContent: ""
4960
5225
  })),
4961
- ...[...this._cachedAgentStreamsMap.values()].flat()
5226
+ ...managedIdes.flatMap((ide) => ide.agentStreams || [])
4962
5227
  ],
4963
5228
  connectedExtensions: extSummary.connectedIdes,
4964
5229
  system: {
@@ -4978,6 +5243,17 @@ var init_adhdev_daemon = __esm({
4978
5243
  }
4979
5244
  sendResult(msg, success, extra) {
4980
5245
  if (!msg.id) return;
5246
+ if (msg.ipcWs && this.localServer) {
5247
+ msg.ipcWs.send(JSON.stringify({
5248
+ type: "daemon:command_result",
5249
+ // 프로토콜은 아니지만 테스트용/확장용으로 추가
5250
+ payload: {
5251
+ requestId: msg.id,
5252
+ success,
5253
+ ...extra
5254
+ }
5255
+ }));
5256
+ }
4981
5257
  this.bridge?.sendMessage("command_result", {
4982
5258
  requestId: msg.id,
4983
5259
  success,
@@ -4990,7 +5266,8 @@ var init_adhdev_daemon = __esm({
4990
5266
  if (!this.running) return;
4991
5267
  this.running = false;
4992
5268
  console.log(import_chalk2.default.yellow("\n Shutting down ADHDev Daemon..."));
4993
- this.adapter?.cancel();
5269
+ for (const adapter of this.adapters.values()) adapter.shutdown();
5270
+ this.adapters.clear();
4994
5271
  if (this.statusTimer) clearTimeout(this.statusTimer);
4995
5272
  if (this.cdpDiscoveryTimer) clearInterval(this.cdpDiscoveryTimer);
4996
5273
  if (this.screenshotTimer) clearInterval(this.screenshotTimer);
@@ -5057,7 +5334,9 @@ var init_adhdev_daemon = __esm({
5057
5334
  portsToTry.push({ port, ide });
5058
5335
  }
5059
5336
  }
5060
- for (const { port, ide } of portsToTry) {
5337
+ const enabledIdes = loadConfig().enabledIdes || [];
5338
+ const filteredPorts = enabledIdes.length > 0 ? portsToTry.filter((p) => enabledIdes.includes(p.ide)) : portsToTry;
5339
+ for (const { port, ide } of filteredPorts) {
5061
5340
  const manager = new DaemonCdpManager(port, (msg) => {
5062
5341
  console.log(import_chalk2.default.gray(msg));
5063
5342
  }, true);
@@ -6340,14 +6619,14 @@ async function injectTokenToIDE(ide, connectionToken) {
6340
6619
  const fs7 = await import("fs");
6341
6620
  const path6 = await import("path");
6342
6621
  const platform9 = os10.platform();
6343
- const home2 = os10.homedir();
6622
+ const home = os10.homedir();
6344
6623
  const getSettingsPath = (appName2) => {
6345
6624
  if (platform9 === "darwin") {
6346
- return path6.join(home2, "Library", "Application Support", appName2, "User", "settings.json");
6625
+ return path6.join(home, "Library", "Application Support", appName2, "User", "settings.json");
6347
6626
  } else if (platform9 === "win32") {
6348
- return path6.join(process.env.APPDATA || path6.join(home2, "AppData", "Roaming"), appName2, "User", "settings.json");
6627
+ return path6.join(process.env.APPDATA || path6.join(home, "AppData", "Roaming"), appName2, "User", "settings.json");
6349
6628
  } else {
6350
- return path6.join(home2, ".config", appName2, "User", "settings.json");
6629
+ return path6.join(home, ".config", appName2, "User", "settings.json");
6351
6630
  }
6352
6631
  };
6353
6632
  const appNameMap = {
@@ -6464,6 +6743,7 @@ async function installCliOnly() {
6464
6743
 
6465
6744
  // src/index.ts
6466
6745
  init_detector();
6746
+ init_cli_detector();
6467
6747
  init_config();
6468
6748
  init_launch();
6469
6749
  var import_fs3 = require("fs");
@@ -6593,6 +6873,15 @@ program.command("status").description("Show current ADHDev setup status").action
6593
6873
  }
6594
6874
  }
6595
6875
  }
6876
+ const clis = await detectCLIs();
6877
+ const installedClis = clis.filter((c) => c.installed);
6878
+ if (installedClis.length > 0) {
6879
+ console.log(` ${import_chalk4.default.bold("CLI Agents:")}`);
6880
+ installedClis.forEach((cli) => {
6881
+ const ver = cli.version ? import_chalk4.default.gray(` v${cli.version}`) : "";
6882
+ console.log(` ${import_chalk4.default.green("\u2713")} ${cli.icon} ${cli.displayName}${ver}`);
6883
+ });
6884
+ }
6596
6885
  console.log(` ${import_chalk4.default.bold("Extensions:")} ${config.installedExtensions.length} installed`);
6597
6886
  config.installedExtensions.forEach((ext) => {
6598
6887
  console.log(import_chalk4.default.gray(` \u2022 ${ext}`));
@@ -6620,6 +6909,17 @@ program.command("detect").description("Detect installed IDEs on your system").ac
6620
6909
  console.log(` ${import_chalk4.default.gray("\u2717")} ${ide.icon} ${import_chalk4.default.gray(ide.displayName)} \u2014 not found`);
6621
6910
  }
6622
6911
  });
6912
+ console.log(import_chalk4.default.bold("\n\u{1F50D} Detecting installed CLI Agents...\n"));
6913
+ const clis = await detectCLIs();
6914
+ clis.forEach((cli) => {
6915
+ if (cli.installed) {
6916
+ const version = cli.version ? import_chalk4.default.gray(` v${cli.version}`) : "";
6917
+ console.log(` ${import_chalk4.default.green("\u2713")} ${cli.icon} ${import_chalk4.default.bold(cli.displayName)}${version}`);
6918
+ console.log(import_chalk4.default.gray(` Path: ${cli.path}`));
6919
+ } else {
6920
+ console.log(` ${import_chalk4.default.gray("\u2717")} ${cli.icon} ${import_chalk4.default.gray(cli.displayName)} \u2014 not found`);
6921
+ }
6922
+ });
6623
6923
  console.log();
6624
6924
  });
6625
6925
  program.command("reset").description("Reset ADHDev configuration").action(async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adhdev",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "ADHDev CLI — Detect, install and configure your IDE + AI agent extensions",
5
5
  "main": "dist/index.js",
6
6
  "bin": {