adhdev 0.1.43 → 0.1.45

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 (47) hide show
  1. package/dist/index.js +4 -2
  2. package/package.json +2 -1
  3. package/providers/_builtin/cli/claude-cli/provider.js +125 -0
  4. package/providers/_builtin/cli/codex-cli/provider.js +77 -0
  5. package/providers/_builtin/cli/gemini-cli/provider.js +121 -0
  6. package/providers/_builtin/extension/cline/provider.js +77 -0
  7. package/providers/_builtin/extension/cline/scripts/focus_editor.js +48 -0
  8. package/providers/_builtin/extension/cline/scripts/list_chats.js +100 -0
  9. package/providers/_builtin/extension/cline/scripts/new_session.js +85 -0
  10. package/providers/_builtin/extension/cline/scripts/open_panel.js +25 -0
  11. package/providers/_builtin/extension/cline/scripts/read_chat.js +257 -0
  12. package/providers/_builtin/extension/cline/scripts/resolve_action.js +83 -0
  13. package/providers/_builtin/extension/cline/scripts/send_message.js +95 -0
  14. package/providers/_builtin/extension/cline/scripts/switch_session.js +206 -0
  15. package/providers/_builtin/extension/roo-code/provider.js +578 -0
  16. package/providers/_builtin/ide/antigravity/provider.js +92 -0
  17. package/providers/_builtin/ide/antigravity/scripts/focus_editor.js +20 -0
  18. package/providers/_builtin/ide/antigravity/scripts/list_chats.js +137 -0
  19. package/providers/_builtin/ide/antigravity/scripts/new_session.js +75 -0
  20. package/providers/_builtin/ide/antigravity/scripts/read_chat.js +240 -0
  21. package/providers/_builtin/ide/antigravity/scripts/resolve_action.js +64 -0
  22. package/providers/_builtin/ide/antigravity/scripts/send_message.js +56 -0
  23. package/providers/_builtin/ide/antigravity/scripts/switch_session.js +114 -0
  24. package/providers/_builtin/ide/cursor/provider.js +242 -0
  25. package/providers/_builtin/ide/cursor/provider.js.backup +116 -0
  26. package/providers/_builtin/ide/cursor/provider.js.bak +127 -0
  27. package/providers/_builtin/ide/cursor/scripts_backup/focus_editor.js +20 -0
  28. package/providers/_builtin/ide/cursor/scripts_backup/list_chats.js +111 -0
  29. package/providers/_builtin/ide/cursor/scripts_backup/new_session.js +62 -0
  30. package/providers/_builtin/ide/cursor/scripts_backup/open_panel.js +31 -0
  31. package/providers/_builtin/ide/cursor/scripts_backup/read_chat.js +433 -0
  32. package/providers/_builtin/ide/cursor/scripts_backup/resolve_action.js +90 -0
  33. package/providers/_builtin/ide/cursor/scripts_backup/send_message.js +86 -0
  34. package/providers/_builtin/ide/cursor/scripts_backup/switch_session.js +63 -0
  35. package/providers/_builtin/ide/vscode/provider.js +36 -0
  36. package/providers/_builtin/ide/vscode-insiders/provider.js +27 -0
  37. package/providers/_builtin/ide/vscodium/provider.js +27 -0
  38. package/providers/_builtin/ide/windsurf/provider.js +64 -0
  39. package/providers/_builtin/ide/windsurf/scripts/focus_editor.js +30 -0
  40. package/providers/_builtin/ide/windsurf/scripts/list_chats.js +117 -0
  41. package/providers/_builtin/ide/windsurf/scripts/new_session.js +69 -0
  42. package/providers/_builtin/ide/windsurf/scripts/open_panel.js +58 -0
  43. package/providers/_builtin/ide/windsurf/scripts/read_chat.js +297 -0
  44. package/providers/_builtin/ide/windsurf/scripts/resolve_action.js +68 -0
  45. package/providers/_builtin/ide/windsurf/scripts/send_message.js +87 -0
  46. package/providers/_builtin/ide/windsurf/scripts/switch_session.js +58 -0
  47. package/providers/_helpers/index.js +188 -0
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Cursor v1 — resolve_action
3
+ *
4
+ * CURSOR.md 4-3: 탐색 순서 5단계 + 특수 매칭
5
+ * ① run-command-review
6
+ * ② overlay 컨테이너
7
+ * ③ dialog 박스
8
+ * ④ 전역 버튼 검색
9
+ * ⑤ "run" 시 Enter 키 폴백
10
+ *
11
+ * 파라미터: ${ BUTTON_TEXT } (JSON.stringify된 lowercase 문자열)
12
+ * 최종 확인: 2026-03-06
13
+ */
14
+ (() => {
15
+ const want = ${ BUTTON_TEXT };
16
+ function norm(t) { return (t || '').replace(/\s+/g, ' ').trim().toLowerCase(); }
17
+ function matches(el) {
18
+ const t = norm(el.textContent);
19
+ if (t.length > 80) return false;
20
+ if (t === want) return true;
21
+ if (t.indexOf(want) === 0) return true;
22
+ if (want === 'run' && (/^run\s*/.test(t) || t === 'enter' || t === '⏎')) return true;
23
+ if (want === 'skip' && t.indexOf('skip') >= 0) return true;
24
+ if (want === 'reject' && t.indexOf('reject') >= 0) return true;
25
+ if (want === 'accept' && t.indexOf('accept') >= 0) return true;
26
+ if (want === 'approve' && t.indexOf('approve') >= 0) return true;
27
+ if (want === 'allow' && (t.indexOf('allow') >= 0 || t === 'always allow')) return true;
28
+ if (want === 'deny' && (t.indexOf('deny') >= 0 || t === 'always deny')) return true;
29
+ return false;
30
+ }
31
+ // COMMON.md 7장: React 호환 PointerEvent 클릭 (Cursor는 React 합성 이벤트 사용)
32
+ function doClick(el) {
33
+ if (!el) return;
34
+ const rect = el.getBoundingClientRect();
35
+ const x = rect.left + rect.width / 2;
36
+ const y = rect.top + rect.height / 2;
37
+ const evtOpts = { bubbles: true, cancelable: true, view: window, clientX: x, clientY: y, pointerId: 1, pointerType: 'mouse' };
38
+ try {
39
+ el.focus && el.focus();
40
+ // React가 수신하는 전체 이벤트 시퀀스
41
+ for (const type of ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click']) {
42
+ el.dispatchEvent(new PointerEvent(type, evtOpts));
43
+ }
44
+ } catch (_) {
45
+ // PointerEvent 미지원 폴백
46
+ el.click && el.click();
47
+ }
48
+ }
49
+ const sel = 'button, [role="button"], .monaco-button, .solid-dropdown-toggle, [class*="action-button"], [class*="btn"], .anysphere-button, [class*="composer-run-button"], [class*="composer-skip-button"]';
50
+
51
+ // ① run-command-review (CURSOR.md)
52
+ const runReview = document.querySelector('[class*="run-command-review"]');
53
+ if (runReview) {
54
+ const btns = Array.from(runReview.querySelectorAll(sel)).filter(b => b.offsetWidth > 0);
55
+ for (const b of btns) { if (matches(b)) { doClick(b); return true; } }
56
+ }
57
+ // ② overlay (CURSOR.md)
58
+ const overlays = document.querySelectorAll('.quick-agent-overlay-container, [class*="overlay-container"], [class*="tool-call-actions"]');
59
+ for (const overlay of overlays) {
60
+ const btns = Array.from(overlay.querySelectorAll(sel)).filter(b => b.offsetWidth > 0);
61
+ for (const b of btns) { if (matches(b)) { doClick(b); return true; } }
62
+ }
63
+ // ③ dialog (CURSOR.md)
64
+ const dialogs = document.querySelectorAll('.monaco-dialog-box, .monaco-modal-block, [role="dialog"]');
65
+ for (const dialog of dialogs) {
66
+ if (dialog.offsetWidth === 0) continue;
67
+ const btns = Array.from(dialog.querySelectorAll(sel)).filter(b => b.offsetWidth > 0);
68
+ for (const b of btns) { if (matches(b)) { doClick(b); return true; } }
69
+ }
70
+ // ④ 전역 검색 (ui-collapsible-header 제외)
71
+ const allBtns = Array.from(document.querySelectorAll(sel)).filter(b => b.offsetWidth > 0 && b.getBoundingClientRect().height > 0 && !b.classList.contains('ui-collapsible-header'));
72
+ for (const b of allBtns) { if (matches(b)) { doClick(b); return true; } }
73
+ // ④-B: 마지막 AI 메시지 안 인라인 버튼 (Cursor Agent mode Skip/Run)
74
+ const aiMsgs = document.querySelectorAll('[data-message-role="ai"]');
75
+ const lastAi = aiMsgs[aiMsgs.length - 1];
76
+ if (lastAi) {
77
+ const inlineBtns = Array.from(lastAi.querySelectorAll(sel)).filter(b => b.offsetWidth > 0 && !b.classList.contains('ui-collapsible-header'));
78
+ for (const b of inlineBtns) { if (matches(b)) { doClick(b); return true; } }
79
+ }
80
+ // ⑤ Enter 키 폴백 ("run" 요청 시)
81
+ if (want === 'run') {
82
+ const focused = document.activeElement;
83
+ if (focused) {
84
+ focused.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
85
+ focused.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
86
+ return true;
87
+ }
88
+ }
89
+ return false;
90
+ })()
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Cursor v1 — send_message
3
+ *
4
+ * Cursor는 workbench DOM에 직접 접근 (iframe 없음).
5
+ * 입력: [contenteditable="true"][role="textbox"] 또는 textarea.native-input
6
+ *
7
+ * ⚠️ React controlled 입력이므로 nativeSetter + input 이벤트 트리거 필수.
8
+ * ⚠️ CDP에서 setTimeout은 미실행 → async/await 사용.
9
+ *
10
+ * 파라미터: ${ MESSAGE }
11
+ * 최종 확인: 2026-03-10
12
+ */
13
+ (async () => {
14
+ try {
15
+ const msg = ${ MESSAGE };
16
+
17
+ // ─── 1. 입력 필드 찾기 ───
18
+ // Cursor Agent mode: contenteditable div
19
+ let editor = document.querySelector('.composer-view:not([style*="display: none"]) [contenteditable="true"][role="textbox"]')
20
+ || document.querySelector('[contenteditable="true"][role="textbox"]');
21
+
22
+ if (editor) {
23
+ // contenteditable에 값 설정
24
+ editor.focus();
25
+
26
+ // 기존 내용 선택 후 삭제
27
+ document.execCommand('selectAll', false, null);
28
+ document.execCommand('delete', false, null);
29
+
30
+ // 텍스트 삽입
31
+ document.execCommand('insertText', false, msg);
32
+
33
+ // React에 변경 알림
34
+ editor.dispatchEvent(new Event('input', { bubbles: true }));
35
+ editor.dispatchEvent(new Event('change', { bubbles: true }));
36
+
37
+ await new Promise(r => setTimeout(r, 300));
38
+
39
+ // Enter 키로 전송 (full sequence + composed for Shadow DOM)
40
+ const enterOpts = {
41
+ key: 'Enter', code: 'Enter', keyCode: 13, which: 13,
42
+ bubbles: true, cancelable: true, composed: true,
43
+ };
44
+ editor.dispatchEvent(new KeyboardEvent('keydown', enterOpts));
45
+ editor.dispatchEvent(new KeyboardEvent('keypress', enterOpts));
46
+ editor.dispatchEvent(new KeyboardEvent('keyup', enterOpts));
47
+
48
+ return 'sent';
49
+ }
50
+
51
+ // ─── 2. textarea fallback ───
52
+ let textarea = document.querySelector('.composer-view textarea.native-input')
53
+ || document.querySelector('textarea.native-input')
54
+ || document.querySelector('.chat-input textarea')
55
+ || document.querySelector('.composer-input textarea');
56
+
57
+ if (textarea) {
58
+ textarea.focus();
59
+
60
+ const proto = HTMLTextAreaElement.prototype;
61
+ const nativeSetter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
62
+
63
+ if (nativeSetter) nativeSetter.call(textarea, msg);
64
+ else textarea.value = msg;
65
+
66
+ textarea.dispatchEvent(new Event('input', { bubbles: true }));
67
+ textarea.dispatchEvent(new Event('change', { bubbles: true }));
68
+
69
+ await new Promise(r => setTimeout(r, 300));
70
+
71
+ const enterOpts2 = {
72
+ key: 'Enter', code: 'Enter', keyCode: 13, which: 13,
73
+ bubbles: true, cancelable: true, composed: true,
74
+ };
75
+ textarea.dispatchEvent(new KeyboardEvent('keydown', enterOpts2));
76
+ textarea.dispatchEvent(new KeyboardEvent('keypress', enterOpts2));
77
+ textarea.dispatchEvent(new KeyboardEvent('keyup', enterOpts2));
78
+
79
+ return 'sent';
80
+ }
81
+
82
+ return 'error: no input found';
83
+ } catch (e) {
84
+ return 'error: ' + e.message;
85
+ }
86
+ })()
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Cursor v1 — switch_session
3
+ *
4
+ * CURSOR.md 4-4: data-composer-id → DOM 텍스트 매칭 → scrollIntoView + 클릭
5
+ * 파라미터: ${ SESSION_ID }
6
+ * 최종 확인: 2026-03-06
7
+ */
8
+ (async () => {
9
+ const sleep = (ms) => new Promise(r => setTimeout(r, ms));
10
+ const id = ${ SESSION_ID };
11
+
12
+ // 1. Agents 패널 가시성 확인 → 토글 (CURSOR.md 4-4)
13
+ const ensureHistoryVisible = async () => {
14
+ const findAgentsToggle = () => {
15
+ const allHeaders = Array.from(document.querySelectorAll('.pane-header, .monaco-list-row, .tree-item, [role="button"]'));
16
+ return allHeaders.find(h => h.textContent && h.textContent.includes('Agents') && (h.querySelector('.codicon-chevron-right') || h.getAttribute('aria-expanded') === 'false' || h.classList.contains('collapsed')));
17
+ };
18
+ // CURSOR.md: 히스토리 항목 셀렉터
19
+ const hasItems = () => document.querySelectorAll('.agent-sidebar-cell, .composer-history-item, .chat-history-item, .composer-below-chat-history-item').length > 0;
20
+ if (hasItems()) return;
21
+ let agentToggle = findAgentsToggle();
22
+ if (!agentToggle) {
23
+ const sideBarIcon = Array.from(document.querySelectorAll('.action-item, .composite-bar-item, [role="button"]'))
24
+ .find(el => { const label = (el.getAttribute && el.getAttribute('aria-label')) || ''; return label.includes('Agents') || label.includes('Toggle Agents') || label.includes('Chat'); });
25
+ if (sideBarIcon) { sideBarIcon.click(); await sleep(500); agentToggle = findAgentsToggle(); }
26
+ }
27
+ if (agentToggle) { agentToggle.click(); await sleep(600); }
28
+ };
29
+ await ensureHistoryVisible();
30
+
31
+ // 2. 대상 찾기 (CURSOR.md: data-composer-id/data-id → 텍스트 매칭)
32
+ const findTarget = () => {
33
+ const byAttr = document.querySelectorAll('[data-composer-id], [data-id]');
34
+ for (var a = 0; a < byAttr.length; a++) {
35
+ var el = byAttr[a];
36
+ var dataId = el.getAttribute('data-composer-id') || el.getAttribute('data-id') || el.id;
37
+ if (dataId === id) return el;
38
+ }
39
+ const selectors = ['.agent-sidebar-cell', '.composer-below-chat-history-item', '.chat-history-item', '.composer-history-item', '.monaco-list-row'];
40
+ const allItems = document.querySelectorAll(selectors.join(', '));
41
+ for (var j = 0; j < allItems.length; j++) {
42
+ var item = allItems[j];
43
+ var dataId = item.getAttribute && (item.getAttribute('data-composer-id') || item.getAttribute('data-id') || item.id);
44
+ if (dataId === id) return item;
45
+ if (item.textContent && item.textContent.indexOf(id) >= 0) return item;
46
+ }
47
+ return null;
48
+ };
49
+
50
+ // 3. 클릭 (CURSOR.md: scrollIntoView + MouseEvent)
51
+ var target = findTarget();
52
+ if (target) {
53
+ target.scrollIntoView({ block: 'center', behavior: 'auto' });
54
+ await sleep(150);
55
+ const rect = target.getBoundingClientRect();
56
+ const options = { bubbles: true, cancelable: true, view: window, clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2 };
57
+ target.dispatchEvent(new MouseEvent('mousedown', options));
58
+ target.dispatchEvent(new MouseEvent('mouseup', options));
59
+ target.dispatchEvent(new MouseEvent('click', options));
60
+ return true;
61
+ }
62
+ return false;
63
+ })()
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Visual Studio Code — IDE Provider
3
+ *
4
+ * Category: ide
5
+ * 인프라 정보만 제공 (CDP 스크립트는 cursor와 유사 — 향후 추가)
6
+ *
7
+ * @type {import('../../../src/providers/contracts').ProviderModule}
8
+ */
9
+ module.exports = {
10
+ type: 'vscode',
11
+ name: 'Visual Studio Code',
12
+ category: 'ide',
13
+
14
+ // ─── IDE 인프라 ───
15
+ displayName: 'VS Code',
16
+ icon: '💙',
17
+ cli: 'code',
18
+ cdpPorts: [9339, 9340],
19
+ processNames: {
20
+ darwin: 'Visual Studio Code',
21
+ win32: ['Code.exe'],
22
+ },
23
+ paths: {
24
+ darwin: ['/Applications/Visual Studio Code.app'],
25
+ win32: [
26
+ 'C:\\Program Files\\Microsoft VS Code\\Code.exe',
27
+ 'C:\\Users\\*\\AppData\\Local\\Programs\\Microsoft VS Code\\Code.exe',
28
+ ],
29
+ linux: ['/usr/share/code', '/snap/code/current'],
30
+ },
31
+
32
+ inputMethod: 'cdp-type-and-send',
33
+ inputSelector: '[contenteditable="true"][role="textbox"]',
34
+
35
+ scripts: {},
36
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * VS Code Insiders — IDE Provider
3
+ * @type {import('../../../src/providers/contracts').ProviderModule}
4
+ */
5
+ module.exports = {
6
+ type: 'vscode-insiders',
7
+ name: 'Visual Studio Code - Insiders',
8
+ category: 'ide',
9
+
10
+ displayName: 'VS Code Insiders',
11
+ icon: '💚',
12
+ cli: 'code-insiders',
13
+ cdpPorts: [9341, 9342],
14
+ processNames: {
15
+ darwin: 'Visual Studio Code - Insiders',
16
+ win32: ['Code - Insiders.exe'],
17
+ },
18
+ paths: {
19
+ darwin: ['/Applications/Visual Studio Code - Insiders.app'],
20
+ win32: ['C:\\Program Files\\Microsoft VS Code Insiders\\Code - Insiders.exe'],
21
+ linux: ['/usr/share/code-insiders'],
22
+ },
23
+
24
+ inputMethod: 'cdp-type-and-send',
25
+ inputSelector: '[contenteditable="true"][role="textbox"]',
26
+ scripts: {},
27
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * VSCodium — IDE Provider
3
+ * @type {import('../../../src/providers/contracts').ProviderModule}
4
+ */
5
+ module.exports = {
6
+ type: 'vscodium',
7
+ name: 'VSCodium',
8
+ category: 'ide',
9
+
10
+ displayName: 'VSCodium',
11
+ icon: '💚',
12
+ cli: 'codium',
13
+ cdpPorts: [9343, 9344],
14
+ processNames: {
15
+ darwin: 'VSCodium',
16
+ win32: ['VSCodium.exe'],
17
+ },
18
+ paths: {
19
+ darwin: ['/Applications/VSCodium.app'],
20
+ win32: ['C:\\Program Files\\VSCodium\\VSCodium.exe'],
21
+ linux: ['/usr/share/codium', '/snap/codium/current'],
22
+ },
23
+
24
+ inputMethod: 'cdp-type-and-send',
25
+ inputSelector: '[contenteditable="true"][role="textbox"]',
26
+ scripts: {},
27
+ };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Windsurf — IDE Provider
3
+ *
4
+ * Category: ide (workbench CDP session — iframe 없음)
5
+ * Cascade 에디터 기반
6
+ *
7
+ * @type {import('../../../src/providers/contracts').ProviderModule}
8
+ */
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ const SCRIPTS_DIR = path.join(__dirname, 'scripts');
13
+
14
+ function loadScript(name) {
15
+ try {
16
+ return fs.readFileSync(path.join(SCRIPTS_DIR, name), 'utf8');
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+
22
+ module.exports = {
23
+ type: 'windsurf',
24
+ name: 'Windsurf',
25
+ category: 'ide',
26
+
27
+ // ─── IDE 인프라 ───
28
+ displayName: 'Windsurf',
29
+ icon: '🏄',
30
+ cli: 'windsurf',
31
+ cdpPorts: [9337, 9338],
32
+ processNames: {
33
+ darwin: 'Windsurf',
34
+ },
35
+ paths: {
36
+ darwin: ['/Applications/Windsurf.app'],
37
+ },
38
+
39
+ inputMethod: 'cdp-type-and-send',
40
+ inputSelector: '[contenteditable="true"][role="textbox"]',
41
+
42
+ scripts: {
43
+ readChat() { return loadScript('read_chat.js'); },
44
+ sendMessage(text) {
45
+ const s = loadScript('send_message.js');
46
+ return s ? s.replace(/\$\{\s*MESSAGE\s*\}/g, JSON.stringify(text)) : null;
47
+ },
48
+ listSessions() { return loadScript('list_chats.js'); },
49
+ switchSession(sessionId) {
50
+ const s = loadScript('switch_session.js');
51
+ return s ? s.replace(/\$\{\s*SESSION_ID\s*\}/g, JSON.stringify(sessionId)) : null;
52
+ },
53
+ newSession() { return loadScript('new_session.js'); },
54
+ resolveAction(params) {
55
+ const action = typeof params === 'string' ? params : params?.action || 'approve';
56
+ const buttonText = params?.button || params?.buttonText
57
+ || (action === 'approve' ? 'Accept' : action === 'reject' ? 'Reject' : action);
58
+ const s = loadScript('resolve_action.js');
59
+ return s ? s.replace(/\$\{\s*BUTTON_TEXT\s*\}/g, JSON.stringify(buttonText)) : null;
60
+ },
61
+ focusEditor() { return loadScript('focus_editor.js'); },
62
+ openPanel() { return loadScript('open_panel.js'); },
63
+ },
64
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Windsurf v1 — focus_editor
3
+ *
4
+ * Cascade(채팅) 입력창에 포커스를 맞춥니다.
5
+ * Windsurf는 VS Code 포크로, 채팅 UI를 "Cascade"라고 부릅니다.
6
+ *
7
+ * DOM 구조:
8
+ * #windsurf.cascadePanel → .chat-client-root
9
+ * 입력: [contenteditable="true"][role="textbox"]
10
+ * 또는 textarea (미로그인)
11
+ *
12
+ * 최종 확인: Windsurf (2026-03-06)
13
+ */
14
+ (() => {
15
+ try {
16
+ const editor =
17
+ document.querySelector('[contenteditable="true"][role="textbox"]') ||
18
+ document.querySelector('[data-lexical-editor="true"]') ||
19
+ document.querySelector('.chat-input textarea') ||
20
+ document.querySelector('.cascade-input [contenteditable="true"]') ||
21
+ document.querySelector('textarea:not(.xterm-helper-textarea)');
22
+ if (editor) {
23
+ editor.focus();
24
+ return true;
25
+ }
26
+ return false;
27
+ } catch (e) {
28
+ return false;
29
+ }
30
+ })()
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Windsurf v1 — list_chats
3
+ *
4
+ * Cascade 탭 목록을 가져옵니다.
5
+ * 패널이 닫혀 있으면 먼저 열고 탭이 렌더링될 때까지 대기합니다.
6
+ * cascade-tab-{uuid} 요소들의 React Fiber에서 제목을 추출합니다.
7
+ *
8
+ * 최종 확인: Windsurf 1.108.x (2026-03-10)
9
+ */
10
+ (async () => {
11
+ try {
12
+ // ─── 1. 패널이 닫혀 있으면 열기 ───
13
+ let tabs = document.querySelectorAll('[id^="cascade-tab-"]');
14
+ if (tabs.length === 0) {
15
+ // Cascade 패널 보이는지 확인
16
+ const cascade = document.querySelector('#windsurf\\.cascadePanel') ||
17
+ document.querySelector('.chat-client-root');
18
+ const sidebar = document.getElementById('workbench.parts.auxiliarybar');
19
+ const panelVisible = (cascade && cascade.offsetWidth > 0) ||
20
+ (sidebar && sidebar.offsetWidth > 0 && cascade);
21
+
22
+ if (!panelVisible) {
23
+ // Toggle 버튼 클릭 시도
24
+ const toggleBtns = Array.from(document.querySelectorAll('li.action-item a, button, [role="button"]'));
25
+ let toggled = false;
26
+ for (const btn of toggleBtns) {
27
+ const label = (btn.getAttribute('aria-label') || '').toLowerCase();
28
+ if (label.includes('toggle cascade') || label.includes('toggle secondary') ||
29
+ label.includes('toggle auxiliary') || label.includes('cascade')) {
30
+ if (btn.offsetWidth > 0 || btn.offsetHeight > 0) {
31
+ btn.click();
32
+ toggled = true;
33
+ break;
34
+ }
35
+ }
36
+ }
37
+ // 버튼 없으면 Cmd+L
38
+ if (!toggled) {
39
+ document.dispatchEvent(new KeyboardEvent('keydown', {
40
+ key: 'l', code: 'KeyL', keyCode: 76,
41
+ metaKey: true, ctrlKey: false,
42
+ bubbles: true, cancelable: true,
43
+ }));
44
+ document.dispatchEvent(new KeyboardEvent('keyup', {
45
+ key: 'l', code: 'KeyL', keyCode: 76,
46
+ metaKey: true, ctrlKey: false,
47
+ bubbles: true, cancelable: true,
48
+ }));
49
+ }
50
+
51
+ // 패널 렌더링 대기 (최대 3초)
52
+ for (let i = 0; i < 30; i++) {
53
+ await new Promise(r => setTimeout(r, 100));
54
+ tabs = document.querySelectorAll('[id^="cascade-tab-"]');
55
+ if (tabs.length > 0) break;
56
+ }
57
+ }
58
+ }
59
+
60
+ // ─── 2. 탭 정보 수집 ───
61
+ tabs = document.querySelectorAll('[id^="cascade-tab-"]');
62
+ if (tabs.length === 0) return [];
63
+
64
+ const result = [];
65
+ const seen = new Set();
66
+
67
+ tabs.forEach(tab => {
68
+ const tabId = tab.id.replace('cascade-tab-', '');
69
+ if (seen.has(tabId)) return;
70
+ seen.add(tabId);
71
+
72
+ let title = '';
73
+ let cascadeId = tabId;
74
+ let status = 'completed';
75
+
76
+ // React Fiber에서 제목 추출
77
+ const fk = Object.keys(tab).find(k => k.startsWith('__reactFiber'));
78
+ if (fk) {
79
+ let fiber = tab[fk];
80
+ for (let d = 0; d < 30 && fiber; d++) {
81
+ const p = fiber.memoizedProps;
82
+ if (p) {
83
+ if (p.title && typeof p.title === 'string') {
84
+ title = p.title;
85
+ }
86
+ if (p.cascadeId) {
87
+ cascadeId = p.cascadeId;
88
+ }
89
+ if (p.status && typeof p.status === 'string') {
90
+ status = p.status;
91
+ }
92
+ if (title) break;
93
+ }
94
+ fiber = fiber.return;
95
+ }
96
+ }
97
+
98
+ // DOM 폴백
99
+ if (!title) {
100
+ title = tab.textContent?.trim().substring(0, 100) || ('Chat ' + (result.length + 1));
101
+ }
102
+
103
+ const isVisible = tab.offsetHeight > 0 && tab.offsetWidth > 0;
104
+
105
+ result.push({
106
+ id: tabId,
107
+ title: title.substring(0, 100),
108
+ status: status,
109
+ active: isVisible
110
+ });
111
+ });
112
+
113
+ return result;
114
+ } catch (e) {
115
+ return [];
116
+ }
117
+ })()
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Windsurf v1 — new_session
3
+ *
4
+ * 새 Cascade 세션을 시작합니다.
5
+ *
6
+ * 전략:
7
+ * 1. aria-label 기반 "New" 버튼 탐색
8
+ * 2. 텍스트 기반 버튼 탐색
9
+ * 3. Codicon 아이콘(+) 기반 탐색
10
+ * 4. Cmd+L 단축키 폴백 (Windsurf에서 새 Cascade 열기)
11
+ *
12
+ * Windsurf에서는 Cascade가 AI 채팅 패널이며,
13
+ * "New Chat" 또는 "+" 버튼으로 새 세션을 시작합니다.
14
+ *
15
+ * 최종 확인: Windsurf 1.108.x (2026-03-10)
16
+ */
17
+ (() => {
18
+ try {
19
+ // ─── 1. aria-label 기반 ───
20
+ const allBtns = Array.from(document.querySelectorAll('button, [role="button"], .action-item'))
21
+ .filter(b => b.offsetWidth > 0);
22
+
23
+ for (const btn of allBtns) {
24
+ const label = (btn.getAttribute('aria-label') || '').toLowerCase();
25
+ if (label.includes('new chat') || label.includes('new cascade') ||
26
+ label.includes('new conversation') || label.includes('start new') ||
27
+ label.includes('new session')) {
28
+ btn.click();
29
+ return 'clicked (aria)';
30
+ }
31
+ }
32
+
33
+ // ─── 2. 텍스트 기반 ───
34
+ for (const btn of allBtns) {
35
+ const text = (btn.textContent || '').trim();
36
+ if (text === '+' || text === 'New Chat' || text === 'New Cascade' ||
37
+ text === 'Start New Chat' || text === 'New Session') {
38
+ btn.click();
39
+ return 'clicked (text)';
40
+ }
41
+ }
42
+
43
+ // ─── 3. Codicon 아이콘(+) 기반 ───
44
+ for (const btn of allBtns) {
45
+ const hasPlus = btn.querySelector('.codicon-plus, .codicon-add, [class*="plus"]');
46
+ if (hasPlus) {
47
+ const label = (btn.getAttribute('aria-label') || btn.getAttribute('title') || '').toLowerCase();
48
+ // Cascade 관련 컨텍스트이거나 라벨이 비어있으면 새 세션 버튼일 가능성
49
+ if (label.includes('chat') || label.includes('cascade') ||
50
+ label.includes('new') || label === '') {
51
+ btn.click();
52
+ return 'clicked (icon)';
53
+ }
54
+ }
55
+ }
56
+
57
+ // ─── 4. Cmd+L 단축키 (macOS: metaKey, Windows/Linux: ctrlKey) ───
58
+ // Windsurf에서 Cmd+L은 Cascade 패널 토글/새 세션 생성
59
+ document.dispatchEvent(new KeyboardEvent('keydown', {
60
+ key: 'l', code: 'KeyL', keyCode: 76,
61
+ metaKey: true, ctrlKey: false,
62
+ bubbles: true, cancelable: true,
63
+ }));
64
+
65
+ return 'sent Cmd+L';
66
+ } catch (e) {
67
+ return 'error: ' + e.message;
68
+ }
69
+ })()
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Windsurf v1 — open_panel
3
+ *
4
+ * Cascade(AI 채팅) 패널이 닫혀 있을 때 열기.
5
+ *
6
+ * Windsurf의 Cascade 패널은 Secondary Side Bar (#workbench.parts.auxiliarybar)에 위치.
7
+ * 닫혀 있으면 offsetWidth === 0.
8
+ * Cmd+L 단축키로 열 수 있음 (WINDSURF.md §2.5).
9
+ *
10
+ * 반환: 'visible' | 'opened' | 'error: ...'
11
+ * 최종 확인: Windsurf 1.108.x (2026-03-10)
12
+ */
13
+ (() => {
14
+ try {
15
+ // ─── 1. Cascade 패널이 이미 열려 있는지 확인 ───
16
+ const cascade = document.querySelector('#windsurf\\.cascadePanel') ||
17
+ document.querySelector('.chat-client-root');
18
+ const sidebar = document.getElementById('workbench.parts.auxiliarybar');
19
+
20
+ // 패널이 존재하고 보이면 이미 열린 상태
21
+ if (cascade && cascade.offsetWidth > 0 && cascade.offsetHeight > 0) {
22
+ return 'visible';
23
+ }
24
+ if (sidebar && sidebar.offsetWidth > 0 && sidebar.offsetHeight > 0 && cascade) {
25
+ return 'visible';
26
+ }
27
+
28
+ // ─── 2. Toggle 버튼 클릭 시도 ───
29
+ const toggleBtns = Array.from(document.querySelectorAll('li.action-item a, button, [role="button"]'));
30
+ for (const btn of toggleBtns) {
31
+ const label = (btn.getAttribute('aria-label') || '').toLowerCase();
32
+ if (label.includes('toggle cascade') || label.includes('toggle secondary') ||
33
+ label.includes('toggle auxiliary') || label.includes('cascade')) {
34
+ if (btn.offsetWidth > 0 || btn.offsetHeight > 0) {
35
+ btn.click();
36
+ return 'opened (toggle)';
37
+ }
38
+ }
39
+ }
40
+
41
+ // ─── 3. Cmd+L 단축키 폴백 (Windsurf 공식 단축키) ───
42
+ // keyCode: 76, modifiers: 4 (Meta/Cmd)
43
+ document.dispatchEvent(new KeyboardEvent('keydown', {
44
+ key: 'l', code: 'KeyL', keyCode: 76,
45
+ metaKey: true, ctrlKey: false,
46
+ bubbles: true, cancelable: true,
47
+ }));
48
+ document.dispatchEvent(new KeyboardEvent('keyup', {
49
+ key: 'l', code: 'KeyL', keyCode: 76,
50
+ metaKey: true, ctrlKey: false,
51
+ bubbles: true, cancelable: true,
52
+ }));
53
+
54
+ return 'opened (Cmd+L)';
55
+ } catch (e) {
56
+ return 'error: ' + e.message;
57
+ }
58
+ })()