adhdev 0.1.54 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/cli-entrypoint.js +29083 -0
  2. package/dist/cli-entrypoint.js.map +1 -0
  3. package/dist/index.js +26970 -11054
  4. package/dist/index.js.map +1 -0
  5. package/package.json +19 -16
  6. package/providers/_builtin/COMPATIBILITY.md +217 -0
  7. package/providers/_builtin/acp/agentpool/provider.json +54 -0
  8. package/providers/_builtin/acp/amp/provider.json +52 -0
  9. package/providers/_builtin/acp/auggie/provider.json +57 -0
  10. package/providers/_builtin/acp/autodev/provider.json +54 -0
  11. package/providers/_builtin/acp/autohand/provider.json +52 -0
  12. package/providers/_builtin/acp/blackbox-ai/provider.json +54 -0
  13. package/providers/_builtin/acp/claude-agent/provider.json +57 -0
  14. package/providers/_builtin/acp/cline-acp/provider.json +54 -0
  15. package/providers/_builtin/acp/codebuddy/provider.json +54 -0
  16. package/providers/_builtin/acp/codex-cli/provider.json +57 -0
  17. package/providers/_builtin/acp/corust-agent/provider.json +52 -0
  18. package/providers/_builtin/acp/crow-cli/provider.json +54 -0
  19. package/providers/_builtin/acp/cursor-acp/provider.json +54 -0
  20. package/providers/_builtin/acp/deepagents/provider.json +52 -0
  21. package/providers/_builtin/acp/dimcode/provider.json +54 -0
  22. package/providers/_builtin/acp/docker-cagent/provider.json +57 -0
  23. package/providers/_builtin/acp/factory-droid/provider.json +60 -0
  24. package/providers/_builtin/acp/fast-agent/provider.json +52 -0
  25. package/providers/_builtin/acp/gemini-cli/provider.json +114 -0
  26. package/providers/_builtin/acp/github-copilot/provider.json +54 -0
  27. package/providers/_builtin/acp/goose/provider.json +57 -0
  28. package/providers/_builtin/acp/junie/provider.json +52 -0
  29. package/providers/_builtin/acp/kilo/provider.json +54 -0
  30. package/providers/_builtin/acp/kimi-cli/provider.json +57 -0
  31. package/providers/_builtin/acp/minion-code/provider.json +52 -0
  32. package/providers/_builtin/acp/mistral-vibe/provider.json +57 -0
  33. package/providers/_builtin/acp/nova/provider.json +54 -0
  34. package/providers/_builtin/acp/openclaw/provider.json +54 -0
  35. package/providers/_builtin/acp/opencode/provider.json +52 -0
  36. package/providers/_builtin/acp/openhands/provider.json +54 -0
  37. package/providers/_builtin/acp/pi-acp/provider.json +52 -0
  38. package/providers/_builtin/acp/qoder/provider.json +54 -0
  39. package/providers/_builtin/acp/qwen-code/provider.json +60 -0
  40. package/providers/_builtin/acp/stakpak/provider.json +54 -0
  41. package/providers/_builtin/acp/vtcode/provider.json +54 -0
  42. package/providers/_builtin/cli/claude-cli/provider.json +100 -0
  43. package/providers/_builtin/cli/codex-cli/provider.json +89 -0
  44. package/providers/_builtin/cli/gemini-cli/provider.json +93 -0
  45. package/providers/_builtin/docs/CDP_SELECTOR_GUIDE.md +370 -0
  46. package/providers/_builtin/docs/PROVIDER_GUIDE.md +916 -0
  47. package/providers/_builtin/extension/cline/provider.json +35 -0
  48. package/providers/_builtin/extension/cline/scripts/open_panel.js +1 -1
  49. package/providers/_builtin/extension/cline/{provider.js → scripts.js} +29 -55
  50. package/providers/_builtin/extension/roo-code/provider.json +35 -0
  51. package/providers/_builtin/extension/roo-code/{provider.js → scripts.js} +27 -97
  52. package/providers/_builtin/ide/antigravity/provider.json +63 -0
  53. package/providers/_builtin/ide/antigravity/scripts/legacy/list_models.js +38 -0
  54. package/providers/_builtin/ide/antigravity/scripts/legacy/list_modes.js +48 -0
  55. package/providers/_builtin/ide/antigravity/scripts/legacy/scripts.js +64 -0
  56. package/providers/_builtin/ide/antigravity/scripts/legacy/set_mode.js +34 -0
  57. package/providers/_builtin/ide/antigravity/scripts/legacy/set_model.js +47 -0
  58. package/providers/_builtin/ide/antigravity/scripts/list_models.js +31 -8
  59. package/providers/_builtin/ide/antigravity/scripts/list_modes.js +37 -13
  60. package/providers/_builtin/ide/antigravity/scripts/set_mode.js +49 -16
  61. package/providers/_builtin/ide/antigravity/scripts/set_model.js +47 -22
  62. package/providers/_builtin/ide/antigravity/scripts.js +67 -0
  63. package/providers/_builtin/ide/cursor/provider.json +59 -0
  64. package/providers/_builtin/ide/cursor/{provider.js → scripts.js} +47 -79
  65. package/providers/_builtin/ide/kiro/provider.json +60 -0
  66. package/providers/_builtin/ide/kiro/scripts.js +62 -0
  67. package/providers/_builtin/ide/pearai/provider.json +60 -0
  68. package/providers/_builtin/ide/pearai/scripts.js +74 -0
  69. package/providers/_builtin/ide/trae/provider.json +59 -0
  70. package/providers/_builtin/ide/trae/scripts.js +57 -0
  71. package/providers/_builtin/ide/vscode/provider.json +57 -0
  72. package/providers/_builtin/ide/vscode-insiders/provider.json +55 -0
  73. package/providers/_builtin/ide/vscodium/provider.json +56 -0
  74. package/providers/_builtin/ide/windsurf/provider.json +46 -0
  75. package/providers/_builtin/ide/windsurf/scripts.js +57 -0
  76. package/README.md +0 -43
  77. package/dist/dev-console-monaco.js +0 -176
  78. package/dist/dev-console.css +0 -326
  79. package/dist/dev-console.html +0 -148
  80. package/dist/dev-console.js +0 -1165
  81. package/dist/index.d.ts +0 -2
  82. package/providers/_builtin/acp/agentpool/provider.js +0 -59
  83. package/providers/_builtin/acp/amp/provider.js +0 -61
  84. package/providers/_builtin/acp/auggie/provider.js +0 -60
  85. package/providers/_builtin/acp/autodev/provider.js +0 -59
  86. package/providers/_builtin/acp/autohand/provider.js +0 -59
  87. package/providers/_builtin/acp/blackbox-ai/provider.js +0 -59
  88. package/providers/_builtin/acp/claude-agent/provider.js +0 -61
  89. package/providers/_builtin/acp/cline-acp/provider.js +0 -62
  90. package/providers/_builtin/acp/code-assistant/provider.js +0 -59
  91. package/providers/_builtin/acp/codebuddy/provider.js +0 -59
  92. package/providers/_builtin/acp/codex-cli/provider.js +0 -64
  93. package/providers/_builtin/acp/corust-agent/provider.js +0 -59
  94. package/providers/_builtin/acp/crow-cli/provider.js +0 -59
  95. package/providers/_builtin/acp/cursor-acp/provider.js +0 -59
  96. package/providers/_builtin/acp/deepagents/provider.js +0 -59
  97. package/providers/_builtin/acp/dimcode/provider.js +0 -58
  98. package/providers/_builtin/acp/docker-cagent/provider.js +0 -59
  99. package/providers/_builtin/acp/factory-droid/provider.js +0 -59
  100. package/providers/_builtin/acp/fast-agent/provider.js +0 -59
  101. package/providers/_builtin/acp/fount/provider.js +0 -59
  102. package/providers/_builtin/acp/gemini-cli/provider.js +0 -104
  103. package/providers/_builtin/acp/github-copilot/provider.js +0 -60
  104. package/providers/_builtin/acp/goose/provider.js +0 -64
  105. package/providers/_builtin/acp/junie/provider.js +0 -62
  106. package/providers/_builtin/acp/kilo/provider.js +0 -59
  107. package/providers/_builtin/acp/kimi-cli/provider.js +0 -63
  108. package/providers/_builtin/acp/kiro-cli/provider.js +0 -59
  109. package/providers/_builtin/acp/minion-code/provider.js +0 -59
  110. package/providers/_builtin/acp/mistral-vibe/provider.js +0 -63
  111. package/providers/_builtin/acp/nova/provider.js +0 -59
  112. package/providers/_builtin/acp/openclaw/provider.js +0 -59
  113. package/providers/_builtin/acp/opencode/provider.js +0 -60
  114. package/providers/_builtin/acp/openhands/provider.js +0 -59
  115. package/providers/_builtin/acp/pi-acp/provider.js +0 -59
  116. package/providers/_builtin/acp/qoder/provider.js +0 -58
  117. package/providers/_builtin/acp/qwen-code/provider.js +0 -61
  118. package/providers/_builtin/acp/stakpak/provider.js +0 -59
  119. package/providers/_builtin/acp/vtcode/provider.js +0 -59
  120. package/providers/_builtin/cli/claude-cli/provider.js +0 -128
  121. package/providers/_builtin/cli/codex-cli/provider.js +0 -80
  122. package/providers/_builtin/cli/gemini-cli/provider.js +0 -124
  123. package/providers/_builtin/ide/antigravity/provider.js +0 -114
  124. package/providers/_builtin/ide/kiro/provider.js +0 -90
  125. package/providers/_builtin/ide/pearai/provider.js +0 -100
  126. package/providers/_builtin/ide/trae/provider.js +0 -83
  127. package/providers/_builtin/ide/vscode/provider.js +0 -36
  128. package/providers/_builtin/ide/vscode-insiders/provider.js +0 -27
  129. package/providers/_builtin/ide/vscodium/provider.js +0 -27
  130. package/providers/_builtin/ide/windsurf/provider.js +0 -76
  131. package/providers/_helpers/index.js +0 -188
