adhdev 0.1.53 → 0.1.54

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 (68) hide show
  1. package/dist/index.js +1870 -819
  2. package/package.json +1 -1
  3. package/providers/_builtin/CONTRIBUTING.md +141 -0
  4. package/providers/_builtin/README.md +51 -0
  5. package/providers/_builtin/_helpers/index.js +188 -0
  6. package/providers/_builtin/acp/agentpool/provider.js +59 -0
  7. package/providers/_builtin/acp/amp/provider.js +61 -0
  8. package/providers/_builtin/acp/auggie/provider.js +60 -0
  9. package/providers/_builtin/acp/autodev/provider.js +59 -0
  10. package/providers/_builtin/acp/autohand/provider.js +59 -0
  11. package/providers/_builtin/acp/blackbox-ai/provider.js +59 -0
  12. package/providers/_builtin/acp/claude-agent/provider.js +61 -0
  13. package/providers/_builtin/acp/cline-acp/provider.js +62 -0
  14. package/providers/_builtin/acp/code-assistant/provider.js +59 -0
  15. package/providers/_builtin/acp/codebuddy/provider.js +59 -0
  16. package/providers/_builtin/acp/codex-cli/provider.js +11 -1
  17. package/providers/_builtin/acp/corust-agent/provider.js +59 -0
  18. package/providers/_builtin/acp/crow-cli/provider.js +59 -0
  19. package/providers/_builtin/acp/cursor-acp/provider.js +59 -0
  20. package/providers/_builtin/acp/deepagents/provider.js +59 -0
  21. package/providers/_builtin/acp/dimcode/provider.js +58 -0
  22. package/providers/_builtin/acp/docker-cagent/provider.js +59 -0
  23. package/providers/_builtin/acp/factory-droid/provider.js +59 -0
  24. package/providers/_builtin/acp/fast-agent/provider.js +59 -0
  25. package/providers/_builtin/acp/fount/provider.js +59 -0
  26. package/providers/_builtin/acp/gemini-cli/provider.js +104 -0
  27. package/providers/_builtin/acp/github-copilot/provider.js +60 -0
  28. package/providers/_builtin/acp/goose/provider.js +37 -5
  29. package/providers/_builtin/acp/junie/provider.js +62 -0
  30. package/providers/_builtin/acp/kilo/provider.js +59 -0
  31. package/providers/_builtin/acp/kimi-cli/provider.js +63 -0
  32. package/providers/_builtin/acp/kiro-cli/provider.js +59 -0
  33. package/providers/_builtin/acp/minion-code/provider.js +59 -0
  34. package/providers/_builtin/acp/mistral-vibe/provider.js +63 -0
  35. package/providers/_builtin/acp/nova/provider.js +59 -0
  36. package/providers/_builtin/acp/openclaw/provider.js +59 -0
  37. package/providers/_builtin/acp/opencode/provider.js +34 -6
  38. package/providers/_builtin/acp/openhands/provider.js +59 -0
  39. package/providers/_builtin/acp/pi-acp/provider.js +59 -0
  40. package/providers/_builtin/acp/qoder/provider.js +58 -0
  41. package/providers/_builtin/acp/qwen-code/provider.js +61 -0
  42. package/providers/_builtin/acp/stakpak/provider.js +59 -0
  43. package/providers/_builtin/acp/vtcode/provider.js +59 -0
  44. package/providers/_builtin/cli/claude-cli/provider.js +3 -0
  45. package/providers/_builtin/cli/codex-cli/provider.js +3 -0
  46. package/providers/_builtin/cli/gemini-cli/provider.js +3 -0
  47. package/providers/_builtin/ide/kiro/provider.js +6 -2
  48. package/providers/_builtin/ide/kiro/scripts/webview_send_message.js +72 -0
  49. package/providers/_builtin/ide/pearai/provider.js +12 -0
  50. package/providers/_builtin/ide/pearai/scripts/list_sessions.js +38 -0
  51. package/providers/_builtin/ide/pearai/scripts/new_session.js +55 -0
  52. package/providers/_builtin/ide/pearai/scripts/webview_list_sessions.js +62 -0
  53. package/providers/_builtin/ide/pearai/scripts/webview_new_session.js +32 -4
  54. package/providers/_builtin/ide/pearai/scripts/webview_send_message.js +72 -0
  55. package/providers/_builtin/ide/pearai/scripts/webview_switch_session.js +34 -0
  56. package/providers/_builtin/ide/trae/scripts/send_message.js +53 -3
  57. package/providers/_builtin/validate.js +156 -0
  58. package/dist/node_datachannel-LPY6EJH5.node +0 -0
  59. package/providers/_builtin/ide/cursor/provider.js.backup +0 -116
  60. package/providers/_builtin/ide/cursor/provider.js.bak +0 -127
  61. package/providers/_builtin/ide/cursor/scripts_backup/focus_editor.js +0 -20
  62. package/providers/_builtin/ide/cursor/scripts_backup/list_chats.js +0 -111
  63. package/providers/_builtin/ide/cursor/scripts_backup/new_session.js +0 -62
  64. package/providers/_builtin/ide/cursor/scripts_backup/open_panel.js +0 -31
  65. package/providers/_builtin/ide/cursor/scripts_backup/read_chat.js +0 -433
  66. package/providers/_builtin/ide/cursor/scripts_backup/resolve_action.js +0 -90
  67. package/providers/_builtin/ide/cursor/scripts_backup/send_message.js +0 -86
  68. package/providers/_builtin/ide/cursor/scripts_backup/switch_session.js +0 -63
