adhdev 0.1.42 → 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.
- package/dist/index.js +7 -0
- package/package.json +2 -1
- package/providers/_builtin/cli/claude-cli/provider.js +125 -0
- package/providers/_builtin/cli/codex-cli/provider.js +77 -0
- package/providers/_builtin/cli/gemini-cli/provider.js +121 -0
- package/providers/_builtin/extension/cline/provider.js +77 -0
- package/providers/_builtin/extension/cline/scripts/focus_editor.js +48 -0
- package/providers/_builtin/extension/cline/scripts/list_chats.js +100 -0
- package/providers/_builtin/extension/cline/scripts/new_session.js +85 -0
- package/providers/_builtin/extension/cline/scripts/open_panel.js +25 -0
- package/providers/_builtin/extension/cline/scripts/read_chat.js +257 -0
- package/providers/_builtin/extension/cline/scripts/resolve_action.js +83 -0
- package/providers/_builtin/extension/cline/scripts/send_message.js +95 -0
- package/providers/_builtin/extension/cline/scripts/switch_session.js +206 -0
- package/providers/_builtin/extension/roo-code/provider.js +578 -0
- package/providers/_builtin/ide/antigravity/provider.js +92 -0
- package/providers/_builtin/ide/antigravity/scripts/focus_editor.js +20 -0
- package/providers/_builtin/ide/antigravity/scripts/list_chats.js +137 -0
- package/providers/_builtin/ide/antigravity/scripts/new_session.js +75 -0
- package/providers/_builtin/ide/antigravity/scripts/read_chat.js +240 -0
- package/providers/_builtin/ide/antigravity/scripts/resolve_action.js +64 -0
- package/providers/_builtin/ide/antigravity/scripts/send_message.js +56 -0
- package/providers/_builtin/ide/antigravity/scripts/switch_session.js +114 -0
- package/providers/_builtin/ide/cursor/provider.js +242 -0
- package/providers/_builtin/ide/cursor/provider.js.backup +116 -0
- package/providers/_builtin/ide/cursor/provider.js.bak +127 -0
- package/providers/_builtin/ide/cursor/scripts_backup/focus_editor.js +20 -0
- package/providers/_builtin/ide/cursor/scripts_backup/list_chats.js +111 -0
- package/providers/_builtin/ide/cursor/scripts_backup/new_session.js +62 -0
- package/providers/_builtin/ide/cursor/scripts_backup/open_panel.js +31 -0
- package/providers/_builtin/ide/cursor/scripts_backup/read_chat.js +433 -0
- package/providers/_builtin/ide/cursor/scripts_backup/resolve_action.js +90 -0
- package/providers/_builtin/ide/cursor/scripts_backup/send_message.js +86 -0
- package/providers/_builtin/ide/cursor/scripts_backup/switch_session.js +63 -0
- package/providers/_builtin/ide/vscode/provider.js +36 -0
- package/providers/_builtin/ide/vscode-insiders/provider.js +27 -0
- package/providers/_builtin/ide/vscodium/provider.js +27 -0
- package/providers/_builtin/ide/windsurf/provider.js +64 -0
- package/providers/_builtin/ide/windsurf/scripts/focus_editor.js +30 -0
- package/providers/_builtin/ide/windsurf/scripts/list_chats.js +117 -0
- package/providers/_builtin/ide/windsurf/scripts/new_session.js +69 -0
- package/providers/_builtin/ide/windsurf/scripts/open_panel.js +58 -0
- package/providers/_builtin/ide/windsurf/scripts/read_chat.js +297 -0
- package/providers/_builtin/ide/windsurf/scripts/resolve_action.js +68 -0
- package/providers/_builtin/ide/windsurf/scripts/send_message.js +87 -0
- package/providers/_builtin/ide/windsurf/scripts/switch_session.js +58 -0
- package/providers/_helpers/index.js +188 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity v1 — switch_session (v7 — CDP 마우스 클릭 좌표 반환)
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ 삭제 버튼(trash SVG)에 절대 접근하지 않음.
|
|
5
|
+
*
|
|
6
|
+
* 두 가지 모드:
|
|
7
|
+
* 1) 히스토리 토글을 열고 행 좌표를 찾으면 {clickX, clickY} 반환 → daemon이 CDP 클릭으로 처리
|
|
8
|
+
* 2) 워크스페이스 다이얼로그가 뜨면 첫 번째 행 좌표 반환 (자동 선택용)
|
|
9
|
+
*
|
|
10
|
+
* 파라미터: ${SESSION_ID} — 대화 제목 (문자열)
|
|
11
|
+
*/
|
|
12
|
+
(async () => {
|
|
13
|
+
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
14
|
+
const id = ${SESSION_ID};
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
// 1. 히스토리 토글 클릭
|
|
18
|
+
const toggle = document.querySelector('[data-past-conversations-toggle="true"]');
|
|
19
|
+
if (!toggle) return JSON.stringify({ error: 'no_toggle' });
|
|
20
|
+
toggle.click();
|
|
21
|
+
await sleep(1200);
|
|
22
|
+
|
|
23
|
+
// 2. 컨테이너 찾기 (기존 로직: "Current" 텍스트 또는 "Select a conversation" input)
|
|
24
|
+
let container = null;
|
|
25
|
+
|
|
26
|
+
// 2a. "Current" 텍스트 기반
|
|
27
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
|
|
28
|
+
while (walker.nextNode()) {
|
|
29
|
+
if (walker.currentNode.textContent.trim() === 'Current') {
|
|
30
|
+
let el = walker.currentNode.parentElement;
|
|
31
|
+
for (let i = 0; i < 10 && el; i++) {
|
|
32
|
+
const cls = (el.className || '');
|
|
33
|
+
if (typeof cls === 'string' && (cls.includes('overflow-auto') || cls.includes('overflow-y-scroll'))) {
|
|
34
|
+
container = el; break;
|
|
35
|
+
}
|
|
36
|
+
el = el.parentElement;
|
|
37
|
+
}
|
|
38
|
+
if (!container) {
|
|
39
|
+
el = walker.currentNode.parentElement;
|
|
40
|
+
let bestEl = null, bestCount = 0;
|
|
41
|
+
for (let i = 0; i < 8 && el; i++) {
|
|
42
|
+
const rows = el.querySelectorAll('[class*="cursor-pointer"][class*="justify-between"][class*="rounded-md"]');
|
|
43
|
+
if (rows.length > bestCount) { bestCount = rows.length; bestEl = el; }
|
|
44
|
+
el = el.parentElement;
|
|
45
|
+
}
|
|
46
|
+
container = bestEl;
|
|
47
|
+
}
|
|
48
|
+
if (container) break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 2b. 폴백: "Select a conversation" input 기반
|
|
53
|
+
if (!container) {
|
|
54
|
+
const searchInput = Array.from(document.querySelectorAll('input[type="text"]'))
|
|
55
|
+
.find(i => i.offsetWidth > 0 && (i.placeholder || '').includes('Select a conversation'));
|
|
56
|
+
if (searchInput) {
|
|
57
|
+
let el = searchInput.parentElement;
|
|
58
|
+
for (let i = 0; i < 10 && el; i++) {
|
|
59
|
+
const cls = (el.className || '');
|
|
60
|
+
if (typeof cls === 'string' && (cls.includes('overflow-auto') || cls.includes('overflow-y-scroll'))) {
|
|
61
|
+
container = el; break;
|
|
62
|
+
}
|
|
63
|
+
const rows = el.querySelectorAll('[class*="cursor-pointer"][class*="justify-between"][class*="rounded-md"]');
|
|
64
|
+
if (rows.length > 0 && !container) container = el;
|
|
65
|
+
el = el.parentElement;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!container) {
|
|
71
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', bubbles: true }));
|
|
72
|
+
return JSON.stringify({ error: 'no_container' });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 3. 행 매칭
|
|
76
|
+
const rows = container.querySelectorAll('[class*="cursor-pointer"][class*="justify-between"][class*="rounded-md"]');
|
|
77
|
+
const norm = s => (s || '').trim().toLowerCase().replace(/\s+/g, ' ');
|
|
78
|
+
const idNorm = norm(id);
|
|
79
|
+
let targetRow = null;
|
|
80
|
+
|
|
81
|
+
for (const row of rows) {
|
|
82
|
+
// 현재 활성 대화 스킵
|
|
83
|
+
if ((row.className || '').includes('focusBackground')) continue;
|
|
84
|
+
const titleEl = row.querySelector('span span');
|
|
85
|
+
const title = titleEl ? norm(titleEl.textContent) : '';
|
|
86
|
+
if (!title) continue;
|
|
87
|
+
if (title.includes(idNorm) || idNorm.includes(title)) {
|
|
88
|
+
targetRow = row;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!targetRow) {
|
|
94
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', bubbles: true }));
|
|
95
|
+
return JSON.stringify({ error: 'no_match', rowCount: rows.length });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 4. 행의 좌표 계산 → daemon이 CDP Input.dispatchMouseEvent로 클릭
|
|
99
|
+
const rect = targetRow.getBoundingClientRect();
|
|
100
|
+
const clickX = Math.round(rect.left + rect.width * 0.3);
|
|
101
|
+
const clickY = Math.round(rect.top + rect.height / 2);
|
|
102
|
+
|
|
103
|
+
return JSON.stringify({
|
|
104
|
+
action: 'click',
|
|
105
|
+
clickX,
|
|
106
|
+
clickY,
|
|
107
|
+
title: (targetRow.querySelector('span span')?.textContent || '').substring(0, 60)
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
} catch (e) {
|
|
111
|
+
try { document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', bubbles: true })); } catch (_) { }
|
|
112
|
+
return JSON.stringify({ error: e.message });
|
|
113
|
+
}
|
|
114
|
+
})()
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor — IDE Provider
|
|
3
|
+
* @type {import('../../../src/providers/contracts').ProviderModule}
|
|
4
|
+
*/
|
|
5
|
+
module.exports = {
|
|
6
|
+
type: 'cursor',
|
|
7
|
+
name: 'Cursor',
|
|
8
|
+
category: 'ide',
|
|
9
|
+
displayName: 'Cursor',
|
|
10
|
+
icon: '⚡',
|
|
11
|
+
cli: 'cursor',
|
|
12
|
+
cdpPorts: [9333, 9334],
|
|
13
|
+
processNames: { darwin: 'Cursor', win32: ['Cursor.exe'] },
|
|
14
|
+
paths: {
|
|
15
|
+
darwin: ['/Applications/Cursor.app'],
|
|
16
|
+
win32: ['C:\\Users\\*\\AppData\\Local\\Programs\\cursor\\Cursor.exe'],
|
|
17
|
+
linux: ['/opt/Cursor', '/usr/share/cursor'],
|
|
18
|
+
},
|
|
19
|
+
inputMethod: 'cdp-type-and-send',
|
|
20
|
+
inputSelector: '.aislash-editor-input[contenteditable="true"]',
|
|
21
|
+
vscodeCommands: {
|
|
22
|
+
changeModel: 'cursor.model',
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
scripts: {
|
|
26
|
+
readChat(params) {
|
|
27
|
+
return `(() => {
|
|
28
|
+
try {
|
|
29
|
+
const c = document.querySelector('[data-composer-id]');
|
|
30
|
+
const id = c?.getAttribute('data-composer-id') || 'active';
|
|
31
|
+
const rawStatus = c?.getAttribute('data-composer-status') || 'idle';
|
|
32
|
+
let status = rawStatus;
|
|
33
|
+
if (rawStatus === 'thinking' || rawStatus === 'streaming') status = 'generating';
|
|
34
|
+
else if (rawStatus === 'completed' || rawStatus === 'idle' || !rawStatus) status = 'idle';
|
|
35
|
+
|
|
36
|
+
// Detect approval dialogs
|
|
37
|
+
let activeModal = null;
|
|
38
|
+
const approvalBtns = [...document.querySelectorAll('button')].filter(b =>
|
|
39
|
+
b.offsetWidth > 0 && /accept|reject|approve|deny|run command|allow|cancel/i.test(b.textContent || b.getAttribute('aria-label') || '')
|
|
40
|
+
);
|
|
41
|
+
if (approvalBtns.length > 0) {
|
|
42
|
+
status = 'waiting_approval';
|
|
43
|
+
activeModal = {
|
|
44
|
+
message: approvalBtns[0]?.closest('.notification-toast, .dialog, [role=dialog], .composer-message-group')?.textContent?.trim()?.substring(0, 200) || 'Approval required',
|
|
45
|
+
buttons: approvalBtns.map(b => b.textContent.trim()),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const msgs = [];
|
|
50
|
+
document.querySelectorAll('.composer-human-ai-pair-container').forEach((p, i) => {
|
|
51
|
+
const h = p.querySelector('.composer-human-message');
|
|
52
|
+
if (h) msgs.push({ role: 'user', content: h.textContent.trim().substring(0, 6000), index: msgs.length });
|
|
53
|
+
p.querySelectorAll('.composer-rendered-message').forEach(a => {
|
|
54
|
+
if (a.closest('.composer-human-message')) return;
|
|
55
|
+
const t = a.textContent.trim();
|
|
56
|
+
if (t) msgs.push({ role: 'assistant', content: t.substring(0, 6000), index: msgs.length });
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
const inputEl = document.querySelector('.aislash-editor-input[contenteditable="true"]');
|
|
60
|
+
const inputContent = inputEl?.textContent?.trim() || '';
|
|
61
|
+
return JSON.stringify({ id, status, title: document.title.split(' — ')[0], messages: msgs, inputContent, activeModal });
|
|
62
|
+
} catch(e) {
|
|
63
|
+
return JSON.stringify({ id: '', status: 'error', messages: [] });
|
|
64
|
+
}
|
|
65
|
+
})()`;
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
sendMessage(params) {
|
|
69
|
+
const text = typeof params === 'string' ? params : params?.text;
|
|
70
|
+
return `(() => {
|
|
71
|
+
try {
|
|
72
|
+
const input = document.querySelector('.aislash-editor-input[contenteditable="true"]');
|
|
73
|
+
if (!input) return JSON.stringify({ sent: false, error: 'Input box not found' });
|
|
74
|
+
return JSON.stringify({
|
|
75
|
+
sent: false,
|
|
76
|
+
needsTypeAndSend: true,
|
|
77
|
+
selector: '.aislash-editor-input[contenteditable="true"]',
|
|
78
|
+
});
|
|
79
|
+
} catch(e) {
|
|
80
|
+
return JSON.stringify({ sent: false, error: e.message });
|
|
81
|
+
}
|
|
82
|
+
})()`;
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
listSessions(params) {
|
|
86
|
+
return `(() => {
|
|
87
|
+
try {
|
|
88
|
+
const sessions = [];
|
|
89
|
+
const cells = [...document.querySelectorAll('.agent-sidebar-cell')];
|
|
90
|
+
const activeComposer = document.querySelector('[data-composer-id]');
|
|
91
|
+
const activeId = activeComposer?.getAttribute('data-composer-id') || null;
|
|
92
|
+
cells.forEach((cell, i) => {
|
|
93
|
+
const titleEl = cell.querySelector('.agent-sidebar-cell-text');
|
|
94
|
+
const title = titleEl?.textContent?.trim() || 'Untitled';
|
|
95
|
+
const isSelected = cell.getAttribute('data-selected') === 'true';
|
|
96
|
+
sessions.push({
|
|
97
|
+
id: isSelected && activeId ? activeId : 'sidebar-' + i,
|
|
98
|
+
title,
|
|
99
|
+
active: isSelected,
|
|
100
|
+
index: i,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
// If no sidebar cells, fallback to current composer
|
|
104
|
+
if (sessions.length === 0 && activeComposer) {
|
|
105
|
+
sessions.push({
|
|
106
|
+
id: activeId,
|
|
107
|
+
title: document.title.split(' — ')[0],
|
|
108
|
+
active: true,
|
|
109
|
+
index: 0,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return JSON.stringify({ sessions });
|
|
113
|
+
} catch(e) {
|
|
114
|
+
return JSON.stringify({ sessions: [], error: e.message });
|
|
115
|
+
}
|
|
116
|
+
})()`;
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
switchSession(params) {
|
|
120
|
+
const index = typeof params === 'number' ? params : params?.index;
|
|
121
|
+
const title = typeof params === 'string' ? params : params?.title;
|
|
122
|
+
return `(() => {
|
|
123
|
+
try {
|
|
124
|
+
const cells = [...document.querySelectorAll('.agent-sidebar-cell')];
|
|
125
|
+
let target;
|
|
126
|
+
if (${JSON.stringify(title)}) {
|
|
127
|
+
target = cells.find(c => {
|
|
128
|
+
const t = c.querySelector('.agent-sidebar-cell-text')?.textContent?.trim() || '';
|
|
129
|
+
return t.toLowerCase().includes(${JSON.stringify((title||'').toLowerCase())});
|
|
130
|
+
});
|
|
131
|
+
} else {
|
|
132
|
+
target = cells[${index ?? 0}];
|
|
133
|
+
}
|
|
134
|
+
if (!target) return JSON.stringify({ switched: false, error: 'Session not found', available: cells.length });
|
|
135
|
+
target.click();
|
|
136
|
+
return JSON.stringify({ switched: true, title: target.querySelector('.agent-sidebar-cell-text')?.textContent?.trim() });
|
|
137
|
+
} catch(e) {
|
|
138
|
+
return JSON.stringify({ switched: false, error: e.message });
|
|
139
|
+
}
|
|
140
|
+
})()`;
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
newSession(params) {
|
|
144
|
+
return `(() => {
|
|
145
|
+
try {
|
|
146
|
+
const newBtn = [...document.querySelectorAll('a.action-label.codicon-add-two, [aria-label*="New Chat"], [aria-label*="New Composer"]')]
|
|
147
|
+
.find(a => a.offsetWidth > 0);
|
|
148
|
+
if (newBtn) { newBtn.click(); return JSON.stringify({ created: true }); }
|
|
149
|
+
return JSON.stringify({ created: false, error: 'New Chat button not found' });
|
|
150
|
+
} catch(e) {
|
|
151
|
+
return JSON.stringify({ created: false, error: e.message });
|
|
152
|
+
}
|
|
153
|
+
})()`;
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
focusEditor(params) {
|
|
157
|
+
return `(() => {
|
|
158
|
+
try {
|
|
159
|
+
const input = document.querySelector('.aislash-editor-input[contenteditable="true"]');
|
|
160
|
+
if (input) { input.focus(); return 'focused'; }
|
|
161
|
+
return 'not_found';
|
|
162
|
+
} catch(e) { return 'error'; }
|
|
163
|
+
})()`;
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
openPanel(params) {
|
|
167
|
+
return `(() => {
|
|
168
|
+
try {
|
|
169
|
+
const sidebar = document.getElementById('workbench.parts.auxiliarybar');
|
|
170
|
+
if (sidebar && sidebar.offsetWidth > 0) {
|
|
171
|
+
const chatView = document.querySelector('[data-composer-id]');
|
|
172
|
+
if (chatView) return 'visible';
|
|
173
|
+
}
|
|
174
|
+
const btns = [...document.querySelectorAll('li.action-item a, button, [role="tab"]')];
|
|
175
|
+
const toggle = btns.find(b => {
|
|
176
|
+
const label = (b.textContent || b.getAttribute('aria-label') || '').toLowerCase();
|
|
177
|
+
return /agent|chat|composer|cursor tab/i.test(label);
|
|
178
|
+
});
|
|
179
|
+
if (toggle) { toggle.click(); return 'opened'; }
|
|
180
|
+
return 'not_found';
|
|
181
|
+
} catch (e) { return 'error'; }
|
|
182
|
+
})()`;
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
resolveAction(params) {
|
|
186
|
+
const action = typeof params === 'string' ? params : params?.action || 'approve';
|
|
187
|
+
const buttonText = params?.button || params?.buttonText
|
|
188
|
+
|| (action === 'approve' ? 'Accept' : action === 'reject' ? 'Reject' : action);
|
|
189
|
+
return `(() => {
|
|
190
|
+
try {
|
|
191
|
+
const btns = [...document.querySelectorAll('button, [role="button"]')].filter(b => b.offsetWidth > 0);
|
|
192
|
+
const target = btns.find(b => (b.textContent||'').trim().toLowerCase().includes(${JSON.stringify((buttonText||'').toLowerCase())}));
|
|
193
|
+
if (target) { target.click(); return JSON.stringify({ resolved: true, clicked: target.textContent.trim() }); }
|
|
194
|
+
return JSON.stringify({ resolved: false, available: btns.map(b => b.textContent.trim()).filter(Boolean).slice(0, 15) });
|
|
195
|
+
} catch(e) { return JSON.stringify({ resolved: false, error: e.message }); }
|
|
196
|
+
})()`;
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
listNotifications(params) {
|
|
200
|
+
const filter = typeof params === 'string' ? params : params?.message;
|
|
201
|
+
return `(() => {
|
|
202
|
+
try {
|
|
203
|
+
const toasts = [...document.querySelectorAll('.notifications-toasts .notification-toast, .notification-list-item')];
|
|
204
|
+
const visible = toasts.filter(t => t.offsetWidth > 0).map((t, i) => ({
|
|
205
|
+
index: i,
|
|
206
|
+
message: t.querySelector('.notification-list-item-message')?.textContent?.trim() || t.textContent?.trim().substring(0, 200),
|
|
207
|
+
severity: t.querySelector('.codicon-error') ? 'error' : t.querySelector('.codicon-warning') ? 'warning' : 'info',
|
|
208
|
+
buttons: [...t.querySelectorAll('.notification-list-item-buttons-container button, .monaco-button')].map(b => b.textContent?.trim()).filter(Boolean),
|
|
209
|
+
}));
|
|
210
|
+
const f = ${JSON.stringify(filter || null)};
|
|
211
|
+
return JSON.stringify(f ? visible.filter(n => n.message.toLowerCase().includes(f.toLowerCase())) : visible);
|
|
212
|
+
} catch(e) { return JSON.stringify([]); }
|
|
213
|
+
})()`;
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
dismissNotification(params) {
|
|
217
|
+
const index = typeof params === 'number' ? params : params?.index;
|
|
218
|
+
const button = typeof params === 'string' ? params : params?.button;
|
|
219
|
+
const message = params?.message;
|
|
220
|
+
return `(() => {
|
|
221
|
+
try {
|
|
222
|
+
const toasts = [...document.querySelectorAll('.notifications-toasts .notification-toast, .notification-list-item')].filter(t => t.offsetWidth > 0);
|
|
223
|
+
let toast;
|
|
224
|
+
if (${JSON.stringify(message)}) {
|
|
225
|
+
toast = toasts.find(t => (t.querySelector('.notification-list-item-message')?.textContent || t.textContent || '').toLowerCase().includes(${JSON.stringify((message||'').toLowerCase())}));
|
|
226
|
+
} else {
|
|
227
|
+
toast = toasts[${index ?? 0}];
|
|
228
|
+
}
|
|
229
|
+
if (!toast) return JSON.stringify({ dismissed: false, error: 'Toast not found', count: toasts.length });
|
|
230
|
+
if (${JSON.stringify(button)}) {
|
|
231
|
+
const btn = [...toast.querySelectorAll('button')].find(b => b.textContent?.trim().toLowerCase().includes(${JSON.stringify((button||'').toLowerCase())}));
|
|
232
|
+
if (btn) { btn.click(); return JSON.stringify({ dismissed: true, clicked: btn.textContent.trim() }); }
|
|
233
|
+
return JSON.stringify({ dismissed: false, error: 'Button not found' });
|
|
234
|
+
}
|
|
235
|
+
const closeBtn = toast.querySelector('.codicon-notifications-clear, .clear-notification-action, .codicon-close');
|
|
236
|
+
if (closeBtn) { closeBtn.click(); return JSON.stringify({ dismissed: true }); }
|
|
237
|
+
return JSON.stringify({ dismissed: false, error: 'Close button not found' });
|
|
238
|
+
} catch(e) { return JSON.stringify({ dismissed: false, error: e.message }); }
|
|
239
|
+
})()`;
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor — IDE Provider
|
|
3
|
+
*
|
|
4
|
+
* Category: ide (workbench CDP session — iframe 없음)
|
|
5
|
+
*
|
|
6
|
+
* 특이 사항:
|
|
7
|
+
* - .composer-view 컨테이너 (data-composer-id, data-composer-status)
|
|
8
|
+
* - contenteditable[role="textbox"] 또는 textarea.native-input
|
|
9
|
+
* - Fiber summaries 기반 제목 추출
|
|
10
|
+
* - 6단계 모달 감지 계층 (overlay → run-command-review → ancestor → dialog → inline → quickInput → AI msg)
|
|
11
|
+
* - resolve_modal 사용
|
|
12
|
+
*
|
|
13
|
+
* @type {import('../../../src/providers/contracts').ProviderModule}
|
|
14
|
+
*/
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const SCRIPTS_DIR = path.join(__dirname, 'scripts');
|
|
19
|
+
|
|
20
|
+
function loadScript(name) {
|
|
21
|
+
try {
|
|
22
|
+
return fs.readFileSync(path.join(SCRIPTS_DIR, name), 'utf8');
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
type: 'cursor',
|
|
30
|
+
name: 'Cursor',
|
|
31
|
+
category: 'ide',
|
|
32
|
+
|
|
33
|
+
// ─── IDE 인프라 ───
|
|
34
|
+
displayName: 'Cursor',
|
|
35
|
+
icon: '⚡',
|
|
36
|
+
cli: 'cursor',
|
|
37
|
+
cdpPorts: [9333, 9334],
|
|
38
|
+
processNames: {
|
|
39
|
+
darwin: 'Cursor',
|
|
40
|
+
win32: ['Cursor.exe'],
|
|
41
|
+
},
|
|
42
|
+
paths: {
|
|
43
|
+
darwin: ['/Applications/Cursor.app'],
|
|
44
|
+
win32: ['C:\\Users\\*\\AppData\\Local\\Programs\\cursor\\Cursor.exe'],
|
|
45
|
+
linux: ['/opt/Cursor', '/usr/share/cursor'],
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
inputMethod: 'cdp-type-and-send',
|
|
49
|
+
inputSelector: '[contenteditable="true"][role="textbox"]',
|
|
50
|
+
|
|
51
|
+
scripts: {
|
|
52
|
+
readChat() {
|
|
53
|
+
return loadScript('read_chat.js');
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
sendMessage(text) {
|
|
57
|
+
const script = loadScript('send_message.js');
|
|
58
|
+
if (!script) return null;
|
|
59
|
+
return script.replace(/\$\{\s*MESSAGE\s*\}/g, JSON.stringify(text));
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
listSessions() {
|
|
63
|
+
return loadScript('list_chats.js');
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
switchSession(sessionId) {
|
|
67
|
+
const script = loadScript('switch_session.js');
|
|
68
|
+
if (!script) return null;
|
|
69
|
+
return script.replace(/\$\{\s*SESSION_ID\s*\}/g, JSON.stringify(sessionId));
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
newSession() {
|
|
73
|
+
return loadScript('new_session.js');
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
resolveModal(buttonText) {
|
|
77
|
+
const script = loadScript('resolve_modal.js');
|
|
78
|
+
if (!script) return null;
|
|
79
|
+
return script.replace(/\$\{\s*BUTTON_TEXT\s*\}/g, JSON.stringify(buttonText));
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
focusEditor() {
|
|
83
|
+
return loadScript('focus_editor.js');
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
openPanel() {
|
|
87
|
+
return loadScript('open_panel.js');
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
probe() {
|
|
91
|
+
return `(() => {
|
|
92
|
+
const checks = [];
|
|
93
|
+
try {
|
|
94
|
+
const composer = document.querySelector('.composer-view, .chat-view, [data-composer-id]');
|
|
95
|
+
checks.push({ name: 'composer_view', passed: !!composer });
|
|
96
|
+
|
|
97
|
+
const editor = document.querySelector('[contenteditable="true"][role="textbox"]')
|
|
98
|
+
|| document.querySelector('textarea.native-input');
|
|
99
|
+
checks.push({ name: 'editor', passed: !!editor });
|
|
100
|
+
|
|
101
|
+
const composerId = composer?.getAttribute('data-composer-id');
|
|
102
|
+
checks.push({ name: 'composer_id', passed: !!composerId });
|
|
103
|
+
|
|
104
|
+
const msgEls = document.querySelectorAll('[data-message-id]');
|
|
105
|
+
checks.push({ name: 'message_elements', passed: msgEls.length > 0, message: msgEls.length + ' messages' });
|
|
106
|
+
|
|
107
|
+
const healthy = checks.filter(c => c.passed).length >= 2;
|
|
108
|
+
return JSON.stringify({ healthy, checks });
|
|
109
|
+
} catch (e) {
|
|
110
|
+
checks.push({ name: 'error', passed: false, message: e.message });
|
|
111
|
+
return JSON.stringify({ healthy: false, checks });
|
|
112
|
+
}
|
|
113
|
+
})()`;
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor — IDE Provider
|
|
3
|
+
* @type {import('../../../src/providers/contracts').ProviderModule}
|
|
4
|
+
*/
|
|
5
|
+
module.exports = {
|
|
6
|
+
type: 'cursor',
|
|
7
|
+
name: 'Cursor',
|
|
8
|
+
category: 'ide',
|
|
9
|
+
displayName: 'Cursor',
|
|
10
|
+
icon: '⚡',
|
|
11
|
+
cli: 'cursor',
|
|
12
|
+
cdpPorts: [9333, 9334],
|
|
13
|
+
processNames: { darwin: 'Cursor', win32: ['Cursor.exe'] },
|
|
14
|
+
paths: {
|
|
15
|
+
darwin: ['/Applications/Cursor.app'],
|
|
16
|
+
win32: ['C:\\Users\\*\\AppData\\Local\\Programs\\cursor\\Cursor.exe'],
|
|
17
|
+
linux: ['/opt/Cursor', '/usr/share/cursor'],
|
|
18
|
+
},
|
|
19
|
+
inputMethod: 'cdp-type-and-send',
|
|
20
|
+
inputSelector: '.aislash-editor-input[contenteditable="true"]',
|
|
21
|
+
|
|
22
|
+
scripts: {
|
|
23
|
+
readChat() {
|
|
24
|
+
return `(() => {
|
|
25
|
+
try {
|
|
26
|
+
const c = document.querySelector('[data-composer-id]');
|
|
27
|
+
const id = c?.getAttribute('data-composer-id') || 'active';
|
|
28
|
+
const rawStatus = c?.getAttribute('data-composer-status') || 'idle';
|
|
29
|
+
const status = rawStatus === 'thinking' ? 'generating' : rawStatus;
|
|
30
|
+
const msgs = [];
|
|
31
|
+
document.querySelectorAll('.composer-human-ai-pair-container').forEach((p, i) => {
|
|
32
|
+
const h = p.querySelector('.composer-human-message');
|
|
33
|
+
if (h) msgs.push({ role: 'user', content: h.textContent.trim().substring(0, 6000), index: msgs.length });
|
|
34
|
+
p.querySelectorAll('.composer-rendered-message').forEach(a => {
|
|
35
|
+
if (a.closest('.composer-human-message')) return;
|
|
36
|
+
const t = a.textContent.trim();
|
|
37
|
+
if (t) msgs.push({ role: 'assistant', content: t.substring(0, 6000), index: msgs.length });
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
const inputEl = document.querySelector('.aislash-editor-input[contenteditable="true"]');
|
|
41
|
+
const inputContent = inputEl?.textContent?.trim() || '';
|
|
42
|
+
return JSON.stringify({ id, status, title: document.title.split(' — ')[0], messages: msgs, inputContent });
|
|
43
|
+
} catch(e) {
|
|
44
|
+
return JSON.stringify({ id: '', status: 'error', messages: [] });
|
|
45
|
+
}
|
|
46
|
+
})()`;
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
sendMessage(text) {
|
|
50
|
+
return `(() => {
|
|
51
|
+
try {
|
|
52
|
+
const input = document.querySelector('.aislash-editor-input[contenteditable="true"]');
|
|
53
|
+
if (!input) return JSON.stringify({ sent: false, error: 'Input box not found' });
|
|
54
|
+
return JSON.stringify({
|
|
55
|
+
sent: false,
|
|
56
|
+
needsTypeAndSend: true,
|
|
57
|
+
selector: '.aislash-editor-input[contenteditable="true"]',
|
|
58
|
+
});
|
|
59
|
+
} catch(e) {
|
|
60
|
+
return JSON.stringify({ sent: false, error: e.message });
|
|
61
|
+
}
|
|
62
|
+
})()`;
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
listSessions() {
|
|
66
|
+
return `(() => {
|
|
67
|
+
try {
|
|
68
|
+
const sessions = [];
|
|
69
|
+
const composer = document.querySelector('[data-composer-id]');
|
|
70
|
+
if (composer) {
|
|
71
|
+
sessions.push({
|
|
72
|
+
id: composer.getAttribute('data-composer-id'),
|
|
73
|
+
title: document.title.split(' — ')[0],
|
|
74
|
+
active: true,
|
|
75
|
+
status: composer.getAttribute('data-composer-status') || 'idle',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return JSON.stringify(sessions);
|
|
79
|
+
} catch(e) {
|
|
80
|
+
return JSON.stringify([]);
|
|
81
|
+
}
|
|
82
|
+
})()`;
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
newSession() {
|
|
86
|
+
return `(() => {
|
|
87
|
+
try {
|
|
88
|
+
const newBtn = [...document.querySelectorAll('a.action-label.codicon-add-two')]
|
|
89
|
+
.find(a => (a.getAttribute('aria-label') || '').startsWith('New Chat'));
|
|
90
|
+
if (newBtn) { newBtn.click(); return JSON.stringify({ created: true }); }
|
|
91
|
+
return JSON.stringify({ created: false, error: 'New Chat button not found' });
|
|
92
|
+
} catch(e) {
|
|
93
|
+
return JSON.stringify({ created: false, error: e.message });
|
|
94
|
+
}
|
|
95
|
+
})()`;
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
focusEditor() {
|
|
99
|
+
return `(() => {
|
|
100
|
+
try {
|
|
101
|
+
const input = document.querySelector('.aislash-editor-input[contenteditable="true"]');
|
|
102
|
+
if (input) { input.focus(); return 'focused'; }
|
|
103
|
+
return 'not_found';
|
|
104
|
+
} catch(e) { return 'error'; }
|
|
105
|
+
})()`;
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
openPanel() {
|
|
109
|
+
return `(() => {
|
|
110
|
+
try {
|
|
111
|
+
const sidebar = document.getElementById('workbench.parts.auxiliarybar');
|
|
112
|
+
if (sidebar && sidebar.offsetWidth > 0) {
|
|
113
|
+
const chatView = document.querySelector('[data-composer-id]');
|
|
114
|
+
if (chatView) return 'visible';
|
|
115
|
+
}
|
|
116
|
+
const btns = [...document.querySelectorAll('li.action-item a, button, [role="tab"]')];
|
|
117
|
+
const toggle = btns.find(b => {
|
|
118
|
+
const label = (b.textContent || b.getAttribute('aria-label') || '').toLowerCase();
|
|
119
|
+
return /agent|chat|composer|cursor tab/i.test(label);
|
|
120
|
+
});
|
|
121
|
+
if (toggle) { toggle.click(); return 'opened'; }
|
|
122
|
+
return 'not_found';
|
|
123
|
+
} catch (e) { return 'error'; }
|
|
124
|
+
})()`;
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
};
|
|
@@ -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
|
+
})()
|