adhdev 0.1.43 → 0.1.44

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 (46) hide show
  1. package/package.json +2 -1
  2. package/providers/_builtin/cli/claude-cli/provider.js +125 -0
  3. package/providers/_builtin/cli/codex-cli/provider.js +77 -0
  4. package/providers/_builtin/cli/gemini-cli/provider.js +121 -0
  5. package/providers/_builtin/extension/cline/provider.js +77 -0
  6. package/providers/_builtin/extension/cline/scripts/focus_editor.js +48 -0
  7. package/providers/_builtin/extension/cline/scripts/list_chats.js +100 -0
  8. package/providers/_builtin/extension/cline/scripts/new_session.js +85 -0
  9. package/providers/_builtin/extension/cline/scripts/open_panel.js +25 -0
  10. package/providers/_builtin/extension/cline/scripts/read_chat.js +257 -0
  11. package/providers/_builtin/extension/cline/scripts/resolve_action.js +83 -0
  12. package/providers/_builtin/extension/cline/scripts/send_message.js +95 -0
  13. package/providers/_builtin/extension/cline/scripts/switch_session.js +206 -0
  14. package/providers/_builtin/extension/roo-code/provider.js +578 -0
  15. package/providers/_builtin/ide/antigravity/provider.js +92 -0
  16. package/providers/_builtin/ide/antigravity/scripts/focus_editor.js +20 -0
  17. package/providers/_builtin/ide/antigravity/scripts/list_chats.js +137 -0
  18. package/providers/_builtin/ide/antigravity/scripts/new_session.js +75 -0
  19. package/providers/_builtin/ide/antigravity/scripts/read_chat.js +240 -0
  20. package/providers/_builtin/ide/antigravity/scripts/resolve_action.js +64 -0
  21. package/providers/_builtin/ide/antigravity/scripts/send_message.js +56 -0
  22. package/providers/_builtin/ide/antigravity/scripts/switch_session.js +114 -0
  23. package/providers/_builtin/ide/cursor/provider.js +242 -0
  24. package/providers/_builtin/ide/cursor/provider.js.backup +116 -0
  25. package/providers/_builtin/ide/cursor/provider.js.bak +127 -0
  26. package/providers/_builtin/ide/cursor/scripts_backup/focus_editor.js +20 -0
  27. package/providers/_builtin/ide/cursor/scripts_backup/list_chats.js +111 -0
  28. package/providers/_builtin/ide/cursor/scripts_backup/new_session.js +62 -0
  29. package/providers/_builtin/ide/cursor/scripts_backup/open_panel.js +31 -0
  30. package/providers/_builtin/ide/cursor/scripts_backup/read_chat.js +433 -0
  31. package/providers/_builtin/ide/cursor/scripts_backup/resolve_action.js +90 -0
  32. package/providers/_builtin/ide/cursor/scripts_backup/send_message.js +86 -0
  33. package/providers/_builtin/ide/cursor/scripts_backup/switch_session.js +63 -0
  34. package/providers/_builtin/ide/vscode/provider.js +36 -0
  35. package/providers/_builtin/ide/vscode-insiders/provider.js +27 -0
  36. package/providers/_builtin/ide/vscodium/provider.js +27 -0
  37. package/providers/_builtin/ide/windsurf/provider.js +64 -0
  38. package/providers/_builtin/ide/windsurf/scripts/focus_editor.js +30 -0
  39. package/providers/_builtin/ide/windsurf/scripts/list_chats.js +117 -0
  40. package/providers/_builtin/ide/windsurf/scripts/new_session.js +69 -0
  41. package/providers/_builtin/ide/windsurf/scripts/open_panel.js +58 -0
  42. package/providers/_builtin/ide/windsurf/scripts/read_chat.js +297 -0
  43. package/providers/_builtin/ide/windsurf/scripts/resolve_action.js +68 -0
  44. package/providers/_builtin/ide/windsurf/scripts/send_message.js +87 -0
  45. package/providers/_builtin/ide/windsurf/scripts/switch_session.js +58 -0
  46. package/providers/_helpers/index.js +188 -0