@@ -45,11 +45,15 @@ module.exports = {
45
45
  inputMethod: 'cdp-type-and-send',
46
46
  inputSelector: '[contenteditable="true"][role="textbox"]',
47
47
 
48
- // Kiro의 채팅 UI는 webview iframe 내부
49
- webviewMatchText: 'kiro-chat',
48
+ // Kiro의 채팅 UI는 webview iframe 내부 (extensionId=kiro.kiroAgent)
49
+ webviewMatchText: 'kiro',
50
50
 
51
51
  scripts: {
52
52
  webviewReadChat() { return loadScript('webview_read_chat.js'); },
53
+ webviewSendMessage(text) {
54
+ const s = loadScript('webview_send_message.js');
55
+ return s ? s.replace(/\$\{\s*MESSAGE\s*\}/g, JSON.stringify(text)) : null;
56
+ },
53
57
  sendMessage(text) {
54
58
  const s = loadScript('send_message.js');
55
59
  return s ? s.replace(/\$\{\s*MESSAGE\s*\}/g, JSON.stringify(text)) : null;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Kiro — webview_send_message (webview iframe 내부에서 실행)
3
+ *
4
+ * Kiro의 채팅 입력은 webview iframe 안의 ProseMirror/tiptap 에디터.
5
+ * execCommand('insertText') + Enter 키 이벤트로 메시지 전송.
6
+ *
7
+ * 파라미터: ${ MESSAGE }
8
+ */
9
+ (async () => {
10
+ try {
11
+ const msg = ${ MESSAGE };
12
+
13
+ // ─── 1. 입력 필드 찾기 ───
14
+ const editor =
15
+ document.querySelector('.tiptap.ProseMirror') ||
16
+ document.querySelector('[contenteditable="true"]') ||
17
+ document.querySelector('textarea');
18
+
19
+ if (!editor) return JSON.stringify({ sent: false, error: 'no input found in webview' });
20
+
21
+ const isTextarea = editor.tagName === 'TEXTAREA';
22
+
23
+ if (isTextarea) {
24
+ editor.focus();
25
+ const nativeSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set;
26
+ if (nativeSetter) nativeSetter.call(editor, msg);
27
+ else editor.value = msg;
28
+ editor.dispatchEvent(new Event('input', { bubbles: true }));
29
+ await new Promise(r => setTimeout(r, 300));
30
+
31
+ const enterOpts = { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true, composed: true };
32
+ editor.dispatchEvent(new KeyboardEvent('keydown', enterOpts));
33
+ editor.dispatchEvent(new KeyboardEvent('keypress', enterOpts));
34
+ editor.dispatchEvent(new KeyboardEvent('keyup', enterOpts));
35
+ return JSON.stringify({ sent: true });
36
+ }
37
+
38
+ // ─── 2. contenteditable (ProseMirror / tiptap) ───
39
+ editor.focus();
40
+ await new Promise(r => setTimeout(r, 100));
41
+
42
+ // 전체 선택 + 삭제 + 삽입
43
+ const sel = window.getSelection();
44
+ const range = document.createRange();
45
+ range.selectNodeContents(editor);
46
+ sel.removeAllRanges();
47
+ sel.addRange(range);
48
+ await new Promise(r => setTimeout(r, 50));
49
+
50
+ document.execCommand('delete', false, null);
51
+ await new Promise(r => setTimeout(r, 50));
52
+ document.execCommand('insertText', false, msg);
53
+
54
+ editor.dispatchEvent(new Event('input', { bubbles: true }));
55
+ await new Promise(r => setTimeout(r, 400));
56
+
57
+ // ─── 3. Enter 키 전송 ───
58
+ const enterOpts = {
59
+ key: 'Enter', code: 'Enter',
60
+ keyCode: 13, which: 13,
61
+ bubbles: true, cancelable: true, composed: true,
62
+ };
63
+ editor.dispatchEvent(new KeyboardEvent('keydown', enterOpts));
64
+ await new Promise(r => setTimeout(r, 50));
65
+ editor.dispatchEvent(new KeyboardEvent('keypress', enterOpts));
66
+ editor.dispatchEvent(new KeyboardEvent('keyup', enterOpts));
67
+
68
+ return JSON.stringify({ sent: true });
69
+ } catch (e) {
70
+ return JSON.stringify({ sent: false, error: e.message });
71
+ }
72
+ })()
@@ -50,6 +50,10 @@ module.exports = {
50
50
 
51
51
  scripts: {
52
52
  webviewReadChat() { return loadScript('webview_read_chat.js'); },
53
+ webviewSendMessage(text) {
54
+ const s = loadScript('webview_send_message.js');
55
+ return s ? s.replace(/\$\{\s*MESSAGE\s*\}/g, JSON.stringify(text)) : null;
56
+ },
53
57
  sendMessage(text) {
54
58
  const s = loadScript('send_message.js');
55
59
  return s ? s.replace(/\$\{\s*MESSAGE\s*\}/g, JSON.stringify(text)) : null;
@@ -70,8 +74,16 @@ module.exports = {
70
74
  },
71
75
  openPanel() { return loadScript('open_panel.js'); },
72
76
  focusEditor() { return loadScript('focus_editor.js'); },
77
+ // 세션 관리 (IDE 메인 프레임)
78
+ newSession() { return loadScript('new_session.js'); },
79
+ listSessions() { return loadScript('list_sessions.js'); },
73
80
  // 세션 관리 (webview)
74
81
  webviewNewSession() { return loadScript('webview_new_session.js'); },
82
+ webviewListSessions() { return loadScript('webview_list_sessions.js'); },
83
+ webviewSwitchSession(sessionId) {
84
+ const s = loadScript('webview_switch_session.js');
85
+ return s ? s.replace(/\$\{\s*SESSION_ID\s*\}/g, JSON.stringify(sessionId)) : null;
86
+ },
75
87
  webviewListModels() { return loadScript('webview_list_models.js'); },
76
88
  webviewSetModel(params) {
77
89
  const model = typeof params === 'string' ? params : params?.model;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * PearAI — list_sessions (IDE 메인 프레임에서 실행)
3
+ *
4
+ * 히스토리 패널 토글 버튼을 클릭하여 히스토리 뷰를 오픈합니다.
5
+ * 히스토리 뷰가 열리면 webviewListSessions로 항목을 읽을 수 있습니다.
6
+ */
7
+ (() => {
8
+ try {
9
+ // Panel title bar에서 히스토리 관련 버튼 찾기
10
+ const actionBtns = document.querySelectorAll('.action-item a.action-label, .action-item .action-label');
11
+ for (const btn of actionBtns) {
12
+ const title = (btn.getAttribute('title') || '').toLowerCase();
13
+ const ariaLabel = (btn.getAttribute('aria-label') || '').toLowerCase();
14
+ const cls = btn.className || '';
15
+
16
+ if (title.includes('history') || title.includes('히스토리') ||
17
+ ariaLabel.includes('history') || ariaLabel.includes('히스토리') ||
18
+ cls.includes('codicon-history')) {
19
+ btn.click();
20
+ return JSON.stringify({ toggled: true, method: 'panelAction', title: btn.getAttribute('title') });
21
+ }
22
+ }
23
+
24
+ // Broader search
25
+ const allBtns = document.querySelectorAll('a[title], button[title]');
26
+ for (const btn of allBtns) {
27
+ const title = (btn.getAttribute('title') || '').toLowerCase();
28
+ if (title.includes('history') || title.includes('task history')) {
29
+ btn.click();
30
+ return JSON.stringify({ toggled: true, method: 'titleBtn', title: btn.getAttribute('title') });
31
+ }
32
+ }
33
+
34
+ return JSON.stringify({ toggled: false, error: 'History toggle button not found' });
35
+ } catch (e) {
36
+ return JSON.stringify({ toggled: false, error: e.message });
37
+ }
38
+ })()
@@ -0,0 +1,55 @@
1
+ /**
2
+ * PearAI — new_session (IDE 메인 프레임에서 실행)
3
+ *
4
+ * Panel title bar의 "+" 버튼(New Task)을 찾아 클릭합니다.
5
+ * PearAI(Roo Code 기반)에서 새 태스크 버튼은 webview 바깥의 VS Code panel 헤더에 위치합니다.
6
+ */
7
+ (() => {
8
+ try {
9
+ // Strategy 1: Find the codicon-add / plus button in panel title actions
10
+ const actionBtns = document.querySelectorAll('.panel .title .actions-container .action-item a, .pane-header .actions-container .action-item a, .title-actions .action-item a');
11
+ for (const btn of actionBtns) {
12
+ const title = btn.getAttribute('title') || '';
13
+ const ariaLabel = btn.getAttribute('aria-label') || '';
14
+ const cls = btn.className || '';
15
+
16
+ if (title.toLowerCase().includes('new task') ||
17
+ title.toLowerCase().includes('new chat') ||
18
+ ariaLabel.toLowerCase().includes('new task') ||
19
+ ariaLabel.toLowerCase().includes('new chat') ||
20
+ cls.includes('codicon-add') ||
21
+ cls.includes('codicon-plus')) {
22
+ btn.click();
23
+ return JSON.stringify({ created: true, method: 'panelAction', title: title || ariaLabel });
24
+ }
25
+ }
26
+
27
+ // Strategy 2: Broader search for action items with "+" or "new"
28
+ const allActions = document.querySelectorAll('.action-item a.action-label');
29
+ for (const a of allActions) {
30
+ const title = (a.getAttribute('title') || '').toLowerCase();
31
+ const cls = a.className || '';
32
+ if ((title.includes('new') && title.includes('task')) ||
33
+ (title.includes('plus') || cls.includes('codicon-add'))) {
34
+ a.click();
35
+ return JSON.stringify({ created: true, method: 'actionLabel', title: a.getAttribute('title') });
36
+ }
37
+ }
38
+
39
+ // Strategy 3: Use keybinding (Cmd+Shift+P -> "new task")
40
+ // Simulate keyboard shortcut for Roo Code: typically there's a command
41
+ // registered as roo-cline.plusButtonClicked or similar
42
+ const allBtns = document.querySelectorAll('a[title], button[title]');
43
+ const matches = [];
44
+ for (const btn of allBtns) {
45
+ const t = btn.getAttribute('title') || '';
46
+ if (t.toLowerCase().includes('new') || t.toLowerCase().includes('plus') || t === '+') {
47
+ matches.push({ tag: btn.tagName, title: t, cls: (btn.className || '').substring(0, 60) });
48
+ }
49
+ }
50
+
51
+ return JSON.stringify({ created: false, error: 'New Task button not found in panel', candidates: matches.slice(0, 5) });
52
+ } catch (e) {
53
+ return JSON.stringify({ created: false, error: e.message });
54
+ }
55
+ })()
@@ -0,0 +1,62 @@
1
+ /**
2
+ * PearAI — webview_list_sessions (webview iframe 내부에서 실행)
3
+ *
4
+ * PearAI(Roo Code/Cline 기반) 히스토리 뷰에서 세션 목록을 추출.
5
+ * 각 항목은 data-testid="task-item-{UUID}" 로 식별됨.
6
+ */
7
+ (() => {
8
+ try {
9
+ // ─── 히스토리 항목: data-testid="task-item-*" ───
10
+ const taskItems = document.querySelectorAll('[data-testid^="task-item-"]');
11
+
12
+ if (taskItems.length > 0) {
13
+ const sessions = [];
14
+ for (let i = 0; i < taskItems.length; i++) {
15
+ const item = taskItems[i];
16
+ if (!item) continue;
17
+
18
+ const testId = item.getAttribute('data-testid') || '';
19
+ const taskId = testId.replace('task-item-', '');
20
+
21
+ // 전체 텍스트에서 제목 추출
22
+ const fullText = (item.textContent || '').trim();
23
+
24
+ // 구조: "MARCH 16, 1:31 AM 173 B test123 Tokens:10.4k 229"
25
+ // 전략: Tokens: 앞의 마지막 의미있는 텍스트를 제목으로 사용
26
+ let title = '';
27
+ let date = '';
28
+
29
+ // 날짜 추출
30
+ const dateMatch = fullText.match(/^([A-Z]+\s+\d+,\s*\d+:\d+\s*[AP]M)/);
31
+ if (dateMatch) date = dateMatch[1];
32
+
33
+ // 제목 추출: 날짜와 크기(B/kB) 뒤, Tokens: 앞
34
+ const titleMatch = fullText.match(/[AP]M[\d.\s]*[kMG]?B\s*(.*?)(?:Tokens:|$)/s);
35
+ if (titleMatch && titleMatch[1]) {
36
+ title = titleMatch[1].trim();
37
+ }
38
+
39
+ // fallback: 첫 80자
40
+ if (!title) {
41
+ title = fullText.substring(0, 80);
42
+ }
43
+
44
+ sessions.push({
45
+ id: taskId,
46
+ title: title.substring(0, 100),
47
+ date: date,
48
+ active: false
49
+ });
50
+ }
51
+ return JSON.stringify({ sessions: sessions });
52
+ }
53
+
54
+ // ─── 히스토리 뷰가 열려 있지 않음 ───
55
+ return JSON.stringify({
56
+ sessions: [],
57
+ note: 'History view not open. Toggle history first.'
58
+ });
59
+ } catch (e) {
60
+ return JSON.stringify({ sessions: [], error: String(e.message || e) });
61
+ }
62
+ })()
@@ -1,19 +1,47 @@
1
1
  /**
2
2
  * PearAI — webview_new_session (webview iframe 내부에서 실행)
3
3
  *
4
- * "Start New Task" 버튼 클릭.
5
- * PearAI Agent(Roo Code/Cline 기반)는 Task 단위로 세션 관리.
4
+ * Roo Code/Cline 기반 PearAI에서 새 세션(태스크)을 시작합니다.
5
+ * 1) "New Task" 텍스트를 가진 버튼 클릭 시도
6
+ * 2) 실패 시, vscode postMessage API로 newTask 명령 전송 시도
7
+ * 3) 실패 시, chat-text-area를 초기화하여 새 세션 상태로 진입
6
8
  */
7
9
  (() => {
8
10
  try {
11
+ // Strategy 1: Find "New Task" button text
9
12
  const buttons = document.querySelectorAll('button, [role="button"]');
10
13
  for (const btn of buttons) {
11
14
  const text = (btn.textContent || '').trim();
12
- if (text.includes('Start New Task') || text.includes('New Task')) {
15
+ if (text.includes('Start New Task') || text.includes('New Task') || text.includes('New Chat')) {
13
16
  btn.click();
14
- return JSON.stringify({ created: true, method: 'button' });
17
+ return JSON.stringify({ created: true, method: 'button', text });
15
18
  }
16
19
  }
20
+
21
+ // Strategy 2: Look for a "+" (plus/add) button that might create new tasks
22
+ for (const btn of buttons) {
23
+ const title = btn.getAttribute('title') || '';
24
+ const ariaLabel = btn.getAttribute('aria-label') || '';
25
+ if (title.includes('New') || ariaLabel.includes('New') ||
26
+ title.includes('new') || ariaLabel.includes('new')) {
27
+ btn.click();
28
+ return JSON.stringify({ created: true, method: 'title', title: title || ariaLabel });
29
+ }
30
+ }
31
+
32
+ // Strategy 3: Use vscode API to post message for new task
33
+ if (typeof acquireVsCodeApi !== 'undefined') {
34
+ const vscode = acquireVsCodeApi();
35
+ vscode.postMessage({ type: 'newTask' });
36
+ return JSON.stringify({ created: true, method: 'vscodeApi' });
37
+ }
38
+
39
+ // Strategy 4: If already in empty state (no messages), report as already new
40
+ const messages = document.querySelectorAll('[class*="message"], [class*="chat-row"]');
41
+ if (messages.length === 0) {
42
+ return JSON.stringify({ created: true, method: 'alreadyNew', note: 'No messages - already in new session state' });
43
+ }
44
+
17
45
  return JSON.stringify({ created: false, error: 'New Task button not found' });
18
46
  } catch (e) {
19
47
  return JSON.stringify({ created: false, error: e.message });
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Kiro — webview_send_message (webview iframe 내부에서 실행)
3
+ *
4
+ * Kiro의 채팅 입력은 webview iframe 안의 ProseMirror/tiptap 에디터.
5
+ * execCommand('insertText') + Enter 키 이벤트로 메시지 전송.
6
+ *
7
+ * 파라미터: ${ MESSAGE }
8
+ */
9
+ (async () => {
10
+ try {
11
+ const msg = ${ MESSAGE };
12
+
13
+ // ─── 1. 입력 필드 찾기 ───
14
+ const editor =
15
+ document.querySelector('.tiptap.ProseMirror') ||
16
+ document.querySelector('[contenteditable="true"]') ||
17
+ document.querySelector('textarea');
18
+
19
+ if (!editor) return JSON.stringify({ sent: false, error: 'no input found in webview' });
20
+
21
+ const isTextarea = editor.tagName === 'TEXTAREA';
22
+
23
+ if (isTextarea) {
24
+ editor.focus();
25
+ const nativeSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set;
26
+ if (nativeSetter) nativeSetter.call(editor, msg);
27
+ else editor.value = msg;
28
+ editor.dispatchEvent(new Event('input', { bubbles: true }));
29
+ await new Promise(r => setTimeout(r, 300));
30
+
31
+ const enterOpts = { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true, composed: true };
32
+ editor.dispatchEvent(new KeyboardEvent('keydown', enterOpts));
33
+ editor.dispatchEvent(new KeyboardEvent('keypress', enterOpts));
34
+ editor.dispatchEvent(new KeyboardEvent('keyup', enterOpts));
35
+ return JSON.stringify({ sent: true });
36
+ }
37
+
38
+ // ─── 2. contenteditable (ProseMirror / tiptap) ───
39
+ editor.focus();
40
+ await new Promise(r => setTimeout(r, 100));
41
+
42
+ // 전체 선택 + 삭제 + 삽입
43
+ const sel = window.getSelection();
44
+ const range = document.createRange();
45
+ range.selectNodeContents(editor);
46
+ sel.removeAllRanges();
47
+ sel.addRange(range);
48
+ await new Promise(r => setTimeout(r, 50));
49
+
50
+ document.execCommand('delete', false, null);
51
+ await new Promise(r => setTimeout(r, 50));
52
+ document.execCommand('insertText', false, msg);
53
+
54
+ editor.dispatchEvent(new Event('input', { bubbles: true }));
55
+ await new Promise(r => setTimeout(r, 400));
56
+
57
+ // ─── 3. Enter 키 전송 ───
58
+ const enterOpts = {
59
+ key: 'Enter', code: 'Enter',
60
+ keyCode: 13, which: 13,
61
+ bubbles: true, cancelable: true, composed: true,
62
+ };
63
+ editor.dispatchEvent(new KeyboardEvent('keydown', enterOpts));
64
+ await new Promise(r => setTimeout(r, 50));
65
+ editor.dispatchEvent(new KeyboardEvent('keypress', enterOpts));
66
+ editor.dispatchEvent(new KeyboardEvent('keyup', enterOpts));
67
+
68
+ return JSON.stringify({ sent: true });
69
+ } catch (e) {
70
+ return JSON.stringify({ sent: false, error: e.message });
71
+ }
72
+ })()
@@ -0,0 +1,34 @@
1
+ /**
2
+ * PearAI — webview_switch_session (webview iframe 내부에서 실행)
3
+ *
4
+ * data-testid="task-item-{UUID}" 항목을 클릭하여 세션 전환.
5
+ * SESSION_ID는 task UUID 형태 (예: "51a08aba-1078-410c-a601-0e859205b12c")
6
+ */
7
+ (() => {
8
+ try {
9
+ const targetId = ${SESSION_ID};
10
+
11
+ // ─── UUID 기반 매칭 ───
12
+ const selector = '[data-testid="task-item-' + targetId + '"]';
13
+ const targetItem = document.querySelector(selector);
14
+ if (targetItem) {
15
+ targetItem.click();
16
+ return JSON.stringify({ switched: true, id: targetId });
17
+ }
18
+
19
+ // ─── 인덱스 기반 fallback (숫자로 전달된 경우) ───
20
+ const index = parseInt(targetId, 10);
21
+ if (!isNaN(index)) {
22
+ const allItems = document.querySelectorAll('[data-testid^="task-item-"]');
23
+ if (allItems[index]) {
24
+ allItems[index].click();
25
+ const taskId = (allItems[index].getAttribute('data-testid') || '').replace('task-item-', '');
26
+ return JSON.stringify({ switched: true, id: taskId, method: 'index' });
27
+ }
28
+ }
29
+
30
+ return JSON.stringify({ switched: false, error: 'Task item not found: ' + targetId });
31
+ } catch (e) {
32
+ return JSON.stringify({ switched: false, error: e.message });
33
+ }
34
+ })()
@@ -1,13 +1,63 @@
1
1
  /**
2
2
  * Trae — send_message
3
3
  *
4
- * Trae는 .chat-input-v2-input-box-editable (contenteditable) 사용.
5
- * 메인 DOM에서 접근 가능 selector를 반환하여 데몬의 typeAndSend가 처리.
4
+ * Trae는 .chat-input-v2-input-box-editable (contenteditable / Lexical) 사용.
5
+ * Lexical의 내부 state를 올바르게 업데이트하기 위해 Selection API + execCommand 사용.
6
6
  *
7
7
  * 파라미터: ${ MESSAGE }
8
8
  */
9
- (() => {
9
+ (async () => {
10
10
  try {
11
+ const msg = ${ MESSAGE };
12
+
13
+ // ─── 1. 입력 필드 찾기 ───
14
+ const editor =
15
+ document.querySelector('.chat-input-v2-input-box-editable') ||
16
+ document.querySelector('[contenteditable="true"][role="textbox"]') ||
17
+ document.querySelector('[data-lexical-editor="true"]');
18
+
19
+ if (!editor) return JSON.stringify({ sent: false, error: 'no input found' });
20
+
21
+ // ─── 2. 포커스 + 전체 선택 + 삭제 + 삽입 ───
22
+ editor.focus();
23
+ await new Promise(r => setTimeout(r, 100));
24
+
25
+ // Selection API로 전체 선택
26
+ const sel = window.getSelection();
27
+ const range = document.createRange();
28
+ range.selectNodeContents(editor);
29
+ sel.removeAllRanges();
30
+ sel.addRange(range);
31
+ await new Promise(r => setTimeout(r, 50));
32
+
33
+ // 삭제 후 삽입 (Lexical state 동기화)
34
+ document.execCommand('delete', false, null);
35
+ await new Promise(r => setTimeout(r, 50));
36
+ document.execCommand('insertText', false, msg);
37
+
38
+ // Input 이벤트
39
+ editor.dispatchEvent(new Event('input', { bubbles: true }));
40
+ await new Promise(r => setTimeout(r, 500));
41
+
42
+ // ─── 3. send 버튼 클릭 ───
43
+ const sendBtn = document.querySelector('.chat-input-v2-send-button');
44
+ if (sendBtn && !sendBtn.disabled) {
45
+ sendBtn.click();
46
+ return JSON.stringify({ sent: true, method: 'button' });
47
+ }
48
+
49
+ // 버튼이 아직 disabled이면 Enter 키 시도
50
+ const enterOpts = {
51
+ key: 'Enter', code: 'Enter',
52
+ keyCode: 13, which: 13,
53
+ bubbles: true, cancelable: true, composed: true,
54
+ };
55
+ editor.dispatchEvent(new KeyboardEvent('keydown', enterOpts));
56
+ await new Promise(r => setTimeout(r, 50));
57
+ editor.dispatchEvent(new KeyboardEvent('keypress', enterOpts));
58
+ editor.dispatchEvent(new KeyboardEvent('keyup', enterOpts));
59
+
60
+ // 여전히 안 되면 needsTypeAndSend 폴백
11
61
  return JSON.stringify({
12
62
  sent: false,
13
63
  needsTypeAndSend: true,
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ADHDev Provider Validator
4
+ *
5
+ * Usage:
6
+ * node validate.js # validate all providers
7
+ * node validate.js ide/my-ide/provider.js # validate a single file
8
+ */
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ const REQUIRED_FIELDS = ['type', 'name', 'category'];
13
+ const VALID_CATEGORIES = ['ide', 'extension', 'cli', 'acp'];
14
+ const USED_PORTS = new Map();
15
+ const USED_TYPES = new Map();
16
+
17
+ let errors = 0;
18
+ let warnings = 0;
19
+ let validated = 0;
20
+
21
+ function validate(filePath) {
22
+ const rel = path.relative(process.cwd(), filePath);
23
+
24
+ // 1. Syntax check
25
+ try {
26
+ delete require.cache[require.resolve(filePath)];
27
+ } catch {}
28
+
29
+ let mod;
30
+ try {
31
+ mod = require(filePath);
32
+ } catch (e) {
33
+ console.error(`❌ ${rel}: syntax error — ${e.message}`);
34
+ errors++;
35
+ return;
36
+ }
37
+
38
+ // 2. Required fields
39
+ for (const field of REQUIRED_FIELDS) {
40
+ if (!mod[field]) {
41
+ console.error(`❌ ${rel}: missing required field '${field}'`);
42
+ errors++;
43
+ return;
44
+ }
45
+ }
46
+
47
+ // 3. Valid category
48
+ if (!VALID_CATEGORIES.includes(mod.category)) {
49
+ console.error(`❌ ${rel}: invalid category '${mod.category}' (must be: ${VALID_CATEGORIES.join(', ')})`);
50
+ errors++;
51
+ return;
52
+ }
53
+
54
+ // 4. Unique type
55
+ if (USED_TYPES.has(mod.type)) {
56
+ console.error(`❌ ${rel}: duplicate type '${mod.type}' (also in ${USED_TYPES.get(mod.type)})`);
57
+ errors++;
58
+ return;
59
+ }
60
+ USED_TYPES.set(mod.type, rel);
61
+
62
+ // 5. IDE-specific checks
63
+ if (mod.category === 'ide') {
64
+ if (!mod.cdpPorts || !Array.isArray(mod.cdpPorts) || mod.cdpPorts.length < 2) {
65
+ console.warn(`⚠ ${rel}: IDE missing cdpPorts [primary, fallback]`);
66
+ warnings++;
67
+ } else {
68
+ for (const port of mod.cdpPorts) {
69
+ if (USED_PORTS.has(port)) {
70
+ console.error(`❌ ${rel}: CDP port ${port} conflicts with ${USED_PORTS.get(port)}`);
71
+ errors++;
72
+ }
73
+ USED_PORTS.set(port, rel);
74
+ }
75
+ }
76
+
77
+ if (!mod.cli) {
78
+ console.warn(`⚠ ${rel}: IDE missing 'cli' field`);
79
+ warnings++;
80
+ }
81
+
82
+ if (!mod.paths) {
83
+ console.warn(`⚠ ${rel}: IDE missing 'paths' (install detection won't work)`);
84
+ warnings++;
85
+ }
86
+ }
87
+
88
+ // 6. ACP-specific checks
89
+ if (mod.category === 'acp') {
90
+ if (!mod.spawn || !mod.spawn.command) {
91
+ console.warn(`⚠ ${rel}: ACP missing spawn.command`);
92
+ warnings++;
93
+ }
94
+ }
95
+
96
+ // 7. Scripts check
97
+ if (mod.category === 'ide' || mod.category === 'extension') {
98
+ const hasRead = mod.scripts?.readChat || mod.scripts?.webviewReadChat;
99
+ const hasSend = mod.scripts?.sendMessage || mod.scripts?.webviewSendMessage;
100
+ if (!hasRead) {
101
+ console.warn(`⚠ ${rel}: no readChat/webviewReadChat script`);
102
+ warnings++;
103
+ }
104
+ if (!hasSend) {
105
+ console.warn(`⚠ ${rel}: no sendMessage/webviewSendMessage script`);
106
+ warnings++;
107
+ }
108
+ }
109
+
110
+ // 8. Webview IDE consistency
111
+ if (mod.category === 'ide' && mod.webviewMatchText) {
112
+ if (!mod.scripts?.webviewReadChat) {
113
+ console.warn(`⚠ ${rel}: has webviewMatchText but no webviewReadChat script`);
114
+ warnings++;
115
+ }
116
+ }
117
+
118
+ validated++;
119
+ console.log(`✅ ${rel}: ${mod.type} (${mod.category}) — ${mod.name}`);
120
+ }
121
+
122
+ function scanDir(dir) {
123
+ if (!fs.existsSync(dir)) return;
124
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
125
+ const full = path.join(dir, entry.name);
126
+ if (entry.isDirectory()) {
127
+ if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue;
128
+ scanDir(full);
129
+ } else if (entry.name === 'provider.js') {
130
+ validate(full);
131
+ }
132
+ }
133
+ }
134
+
135
+ // ─── Main ───
136
+ const args = process.argv.slice(2);
137
+
138
+ if (args.length > 0) {
139
+ // Validate specific file(s)
140
+ for (const arg of args) {
141
+ const filePath = path.resolve(arg);
142
+ if (!fs.existsSync(filePath)) {
143
+ console.error(`❌ File not found: ${arg}`);
144
+ errors++;
145
+ continue;
146
+ }
147
+ validate(filePath);
148
+ }
149
+ } else {
150
+ // Validate all
151
+ console.log('🔍 Validating all providers...\n');
152
+ scanDir(process.cwd());
153
+ }
154
+
155
+ console.log(`\n━━━ Result: ${validated} passed, ${errors} errors, ${warnings} warnings ━━━`);
156
+ process.exit(errors > 0 ? 1 : 0);
Binary file