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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adhdev",
3
- "version": "0.1.43",
3
+ "version": "0.1.44",
4
4
  "description": "ADHDev CLI — Detect, install and configure your IDE + AI agent extensions",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -16,6 +16,7 @@
16
16
  },
17
17
  "files": [
18
18
  "dist",
19
+ "providers",
19
20
  "README.md"
20
21
  ],
21
22
  "keywords": [
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Claude Code — CLI Provider
3
+ *
4
+ * Category: cli (PTY 기반 — node-pty로 claude 프로세스 관리)
5
+ *
6
+ * Claude Code는 ⏺/⎿ 프리픽스로 응답 구조를 표시.
7
+ * TerminalBuffer를 사용하여 스크린 콘텐츠를 읽음.
8
+ *
9
+ * @type {import('../../../../src/providers/contracts').ProviderModule}
10
+ */
11
+ module.exports = {
12
+ type: 'claude-cli',
13
+ name: 'Claude Code',
14
+ category: 'cli',
15
+
16
+ // ─── Provider Settings (대시보드에서 제어 가능) ───
17
+ settings: {
18
+ mode: {
19
+ type: 'select',
20
+ default: 'terminal',
21
+ public: true,
22
+ label: '표시 모드',
23
+ description: 'terminal: PTY 터미널 뷰, chat: 파싱된 대화 뷰',
24
+ options: ['terminal', 'chat'],
25
+ },
26
+ notifications: {
27
+ type: 'boolean',
28
+ default: true,
29
+ public: true,
30
+ label: '알림',
31
+ description: '상태 변경 시 알림을 표시합니다',
32
+ },
33
+ autoApprove: {
34
+ type: 'boolean',
35
+ default: false,
36
+ public: true,
37
+ label: '자동 승인',
38
+ description: '도구 실행 승인을 자동으로 허용합니다',
39
+ },
40
+ },
41
+
42
+ // ─── CLI 실행 설정 ───
43
+ binary: 'claude',
44
+ spawn: {
45
+ command: 'claude',
46
+ args: [],
47
+ shell: false,
48
+ env: {},
49
+ },
50
+
51
+ // ─── PTY 출력 패턴 매칭 ───
52
+ patterns: {
53
+ prompt: [
54
+ /❯\s*$/m,
55
+ /[a-zA-Z0-9._-]+\s*>\s*$/m,
56
+ /claude\s*>\s*$/m,
57
+ /➜\s+.*\s*$/m,
58
+ /\s+✗\s*$/m,
59
+ /\?\s*for\s*shortcuts/i,
60
+ /effort/i,
61
+ ],
62
+ generating: [
63
+ /esc\s+to\s+interrupt/i,
64
+ /⎋\s*to\s+interrupt/i,
65
+ /press\s+esc\s+to\s+interrupt/i,
66
+ /accept\s+edits\s+on/i,
67
+ /shift\+tab\s+to\s+cycle/i,
68
+ ],
69
+ approval: [
70
+ /allow\s+once/i,
71
+ /always\s+allow/i,
72
+ /\(y\)es\s*\/\s*\(n\)o/i,
73
+ /\[y\/n\/a\]/i,
74
+ /do\s+you\s+want\s+to\s+(run|proceed|allow|execute|make|create|delete|write|edit)/i,
75
+ /proceed\?/i,
76
+ /esc\s+to\s+cancel/i,
77
+ /tab\s+to\s+amend/i,
78
+ /yes,\s+allow\s+all\s+edits/i,
79
+ ],
80
+ ready: [
81
+ /❯\s*$/m,
82
+ /\?\s*for\s*shortcuts/i,
83
+ ],
84
+ },
85
+
86
+ /**
87
+ * Claude Code TUI 아티팩트 제거
88
+ * ⏺ 프리픽스 응답 본문 추출, TUI 크롬 제거
89
+ */
90
+ cleanOutput(raw, _lastUserInput) {
91
+ const allSegments = [];
92
+ for (const line of raw.split('\n')) {
93
+ const segs = line.split('\r').map(s => s.trim()).filter(Boolean);
94
+ allSegments.push(...segs);
95
+ }
96
+
97
+ const responseLines = [];
98
+ let foundResponse = false;
99
+ for (const seg of allSegments) {
100
+ if (seg.startsWith('⏺')) {
101
+ foundResponse = true;
102
+ responseLines.push(seg.replace(/^⏺\s*/, ''));
103
+ continue;
104
+ }
105
+ if (seg.startsWith('⎿')) {
106
+ if (foundResponse) responseLines.push(seg.replace(/^⎿\s*/, ''));
107
+ continue;
108
+ }
109
+ if (foundResponse) {
110
+ const s = seg.trim();
111
+ // TUI junk
112
+ if (/^❯/.test(s)) continue;
113
+ if (/^\?\s*for\s*shortcuts/.test(s)) continue;
114
+ if (/^[╭╰│├╮╯─═]+$/.test(s)) continue;
115
+ if (/^◐\s/.test(s) || /\/effort/.test(s)) continue;
116
+ if (/esc\s*to\s*interrupt/i.test(s)) continue;
117
+ if (/^0;/.test(s)) continue;
118
+ if (/^[✻✶✳✢✽·◆◇◐◑◒◓⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/.test(s) && s.length < 40) continue;
119
+ responseLines.push(seg);
120
+ }
121
+ }
122
+
123
+ return responseLines.join('\n').replace(/\n{3,}/g, '\n\n').trim();
124
+ },
125
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Codex CLI — CLI Provider
3
+ *
4
+ * Category: cli (PTY 기반 — node-pty로 codex 프로세스 관리)
5
+ *
6
+ * OpenAI Codex CLI — 비교적 단순한 TUI.
7
+ *
8
+ * @type {import('../../../../src/providers/contracts').ProviderModule}
9
+ */
10
+ module.exports = {
11
+ type: 'codex-cli',
12
+ name: 'Codex CLI',
13
+ category: 'cli',
14
+
15
+ settings: {
16
+ mode: {
17
+ type: 'select', default: 'terminal', public: true,
18
+ label: '표시 모드', options: ['terminal', 'chat'],
19
+ },
20
+ notifications: {
21
+ type: 'boolean', default: true, public: true,
22
+ label: '알림',
23
+ },
24
+ },
25
+
26
+ // ─── CLI 실행 설정 ───
27
+ binary: 'codex',
28
+ spawn: {
29
+ command: 'codex',
30
+ args: ['--full-auto'],
31
+ shell: false,
32
+ env: {},
33
+ },
34
+
35
+ // ─── PTY 출력 패턴 매칭 ───
36
+ patterns: {
37
+ prompt: [
38
+ /[❯>]\s*$/,
39
+ /codex\s*>\s*$/m,
40
+ /\$\s*$/m,
41
+ ],
42
+ generating: [
43
+ /generating/i,
44
+ /thinking/i,
45
+ /esc\s+to\s+(interrupt|cancel)/i,
46
+ ],
47
+ approval: [
48
+ /\(y\)es\s*\/\s*\(n\)o/i,
49
+ /\[y\/n\]/i,
50
+ /do\s+you\s+want\s+to\s+(run|proceed|allow|execute)/i,
51
+ /proceed\?/i,
52
+ /approve/i,
53
+ ],
54
+ ready: [
55
+ /[❯>]\s*$/,
56
+ /codex\s*>\s*$/m,
57
+ ],
58
+ },
59
+
60
+ /**
61
+ * Codex CLI 출력 정리 — 프롬프트, 상태바 제거
62
+ */
63
+ cleanOutput(raw, _lastUserInput) {
64
+ const lines = raw.split('\n');
65
+ const cleaned = [];
66
+ for (const line of lines) {
67
+ const t = line.trim();
68
+ if (!t) { cleaned.push(''); continue; }
69
+ if (/^[❯>]\s*$/.test(t)) continue;
70
+ if (/^codex\s*>/.test(t)) continue;
71
+ if (/^\$\s*$/.test(t)) continue;
72
+ if (/^[\u2500\u2501\u2550]{4,}/.test(t)) continue;
73
+ cleaned.push(line);
74
+ }
75
+ return cleaned.join('\n').replace(/\n{3,}/g, '\n\n').trim();
76
+ },
77
+ };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Gemini CLI — CLI Provider
3
+ *
4
+ * Category: cli (PTY 기반 — node-pty로 gemini 프로세스 관리)
5
+ *
6
+ * Gemini CLI는 INK/React TUI를 사용하여 터미널에 렌더링.
7
+ * 출력에 박스 문자, 구분선, 상태바 등 TUI 아티팩트가 포함됨.
8
+ * cleanOutput()에서 이를 제거하여 순수 응답만 추출.
9
+ *
10
+ * @type {import('../../../../src/providers/contracts').ProviderModule}
11
+ */
12
+ module.exports = {
13
+ type: 'gemini-cli',
14
+ name: 'Gemini CLI',
15
+ category: 'cli',
16
+
17
+ settings: {
18
+ mode: {
19
+ type: 'select', default: 'terminal', public: true,
20
+ label: '표시 모드', options: ['terminal', 'chat'],
21
+ },
22
+ notifications: {
23
+ type: 'boolean', default: true, public: true,
24
+ label: '알림',
25
+ },
26
+ },
27
+
28
+ // ─── CLI 실행 설정 ───
29
+ binary: 'gemini',
30
+ spawn: {
31
+ command: 'gemini',
32
+ args: ['--yolo'],
33
+ shell: true,
34
+ env: {},
35
+ },
36
+
37
+ // ─── PTY 출력 패턴 매칭 ───
38
+ patterns: {
39
+ prompt: [
40
+ /Type your message/,
41
+ /[❯>]\s*$/,
42
+ /\n\s*[❯>]\s*$/,
43
+ ],
44
+ generating: [
45
+ /esc\s+to\s+interrupt/i,
46
+ /⎋\s*to\s+interrupt/i,
47
+ /press\s+esc\s+to\s+interrupt/i,
48
+ /accept\s+edits\s+on/i,
49
+ /shift\+tab\s+to\s+cycle/i,
50
+ /ctrl\+c\s+to\s+(cancel|interrupt|stop)/i,
51
+ ],
52
+ approval: [
53
+ /allow\s+once/i,
54
+ /always\s+allow/i,
55
+ /\(y\)es\s*\/\s*\(n\)o/i,
56
+ /\[y\/n\/a\]/i,
57
+ /do\s+you\s+want\s+to\s+(run|proceed|allow|execute)/i,
58
+ /proceed\?/i,
59
+ ],
60
+ ready: [
61
+ /[❯>]\s*$/,
62
+ /Gemini \d/i,
63
+ ],
64
+ },
65
+
66
+ /**
67
+ * Gemini CLI TUI 아티팩트 제거 — AI 응답 본문만 추출
68
+ * INK/React TUI의 박스 문자, 구분선, 상태바, 프롬프트 등을 제거
69
+ */
70
+ cleanOutput(raw, lastUserInput) {
71
+ const lines = raw.split('\n');
72
+ const cleaned = [];
73
+
74
+ for (const line of lines) {
75
+ const trimmed = line.trim();
76
+ if (!trimmed) { cleaned.push(''); continue; }
77
+
78
+ // 박스 문자 줄
79
+ if (/^[\u256d\u256e\u2570\u256f\u2502\u251c\u2524\u252c\u2534\u253c\u2500\u2501\u2550\u2554\u2557\u255a\u255d\u2551]+$/.test(trimmed)) continue;
80
+ if (/^[\u256d\u2570]\u2500\u2500/.test(trimmed) || /\u2500\u2500[\u256e\u256f]$/.test(trimmed)) continue;
81
+ if (/^\u2502.*\u2502$/.test(trimmed)) continue;
82
+
83
+ // 구분선
84
+ if (/[\u2500\u2501\u2550\u2580\u2584]{4,}/.test(trimmed)) continue;
85
+
86
+ // 상태바/메타
87
+ if (/^YOLO\s+ctrl\+y/i.test(trimmed)) continue;
88
+ if (/^\? for shortcuts/.test(trimmed)) continue;
89
+ if (/^\/model\s/i.test(trimmed)) continue;
90
+ if (/^~\s.*no sandbox/i.test(trimmed)) continue;
91
+ if (/^Type your message/i.test(trimmed)) continue;
92
+ if (/ctrl\+[a-z]/i.test(trimmed) && trimmed.length < 50) continue;
93
+
94
+ // 단축키 도움말
95
+ if (/Shortcuts?\s*\(for more/i.test(trimmed)) continue;
96
+ if (/shell mode|cycle mode|paste images|select file or folder/i.test(trimmed)) continue;
97
+
98
+ // 프롬프트 줄
99
+ if (/^[❯>]\s*$/.test(trimmed)) continue;
100
+ if (/^[*•]\s*$/.test(trimmed)) continue;
101
+
102
+ // 사용자 입력 에코
103
+ if (lastUserInput && /^[*•]\s/.test(trimmed)) {
104
+ const bulletContent = trimmed.replace(/^[*•]\s*/, '').trim();
105
+ if (bulletContent === lastUserInput.trim()) continue;
106
+ }
107
+
108
+ // 업데이트/인증 안내
109
+ if (/Gemini CLI update available/i.test(trimmed)) continue;
110
+ if (/brew upgrade gemini-cli/i.test(trimmed)) continue;
111
+ if (/Waiting for auth/i.test(trimmed)) continue;
112
+
113
+ // 스피너 문자만 있는 줄
114
+ if (/^[\u280b\u2819\u2839\u2838\u283c\u2834\u2826\u2827\u2807\u280f\s]+$/.test(trimmed)) continue;
115
+
116
+ cleaned.push(line);
117
+ }
118
+
119
+ return cleaned.join('\n').replace(/\n{3,}/g, '\n\n').trim();
120
+ },
121
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Cline — Extension Provider
3
+ *
4
+ * Category: extension (webview CDP session)
5
+ * Roo Code의 원본 fork — 구조 거의 동일 (Fiber 기반)
6
+ *
7
+ * 차이점:
8
+ * - extensionId: 'saoudrizwan.claude-dev'
9
+ * - resolve_action 사용 (Roo Code와 동일 패턴)
10
+ *
11
+ * @type {import('../../../src/providers/contracts').ProviderModule}
12
+ */
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ const SCRIPTS_DIR = path.join(__dirname, 'scripts');
17
+
18
+ function loadScript(name) {
19
+ try {
20
+ return fs.readFileSync(path.join(SCRIPTS_DIR, name), 'utf8');
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ module.exports = {
27
+ type: 'cline',
28
+ name: 'Cline',
29
+ category: 'extension',
30
+
31
+ extensionId: 'saoudrizwan.claude-dev',
32
+ extensionIdPattern: /extensionId=saoudrizwan\.claude-dev/i,
33
+
34
+ vscodeCommands: {
35
+ focusPanel: 'claude-dev.SidebarProvider.focus',
36
+ },
37
+
38
+ scripts: {
39
+ readChat() {
40
+ return loadScript('read_chat.js');
41
+ },
42
+
43
+ sendMessage(text) {
44
+ const script = loadScript('send_message.js');
45
+ if (!script) return null;
46
+ return script.replace(/\$\{\s*MESSAGE\s*\}/g, JSON.stringify(text));
47
+ },
48
+
49
+ listSessions() {
50
+ return loadScript('list_chats.js');
51
+ },
52
+
53
+ switchSession(sessionId) {
54
+ const script = loadScript('switch_session.js');
55
+ if (!script) return null;
56
+ return script.replace(/\$\{\s*SESSION_ID\s*\}/g, JSON.stringify(sessionId));
57
+ },
58
+
59
+ newSession() {
60
+ return loadScript('new_session.js');
61
+ },
62
+
63
+ resolveAction(action) {
64
+ const script = loadScript('resolve_action.js');
65
+ if (!script) return null;
66
+ return script.replace(/\$\{\s*ACTION\s*\}/g, JSON.stringify(action));
67
+ },
68
+
69
+ focusEditor() {
70
+ return loadScript('focus_editor.js');
71
+ },
72
+
73
+ openPanel() {
74
+ return loadScript('open_panel.js');
75
+ },
76
+ },
77
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Cline v1 — focus_editor
3
+ *
4
+ * Cline webview iframe 내부의 입력 필드에 포커스.
5
+ * send_message 전에 호출하거나, 대시보드 "Focus" 버튼에 사용.
6
+ *
7
+ * 최종 확인: 2026-03-07
8
+ */
9
+ (() => {
10
+ try {
11
+ const inner = document.querySelector('iframe');
12
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
13
+ if (!doc) return 'no doc';
14
+
15
+ // data-testid 우선 → fallback
16
+ let target = doc.querySelector('[data-testid="chat-input"]');
17
+ if (!target) {
18
+ const textareas = doc.querySelectorAll('textarea');
19
+ for (const ta of textareas) {
20
+ if (ta.offsetParent !== null && ta.offsetHeight > 20) {
21
+ target = ta;
22
+ break;
23
+ }
24
+ }
25
+ }
26
+ if (!target) {
27
+ // contenteditable fallback
28
+ const editables = doc.querySelectorAll('[contenteditable="true"]');
29
+ for (const el of editables) {
30
+ if (el.offsetParent !== null && el.offsetHeight > 10) {
31
+ target = el;
32
+ break;
33
+ }
34
+ }
35
+ }
36
+ if (!target) return 'no input';
37
+
38
+ target.focus();
39
+ // 커서를 끝으로 이동
40
+ if (target.tagName === 'TEXTAREA' || target.tagName === 'INPUT') {
41
+ const len = (target.value || '').length;
42
+ target.setSelectionRange(len, len);
43
+ }
44
+ return 'focused';
45
+ } catch (e) {
46
+ return 'error: ' + e.message;
47
+ }
48
+ })()
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Cline v1 — list_chats (Fiber + DOM 이중 접근)
3
+ *
4
+ * 1차: React Fiber에서 taskHistory 배열 추출
5
+ * __reactFiber, __reactProps, __reactContainer 모두 탐색
6
+ * 2차: Virtuoso DOM 파싱
7
+ *
8
+ * 반환: JSON 문자열 — [{id, title, status, time, cost, tokensIn, tokensOut, modelId}]
9
+ *
10
+ * 최종 확인: 2026-03-07
11
+ */
12
+ (() => {
13
+ try {
14
+ const inner = document.querySelector('iframe');
15
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
16
+ if (!doc) return JSON.stringify({ error: 'no_doc', panel: 'hidden' });
17
+
18
+ // Fiber 키 찾기 helper — __reactFiber, __reactProps, __reactContainer 모두 검색
19
+ const findFiberKey = (el) => Object.keys(el).find(k =>
20
+ k.startsWith('__reactFiber') || k.startsWith('__reactProps') || k.startsWith('__reactContainer'));
21
+
22
+ const getFiber = (el) => {
23
+ const fk = findFiberKey(el);
24
+ if (!fk) return null;
25
+ let fiber = el[fk];
26
+ // __reactContainer인 경우 내부 fiber tree root로 접근
27
+ if (fk.startsWith('__reactContainer') && fiber?._internalRoot?.current) {
28
+ fiber = fiber._internalRoot.current;
29
+ }
30
+ return fiber;
31
+ };
32
+
33
+ // ─── 1차: Fiber props에서 taskHistory 찾기 ───
34
+ const allEls = doc.querySelectorAll('*');
35
+ let taskHistory = null;
36
+
37
+ for (const el of allEls) {
38
+ let fiber = getFiber(el);
39
+ if (!fiber) continue;
40
+
41
+ for (let d = 0; d < 30 && fiber; d++) {
42
+ const props = fiber.memoizedProps || fiber.pendingProps;
43
+ if (props && props.taskHistory && Array.isArray(props.taskHistory)) {
44
+ taskHistory = props.taskHistory;
45
+ break;
46
+ }
47
+ // memoizedState 체인에서도 탐색
48
+ if (fiber.memoizedState) {
49
+ let st = fiber.memoizedState;
50
+ while (st) {
51
+ try {
52
+ const ms = st.memoizedState;
53
+ if (ms && typeof ms === 'object' && !Array.isArray(ms)) {
54
+ if (ms.taskHistory && Array.isArray(ms.taskHistory)) {
55
+ taskHistory = ms.taskHistory;
56
+ break;
57
+ }
58
+ }
59
+ } catch { }
60
+ st = st.next;
61
+ }
62
+ }
63
+ if (taskHistory) break;
64
+ fiber = fiber.return;
65
+ }
66
+ if (taskHistory) break;
67
+ }
68
+
69
+ if (taskHistory && taskHistory.length > 0) {
70
+ const results = taskHistory.slice(0, 50).map((task, i) => ({
71
+ id: task.id || String(task.ts),
72
+ title: (task.task || '').substring(0, 120),
73
+ status: i === 0 ? 'current' : '',
74
+ time: task.ts ? new Date(task.ts).toISOString() : '',
75
+ cost: task.totalCost ? `$${task.totalCost.toFixed(4)}` : '',
76
+ tokensIn: task.tokensIn || 0,
77
+ tokensOut: task.tokensOut || 0,
78
+ modelId: task.modelId || '',
79
+ size: task.size || 0,
80
+ isFavorited: !!task.isFavorited,
81
+ }));
82
+ return JSON.stringify(results);
83
+ }
84
+
85
+ // ─── 2차: Virtuoso DOM 파싱 ───
86
+ const items = doc.querySelectorAll('[data-item-index]');
87
+ if (items.length > 0) {
88
+ const results = Array.from(items).slice(0, 50).map(el => ({
89
+ id: el.getAttribute('data-item-index') || '',
90
+ title: (el.textContent || '').trim().substring(0, 120),
91
+ status: '',
92
+ }));
93
+ return JSON.stringify(results);
94
+ }
95
+
96
+ return JSON.stringify([]);
97
+ } catch (e) {
98
+ return JSON.stringify({ error: e.message || String(e) });
99
+ }
100
+ })()
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Cline v1 — new_session
3
+ *
4
+ * 구조:
5
+ * 1. "New Task" 버튼 또는 "+" 버튼 클릭
6
+ * 2. data-testid 우선 → aria-label → 텍스트 매칭
7
+ *
8
+ * 최종 확인: 2026-03-07
9
+ */
10
+ (() => {
11
+ try {
12
+ const inner = document.querySelector('iframe');
13
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
14
+ if (!doc) return 'no doc';
15
+
16
+ const buttons = Array.from(doc.querySelectorAll('button'))
17
+ .filter(b => b.offsetWidth > 0 && b.offsetHeight > 0);
18
+
19
+ // ─── 1단계: data-testid 기반 ───
20
+ for (const btn of buttons) {
21
+ const testId = (btn.getAttribute('data-testid') || '').toLowerCase();
22
+ if (testId.includes('new-task') || testId.includes('new-chat') || testId.includes('new_task')) {
23
+ btn.click();
24
+ return 'clicked (testid)';
25
+ }
26
+ }
27
+
28
+ // ─── 2단계: aria-label 기반 ───
29
+ for (const btn of buttons) {
30
+ const ariaLabel = (btn.getAttribute('aria-label') || '').toLowerCase();
31
+ if (ariaLabel.includes('new task') || ariaLabel.includes('new chat')
32
+ || ariaLabel.includes('plus') || ariaLabel === 'new') {
33
+ btn.click();
34
+ return 'clicked (aria)';
35
+ }
36
+ }
37
+
38
+ // ─── 3단계: 텍스트 매칭 ───
39
+ for (const btn of buttons) {
40
+ const text = (btn.textContent || '').trim();
41
+ if (text === '+' || text.includes('New Task') || text.includes('New Chat')) {
42
+ btn.click();
43
+ return 'clicked (text)';
44
+ }
45
+ }
46
+
47
+ // ─── 4단계: SVG plus 아이콘 버튼 ───
48
+ for (const btn of buttons) {
49
+ const svg = btn.querySelector('svg');
50
+ if (!svg) continue;
51
+ const path = svg.querySelector('path');
52
+ if (path) {
53
+ const d = path.getAttribute('d') || '';
54
+ // SVG plus icon의 일반적인 path pattern
55
+ if (d.includes('M12') && (d.includes('H5') || d.includes('h') || d.includes('v'))) {
56
+ // 다른 버튼 텍스트가 없는 아이콘 전용 버튼
57
+ const text = (btn.textContent || '').trim();
58
+ if (text.length < 3) {
59
+ btn.click();
60
+ return 'clicked (svg)';
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ // ─── 5단계: 웰컴 화면 감지 (이미 새 세션) ───
67
+ const bodyText = (doc.body.textContent || '').toLowerCase();
68
+ if (bodyText.includes('what can i do for you') || bodyText.includes('start a new task') || bodyText.includes('type your task')) {
69
+ return 'clicked (already new)';
70
+ }
71
+
72
+ // ─── 6단계: History 뷰 감지 → Done 클릭으로 웰컴 복귀 ───
73
+ if (bodyText.includes('history') && bodyText.includes('done')) {
74
+ for (const btn of buttons) {
75
+ const text = (btn.textContent || '').trim();
76
+ if (text === 'Done') {
77
+ btn.click();
78
+ return 'clicked (history done)';
79
+ }
80
+ }
81
+ }
82
+
83
+ return 'no button found';
84
+ } catch (e) { return 'error: ' + e.message; }
85
+ })()
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Cline v1 — open_panel
3
+ *
4
+ * 패널 상태 확인 및 열기 시도.
5
+ *
6
+ * iframe 컨텍스트에서는 VS Code API 접근이 제한적이므로,
7
+ * 패널이 숨겨져 있을 때는 'panel_hidden' 상태를 반환.
8
+ * → bridge extension의 ensureAgentPanelOpen() 또는
9
+ * agent_stream_focus 메시지를 통해 열어야 함.
10
+ *
11
+ * 반환: 'visible' | 'panel_hidden'
12
+ * 최종 확인: 2026-03-07
13
+ */
14
+ (() => {
15
+ try {
16
+ const inner = document.querySelector('iframe');
17
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
18
+ if (!doc) return 'panel_hidden';
19
+
20
+ const root = doc.getElementById('root');
21
+ if (root && root.offsetHeight > 0) return 'visible';
22
+
23
+ return 'panel_hidden';
24
+ } catch (e) { return 'error: ' + e.message; }
25
+ })()