@@ -0,0 +1,578 @@
1
+ /**
2
+ * Roo Code — Extension Provider (Reference Implementation)
3
+ *
4
+ * Category: extension (webview CDP session)
5
+ * 구조: iframe → contentDocument, Fiber 기반 데이터 추출
6
+ *
7
+ * Output Contract: ReadChatResult, SendMessageResult, etc.
8
+ * 각 scripts 함수는 CDP evaluate에 넣을 JS 코드 문자열을 반환.
9
+ *
10
+ * @type {import('../../../src/providers/contracts').ProviderModule}
11
+ */
12
+ module.exports = {
13
+ // ─── 메타데이터 ───
14
+ type: 'roo-code',
15
+ name: 'Roo Code',
16
+ category: 'extension',
17
+
18
+ // ─── Extension 식별 ───
19
+ extensionId: 'RooVeterinaryInc.roo-cline',
20
+ extensionIdPattern: /extensionId=RooVeterinaryInc\.roo-cline/i,
21
+
22
+ // ─── VS Code Commands ───
23
+ vscodeCommands: {
24
+ focusPanel: 'roo-cline.SidebarProvider.focus',
25
+ },
26
+
27
+ // ─── CDP 스크립트 ───
28
+ scripts: {
29
+ /**
30
+ * readChat → ReadChatResult
31
+ * Fiber 기반 역할 판별 + DOM fallback
32
+ */
33
+ readChat() {
34
+ return `(() => {
35
+ try {
36
+ const inner = document.querySelector('iframe');
37
+ if (!inner) return JSON.stringify({ error: 'no inner iframe' });
38
+ const doc = inner.contentDocument || inner.contentWindow?.document;
39
+ if (!doc) return JSON.stringify({ error: 'cannot access contentDocument' });
40
+
41
+ const chatView = doc.querySelector('[data-testid="chat-view"]');
42
+ const root = doc.getElementById('root') || doc.body;
43
+ const isVisible = root ? (root.offsetHeight > 0 || (root.offsetWidth > 0 && !!chatView)) : false;
44
+
45
+ // ─── 1. Fiber data 배열 추출 ───
46
+ const virtuosoList = doc.querySelector('[data-testid="virtuoso-item-list"]');
47
+ let fiberData = null;
48
+
49
+ if (virtuosoList && virtuosoList.children.length > 0) {
50
+ const firstItem = virtuosoList.children[0];
51
+ const fiberKey = Object.keys(firstItem).find(k =>
52
+ k.startsWith('__reactFiber') || k.startsWith('__reactInternalInstance')
53
+ );
54
+ if (fiberKey) {
55
+ let fiber = firstItem[fiberKey];
56
+ for (let d = 0; d < 25 && fiber; d++) {
57
+ const props = fiber.memoizedProps || fiber.pendingProps;
58
+ if (props && props.data && Array.isArray(props.data) && props.data.length > 0) {
59
+ fiberData = props.data;
60
+ break;
61
+ }
62
+ fiber = fiber.return;
63
+ }
64
+ }
65
+ }
66
+
67
+ // ─── 2. 메시지 파싱 ───
68
+ const messages = [];
69
+
70
+ if (fiberData) {
71
+ for (let i = 0; i < fiberData.length; i++) {
72
+ const item = fiberData[i];
73
+ if (!item || typeof item !== 'object') continue;
74
+
75
+ const msgType = item.type;
76
+ const saySub = item.say;
77
+ const askSub = item.ask;
78
+ const text = item.text || '';
79
+
80
+ if (saySub === 'checkpoint_created') continue;
81
+ if (saySub === 'api_req_started' || saySub === 'api_req_finished') continue;
82
+ if (saySub === 'shell_integration_warning') continue;
83
+
84
+ let role = 'assistant';
85
+ if (saySub === 'user_feedback') role = 'user';
86
+ if (saySub === 'user_feedback_diff') role = 'user';
87
+
88
+ let content = '';
89
+ if (text) {
90
+ if (askSub === 'followup' && text.startsWith('{')) {
91
+ try {
92
+ const parsed = JSON.parse(text);
93
+ content = parsed.question || parsed.text || text;
94
+ } catch { content = text; }
95
+ } else {
96
+ content = text;
97
+ }
98
+ }
99
+
100
+ if (!content && virtuosoList && virtuosoList.children[i]) {
101
+ content = (virtuosoList.children[i].textContent || '').trim();
102
+ }
103
+
104
+ if (!content || content.length < 2) continue;
105
+
106
+ content = content
107
+ .replace(/CheckpointCompareRestore(Save)?/gi, '')
108
+ .replace(/^\\s*API Request.*$/gm, '')
109
+ .replace(/^\\s*Cost:.*$/gm, '')
110
+ .replace(/\\s{3,}/g, '\\n')
111
+ .trim();
112
+
113
+ if (content.length < 2) continue;
114
+ if (content.length > 2000) content = content.substring(0, 2000) + '…';
115
+
116
+ messages.push({
117
+ role,
118
+ content,
119
+ timestamp: item.ts || (Date.now() - (fiberData.length - i) * 1000),
120
+ _type: msgType,
121
+ _sub: saySub || askSub,
122
+ });
123
+ }
124
+ } else if (virtuosoList && virtuosoList.children.length > 0) {
125
+ for (const item of virtuosoList.children) {
126
+ const text = (item.textContent || '').trim();
127
+ if (!text || text.length < 2) continue;
128
+ if (/^API Request/i.test(text) || /^Thinking$/i.test(text)) continue;
129
+ let role = 'assistant';
130
+ if (/^You (said|asked)/i.test(text)) role = 'user';
131
+ let content = text.substring(0, 500);
132
+ messages.push({ role, content, timestamp: Date.now() - messages.length * 1000 });
133
+ }
134
+ }
135
+
136
+ // ─── 3. 웰컴 화면 감지 ───
137
+ const fullText = (chatView?.textContent || '').trim();
138
+ const isWelcomeScreen = messages.length === 0 &&
139
+ /Roo is a whole AI dev team/i.test(fullText);
140
+
141
+ // ─── 4. 입력 필드 ───
142
+ let inputContent = '';
143
+ const textareas = doc.querySelectorAll('textarea');
144
+ for (const ta of textareas) {
145
+ const val = ta.value || ta.textContent || '';
146
+ if (val.trim()) { inputContent = val; break; }
147
+ }
148
+
149
+ // ─── 5. 상태 ───
150
+ let status = 'idle';
151
+ const allBtns = [...doc.querySelectorAll('button'), ...doc.querySelectorAll('vscode-button')];
152
+ const buttonTexts = allBtns.map(b => (b.textContent || '').trim().toLowerCase());
153
+
154
+ if (buttonTexts.includes('cancel')) status = 'generating';
155
+
156
+ if (fiberData && fiberData.length > 0) {
157
+ const last = fiberData[fiberData.length - 1];
158
+ if (last.type === 'ask') {
159
+ if (last.ask === 'followup') status = 'waiting_approval';
160
+ if (last.ask === 'tool' || last.ask === 'command') status = 'waiting_approval';
161
+ }
162
+ }
163
+
164
+ const approvalPatterns = /^(proceed|approve|allow|accept|save|run|yes|confirm|resume)/i;
165
+ if (buttonTexts.some(b => approvalPatterns.test(b))) status = 'waiting_approval';
166
+
167
+ if ((!isVisible && messages.length === 0) || isWelcomeScreen) {
168
+ status = !isVisible ? 'panel_hidden' : 'idle';
169
+ }
170
+
171
+ // ─── 6. 모드/모델 ───
172
+ const modeBtn = doc.querySelector('[data-testid="mode-selector-trigger"]');
173
+ const mode = modeBtn ? (modeBtn.textContent || '').trim() : '';
174
+ const modelBtn = doc.querySelector('[data-testid="dropdown-trigger"]');
175
+ const model = modelBtn ? (modelBtn.textContent || '').trim() : '';
176
+ const autoApproveBtn = doc.querySelector('[data-testid="auto-approve-dropdown-trigger"]');
177
+ const autoApprove = autoApproveBtn ? (autoApproveBtn.textContent || '').trim() : '';
178
+
179
+ // ─── 7. 승인 모달 ───
180
+ let activeModal = null;
181
+ if (status === 'waiting_approval') {
182
+ const approvalBtns = buttonTexts
183
+ .filter(b => /proceed|approve|allow|accept|run|yes|reject|deny|cancel|no|skip|save|confirm|resume/i.test(b))
184
+ .map(b => b.substring(0, 30));
185
+ activeModal = { message: 'Roo Code requires your input', buttons: [...new Set(approvalBtns)] };
186
+ }
187
+
188
+ return JSON.stringify({
189
+ agentType: 'roo-code',
190
+ agentName: 'Roo Code',
191
+ extensionId: 'RooVeterinaryInc.roo-cline',
192
+ status,
193
+ isVisible,
194
+ isWelcomeScreen,
195
+ messages: messages.slice(-30),
196
+ inputContent,
197
+ model,
198
+ mode,
199
+ autoApprove,
200
+ activeModal,
201
+ });
202
+ } catch (e) {
203
+ return JSON.stringify({ error: e.message || String(e) });
204
+ }
205
+ })()`;
206
+ },
207
+
208
+ /**
209
+ * sendMessage(text) → 'sent' | 'error: ...'
210
+ * Fiber onSend 직접 호출 방식
211
+ */
212
+ sendMessage(text) {
213
+ const escaped = JSON.stringify(text);
214
+ return `(async () => {
215
+ try {
216
+ const inner = document.querySelector('iframe');
217
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
218
+ if (!doc) return 'error: no doc';
219
+
220
+ let target = null;
221
+ const textareas = doc.querySelectorAll('textarea');
222
+ for (const ta of textareas) {
223
+ if (ta.offsetParent !== null && ta.offsetHeight > 20) { target = ta; break; }
224
+ }
225
+ if (!target) return 'error: no textarea';
226
+
227
+ const proto = inner.contentWindow?.HTMLTextAreaElement?.prototype
228
+ || HTMLTextAreaElement.prototype;
229
+ const nativeSetter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
230
+
231
+ if (nativeSetter) nativeSetter.call(target, ${escaped});
232
+ else target.value = ${escaped};
233
+
234
+ target.dispatchEvent(new Event('input', { bubbles: true }));
235
+ target.dispatchEvent(new Event('change', { bubbles: true }));
236
+
237
+ await new Promise(r => setTimeout(r, 300));
238
+
239
+ // Fiber onSend 직접 호출
240
+ const allEls = doc.querySelectorAll('*');
241
+ for (const el of allEls) {
242
+ const fk = Object.keys(el).find(k => k.startsWith('__reactFiber'));
243
+ if (!fk) continue;
244
+ let fiber = el[fk];
245
+ for (let d = 0; d < 15 && fiber; d++) {
246
+ const props = fiber.memoizedProps || fiber.pendingProps;
247
+ if (props && typeof props.onSend === 'function') {
248
+ props.onSend();
249
+ return JSON.stringify({ sent: true });
250
+ }
251
+ fiber = fiber.return;
252
+ }
253
+ }
254
+
255
+ // Fallback: Enter 키
256
+ target.focus();
257
+ target.dispatchEvent(new KeyboardEvent('keydown', {
258
+ key: 'Enter', code: 'Enter', keyCode: 13,
259
+ bubbles: true, cancelable: true,
260
+ }));
261
+
262
+ return JSON.stringify({ sent: true });
263
+ } catch (e) { return JSON.stringify({ sent: false, error: e.message }); }
264
+ })()`;
265
+ },
266
+
267
+ /**
268
+ * listSessions → SessionInfo[]
269
+ * Fiber taskHistory 기반
270
+ */
271
+ listSessions() {
272
+ return `(() => {
273
+ try {
274
+ const inner = document.querySelector('iframe');
275
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
276
+ if (!doc) return JSON.stringify({ sessions: [] });
277
+
278
+ const findFiberKey = (el) => Object.keys(el).find(k =>
279
+ k.startsWith('__reactFiber') || k.startsWith('__reactProps') || k.startsWith('__reactContainer'));
280
+ const getFiber = (el) => {
281
+ const fk = findFiberKey(el);
282
+ if (!fk) return null;
283
+ let fiber = el[fk];
284
+ if (fk.startsWith('__reactContainer') && fiber?._internalRoot?.current)
285
+ fiber = fiber._internalRoot.current;
286
+ return fiber;
287
+ };
288
+
289
+ const allEls = doc.querySelectorAll('*');
290
+ let taskHistory = null;
291
+
292
+ for (const el of allEls) {
293
+ let fiber = getFiber(el);
294
+ if (!fiber) continue;
295
+ for (let d = 0; d < 30 && fiber; d++) {
296
+ const props = fiber.memoizedProps || fiber.pendingProps;
297
+ if (props && props.taskHistory && Array.isArray(props.taskHistory)) {
298
+ taskHistory = props.taskHistory;
299
+ break;
300
+ }
301
+ if (fiber.memoizedState) {
302
+ let st = fiber.memoizedState;
303
+ while (st) {
304
+ try {
305
+ const ms = st.memoizedState;
306
+ if (ms && typeof ms === 'object' && !Array.isArray(ms)) {
307
+ if (ms.taskHistory && Array.isArray(ms.taskHistory)) {
308
+ taskHistory = ms.taskHistory;
309
+ break;
310
+ }
311
+ }
312
+ } catch { }
313
+ st = st.next;
314
+ }
315
+ }
316
+ if (taskHistory) break;
317
+ fiber = fiber.return;
318
+ }
319
+ if (taskHistory) break;
320
+ }
321
+
322
+ if (taskHistory && taskHistory.length > 0) {
323
+ const sessions = taskHistory.slice(0, 50).map((task, i) => ({
324
+ id: task.id || String(task.ts),
325
+ title: (task.task || '').substring(0, 120),
326
+ time: task.ts ? new Date(task.ts).toISOString() : '',
327
+ }));
328
+ return JSON.stringify({ sessions });
329
+ }
330
+
331
+ // DOM fallback
332
+ const taskItems = doc.querySelectorAll('[data-testid^="task-item-"]');
333
+ if (taskItems.length > 0) {
334
+ const sessions = [];
335
+ for (const item of taskItems) {
336
+ const testId = item.getAttribute('data-testid') || '';
337
+ const id = testId.replace('task-item-', '');
338
+ const contentEl = item.querySelector('[data-testid="task-content"]');
339
+ const title = contentEl ? (contentEl.textContent || '').trim() : (item.textContent || '').trim().substring(0, 80);
340
+ sessions.push({ id, title, time: '' });
341
+ }
342
+ return JSON.stringify({ sessions });
343
+ }
344
+
345
+ return JSON.stringify({ sessions: [] });
346
+ } catch (e) {
347
+ return JSON.stringify({ sessions: [], error: e.message });
348
+ }
349
+ })()`;
350
+ },
351
+
352
+ /**
353
+ * switchSession(sessionId) → SwitchSessionResult
354
+ * postMessage → showTaskWithId
355
+ */
356
+ switchSession(sessionId) {
357
+ const escaped = JSON.stringify(sessionId);
358
+ return `(async () => {
359
+ try {
360
+ const inner = document.querySelector('iframe');
361
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
362
+ if (!doc) return JSON.stringify({ switched: false, error: 'no doc' });
363
+
364
+ const sid = ${escaped};
365
+ if (!sid) return JSON.stringify({ switched: false, error: 'no sessionId' });
366
+
367
+ const pm = window.__vscode_post_message__;
368
+ if (typeof pm !== 'function') return JSON.stringify({ switched: false, error: 'no postMessage' });
369
+
370
+ const findFiberKey = (el) => Object.keys(el).find(k =>
371
+ k.startsWith('__reactFiber') || k.startsWith('__reactProps') || k.startsWith('__reactContainer'));
372
+
373
+ const root = doc.getElementById('root');
374
+ if (!root) return JSON.stringify({ switched: false, error: 'no root' });
375
+ const fk = findFiberKey(root);
376
+ if (!fk) return JSON.stringify({ switched: false, error: 'no fiber' });
377
+ let rootFiber = root[fk];
378
+ if (fk.startsWith('__reactContainer') && rootFiber?._internalRoot?.current)
379
+ rootFiber = rootFiber._internalRoot.current;
380
+
381
+ let taskHistory = null;
382
+ const visited = new Set();
383
+ const dfs = (f, depth) => {
384
+ if (!f || depth > 80 || visited.has(f) || taskHistory) return;
385
+ visited.add(f);
386
+ const props = f.memoizedProps || f.pendingProps;
387
+ if (props?.taskHistory && Array.isArray(props.taskHistory)) {
388
+ taskHistory = props.taskHistory;
389
+ return;
390
+ }
391
+ if (f.memoizedState) {
392
+ let st = f.memoizedState; let i = 0;
393
+ while (st && i < 20 && !taskHistory) {
394
+ try {
395
+ const ms = st.memoizedState;
396
+ if (ms?.taskHistory && Array.isArray(ms.taskHistory)) taskHistory = ms.taskHistory;
397
+ } catch { }
398
+ st = st.next; i++;
399
+ }
400
+ }
401
+ if (f.child) dfs(f.child, depth + 1);
402
+ if (f.sibling) dfs(f.sibling, depth + 1);
403
+ };
404
+ dfs(rootFiber, 0);
405
+
406
+ if (!taskHistory || taskHistory.length === 0) return JSON.stringify({ switched: false, error: 'no history' });
407
+
408
+ const norm = s => (s || '').trim().toLowerCase().replace(/\\s+/g, ' ');
409
+ const idNorm = norm(sid);
410
+ let targetTask = taskHistory.find(t => String(t.id) === sid);
411
+ if (!targetTask) {
412
+ targetTask = taskHistory.find(t => {
413
+ const title = norm(t.task || '');
414
+ return title.includes(idNorm) || idNorm.includes(title);
415
+ });
416
+ }
417
+ if (!targetTask) return JSON.stringify({ switched: false, error: 'task not found' });
418
+
419
+ pm('onmessage', { message: { type: 'showTaskWithId', text: String(targetTask.id) } });
420
+
421
+ await new Promise(r => setTimeout(r, 2000));
422
+ return JSON.stringify({ switched: true });
423
+ } catch (e) { return JSON.stringify({ switched: false, error: e.message }); }
424
+ })()`;
425
+ },
426
+
427
+ /**
428
+ * newSession → string
429
+ */
430
+ newSession() {
431
+ return `(() => {
432
+ try {
433
+ const inner = document.querySelector('iframe');
434
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
435
+ if (!doc) return 'no doc';
436
+
437
+ const allBtns = [
438
+ ...Array.from(doc.querySelectorAll('button')),
439
+ ...Array.from(doc.querySelectorAll('vscode-button')),
440
+ ].filter(b => b.offsetWidth > 0 || b.offsetHeight > 0 || b.textContent?.trim());
441
+
442
+ for (const btn of allBtns) {
443
+ const tid = (btn.getAttribute('data-testid') || '').toLowerCase();
444
+ if (tid.includes('new-task') || tid.includes('new-chat') || tid.includes('new_session')) {
445
+ btn.click(); return 'clicked (testid)';
446
+ }
447
+ }
448
+ for (const btn of allBtns) {
449
+ const label = (btn.getAttribute('aria-label') || '').toLowerCase();
450
+ if (label.includes('new task') || label.includes('new chat') || label.includes('plus')) {
451
+ btn.click(); return 'clicked (aria)';
452
+ }
453
+ }
454
+ for (const btn of allBtns) {
455
+ const text = (btn.textContent || '').trim();
456
+ if (text === '+' || text.includes('New Task') || text.includes('New Chat')) {
457
+ btn.click(); return 'clicked (text)';
458
+ }
459
+ }
460
+
461
+ const bodyText = (doc.body.textContent || '').toLowerCase();
462
+ if (bodyText.includes('what can i do for you') || bodyText.includes('type your task') || bodyText.includes('start a new')) {
463
+ return 'clicked (already new)';
464
+ }
465
+
466
+ const root = doc.getElementById('root');
467
+ const chatView = doc.querySelector('[data-testid="chat-view"]');
468
+ if (root && root.offsetHeight === 0 && root.offsetWidth === 0 && !chatView) return 'panel_hidden';
469
+
470
+ return 'no button found';
471
+ } catch (e) { return 'error: ' + e.message; }
472
+ })()`;
473
+ },
474
+
475
+ /**
476
+ * resolveAction(action) → boolean
477
+ * action: 'approve' | 'reject'
478
+ */
479
+ resolveAction(action) {
480
+ const escaped = JSON.stringify(action);
481
+ return `(() => {
482
+ try {
483
+ const inner = document.querySelector('iframe');
484
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
485
+ if (!doc) return false;
486
+
487
+ const action = ${escaped};
488
+ const approvePatterns = ['proceed', 'approve', 'allow', 'accept', 'save', 'run command', 'yes', 'confirm', 'resume'];
489
+ const rejectPatterns = ['reject', 'deny', 'cancel', 'skip'];
490
+ const patterns = action === 'approve' ? approvePatterns : rejectPatterns;
491
+ const excludeTexts = ['auto-approve', 'read', 'write', 'mcp', 'mode', 'subtasks', 'execute', 'question', 'all', 'none', 'enabled'];
492
+
493
+ try { doc.body.click(); } catch { }
494
+
495
+ const allBtns = [
496
+ ...Array.from(doc.querySelectorAll('button')),
497
+ ...Array.from(doc.querySelectorAll('vscode-button')),
498
+ ].filter(b => b.offsetWidth > 0 && b.offsetHeight > 0);
499
+
500
+ for (const btn of allBtns) {
501
+ const testId = (btn.getAttribute('data-testid') || '').toLowerCase();
502
+ if (action === 'approve' && (testId.includes('approve') || testId.includes('proceed') || testId.includes('accept') || testId.includes('primary'))) {
503
+ btn.click(); return true;
504
+ }
505
+ if (action === 'reject' && (testId.includes('reject') || testId.includes('deny') || testId.includes('cancel') || testId.includes('secondary'))) {
506
+ btn.click(); return true;
507
+ }
508
+ }
509
+
510
+ const largeBtns = allBtns.filter(b => b.offsetWidth > 100);
511
+ for (const btn of largeBtns) {
512
+ const text = (btn.textContent || '').trim().toLowerCase();
513
+ if (text.length === 0 || text.length > 30) continue;
514
+ if (excludeTexts.some(e => text === e || text.startsWith('auto-approve'))) continue;
515
+ if (patterns.some(p => text === p || text.startsWith(p))) {
516
+ btn.click(); return true;
517
+ }
518
+ }
519
+
520
+ for (const btn of allBtns) {
521
+ const text = (btn.textContent || '').trim().toLowerCase();
522
+ if (text.length === 0 || text.length > 30) continue;
523
+ if (excludeTexts.some(e => text === e || text.startsWith('auto-approve'))) continue;
524
+ if (patterns.some(p => text === p || text.startsWith(p))) {
525
+ btn.click(); return true;
526
+ }
527
+ }
528
+
529
+ for (const btn of allBtns) {
530
+ const label = (btn.getAttribute('aria-label') || '').toLowerCase();
531
+ if (patterns.some(p => label.includes(p))) {
532
+ btn.click(); return true;
533
+ }
534
+ }
535
+
536
+ return false;
537
+ } catch { return false; }
538
+ })()`;
539
+ },
540
+
541
+ /**
542
+ * focusEditor → string
543
+ */
544
+ focusEditor() {
545
+ return `(() => {
546
+ try {
547
+ const inner = document.querySelector('iframe');
548
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
549
+ if (!doc) return 'no doc';
550
+ const textareas = doc.querySelectorAll('textarea');
551
+ for (const ta of textareas) {
552
+ if (ta.offsetParent !== null && ta.offsetHeight > 20) {
553
+ ta.focus();
554
+ return 'focused';
555
+ }
556
+ }
557
+ return 'no textarea found';
558
+ } catch (e) { return 'error: ' + e.message; }
559
+ })()`;
560
+ },
561
+
562
+ /**
563
+ * openPanel → 'visible' | 'panel_hidden'
564
+ */
565
+ openPanel() {
566
+ return `(() => {
567
+ try {
568
+ const inner = document.querySelector('iframe');
569
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
570
+ if (!doc) return 'panel_hidden';
571
+ const root = doc.getElementById('root');
572
+ if (root && root.offsetHeight > 0) return 'visible';
573
+ return 'panel_hidden';
574
+ } catch (e) { return 'error: ' + e.message; }
575
+ })()`;
576
+ },
577
+ },
578
+ };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Antigravity — IDE Provider
3
+ *
4
+ * Category: ide (workbench CDP session — iframe 없음, 직접 DOM 접근)
5
+ *
6
+ * 특이 사항:
7
+ * - contenteditable[role="textbox"] 입력 (Lexical 에디터)
8
+ * - send_message에서 Enter keydown+keypress+keyup full sequence 필요 (composed: true)
9
+ * - switch_session에서 CDP 마우스 클릭 좌표 반환 (needsTypeAndSend 아님, Input.dispatchMouseEvent)
10
+ * - resolve_action 사용
11
+ *
12
+ * @type {import('../../../src/providers/contracts').ProviderModule}
13
+ */
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ const SCRIPTS_DIR = path.join(__dirname, 'scripts');
18
+
19
+ function loadScript(name) {
20
+ try {
21
+ return fs.readFileSync(path.join(SCRIPTS_DIR, name), 'utf8');
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ module.exports = {
28
+ // ─── 메타데이터 ───
29
+ type: 'antigravity',
30
+ name: 'Antigravity',
31
+ category: 'ide',
32
+
33
+ // ─── IDE 인프라 ───
34
+ displayName: 'Antigravity',
35
+ icon: '🚀',
36
+ cli: 'antigravity',
37
+ cdpPorts: [9335, 9336],
38
+ processNames: {
39
+ darwin: 'Antigravity',
40
+ win32: ['Antigravity.exe'],
41
+ },
42
+ paths: {
43
+ darwin: ['/Applications/Antigravity.app'],
44
+ win32: ['C:\\Users\\*\\AppData\\Local\\Programs\\antigravity\\Antigravity.exe'],
45
+ linux: ['/opt/Antigravity', '/usr/share/antigravity'],
46
+ },
47
+
48
+ // ─── Input method ───
49
+ inputMethod: 'cdp-type-and-send',
50
+ inputSelector: '[contenteditable="true"][role="textbox"]',
51
+
52
+ // ─── CDP 스크립트 ───
53
+ scripts: {
54
+ readChat() {
55
+ return loadScript('read_chat.js');
56
+ },
57
+
58
+ sendMessage(text) {
59
+ const script = loadScript('send_message.js');
60
+ if (!script) return null;
61
+ // ${ MESSAGE } 템플릿 변수 치환
62
+ return script.replace(/\$\{\s*MESSAGE\s*\}/g, JSON.stringify(text));
63
+ },
64
+
65
+ listSessions() {
66
+ return loadScript('list_chats.js');
67
+ },
68
+
69
+ switchSession(sessionId) {
70
+ const script = loadScript('switch_session.js');
71
+ if (!script) return null;
72
+ return script.replace(/\$\{\s*SESSION_ID\s*\}/g, JSON.stringify(sessionId));
73
+ },
74
+
75
+ newSession() {
76
+ return loadScript('new_session.js');
77
+ },
78
+
79
+ resolveAction(params) {
80
+ const action = typeof params === 'string' ? params : params?.action || 'approve';
81
+ const buttonText = params?.button || params?.buttonText
82
+ || (action === 'approve' ? 'Accept' : action === 'reject' ? 'Reject' : action);
83
+ const script = loadScript('resolve_action.js');
84
+ if (!script) return null;
85
+ return script.replace(/\$\{\s*BUTTON_TEXT\s*\}/g, JSON.stringify(buttonText));
86
+ },
87
+
88
+ focusEditor() {
89
+ return loadScript('focus_editor.js');
90
+ },
91
+ },
92
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Cursor v1 — focus_editor
3
+ *
4
+ * CURSOR.md 4-5: 셀렉터 우선순위
5
+ * [contenteditable="true"][role="textbox"]
6
+ * → .chat-input textarea
7
+ * → .composer-input
8
+ * → textarea
9
+ *
10
+ * 최종 확인: 2026-03-06
11
+ */
12
+ (() => {
13
+ const editor = document.querySelector('[contenteditable="true"][role="textbox"]')
14
+ || document.querySelector('.chat-input textarea')
15
+ || document.querySelector('.composer-input')
16
+ || document.querySelector('textarea.native-input')
17
+ || document.querySelector('textarea');
18
+ if (editor) { editor.focus(); return 'focused'; }
19
+ return 'no editor found';
20
+ })()