@@ -1,1165 +0,0 @@
1
- const API = 'http://127.0.0.1:19280';
2
- let currentProvider = '';
3
-
4
- // ─── Init ───
5
- async function init() {
6
- await refreshProviders();
7
- await refreshStatus();
8
- await refreshTargets();
9
- setInterval(refreshStatus, 5000);
10
- }
11
-
12
- async function api(path, method = 'GET', body = null) {
13
- const opts = { method, headers: { 'Content-Type': 'application/json' } };
14
- if (body) opts.body = JSON.stringify(body);
15
- const res = await fetch(API + path, opts);
16
- return res.json();
17
- }
18
-
19
- async function apiBinary(path) {
20
- const res = await fetch(API + path);
21
- return res;
22
- }
23
-
24
- // ─── Providers ───
25
- async function refreshProviders() {
26
- try {
27
- const data = await api('/api/providers');
28
- const sel = document.getElementById('providerSelect');
29
- sel.innerHTML = '<option value="">— Select Provider —</option>';
30
- for (const p of data.providers || []) {
31
- sel.innerHTML += `<option value="${p.type}">${p.category === 'ide' ? '💻' : p.category === 'extension' ? '🧩' : '⌨️'} ${p.name} (${p.type})</option>`;
32
- }
33
- sel.onchange = () => {
34
- currentProvider = sel.value;
35
- updateScriptMenu();
36
- exitEditMode();
37
- };
38
- } catch (e) {
39
- log('❌ Cannot reach DevServer: ' + e.message, 'error');
40
- }
41
- }
42
-
43
- async function refreshTargets() {
44
- try {
45
- const data = await api('/api/cdp/targets');
46
- const sel = document.getElementById('ideTargetSelect');
47
- sel.innerHTML = '<option value="">Auto</option>';
48
- for (const t of data.targets || []) {
49
- const dot = t.connected ? '🟢' : '🔴';
50
- sel.innerHTML += `<option value="${t.ide}">${dot} ${t.ide} (:${t.port})</option>`;
51
- }
52
- } catch {}
53
- }
54
-
55
- // ─── Script Menu ───
56
- function updateScriptMenu() {
57
- const dropdown = document.getElementById('scriptDropdown');
58
- dropdown.innerHTML = '';
59
- if (!currentProvider) return;
60
-
61
- api('/api/providers').then(data => {
62
- const provider = (data.providers || []).find(p => p.type === currentProvider);
63
- if (!provider) return;
64
- for (const script of provider.scripts || []) {
65
- const item = document.createElement('div');
66
- item.className = 'item';
67
- item.style.cssText = 'display:flex;justify-content:space-between;align-items:center;';
68
- const left = document.createElement('span');
69
- left.textContent = script;
70
- left.style.cursor = 'pointer';
71
- left.onclick = () => loadProviderScript(script);
72
- left.title = 'Load into editor';
73
- const right = document.createElement('div');
74
- right.style.cssText = 'display:flex;gap:4px;';
75
- // Quick run button (no params)
76
- const runBtn = document.createElement('button');
77
- runBtn.textContent = '▶';
78
- runBtn.title = 'Quick run (no params)';
79
- runBtn.style.cssText = 'padding:2px 6px;font-size:10px;background:var(--accent);color:#000;border:none;border-radius:3px;cursor:pointer;';
80
- runBtn.onclick = (e) => { e.stopPropagation(); dropdown.classList.remove('show'); runProviderScript(script); };
81
- right.appendChild(runBtn);
82
- // Params run button
83
- const paramsBtn = document.createElement('button');
84
- paramsBtn.textContent = '⚙ params';
85
- paramsBtn.title = 'Run with custom JSON params';
86
- paramsBtn.style.cssText = 'padding:2px 8px;font-size:10px;background:var(--bg-input);border:1px solid var(--border);color:var(--text-dim);border-radius:3px;cursor:pointer;display:flex;align-items:center;gap:4px;';
87
- paramsBtn.onmouseover = () => { paramsBtn.style.color = 'var(--text)'; paramsBtn.style.borderColor = 'var(--text-dim)'; };
88
- paramsBtn.onmouseout = () => { paramsBtn.style.color = 'var(--text-dim)'; paramsBtn.style.borderColor = 'var(--border)'; };
89
- paramsBtn.onclick = (e) => { e.stopPropagation(); dropdown.classList.remove('show'); showParamsPrompt(script); };
90
- right.appendChild(paramsBtn);
91
- item.appendChild(left);
92
- item.appendChild(right);
93
- dropdown.appendChild(item);
94
- }
95
- });
96
- }
97
-
98
- function showParamsPrompt(scriptName) {
99
- const raw = prompt(`Params for "${scriptName}" (JSON object):\n\nExamples:\n {"text": "hello"}\n {"buttonText": "Accept"}\n {"index": 0, "button": "Restart"}`, '{}');
100
- if (raw === null) return;
101
- try {
102
- const params = JSON.parse(raw);
103
- runProviderScript(scriptName, params);
104
- } catch (e) {
105
- log('⚠ Invalid JSON: ' + e.message, 'warn');
106
- }
107
- }
108
-
109
- document.getElementById('scriptBtn').onclick = (e) => {
110
- e.stopPropagation();
111
- const d = document.getElementById('scriptDropdown');
112
- d.classList.toggle('show');
113
- };
114
- document.addEventListener('click', () => {
115
- document.getElementById('scriptDropdown').classList.remove('show');
116
- });
117
-
118
- async function loadProviderScript(scriptName) {
119
- document.getElementById('scriptDropdown').classList.remove('show');
120
- const editor = document.getElementById('editor');
121
- activeScript = scriptName; // track which script is being edited
122
-
123
- // Show script editing mode indicator
124
- const tabHeader = document.querySelector('.editor-header');
125
- document.querySelectorAll('.editor-header .tab').forEach(t => t.classList.remove('active'));
126
-
127
- // Add/update script mode indicator (replaces Editor tab with script name + close button)
128
- let scriptTab = document.getElementById('scriptModeTab');
129
- if (!scriptTab) {
130
- scriptTab = document.createElement('div');
131
- scriptTab.id = 'scriptModeTab';
132
- scriptTab.style.cssText = 'display:inline-flex;align-items:center;gap:6px;padding:4px 10px;background:rgba(88,166,255,0.15);color:var(--accent);font-weight:600;border-radius:4px;margin-left:4px;font-size:12px;';
133
- }
134
- scriptTab.innerHTML = `✏️ Editing: <b>${scriptName}</b> <button id="scriptModeClose" style="background:none;border:1px solid var(--border);color:var(--text-dim);cursor:pointer;border-radius:3px;padding:1px 6px;font-size:10px;margin-left:4px" title="Exit edit mode and restore editor">✕ Close</button>`;
135
- tabHeader.appendChild(scriptTab);
136
- document.getElementById('scriptModeClose').onclick = () => {
137
- activeScript = null;
138
- scriptTab.remove();
139
- editor.value = editor.dataset.backup || '';
140
- document.querySelector('.editor-header .tab[data-tab="editor"]').classList.add('active');
141
- updateSaveBtn();
142
- editor.style.borderLeft = '';
143
- document.getElementById('saveBtn').style.display = 'none';
144
- log('✏️ Script edit mode closed, editor restored.', 'log');
145
- };
146
-
147
- // 탭 클릭했을 때 복구를 위한 임시 저장
148
- scriptTab.onclick = (e) => {
149
- // 닫기 버튼이 아닌 여백/글씨 영역을 클릭했을 때
150
- if (e.target.id !== 'scriptModeClose') {
151
- document.querySelectorAll('.editor-header .tab, #scriptModeTab').forEach(t => t.classList.remove('active'));
152
- scriptTab.classList.add('active');
153
- document.getElementById('editor').value = scriptTab.dataset.scriptCode || '';
154
- document.getElementById('saveBtn').style.display = 'inline-block';
155
- document.getElementById('editor').style.borderLeft = '3px solid var(--accent)';
156
- }
157
- };
158
- scriptTab.classList.add('active');
159
-
160
- try {
161
- const result = await api(`/api/providers/${currentProvider}/source`);
162
- const source = result.source || '';
163
- // Extract function body using brace counting (regex fails with template literals)
164
- const fnStart = source.indexOf(scriptName + '(');
165
- if (fnStart >= 0) {
166
- const braceStart = source.indexOf('{', fnStart);
167
- if (braceStart >= 0) {
168
- let depth = 1, i = braceStart + 1;
169
- let inTmpl = false, inStr = null;
170
- while (i < source.length && depth > 0) {
171
- const ch = source[i], prev = source[i-1] || '';
172
- if (!inStr && !inTmpl && ch === '`') inTmpl = true;
173
- else if (inTmpl && ch === '`' && prev !== '\\') inTmpl = false;
174
- else if (!inStr && !inTmpl && (ch === "'" || ch === '"')) inStr = ch;
175
- else if (inStr && ch === inStr && prev !== '\\') inStr = null;
176
- else if (!inStr && !inTmpl) { if (ch === '{') depth++; else if (ch === '}') depth--; }
177
- if (depth > 0) i++;
178
- }
179
- if (depth === 0) {
180
- const body = source.slice(braceStart + 1, i).trim();
181
- // Extract CDP code from template literal: return `...code...`;
182
- const tmplMatch = body.match(/return\s*`([\s\S]*)`;?\s*$/);
183
- if (tmplMatch) {
184
- // Unescape template literal chars
185
- editor.value = tmplMatch[1].replace(/\\`/g, '`').replace(/\\\$\{/g, '${');
186
- } else if (body.includes('return null')) {
187
- editor.value = getScriptTemplate(scriptName);
188
- } else {
189
- editor.value = body;
190
- }
191
- } else {
192
- editor.value = getScriptTemplate(scriptName);
193
- }
194
- } else {
195
- editor.value = getScriptTemplate(scriptName);
196
- }
197
- } else {
198
- editor.value = getScriptTemplate(scriptName);
199
- }
200
- } catch {
201
- editor.value = getScriptTemplate(scriptName);
202
- }
203
- updateSaveBtn();
204
- document.getElementById('saveBtn').style.display = 'inline-block';
205
- editor.style.borderLeft = '3px solid var(--accent)';
206
- scriptTab.dataset.scriptCode = editor.value; // 백업용 저장
207
- log(`✏️ Script edit mode: ${scriptName} — Edit the CDP JS below, click ▶ Run to test, 💾 Save to update provider.js`, 'info');
208
- }
209
-
210
- let activeScript = null; // current script being edited
211
-
212
- function getScriptTemplate(script) {
213
- const templates = {
214
- readChat: `(() => {
215
- try {
216
- const messages = [];
217
- let status = 'idle';
218
- // TODO: Find your message elements
219
- // Use 🔍 DOM to discover selectors
220
- // const msgEls = document.querySelectorAll('[data-message-id]');
221
- return JSON.stringify({ messages, status, id: 'active', title: document.title });
222
- } catch(e) {
223
- return JSON.stringify({ messages: [], status: 'error', error: e.message });
224
- }
225
- })()`,
226
- sendMessage: `(async () => {
227
- const msg = \${TEXT};
228
- // TODO: Type and send message
229
- // Return needsTypeAndSend if using CDP type method
230
- return JSON.stringify({ sent: false, needsTypeAndSend: true, selector: '[contenteditable]' });
231
- })()`,
232
- listSessions: `(() => {
233
- const sessions = [];
234
- // TODO: Find session list elements
235
- return JSON.stringify(sessions);
236
- })()`,
237
-
238
- openPanel: `(() => {
239
- // TODO: Open the chat/agent panel if hidden
240
- const sidebar = document.getElementById('workbench.parts.auxiliarybar');
241
- if (sidebar && sidebar.offsetWidth > 0) return 'visible';
242
- // Try clicking a toggle button
243
- return 'not_found';
244
- })()`,
245
- };
246
- return templates[script] || `(() => {\n // TODO: Implement ${script}\n return JSON.stringify({});\n})()`;
247
- }
248
-
249
- function updateSaveBtn() {
250
- const saveBtn = document.getElementById('saveBtn');
251
- if (activeScript) {
252
- saveBtn.textContent = '💾 Save Script';
253
- saveBtn.title = `Save ${activeScript} to provider.js`;
254
- } else {
255
- saveBtn.textContent = '💾 Save & Run';
256
- saveBtn.title = 'Save provider.js and re-run last script';
257
- }
258
- }
259
-
260
- async function runProviderScript(scriptName, params) {
261
- if (scriptName && !params) lastRunScript = scriptName; // track for Save & Run
262
- const ideTarget = document.getElementById('ideTargetSelect').value || undefined;
263
- const start = Date.now();
264
- try {
265
- const result = await api(`/api/providers/${currentProvider}/script`, 'POST', {
266
- script: scriptName,
267
- params: params || {},
268
- ideType: ideTarget,
269
- });
270
- const elapsed = Date.now() - start;
271
- document.getElementById('execTime').textContent = `${elapsed}ms`;
272
- // null 또는 undefined 처리
273
- const resValue = result.result !== undefined ? result.result : result;
274
- showResult(resValue, result.error ? 'error' : 'ok');
275
- } catch (e) {
276
- showResult({ error: e.message }, 'error');
277
- }
278
- }
279
-
280
- // ─── Run Script ───
281
- document.getElementById('runBtn').onclick = runEditor;
282
- // CM handles Ctrl+Enter via keymap; expose runEditor for CM module
283
- window.runEditor = runEditor;
284
-
285
- // ─── Helper runtime injection ───
286
- const HELPER_PREAMBLE = `
287
- var __logs = [];
288
- function log() { var args = Array.prototype.slice.call(arguments); __logs.push(args.map(function(a) { return typeof a === 'object' ? JSON.stringify(a) : String(a); }).join(' ')); }
289
- function queryAll(sel, limit) {
290
- var els = Array.from(document.querySelectorAll(sel)).slice(0, limit || 20);
291
- return els.map(function(el, i) {
292
- var r = el.getBoundingClientRect();
293
- return { index: i, tag: el.tagName.toLowerCase(), id: el.id || undefined, text: (el.textContent||'').trim().substring(0,100), visible: el.offsetWidth > 0, bounds: { top: Math.round(r.top), left: Math.round(r.left), w: Math.round(r.width), h: Math.round(r.height) } };
294
- });
295
- }
296
- function click(sel) {
297
- var el = document.querySelector(sel);
298
- if (!el) return false;
299
- el.click();
300
- return true;
301
- }
302
- function getStyle(el, prop) { return getComputedStyle(el)[prop]; }
303
- function sleep(ms) { return new Promise(function(r) { setTimeout(r, ms); }); }
304
- function waitFor(sel, timeout) {
305
- timeout = timeout || 5000;
306
- return new Promise(function(resolve) {
307
- var start = Date.now();
308
- (function check() {
309
- var el = document.querySelector(sel);
310
- if (el) return resolve(el);
311
- if (Date.now() - start > timeout) return resolve(null);
312
- setTimeout(check, 200);
313
- })();
314
- });
315
- }
316
- `;
317
-
318
- function wrapWithHelpers(code) {
319
- // If the code is an IIFE or expression, prepend "return await" to catch its result.
320
- // Otherwise, run it directly (assuming user will use "return" for raw scripts, or just wants to run statements).
321
- const isIIFE = /^\s*\(?\s*(async\s*)?\(\s*\)\s*=>\s*\{/.test(code);
322
- const wrappedBody = isIIFE ? `return await ${code};` : code;
323
-
324
- return `(async () => {
325
- ${HELPER_PREAMBLE}
326
- try {
327
- const __result = await (async () => {
328
- ${wrappedBody}
329
- })();
330
- return JSON.stringify({ __helpers: true, logs: __logs, result: __result });
331
- } catch(e) {
332
- return JSON.stringify({ __helpers: true, logs: __logs, error: Object.getOwnPropertyNames(e).reduce((a, k) => { a[k] = e[k]; return a; }, {}) });
333
- }
334
- })()`;
335
- }
336
-
337
- async function runEditor() {
338
- const code = document.getElementById('editor').value.trim();
339
- if (!code) return;
340
-
341
- const ideTarget = document.getElementById('ideTargetSelect').value || undefined;
342
- const wrappedCode = wrapWithHelpers(code);
343
- const start = Date.now();
344
- try {
345
- const result = await api('/api/cdp/evaluate', 'POST', {
346
- expression: wrappedCode,
347
- timeout: 30000,
348
- ideType: ideTarget,
349
- });
350
- const elapsed = Date.now() - start;
351
- document.getElementById('execTime').textContent = `${elapsed}ms`;
352
-
353
- // Parse helper wrapper result
354
- let raw = result.result !== undefined ? result.result : result;
355
- let parsed = null;
356
- if (typeof raw === 'object' && raw !== null && raw.__helpers) {
357
- parsed = raw;
358
- } else if (typeof raw === 'string') {
359
- try { parsed = JSON.parse(raw); } catch(e) {}
360
- }
361
-
362
- if (parsed && parsed.__helpers) {
363
- // Show logs first
364
- for (const l of (parsed.logs || [])) {
365
- appendOutput(l, 'log');
366
- }
367
- // Show result or error
368
- if (parsed.error) {
369
- appendOutput(parsed.error, 'error');
370
- setBadge('error');
371
- } else {
372
- const val = parsed.result;
373
- // Try to parse if JSON string
374
- let display = val;
375
- try {
376
- if (typeof val === 'string') display = JSON.parse(val);
377
- } catch(e) {}
378
- appendOutput(typeof display === 'object' ? JSON.stringify(display, null, 2) : String(display ?? 'undefined'), 'result');
379
- setBadge('ok');
380
- }
381
- } else {
382
- // Fallback: non-helper result
383
- showResult(raw, result.error ? 'error' : 'ok');
384
- }
385
- } catch (e) {
386
- appendOutput(e.message, 'error');
387
- setBadge('error');
388
- }
389
- }
390
-
391
-
392
- // ─── Screenshot ───
393
- document.getElementById('screenshotBtn').onclick = takeScreenshot;
394
- async function takeScreenshot() {
395
- const ideTarget = document.getElementById('ideTargetSelect').value || '';
396
- try {
397
- const res = await apiBinary('/api/cdp/screenshot' + (ideTarget ? '?ideType=' + ideTarget : ''));
398
- if (!res.ok) {
399
- const err = await res.json();
400
- log('❌ Screenshot failed: ' + err.error, 'error');
401
- return;
402
- }
403
- const blob = await res.blob();
404
- const url = URL.createObjectURL(blob);
405
- const img = document.getElementById('screenshotImg');
406
- img.src = url;
407
- img.style.display = 'block';
408
- document.getElementById('screenshotPlaceholder').style.display = 'none';
409
- } catch (e) {
410
- log('❌ Screenshot: ' + e.message, 'error');
411
- }
412
- }
413
-
414
- // ─── DOM Inspector (click-to-inspect) ───
415
- // object-fit: contain 보정: 이미지 실제 표시 영역 계산
416
- function getContainedImageRect(img) {
417
- const containerW = img.clientWidth;
418
- const containerH = img.clientHeight;
419
- const natW = img.naturalWidth || 1;
420
- const natH = img.naturalHeight || 1;
421
- const scale = Math.min(containerW / natW, containerH / natH);
422
- const renderedW = natW * scale;
423
- const renderedH = natH * scale;
424
- const offsetX = (containerW - renderedW) / 2;
425
- const offsetY = (containerH - renderedH) / 2;
426
- return { offsetX, offsetY, renderedW, renderedH, scale };
427
- }
428
-
429
- document.getElementById('screenshotImg').addEventListener('click', async (e) => {
430
- const img = e.target;
431
- const imgRect = img.getBoundingClientRect();
432
- const { offsetX, offsetY, renderedW, renderedH, scale } = getContainedImageRect(img);
433
- // 클릭 좌표를 이미지 내부 좌표로 변환
434
- const clickX = e.clientX - imgRect.left - offsetX;
435
- const clickY = e.clientY - imgRect.top - offsetY;
436
- // 이미지 밖 클릭 무시
437
- if (clickX < 0 || clickY < 0 || clickX > renderedW || clickY > renderedH) return;
438
- const px = Math.round(clickX / scale);
439
- const py = Math.round(clickY / scale);
440
- const ch = document.getElementById('inspectCrosshair');
441
- ch.style.display = 'block';
442
- ch.style.left = (offsetX + clickX) + 'px';
443
- ch.style.top = (offsetY + clickY) + 'px';
444
- const ideTarget = document.getElementById('ideTargetSelect').value || undefined;
445
- try {
446
- const result = await api('/api/cdp/dom/inspect', 'POST', { x: px, y: py, ideType: ideTarget });
447
- if (result.error) { showResult({ error: result.error }, 'error'); return; }
448
- renderInspector(result);
449
- } catch (err) {
450
- showResult({ error: 'Inspect failed: ' + err.message }, 'error');
451
- }
452
- });
453
-
454
- async function inspectSelector(selector) {
455
- const ideTarget = document.getElementById('ideTargetSelect').value || undefined;
456
- try {
457
- const result = await api('/api/cdp/dom/inspect', 'POST', { selector, ideType: ideTarget });
458
- if (result.error) { showResult({ error: result.error }, 'error'); return; }
459
- renderInspector(result);
460
- } catch (err) {
461
- showResult({ error: 'Inspect failed: ' + err.message }, 'error');
462
- }
463
- }
464
-
465
- function htmlEsc(s) { return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
466
- function jsEsc(s) { return (s||'').replace(/\\/g,'\\\\').replace(/'/g,"\\'"); }
467
- function insertSelector(sel) {
468
- const ed = document.getElementById('editor');
469
- const pos = ed.selectionStart || ed.value.length;
470
- ed.value = ed.value.slice(0, pos) + "document.querySelector('" + sel + "')" + ed.value.slice(pos);
471
- ed.focus();
472
- }
473
-
474
- function renderInspector(data) {
475
- const { element, ancestors, children } = data;
476
- const out = document.getElementById('output');
477
- out.innerHTML = '';
478
- const badge = document.getElementById('outputBadge');
479
- badge.style.display = 'inline-block';
480
- badge.textContent = '🔍';
481
- badge.className = 'badge ok';
482
-
483
- // Breadcrumb
484
- const bc = document.createElement('div');
485
- bc.style.cssText = 'padding:6px 10px;background:var(--bg-input);border-radius:6px;margin-bottom:8px;font-size:12px;overflow-x:auto;white-space:nowrap;';
486
- let bcHTML = '';
487
- for (const a of ancestors) {
488
- bcHTML += '<span class="bc-node" data-sel="' + htmlEsc(a.selector) + '" style="cursor:pointer;color:var(--text-dim)">' + htmlEsc(a.tag);
489
- if (a.cls.length) bcHTML += '<span style="color:var(--accent)">.' + htmlEsc(a.cls[0]) + '</span>';
490
- bcHTML += '</span> <span style="color:var(--text-dim)">›</span> ';
491
- }
492
- bcHTML += '<span style="color:var(--accent);font-weight:600">' + htmlEsc(element.tag);
493
- if (element.cls.length) bcHTML += '.' + htmlEsc(element.cls[0]);
494
- bcHTML += '</span>';
495
- bc.innerHTML = bcHTML;
496
- bc.querySelectorAll('.bc-node').forEach(n => {
497
- n.onmouseover = () => n.style.color = 'var(--accent)';
498
- n.onmouseout = () => n.style.color = 'var(--text-dim)';
499
- n.onclick = () => inspectSelector(n.dataset.sel);
500
- });
501
- out.appendChild(bc);
502
-
503
- // Element details
504
- const det = document.createElement('div');
505
- det.style.cssText = 'padding:10px;background:var(--bg-input);border-radius:6px;margin-bottom:8px;font-size:12px;';
506
- let h = '<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">';
507
- h += '<span style="font-weight:700;color:var(--accent);font-size:14px">&lt;' + htmlEsc(element.tag) + '&gt;</span>';
508
- if (element.rect) h += '<span style="color:var(--text-dim)">' + element.rect.w + '×' + element.rect.h + ' @ (' + element.rect.x + ',' + element.rect.y + ')</span>';
509
- h += '<span style="color:var(--text-dim)">' + element.childCount + ' children</span></div>';
510
- // Full selector + copy/insert
511
- h += '<div style="margin-bottom:6px;display:flex;align-items:center;gap:6px">';
512
- h += '<code style="background:#000;color:var(--accent);padding:3px 8px;border-radius:4px;font-size:11px;flex:1;overflow:hidden;text-overflow:ellipsis">' + htmlEsc(element.fullSelector) + '</code>';
513
- h += '<button onclick="navigator.clipboard.writeText(\'' + jsEsc(element.fullSelector) + '\');this.textContent=\'✓\';setTimeout(()=>this.textContent=\'📋\',800)" style="padding:2px 6px;font-size:11px;background:transparent;border:1px solid var(--border);border-radius:3px;cursor:pointer;color:var(--text-dim)">📋</button>';
514
- h += '<button onclick="insertSelector(\'' + jsEsc(element.fullSelector) + '\')" style="padding:2px 6px;font-size:11px;background:var(--accent);color:#000;border:none;border-radius:3px;cursor:pointer;font-weight:600">→ Editor</button>';
515
- h += '</div>';
516
- // Classes
517
- if (element.cls.length) {
518
- h += '<div style="margin-bottom:4px"><span style="color:var(--text-dim)">classes:</span> ';
519
- h += element.cls.map(c => '<span style="background:rgba(0,255,136,0.15);color:var(--accent);padding:1px 6px;border-radius:3px;font-size:11px;margin-right:3px">.' + htmlEsc(c) + '</span>').join('');
520
- h += '</div>';
521
- }
522
- // Attributes
523
- const attrKeys = Object.keys(element.attrs || {});
524
- if (attrKeys.length) {
525
- h += '<div style="margin-bottom:4px"><span style="color:var(--text-dim)">attrs:</span> ';
526
- h += attrKeys.map(k => '<span style="color:#ff9f43">' + htmlEsc(k) + '</span>=<span style="color:#a29bfe">"' + htmlEsc(element.attrs[k]) + '"</span>').join(' ');
527
- h += '</div>';
528
- }
529
- // Direct text
530
- if (element.directText) {
531
- h += '<div><span style="color:var(--text-dim)">text:</span> <span style="color:#ddd">"' + htmlEsc(element.directText.substring(0, 80)) + '"</span></div>';
532
- }
533
- det.innerHTML = h;
534
- out.appendChild(det);
535
-
536
- // Children tree
537
- if (children && children.length) {
538
- const tree = document.createElement('div');
539
- tree.style.cssText = 'font-size:12px;';
540
- const tt = document.createElement('div');
541
- tt.style.cssText = 'font-weight:600;margin-bottom:6px;color:var(--text-dim);padding:0 4px;';
542
- tt.textContent = 'Children (' + children.length + ')';
543
- tree.appendChild(tt);
544
- for (const child of children) {
545
- if (!child) continue;
546
- const row = document.createElement('div');
547
- row.style.cssText = 'padding:4px 8px;border-left:2px solid var(--border);margin-left:8px;margin-bottom:2px;cursor:pointer;border-radius:0 4px 4px 0;transition:background 0.15s;';
548
- row.onmouseover = () => row.style.background = 'rgba(255,255,255,0.05)';
549
- row.onmouseout = () => row.style.background = 'transparent';
550
- let lbl = '<span style="color:var(--accent)">&lt;' + htmlEsc(child.tag) + '&gt;</span>';
551
- if (child.cls.length) lbl += '<span style="color:var(--text-dim)">.' + htmlEsc(child.cls[0]) + '</span>';
552
- if (child.childCount > 0) lbl += ' <span style="color:var(--text-dim);font-size:10px">(' + child.childCount + ')</span>';
553
- if (child.directText) lbl += ' <span style="color:#888;font-size:11px">"' + htmlEsc(child.directText.substring(0, 40)) + '"</span>';
554
- const hints = Object.entries(child.attrs || {}).slice(0, 2).map(([k,v]) => k + '="' + (v+'').substring(0, 20) + '"').join(' ');
555
- if (hints) lbl += ' <span style="color:#666;font-size:10px">' + htmlEsc(hints) + '</span>';
556
- row.innerHTML = lbl;
557
- row.onclick = () => inspectSelector(child.selector);
558
- tree.appendChild(row);
559
- }
560
- out.appendChild(tree);
561
- }
562
- }
563
-
564
- // ─── Selector Query ───
565
- document.getElementById('selectorBtn').onclick = querySel;
566
- document.getElementById('selectorInput').addEventListener('keydown', (e) => {
567
- if (e.key === 'Enter') querySel();
568
- });
569
-
570
- async function querySel() {
571
- const sel = document.getElementById('selectorInput').value.trim();
572
- if (!sel) return;
573
- const ideTarget = document.getElementById('ideTargetSelect').value || undefined;
574
- try {
575
- const result = await api('/api/cdp/dom/query', 'POST', { selector: sel, limit: 20, ideType: ideTarget });
576
- document.getElementById('selectorCount').textContent = (result.total || 0) + ' matches';
577
-
578
- let output = `🔍 Selector: ${sel}\nTotal matches: ${result.total || 0}\n\n`;
579
- for (const item of (result.results || [])) {
580
- const vis = item.visible ? '✅' : '❌hidden';
581
- const rect = item.rect ? `${item.rect.x},${item.rect.y} ${item.rect.w}×${item.rect.h}` : '';
582
- output += `[${item.index}] <${item.tag}> ${vis} ${rect}\n`;
583
- if (item.id) output += ` id: ${item.id}\n`;
584
- if (item.class) output += ` class: ${item.class.slice(0, 80)}\n`;
585
- if (item.text) output += ` text: "${item.text.slice(0, 80)}"\n`;
586
- if (item.role) output += ` role: ${item.role}\n`;
587
- output += '\n';
588
- }
589
- showResult(output, result.total > 0 ? 'ok' : 'error');
590
-
591
- // Draw overlays on screenshot
592
- drawOverlays(result.results || []);
593
- } catch (e) {
594
- showResult({ error: e.message }, 'error');
595
- }
596
- }
597
-
598
- function drawOverlays(items) {
599
- const panel = document.getElementById('screenshotPanel');
600
- // Remove old overlays
601
- panel.querySelectorAll('.overlay').forEach(el => el.remove());
602
-
603
- const img = document.getElementById('screenshotImg');
604
- if (!img || img.style.display === 'none') return;
605
-
606
- const imgRect = img.getBoundingClientRect();
607
- const panelRect = panel.getBoundingClientRect();
608
-
609
- // We need to know the actual image dimensions to scale
610
- const scaleX = imgRect.width / (img.naturalWidth || 1);
611
- const scaleY = imgRect.height / (img.naturalHeight || 1);
612
- const offsetX = imgRect.left - panelRect.left;
613
- const offsetY = imgRect.top - panelRect.top;
614
-
615
- const colors = ['#58a6ff', '#3fb950', '#d29922', '#f85149', '#bc8cff'];
616
-
617
- items.forEach((item, i) => {
618
- if (!item.rect || !item.visible) return;
619
- const div = document.createElement('div');
620
- div.className = 'overlay';
621
- const color = colors[i % colors.length];
622
- div.style.borderColor = color;
623
- div.style.left = (offsetX + item.rect.x * scaleX) + 'px';
624
- div.style.top = (offsetY + item.rect.y * scaleY) + 'px';
625
- div.style.width = (item.rect.w * scaleX) + 'px';
626
- div.style.height = (item.rect.h * scaleY) + 'px';
627
-
628
- const label = document.createElement('span');
629
- label.className = 'label';
630
- label.style.background = color;
631
- label.textContent = `<${item.tag}>${item.id ? '#' + item.id : item.class ? '.' + item.class.split(' ')[0].slice(0, 20) : ''}`;
632
- div.appendChild(label);
633
-
634
- panel.appendChild(div);
635
- });
636
- }
637
-
638
- // ─── Reload ───
639
- document.getElementById('reloadBtn').onclick = async () => {
640
- try {
641
- const result = await api('/api/providers/reload', 'POST');
642
- if (result.reloaded) {
643
- log(`🔄 Reloaded: ${result.providers?.length || 0} providers`, 'ok');
644
- await refreshProviders();
645
- } else {
646
- log('❌ Reload failed: ' + (result.error || 'unknown'), 'error');
647
- }
648
- } catch (e) {
649
- log('❌ ' + e.message, 'error');
650
- }
651
- };
652
-
653
- // ─── Clear ───
654
- document.getElementById('clearBtn').onclick = () => {
655
- document.getElementById('output').innerHTML = '';
656
- document.getElementById('outputBadge').style.display = 'none';
657
- };
658
-
659
- // ─── Status ───
660
- async function refreshStatus() {
661
- try {
662
- const data = await api('/api/status');
663
- const bar = document.getElementById('statusBar');
664
- const hasCdp = Object.values(data.cdp || {}).some(c => c.connected);
665
- bar.innerHTML = `<span class="dot ${hasCdp ? 'on' : 'off'}"></span> ${hasCdp ? 'CDP Connected' : 'No CDP'} · ${data.providers?.length || 0} providers`;
666
- } catch {
667
- document.getElementById('statusBar').innerHTML = '<span class="dot off"></span> DevServer offline';
668
- }
669
- }
670
-
671
- // ─── Output helpers ───
672
- const OUTPUT_ICONS = { log: '📝', result: '✅', error: '❌', warn: '⚠️', query: '🔍' };
673
-
674
- function getTimestamp() {
675
- const d = new Date();
676
- return d.toTimeString().split(' ')[0].substring(0, 8);
677
- }
678
-
679
- function appendOutput(text, type = 'log') {
680
- const output = document.getElementById('output');
681
- const entry = document.createElement('div');
682
- entry.className = `output-entry type-${type}`;
683
- entry.innerHTML = `<span class="ts">${getTimestamp()}</span><span class="icon">${OUTPUT_ICONS[type] || '•'}</span><span class="content"></span>`;
684
- entry.querySelector('.content').textContent = text;
685
- output.appendChild(entry);
686
- output.scrollTop = output.scrollHeight;
687
- // Apply current filter
688
- const filter = document.getElementById('outputSearch').value.toLowerCase();
689
- if (filter && !text.toLowerCase().includes(filter)) entry.classList.add('hidden');
690
- }
691
-
692
- function setBadge(type) {
693
- const badge = document.getElementById('outputBadge');
694
- badge.style.display = 'inline';
695
- badge.className = 'badge ' + (type === 'error' ? 'err' : 'ok');
696
- badge.textContent = type === 'error' ? 'ERROR' : 'OK';
697
- }
698
-
699
- function showResult(data, type = 'ok') {
700
- let text;
701
- if (typeof data === 'string') {
702
- text = data;
703
- } else {
704
- try { text = JSON.stringify(data, null, 2); } catch { text = String(data); }
705
- }
706
- appendOutput(text, type === 'error' ? 'error' : 'result');
707
- setBadge(type);
708
- }
709
-
710
- function log(msg, type = 'ok') {
711
- appendOutput(msg, type === 'error' ? 'error' : type === 'warn' ? 'warn' : 'log');
712
- setBadge(type);
713
- }
714
-
715
- // Output search filter
716
- document.getElementById('outputSearch').addEventListener('input', (e) => {
717
- const filter = e.target.value.toLowerCase();
718
- document.querySelectorAll('.output-entry').forEach(entry => {
719
- const text = entry.querySelector('.content')?.textContent?.toLowerCase() || '';
720
- entry.classList.toggle('hidden', filter && !text.includes(filter));
721
- });
722
- });
723
-
724
- // ─── Template tab ───
725
- const TEMPLATE = `/**
726
- * MyAgent — Provider Template
727
- *
728
- * Category: 'ide' | 'extension'
729
- * @type {import('../../../src/providers/contracts').ProviderModule}
730
- */
731
- module.exports = {
732
- type: 'my-agent',
733
- name: 'My Agent',
734
- category: 'ide', // or 'extension'
735
-
736
- // Extension only:
737
- // extensionId: 'publisher.extension-name',
738
- // extensionIdPattern: /extensionId=publisher\.extension-name/i,
739
-
740
- inputMethod: 'cdp-type-and-send',
741
- inputSelector: '[contenteditable="true"][role="textbox"]',
742
-
743
- scripts: {
744
- readChat() {
745
- return \`(() => {
746
- // TODO: Parse chat messages from DOM
747
- const messages = [];
748
- let status = 'idle';
749
-
750
- return JSON.stringify({
751
- id: 'active_session',
752
- status,
753
- title: document.title || 'Session',
754
- messages,
755
- inputContent: '',
756
- activeModal: null,
757
- });
758
- })()\`;
759
- },
760
-
761
- sendMessage(text) {
762
- return \`(async () => {
763
- const msg = \${JSON.stringify(text)};
764
- // TODO: Find input, type text, press Enter
765
- return JSON.stringify({ sent: false, needsTypeAndSend: true, selector: '[contenteditable]' });
766
- })()\`;
767
- },
768
-
769
- listSessions() {
770
- return \`(async () => {
771
- // TODO: Parse session list
772
- return JSON.stringify([]);
773
- })()\`;
774
- },
775
-
776
- openPanel() {
777
- return \`(() => {
778
- // TODO: Open chat/agent panel if hidden
779
- // Try clicking toggle button or using keyboard shortcut
780
- const sidebar = document.getElementById('workbench.parts.auxiliarybar');
781
- if (sidebar && sidebar.offsetWidth > 0) return 'visible';
782
- // Try toggle button
783
- const btns = Array.from(document.querySelectorAll('li.action-item a, button'));
784
- const toggle = btns.find(b => /agent|chat|composer|panel/i.test(b.textContent || b.getAttribute('aria-label') || ''));
785
- if (toggle) { toggle.click(); return 'opened'; }
786
- return 'not_found';
787
- })()\`;
788
- },
789
- },
790
- };
791
- `;
792
-
793
- const CONTRACTS = `// ═══ Output Contracts Reference ═══
794
- // All script functions must return JSON.stringify(result)
795
-
796
- // ─── readChat() ───────────────────
797
- // Returns: { messages, status, ... }
798
- {
799
- messages: [
800
- { role: 'user' | 'assistant' | 'system',
801
- content: string, // message text
802
- id?: string, // optional unique ID
803
- index?: number }, // optional order
804
- ],
805
- status: 'idle' | 'generating' | 'waiting_approval' | 'error' | 'streaming',
806
- id?: string, // session ID
807
- title?: string, // session title
808
- activeModal?: { // if approval dialog is shown
809
- message: string,
810
- buttons: ['Accept', 'Reject'],
811
- } | null,
812
- inputContent?: string, // current text in input box
813
- }
814
-
815
- // ─── sendMessage(text) ────────────
816
- // Returns: { sent, error?, needsTypeAndSend?, selector? }
817
- {
818
- sent: true, // message was sent
819
- }
820
- // OR if input requires typeAndSend:
821
- {
822
- sent: false,
823
- needsTypeAndSend: true, // daemon will use CDP Input API
824
- selector: '[contenteditable]',
825
- }
826
-
827
- // ─── listSessions() ──────────────
828
- // Returns: array of sessions
829
- [
830
- { id: 'abc123', title: 'Session 1', time?: '2m ago' },
831
- ]
832
-
833
- // ─── switchSession(sessionId) ────
834
- // Returns: { switched: true }
835
- {
836
- switched: true,
837
- }
838
-
839
- // ─── resolveAction({ action, button? }) ────
840
- // Returns: { resolved: true, clicked: 'Accept' }
841
- {
842
- resolved: true, clicked: 'Accept',
843
- }
844
-
845
- // ─── openPanel() ─────────────────
846
- // Returns: string
847
- 'visible' // panel was already visible
848
- 'opened' // panel was opened
849
- 'not_found' // could not find toggle button
850
- `;
851
-
852
- document.querySelectorAll('.editor-header .tab').forEach(tab => {
853
- tab.addEventListener('click', () => {
854
- document.querySelectorAll('.editor-header .tab').forEach(t => t.classList.remove('active'));
855
- tab.classList.add('active');
856
- const editor = document.getElementById('editor');
857
- if (tab.dataset.tab === 'template') {
858
- editor.dataset.backup = editor.value;
859
- editor.value = TEMPLATE;
860
- } else if (tab.dataset.tab === 'contracts') {
861
- editor.dataset.backup = editor.value;
862
- editor.value = CONTRACTS;
863
- } else {
864
- if (editor.dataset.backup) {
865
- editor.value = editor.dataset.backup;
866
- delete editor.dataset.backup;
867
- }
868
- }
869
- });
870
- });
871
-
872
- // ─── Watch Mode (SSE) ───
873
- let watching = false;
874
- let eventSource = null;
875
-
876
- document.getElementById('watchBtn').onclick = async () => {
877
- if (watching) {
878
- // Stop
879
- await api('/api/watch/stop', 'POST');
880
- watching = false;
881
- document.getElementById('watchBtn').textContent = '👁 Watch';
882
- document.getElementById('watchIndicator').classList.remove('active');
883
- if (eventSource) { eventSource.close(); eventSource = null; }
884
- return;
885
- }
886
-
887
- if (!currentProvider) {
888
- log('⚠ Select a provider first', 'warn');
889
- return;
890
- }
891
-
892
- // Start SSE
893
- eventSource = new EventSource(API + '/api/watch/events');
894
- eventSource.onmessage = (e) => {
895
- try {
896
- const data = JSON.parse(e.data);
897
- if (data.type === 'watch_result') {
898
- document.getElementById('execTime').textContent = `${data.elapsed}ms`;
899
- showResult(data.result, 'ok');
900
- } else if (data.type === 'watch_error') {
901
- showResult({ error: data.error }, 'error');
902
- } else if (data.type === 'watch_stopped') {
903
- watching = false;
904
- document.getElementById('watchBtn').textContent = '👁 Watch';
905
- document.getElementById('watchIndicator').classList.remove('active');
906
- }
907
- } catch {}
908
- };
909
-
910
- // Start watch
911
- await api('/api/watch/start', 'POST', {
912
- type: currentProvider,
913
- script: 'readChat',
914
- interval: 3000,
915
- });
916
- watching = true;
917
- document.getElementById('watchBtn').textContent = '⏹ Stop';
918
- document.getElementById('watchIndicator').classList.add('active');
919
- log(`👁 Watching: ${currentProvider} → readChat (every 3s)`, 'ok');
920
- };
921
-
922
- // ─── Source View ───
923
- document.getElementById('sourceBtn').onclick = async () => {
924
- if (!currentProvider) {
925
- log('⚠ Select a provider first', 'warn');
926
- return;
927
- }
928
- try {
929
- const result = await api(`/api/providers/${currentProvider}/source`);
930
- if (result.error) {
931
- log('❌ ' + result.error, 'error');
932
- return;
933
- }
934
- const editor = document.getElementById('editor');
935
- editor.dataset.editMode = 'true';
936
- editor.dataset.editType = currentProvider;
937
- editor.dataset.editPath = result.path;
938
- editor.dataset.backup = editor.value;
939
- editor.value = result.source;
940
-
941
- // Show save button, highlight editor
942
- document.getElementById('saveBtn').style.display = 'inline-block';
943
- editor.style.borderLeft = '3px solid var(--accent-green)';
944
-
945
- // Switch to editor tab
946
- document.querySelectorAll('.editor-header .tab').forEach(t => t.classList.remove('active'));
947
- document.querySelector('[data-tab="editor"]').classList.add('active');
948
- log(`📄 Editing: ${result.path} — make changes and click 💾 Save`, 'ok');
949
- } catch (e) {
950
- log('❌ ' + e.message, 'error');
951
- }
952
- };
953
-
954
- function exitEditMode() {
955
- const editor = document.getElementById('editor');
956
- editor.dataset.editMode = '';
957
- editor.style.borderLeft = '';
958
- document.getElementById('saveBtn').style.display = 'none';
959
- }
960
-
961
- let lastRunScript = 'readChat'; // Track last-run script for Save & Run
962
-
963
- // Save & Run handler
964
- document.getElementById('saveBtn').onclick = async () => {
965
- const editor = document.getElementById('editor');
966
-
967
- // Script edit mode — save individual script to provider.js
968
- if (activeScript && currentProvider) {
969
- const code = editor.value.trim();
970
- if (!code) { log('⚠ Editor is empty', 'warn'); return; }
971
- try {
972
- const result = await api(`/api/providers/${currentProvider}/script-save`, 'POST', {
973
- script: activeScript,
974
- code: code,
975
- });
976
- if (result.error) {
977
- log('❌ Script save failed: ' + result.error, 'error');
978
- return;
979
- }
980
- log(`💾 Saved: ${activeScript} → provider.js — Testing...`, 'ok');
981
- await refreshProviders();
982
- document.getElementById('providerSelect').value = currentProvider;
983
- updateScriptMenu();
984
- // Auto-run the saved script via provider
985
- runProviderScript(activeScript);
986
- } catch (e) {
987
- log('❌ ' + e.message, 'error');
988
- }
989
- return;
990
- }
991
-
992
- // Edit Source mode — save whole provider.js
993
- const type = editor.dataset.editType;
994
- if (!type) {
995
- log('⚠ No provider loaded for editing', 'warn');
996
- return;
997
- }
998
- try {
999
- const result = await api(`/api/providers/${type}/save`, 'POST', { source: editor.value });
1000
- if (result.error) {
1001
- log('❌ Save failed: ' + result.error, 'error');
1002
- return;
1003
- }
1004
- log(`💾 Saved: ${result.path} (${result.chars} chars) — running ${lastRunScript}...`, 'ok');
1005
- await refreshProviders();
1006
- document.getElementById('providerSelect').value = type;
1007
- currentProvider = type;
1008
- updateScriptMenu();
1009
- // Auto-run last script
1010
- runProviderScript(lastRunScript);
1011
- } catch (e) {
1012
- log('❌ ' + e.message, 'error');
1013
- }
1014
- };
1015
-
1016
- // 🔍 DOM Debug — analyze inputs, buttons, textareas, editables
1017
- document.getElementById('domDebugBtn').onclick = async () => {
1018
- const ideTarget = document.getElementById('ideTargetSelect').value || undefined;
1019
- const start = Date.now();
1020
- try {
1021
- const debugExpr = `(() => {
1022
- const r = { url: location.href, title: document.title,
1023
- viewport: { w: innerWidth, h: innerHeight },
1024
- inputs: [], textareas: [], editables: [], textboxes: [], buttons: [], iframes: [] };
1025
- document.querySelectorAll('input[type="text"],input:not([type])').forEach((el,i) => {
1026
- if(i>=10) return;
1027
- r.inputs.push({ id:el.id||null, class:(el.className||'').toString().slice(0,100),
1028
- placeholder:el.getAttribute('placeholder')||null, visible:el.offsetParent!==null });
1029
- });
1030
- document.querySelectorAll('textarea').forEach((el,i) => {
1031
- if(i>=10) return;
1032
- r.textareas.push({ id:el.id||null, class:(el.className||'').toString().slice(0,100),
1033
- placeholder:el.getAttribute('placeholder')||null, rows:el.rows, visible:el.offsetParent!==null });
1034
- });
1035
- document.querySelectorAll('[contenteditable="true"]').forEach((el,i) => {
1036
- if(i>=10) return;
1037
- r.editables.push({ tag:el.tagName?.toLowerCase(), id:el.id||null,
1038
- class:(el.className||'').toString().slice(0,100), role:el.getAttribute('role')||null,
1039
- text:(el.textContent||'').trim().slice(0,80), visible:el.offsetParent!==null });
1040
- });
1041
- document.querySelectorAll('[role="textbox"]').forEach((el,i) => {
1042
- if(i>=10) return;
1043
- r.textboxes.push({ tag:el.tagName?.toLowerCase(), id:el.id||null,
1044
- class:(el.className||'').toString().slice(0,80), visible:el.offsetParent!==null });
1045
- });
1046
- const bk=/send|submit|accept|reject|approve|deny|cancel|confirm|run|execute|apply/i;
1047
- document.querySelectorAll('button,[role="button"]').forEach((el,i) => {
1048
- const t=(el.textContent||el.getAttribute('aria-label')||'').trim();
1049
- if(i<30&&(t.length<30||bk.test(t)))
1050
- r.buttons.push({ tag:el.tagName?.toLowerCase(), text:t.slice(0,60),
1051
- disabled:el.disabled||el.getAttribute('disabled')!==null, visible:el.offsetParent!==null });
1052
- });
1053
- document.querySelectorAll('iframe,webview').forEach((el,i) => {
1054
- if(i>=20) return;
1055
- r.iframes.push({ tag:el.tagName?.toLowerCase(), src:el.getAttribute('src')?.slice(0,200)||null,
1056
- title:el.getAttribute('title')||null });
1057
- });
1058
- return JSON.stringify(r);
1059
- })()`;
1060
- const result = await api('/api/cdp/evaluate', 'POST', {
1061
- expression: debugExpr, timeout: 10000, ideType: ideTarget,
1062
- });
1063
- const data = typeof result.result === 'string' ? JSON.parse(result.result) : result.result;
1064
- const elapsed = Date.now() - start;
1065
- document.getElementById('execTime').textContent = `${elapsed}ms`;
1066
-
1067
- let output = `🔍 DOM Debug — ${data.title || 'N/A'}\n`;
1068
- output += `URL: ${data.url || 'N/A'}\n`;
1069
- output += `Viewport: ${data.viewport?.w}×${data.viewport?.h}\n\n`;
1070
-
1071
- const sections = [
1072
- { key: 'inputs', label: '📝 Inputs' },
1073
- { key: 'textareas', label: '📝 Textareas' },
1074
- { key: 'editables', label: '✏️ ContentEditable' },
1075
- { key: 'textboxes', label: '🔤 [role=textbox]' },
1076
- { key: 'buttons', label: '🔘 Buttons' },
1077
- { key: 'iframes', label: '🖼️ Iframes' },
1078
- ];
1079
- for (const s of sections) {
1080
- const items = data[s.key] || [];
1081
- if (items.length === 0) continue;
1082
- output += `${s.label} (${items.length})\n`;
1083
- for (const item of items) {
1084
- const vis = item.visible ? '✅' : '❌';
1085
- const parts = [vis];
1086
- if (item.tag) parts.push(`<${item.tag}>`);
1087
- if (item.id) parts.push(`#${item.id}`);
1088
- if (item.text) parts.push(`"${item.text}"`);
1089
- if (item.role) parts.push(`role=${item.role}`);
1090
- if (item.placeholder) parts.push(`placeholder="${item.placeholder}"`);
1091
- if (item.class) parts.push(`.${item.class.split(' ')[0].slice(0,30)}`);
1092
- output += ` ${parts.join(' ')}\n`;
1093
- }
1094
- output += '\n';
1095
- }
1096
- showResult(output, 'ok');
1097
- } catch (e) {
1098
- showResult({ error: e.message }, 'error');
1099
- }
1100
- };
1101
-
1102
- // Scaffold
1103
- document.getElementById('newBtn').onclick = () => {
1104
- document.getElementById('scaffoldModal').style.display = 'flex';
1105
- document.getElementById('scaffoldType').value = '';
1106
- document.getElementById('scaffoldName').value = '';
1107
- document.getElementById('scaffoldType').focus();
1108
- };
1109
-
1110
- function toggleScaffoldFields() {
1111
- const cat = document.getElementById('scaffoldCategory').value;
1112
- document.getElementById('scaffoldIdeFields').style.display = (cat === 'ide' || cat === 'extension') ? '' : 'none';
1113
- document.getElementById('scaffoldExtFields').style.display = cat === 'extension' ? '' : 'none';
1114
- document.getElementById('scaffoldCliFields').style.display = cat === 'cli' ? '' : 'none';
1115
- }
1116
-
1117
- async function doScaffold() {
1118
- const type = document.getElementById('scaffoldType').value.trim();
1119
- const name = document.getElementById('scaffoldName').value.trim();
1120
- const category = document.getElementById('scaffoldCategory').value;
1121
- if (!type || !name) {
1122
- log('⚠ Type and Name are required', 'warn');
1123
- return;
1124
- }
1125
- const body = { type, name, category, location: 'user' };
1126
- // CDP ports
1127
- const p1 = document.getElementById('scaffoldCdpPort1').value;
1128
- const p2 = document.getElementById('scaffoldCdpPort2').value;
1129
- if (p1) body.cdpPorts = [parseInt(p1), parseInt(p2 || p1) + 1];
1130
- // CLI
1131
- const cli = document.getElementById('scaffoldCli').value.trim();
1132
- if (cli) body.cli = cli;
1133
- // Process name
1134
- const proc = document.getElementById('scaffoldProcess').value.trim();
1135
- if (proc) body.processName = proc;
1136
- // Install path
1137
- const iPath = document.getElementById('scaffoldInstallPath').value.trim();
1138
- if (iPath) body.installPath = iPath;
1139
- // Extension ID
1140
- const extId = document.getElementById('scaffoldExtId').value.trim();
1141
- if (extId) body.extensionId = extId;
1142
- // Binary (CLI)
1143
- const binary = document.getElementById('scaffoldBinary').value.trim();
1144
- if (binary) body.binary = binary;
1145
-
1146
- try {
1147
- const result = await api('/api/scaffold', 'POST', body);
1148
- document.getElementById('scaffoldModal').style.display = 'none';
1149
- if (result.error) {
1150
- log('❌ ' + result.error, 'error');
1151
- return;
1152
- }
1153
- log(`✅ Created: ${result.path}`, 'ok');
1154
- await api('/api/providers/reload', 'POST');
1155
- await refreshProviders();
1156
- document.getElementById('providerSelect').value = type;
1157
- currentProvider = type;
1158
- updateScriptMenu();
1159
- document.getElementById('sourceBtn').click();
1160
- } catch (e) {
1161
- log('❌ ' + e.message, 'error');
1162
- }
1163
- }
1164
-
1165
- init();