@adhdev/daemon-core 0.5.3 → 0.5.6
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.d.ts +88 -2
- package/dist/index.js +1230 -439
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/providers/_builtin/extension/cline/scripts/read_chat.js +14 -1
- package/providers/_builtin/ide/antigravity/scripts/1.106/read_chat.js +24 -1
- package/providers/_builtin/ide/antigravity/scripts/1.107/read_chat.js +24 -1
- package/providers/_builtin/ide/cursor/scripts/0.49/focus_editor.js +3 -3
- package/providers/_builtin/ide/cursor/scripts/0.49/list_models.js +1 -1
- package/providers/_builtin/ide/cursor/scripts/0.49/list_modes.js +1 -1
- package/providers/_builtin/ide/cursor/scripts/0.49/open_panel.js +4 -4
- package/providers/_builtin/ide/cursor/scripts/0.49/read_chat.js +5 -1
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/dismiss_notification.js +30 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/focus_editor.js +13 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_models.js +78 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_modes.js +40 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_notifications.js +23 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_sessions.js +42 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/new_session.js +20 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/open_panel.js +23 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/read_chat.js +79 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/resolve_action.js +19 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/scripts.js +78 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/send_message.js +23 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/set_mode.js +38 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/set_model.js +81 -0
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/switch_session.js +28 -0
- package/providers/_builtin/ide/windsurf/scripts/read_chat.js +18 -1
- package/src/cli-adapters/provider-cli-adapter.ts +231 -12
- package/src/commands/chat-commands.ts +36 -0
- package/src/commands/cli-manager.ts +128 -30
- package/src/commands/handler.ts +47 -3
- package/src/commands/router.ts +32 -2
- package/src/commands/workspace-commands.ts +108 -0
- package/src/config/config.ts +29 -1
- package/src/config/workspace-activity.ts +65 -0
- package/src/config/workspaces.ts +250 -0
- package/src/daemon/dev-server.ts +1 -1
- package/src/index.ts +5 -0
- package/src/launch.ts +1 -1
- package/src/providers/cli-provider-instance.ts +7 -2
- package/src/providers/ide-provider-instance.ts +11 -0
- package/src/status/reporter.ts +23 -4
- package/src/system/host-memory.ts +65 -0
- package/src/types.ts +8 -1
package/package.json
CHANGED
|
@@ -120,7 +120,20 @@
|
|
|
120
120
|
if (el.tagName === 'PRE') {
|
|
121
121
|
const codeEl = el.querySelector('code');
|
|
122
122
|
const lang = codeEl ? (codeEl.className.match(/language-(\w+)/)?.[1] || '') : '';
|
|
123
|
-
const
|
|
123
|
+
const BLOCK_TAGS_C = new Set(['DIV', 'P', 'BR', 'LI', 'TR']);
|
|
124
|
+
const extractCode = (n) => {
|
|
125
|
+
if (n.nodeType === 3) return n.textContent || '';
|
|
126
|
+
if (n.nodeType !== 1) return '';
|
|
127
|
+
if (n.tagName === 'BR') return '\n';
|
|
128
|
+
const ps = [];
|
|
129
|
+
for (const c of n.childNodes) {
|
|
130
|
+
const ib = c.nodeType === 1 && BLOCK_TAGS_C.has(c.tagName);
|
|
131
|
+
const tx = extractCode(c);
|
|
132
|
+
if (tx) { if (ib && ps.length > 0) ps.push('\n'); ps.push(tx); if (ib) ps.push('\n'); }
|
|
133
|
+
}
|
|
134
|
+
return ps.join('').replace(/\n{2,}/g, '\n');
|
|
135
|
+
};
|
|
136
|
+
const code = extractCode(codeEl || el);
|
|
124
137
|
structured += '\n```' + lang + '\n' + code.trim() + '\n```\n';
|
|
125
138
|
return;
|
|
126
139
|
}
|
|
@@ -33,6 +33,27 @@
|
|
|
33
33
|
const title = document.title.split(' \u2014 ')[0].trim() || 'Active Session';
|
|
34
34
|
|
|
35
35
|
// ─── HTML → Markdown 변환기 (대시보드가 ReactMarkdown+remarkGfm 사용) ───
|
|
36
|
+
// extractCodeText: layout-independent code text extraction
|
|
37
|
+
// Works on cloneNode'd (detached) elements where innerText == textContent
|
|
38
|
+
// Walks child nodes and inserts \n between block-level elements (DIV, P, etc.)
|
|
39
|
+
const BLOCK_TAGS = new Set(['DIV', 'P', 'BR', 'LI', 'TR', 'SECTION', 'ARTICLE', 'HEADER', 'FOOTER']);
|
|
40
|
+
function extractCodeText(node) {
|
|
41
|
+
if (node.nodeType === 3) return node.textContent || '';
|
|
42
|
+
if (node.nodeType !== 1) return '';
|
|
43
|
+
if (node.tagName === 'BR') return '\n';
|
|
44
|
+
const parts = [];
|
|
45
|
+
for (const child of node.childNodes) {
|
|
46
|
+
const isBlock = child.nodeType === 1 && BLOCK_TAGS.has(child.tagName);
|
|
47
|
+
const text = extractCodeText(child);
|
|
48
|
+
if (text) {
|
|
49
|
+
if (isBlock && parts.length > 0) parts.push('\n');
|
|
50
|
+
parts.push(text);
|
|
51
|
+
if (isBlock) parts.push('\n');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Collapse multiple consecutive newlines into single \n
|
|
55
|
+
return parts.join('').replace(/\n{2,}/g, '\n');
|
|
56
|
+
}
|
|
36
57
|
function htmlToMd(node) {
|
|
37
58
|
if (node.nodeType === 3) return node.textContent || '';
|
|
38
59
|
if (node.nodeType !== 1) return '';
|
|
@@ -77,7 +98,9 @@
|
|
|
77
98
|
if (tag === 'PRE') {
|
|
78
99
|
const codeEl = node.querySelector('code');
|
|
79
100
|
const lang = codeEl ? (codeEl.className.match(/language-(\w+)/)?.[1] || '') : '';
|
|
80
|
-
|
|
101
|
+
// Custom extraction: walk child nodes and insert \n between block elements
|
|
102
|
+
// This works on cloneNode'd elements (no layout → innerText == textContent)
|
|
103
|
+
const code = extractCodeText(codeEl || node);
|
|
81
104
|
return '\n```' + lang + '\n' + code.trim() + '\n```\n';
|
|
82
105
|
}
|
|
83
106
|
if (tag === 'CODE') {
|
|
@@ -33,6 +33,27 @@
|
|
|
33
33
|
const title = document.title.split(' \u2014 ')[0].trim() || 'Active Session';
|
|
34
34
|
|
|
35
35
|
// ─── HTML → Markdown 변환기 (대시보드가 ReactMarkdown+remarkGfm 사용) ───
|
|
36
|
+
// extractCodeText: layout-independent code text extraction
|
|
37
|
+
// Works on cloneNode'd (detached) elements where innerText == textContent
|
|
38
|
+
// Walks child nodes and inserts \n between block-level elements (DIV, P, etc.)
|
|
39
|
+
const BLOCK_TAGS = new Set(['DIV', 'P', 'BR', 'LI', 'TR', 'SECTION', 'ARTICLE', 'HEADER', 'FOOTER']);
|
|
40
|
+
function extractCodeText(node) {
|
|
41
|
+
if (node.nodeType === 3) return node.textContent || '';
|
|
42
|
+
if (node.nodeType !== 1) return '';
|
|
43
|
+
if (node.tagName === 'BR') return '\n';
|
|
44
|
+
const parts = [];
|
|
45
|
+
for (const child of node.childNodes) {
|
|
46
|
+
const isBlock = child.nodeType === 1 && BLOCK_TAGS.has(child.tagName);
|
|
47
|
+
const text = extractCodeText(child);
|
|
48
|
+
if (text) {
|
|
49
|
+
if (isBlock && parts.length > 0) parts.push('\n');
|
|
50
|
+
parts.push(text);
|
|
51
|
+
if (isBlock) parts.push('\n');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Collapse multiple consecutive newlines into single \n
|
|
55
|
+
return parts.join('').replace(/\n{2,}/g, '\n');
|
|
56
|
+
}
|
|
36
57
|
function htmlToMd(node) {
|
|
37
58
|
if (node.nodeType === 3) return node.textContent || '';
|
|
38
59
|
if (node.nodeType !== 1) return '';
|
|
@@ -77,7 +98,9 @@
|
|
|
77
98
|
if (tag === 'PRE') {
|
|
78
99
|
const codeEl = node.querySelector('code');
|
|
79
100
|
const lang = codeEl ? (codeEl.className.match(/language-(\w+)/)?.[1] || '') : '';
|
|
80
|
-
|
|
101
|
+
// Custom extraction: walk child nodes and insert \n between block elements
|
|
102
|
+
// This works on cloneNode'd elements (no layout → innerText == textContent)
|
|
103
|
+
const code = extractCodeText(codeEl || node);
|
|
81
104
|
return '\n```' + lang + '\n' + code.trim() + '\n```\n';
|
|
82
105
|
}
|
|
83
106
|
if (tag === 'CODE') {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
(() => {
|
|
8
8
|
try {
|
|
9
9
|
const input = document.querySelector('.aislash-editor-input[contenteditable="true"]');
|
|
10
|
-
if (input) { input.focus(); return
|
|
11
|
-
return '
|
|
12
|
-
} catch(e) { return
|
|
10
|
+
if (input) { input.focus(); return JSON.stringify({ focused: true }); }
|
|
11
|
+
return JSON.stringify({ focused: false, error: 'Input not found' });
|
|
12
|
+
} catch(e) { return JSON.stringify({ focused: false, error: e.message }); }
|
|
13
13
|
})()
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
if (name && name !== 'Add Models') {
|
|
52
52
|
const hasBrain = !!item.querySelector('[class*="codicon-br"]');
|
|
53
53
|
const displayName = hasBrain ? name + ' 🧠' : name;
|
|
54
|
-
models.push(displayName);
|
|
54
|
+
models.push({ name: displayName, id: displayName });
|
|
55
55
|
if (item.getAttribute('data-is-selected') === 'true') current = displayName;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
const nameEl = item.querySelector('.monaco-highlighted-label');
|
|
27
27
|
const name = nameEl?.textContent?.trim() || '';
|
|
28
28
|
if (name) {
|
|
29
|
-
modes.push(name);
|
|
29
|
+
modes.push({ name, id: name });
|
|
30
30
|
if (item.getAttribute('data-is-selected') === 'true') current = name;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
const sidebar = document.getElementById('workbench.parts.auxiliarybar');
|
|
11
11
|
if (sidebar && sidebar.offsetWidth > 0) {
|
|
12
12
|
const chatView = document.querySelector('[data-composer-id]');
|
|
13
|
-
if (chatView) return
|
|
13
|
+
if (chatView) return JSON.stringify({ opened: true });
|
|
14
14
|
}
|
|
15
15
|
const btns = [...document.querySelectorAll('li.action-item a, button, [role="tab"]')];
|
|
16
16
|
const toggle = btns.find(b => {
|
|
17
17
|
const label = (b.textContent || b.getAttribute('aria-label') || '').toLowerCase();
|
|
18
18
|
return /agent|chat|composer|cursor tab/i.test(label);
|
|
19
19
|
});
|
|
20
|
-
if (toggle) { toggle.click(); return
|
|
21
|
-
return '
|
|
22
|
-
} catch (e) { return
|
|
20
|
+
if (toggle) { toggle.click(); return JSON.stringify({ opened: true }); }
|
|
21
|
+
return JSON.stringify({ opened: false, error: 'Panel toggle not found' });
|
|
22
|
+
} catch (e) { return JSON.stringify({ opened: false, error: e.message }); }
|
|
23
23
|
})()
|
|
@@ -58,8 +58,12 @@
|
|
|
58
58
|
if ((b.className || '').includes('opacity-50')) continue;
|
|
59
59
|
const t = (b.innerText || '').trim();
|
|
60
60
|
if (t.length < 2) continue;
|
|
61
|
-
// Filter noise: "Thought for Xs", "Explored N files"
|
|
61
|
+
// Filter noise: "Thought for Xs", "Explored N files", step group timing headers (e.g. "Crafting a minimal response\n1s")
|
|
62
62
|
if (/^Thought\nfor \d+s?$/i.test(t) || /^Explored\n/i.test(t)) continue;
|
|
63
|
+
if (/^.{1,80}\n\d+s$/.test(t) && !t.includes('\n\n')) continue; // step-group collapsible header with timing
|
|
64
|
+
// Skip step-group collapsible headers (e.g. "Crafting a minimal response\n1s")
|
|
65
|
+
if (b.querySelector('.ui-step-group-collapsible, .ui-collapsible-header') &&
|
|
66
|
+
!b.querySelector('.composer-rendered-message, .composer-tool-former-message, .composer-diff-block')) continue;
|
|
63
67
|
const hasTool = b.querySelector('.composer-tool-former-message, .composer-diff-block, .composer-code-block-container');
|
|
64
68
|
msgs.push({ role: hasTool ? 'tool' : 'assistant', content: t.substring(0, 6000), index: msgs.length });
|
|
65
69
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor — dismiss_notification
|
|
3
|
+
*
|
|
4
|
+
* 알림 토스트 닫기/버튼 클릭:
|
|
5
|
+
* 인덱스 또는 메시지 매칭으로 대상 선택
|
|
6
|
+
* 버튼 텍스트 지정 시 해당 버튼 클릭, 아니면 닫기
|
|
7
|
+
*
|
|
8
|
+
* params.index: number, params.button: string|null, params.message: string|null
|
|
9
|
+
* → { dismissed: true/false }
|
|
10
|
+
*/
|
|
11
|
+
(params) => {
|
|
12
|
+
try {
|
|
13
|
+
const toasts = [...document.querySelectorAll('.notifications-toasts .notification-toast, .notification-list-item')].filter(t => t.offsetWidth > 0);
|
|
14
|
+
let toast;
|
|
15
|
+
if (params.message) {
|
|
16
|
+
toast = toasts.find(t => (t.querySelector('.notification-list-item-message')?.textContent || t.textContent || '').toLowerCase().includes(params.message.toLowerCase()));
|
|
17
|
+
} else {
|
|
18
|
+
toast = toasts[params.index || 0];
|
|
19
|
+
}
|
|
20
|
+
if (!toast) return JSON.stringify({ dismissed: false, error: 'Toast not found', count: toasts.length });
|
|
21
|
+
if (params.button) {
|
|
22
|
+
const btn = [...toast.querySelectorAll('button')].find(b => b.textContent?.trim().toLowerCase().includes(params.button.toLowerCase()));
|
|
23
|
+
if (btn) { btn.click(); return JSON.stringify({ dismissed: true, clicked: btn.textContent.trim() }); }
|
|
24
|
+
return JSON.stringify({ dismissed: false, error: 'Button not found' });
|
|
25
|
+
}
|
|
26
|
+
const closeBtn = toast.querySelector('.codicon-notifications-clear, .clear-notification-action, .codicon-close');
|
|
27
|
+
if (closeBtn) { closeBtn.click(); return JSON.stringify({ dismissed: true }); }
|
|
28
|
+
return JSON.stringify({ dismissed: false, error: 'Close button not found' });
|
|
29
|
+
} catch(e) { return JSON.stringify({ dismissed: false, error: e.message }); }
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor — focus_editor
|
|
3
|
+
*
|
|
4
|
+
* 채팅 입력 필드에 포커스:
|
|
5
|
+
* .aislash-editor-input[contenteditable="true"]
|
|
6
|
+
*/
|
|
7
|
+
(() => {
|
|
8
|
+
try {
|
|
9
|
+
const input = document.querySelector('.aislash-editor-input[contenteditable="true"]');
|
|
10
|
+
if (input) { input.focus(); return JSON.stringify({ focused: true }); }
|
|
11
|
+
return JSON.stringify({ focused: false, error: 'Input not found' });
|
|
12
|
+
} catch(e) { return JSON.stringify({ focused: false, error: e.message }); }
|
|
13
|
+
})()
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor — list_models
|
|
3
|
+
*
|
|
4
|
+
* 모델 드롭다운 목록 추출:
|
|
5
|
+
* 버튼: .composer-unified-dropdown-model
|
|
6
|
+
* 메뉴: [data-testid="model-picker-menu"]
|
|
7
|
+
* 아이템: .composer-unified-context-menu-item
|
|
8
|
+
* 모델명: .monaco-highlighted-label
|
|
9
|
+
* Think 모드: codicon-br (brain) 아이콘
|
|
10
|
+
* Auto 토글: rounded-full 24x14 요소
|
|
11
|
+
*
|
|
12
|
+
* → { models[], current }
|
|
13
|
+
*/
|
|
14
|
+
(async () => {
|
|
15
|
+
try {
|
|
16
|
+
let current = '';
|
|
17
|
+
const models = [];
|
|
18
|
+
|
|
19
|
+
const modelBtn = document.querySelector('.composer-unified-dropdown-model');
|
|
20
|
+
if (modelBtn) {
|
|
21
|
+
current = modelBtn.textContent?.trim() || '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 드롭다운 열기
|
|
25
|
+
if (modelBtn) {
|
|
26
|
+
modelBtn.click();
|
|
27
|
+
await new Promise(r => setTimeout(r, 500));
|
|
28
|
+
|
|
29
|
+
const menu = document.querySelector('[data-testid="model-picker-menu"]');
|
|
30
|
+
if (menu) {
|
|
31
|
+
// Auto 토글 확인 및 끄기
|
|
32
|
+
const autoItem = menu.querySelector('.composer-unified-context-menu-item[data-is-selected="true"]');
|
|
33
|
+
const autoToggle = autoItem ? [...autoItem.querySelectorAll('[class*="rounded-full"]')].find(el => el.offsetWidth === 24 && el.offsetHeight === 14) : null;
|
|
34
|
+
let wasAutoOn = false;
|
|
35
|
+
if (autoToggle) {
|
|
36
|
+
const bgStyle = autoToggle.getAttribute('style') || '';
|
|
37
|
+
wasAutoOn = bgStyle.includes('green');
|
|
38
|
+
if (wasAutoOn) {
|
|
39
|
+
autoToggle.click();
|
|
40
|
+
await new Promise(r => setTimeout(r, 500));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 모델 목록 수집 (Auto 끈 상태)
|
|
45
|
+
const refreshedMenu = document.querySelector('[data-testid="model-picker-menu"]');
|
|
46
|
+
if (refreshedMenu) {
|
|
47
|
+
const items = refreshedMenu.querySelectorAll('.composer-unified-context-menu-item');
|
|
48
|
+
for (const item of items) {
|
|
49
|
+
const nameEl = item.querySelector('.monaco-highlighted-label');
|
|
50
|
+
const name = nameEl?.textContent?.trim() || '';
|
|
51
|
+
if (name && name !== 'Add Models') {
|
|
52
|
+
const hasBrain = !!item.querySelector('[class*="codicon-br"]');
|
|
53
|
+
const displayName = hasBrain ? name + ' 🧠' : name;
|
|
54
|
+
models.push({ name: displayName, id: displayName });
|
|
55
|
+
if (item.getAttribute('data-is-selected') === 'true') current = displayName;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Auto 다시 켜기 (원래 상태 복원)
|
|
61
|
+
if (wasAutoOn) {
|
|
62
|
+
const newMenu = document.querySelector('[data-testid="model-picker-menu"]');
|
|
63
|
+
const newAutoItem = newMenu?.querySelector('.composer-unified-context-menu-item');
|
|
64
|
+
const newToggle = newAutoItem ? [...newAutoItem.querySelectorAll('[class*="rounded-full"]')].find(el => el.offsetWidth === 24) : null;
|
|
65
|
+
if (newToggle) {
|
|
66
|
+
newToggle.click();
|
|
67
|
+
await new Promise(r => setTimeout(r, 200));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 닫기 (Escape)
|
|
73
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return JSON.stringify({ models, current });
|
|
77
|
+
} catch(e) { return JSON.stringify({ models: [], current: '', error: e.message }); }
|
|
78
|
+
})()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor — list_modes
|
|
3
|
+
*
|
|
4
|
+
* 모드 드롭다운 목록 추출:
|
|
5
|
+
* 버튼: .composer-unified-dropdown (model이 아닌 것)
|
|
6
|
+
* 메뉴: [data-testid="model-picker-menu"] 또는 .typeahead-popover
|
|
7
|
+
* 아이템: .composer-unified-context-menu-item
|
|
8
|
+
*
|
|
9
|
+
* → { modes[], current }
|
|
10
|
+
*/
|
|
11
|
+
(async () => {
|
|
12
|
+
try {
|
|
13
|
+
const modes = [];
|
|
14
|
+
let current = '';
|
|
15
|
+
|
|
16
|
+
const modeBtn = document.querySelector('.composer-unified-dropdown:not(.composer-unified-dropdown-model)');
|
|
17
|
+
if (!modeBtn) return JSON.stringify({ modes: [], current: '', error: 'Mode button not found' });
|
|
18
|
+
|
|
19
|
+
modeBtn.click();
|
|
20
|
+
await new Promise(r => setTimeout(r, 500));
|
|
21
|
+
|
|
22
|
+
const menu = document.querySelector('[data-testid="model-picker-menu"]') || document.querySelector('.typeahead-popover');
|
|
23
|
+
if (menu) {
|
|
24
|
+
const items = menu.querySelectorAll('.composer-unified-context-menu-item');
|
|
25
|
+
for (const item of items) {
|
|
26
|
+
const nameEl = item.querySelector('.monaco-highlighted-label');
|
|
27
|
+
const name = nameEl?.textContent?.trim() || '';
|
|
28
|
+
if (name) {
|
|
29
|
+
modes.push({ name, id: name });
|
|
30
|
+
if (item.getAttribute('data-is-selected') === 'true') current = name;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 닫기
|
|
36
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
37
|
+
|
|
38
|
+
return JSON.stringify({ modes, current });
|
|
39
|
+
} catch(e) { return JSON.stringify({ modes: [], current: '', error: e.message }); }
|
|
40
|
+
})()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor — list_notifications
|
|
3
|
+
*
|
|
4
|
+
* VS Code 알림 토스트 목록:
|
|
5
|
+
* .notifications-toasts .notification-toast
|
|
6
|
+
* .notification-list-item
|
|
7
|
+
*
|
|
8
|
+
* params.filter: string|null — 메시지 필터
|
|
9
|
+
* → [{ index, message, severity, buttons }]
|
|
10
|
+
*/
|
|
11
|
+
(params) => {
|
|
12
|
+
try {
|
|
13
|
+
const toasts = [...document.querySelectorAll('.notifications-toasts .notification-toast, .notification-list-item')];
|
|
14
|
+
const visible = toasts.filter(t => t.offsetWidth > 0).map((t, i) => ({
|
|
15
|
+
index: i,
|
|
16
|
+
message: t.querySelector('.notification-list-item-message')?.textContent?.trim() || t.textContent?.trim().substring(0, 200),
|
|
17
|
+
severity: t.querySelector('.codicon-error') ? 'error' : t.querySelector('.codicon-warning') ? 'warning' : 'info',
|
|
18
|
+
buttons: [...t.querySelectorAll('.notification-list-item-buttons-container button, .monaco-button')].map(b => b.textContent?.trim()).filter(Boolean),
|
|
19
|
+
}));
|
|
20
|
+
const f = params.filter || null;
|
|
21
|
+
return JSON.stringify(f ? visible.filter(n => n.message.toLowerCase().includes(f.toLowerCase())) : visible);
|
|
22
|
+
} catch(e) { return JSON.stringify([]); }
|
|
23
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor — list_sessions
|
|
3
|
+
*
|
|
4
|
+
* 사이드바 셀 목록에서 세션 파싱:
|
|
5
|
+
* 셀: .agent-sidebar-cell
|
|
6
|
+
* 제목: .agent-sidebar-cell-text
|
|
7
|
+
* 선택 상태: data-selected="true"
|
|
8
|
+
* 활성 ID: [data-composer-id]에서 추출
|
|
9
|
+
*
|
|
10
|
+
* → { sessions: [{ id, title, active, index }] }
|
|
11
|
+
*/
|
|
12
|
+
(() => {
|
|
13
|
+
try {
|
|
14
|
+
const sessions = [];
|
|
15
|
+
const cells = [...document.querySelectorAll('.agent-sidebar-cell')];
|
|
16
|
+
const activeComposer = document.querySelector('[data-composer-id]');
|
|
17
|
+
const activeId = activeComposer?.getAttribute('data-composer-id') || null;
|
|
18
|
+
cells.forEach((cell, i) => {
|
|
19
|
+
const titleEl = cell.querySelector('.agent-sidebar-cell-text');
|
|
20
|
+
const title = titleEl?.textContent?.trim() || 'Untitled';
|
|
21
|
+
const isSelected = cell.getAttribute('data-selected') === 'true';
|
|
22
|
+
sessions.push({
|
|
23
|
+
id: isSelected && activeId ? activeId : 'sidebar-' + i,
|
|
24
|
+
title,
|
|
25
|
+
active: isSelected,
|
|
26
|
+
index: i,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
// If no sidebar cells, fallback to current composer
|
|
30
|
+
if (sessions.length === 0 && activeComposer) {
|
|
31
|
+
sessions.push({
|
|
32
|
+
id: activeId,
|
|
33
|
+
title: document.title.split(' — ')[0],
|
|
34
|
+
active: true,
|
|
35
|
+
index: 0,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return JSON.stringify({ sessions });
|
|
39
|
+
} catch(e) {
|
|
40
|
+
return JSON.stringify({ sessions: [], error: e.message });
|
|
41
|
+
}
|
|
42
|
+
})()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor — new_session
|
|
3
|
+
*
|
|
4
|
+
* 새 채팅/컴포저 버튼 클릭:
|
|
5
|
+
* a.action-label.codicon-add-two
|
|
6
|
+
* [aria-label*="New Chat"]
|
|
7
|
+
* [aria-label*="New Composer"]
|
|
8
|
+
*
|
|
9
|
+
* → { created: true/false }
|
|
10
|
+
*/
|
|
11
|
+
(() => {
|
|
12
|
+
try {
|
|
13
|
+
const newBtn = [...document.querySelectorAll('a.action-label.codicon-add-two, [aria-label*="New Chat"], [aria-label*="New Composer"]')]
|
|
14
|
+
.find(a => a.offsetWidth > 0);
|
|
15
|
+
if (newBtn) { newBtn.click(); return JSON.stringify({ created: true }); }
|
|
16
|
+
return JSON.stringify({ created: false, error: 'New Chat button not found' });
|
|
17
|
+
} catch(e) {
|
|
18
|
+
return JSON.stringify({ created: false, error: e.message });
|
|
19
|
+
}
|
|
20
|
+
})()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor — open_panel
|
|
3
|
+
*
|
|
4
|
+
* 채팅/컴포저 패널 열기:
|
|
5
|
+
* 1. 이미 보이면 'visible' 반환
|
|
6
|
+
* 2. Agent/Chat/Composer 탭 찾아서 클릭
|
|
7
|
+
*/
|
|
8
|
+
(() => {
|
|
9
|
+
try {
|
|
10
|
+
const sidebar = document.getElementById('workbench.parts.auxiliarybar');
|
|
11
|
+
if (sidebar && sidebar.offsetWidth > 0) {
|
|
12
|
+
const chatView = document.querySelector('[data-composer-id]');
|
|
13
|
+
if (chatView) return JSON.stringify({ opened: true });
|
|
14
|
+
}
|
|
15
|
+
const btns = [...document.querySelectorAll('li.action-item a, button, [role="tab"]')];
|
|
16
|
+
const toggle = btns.find(b => {
|
|
17
|
+
const label = (b.textContent || b.getAttribute('aria-label') || '').toLowerCase();
|
|
18
|
+
return /agent|chat|composer|cursor tab/i.test(label);
|
|
19
|
+
});
|
|
20
|
+
if (toggle) { toggle.click(); return JSON.stringify({ opened: true }); }
|
|
21
|
+
return JSON.stringify({ opened: false, error: 'Panel toggle not found' });
|
|
22
|
+
} catch (e) { return JSON.stringify({ opened: false, error: e.message }); }
|
|
23
|
+
})()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor — read_chat
|
|
3
|
+
*
|
|
4
|
+
* DOM 구조 (v0.49):
|
|
5
|
+
* 컴포저: [data-composer-id] + [data-composer-status]
|
|
6
|
+
* 메시지 쌍: .composer-human-ai-pair-container
|
|
7
|
+
* 사용자: .composer-human-message
|
|
8
|
+
* AI: direct children after first (tool/assistant)
|
|
9
|
+
* 입력: .aislash-editor-input[contenteditable="true"]
|
|
10
|
+
* 승인: .run-command-review-active + button/cursor-pointer 요소
|
|
11
|
+
*
|
|
12
|
+
* → { id, status, title, messages[], inputContent, activeModal }
|
|
13
|
+
*/
|
|
14
|
+
(() => {
|
|
15
|
+
try {
|
|
16
|
+
const c = document.querySelector('[data-composer-id]');
|
|
17
|
+
const id = c?.getAttribute('data-composer-id') || 'active';
|
|
18
|
+
const rawStatus = c?.getAttribute('data-composer-status') || 'idle';
|
|
19
|
+
let status = rawStatus;
|
|
20
|
+
if (rawStatus === 'thinking' || rawStatus === 'streaming') status = 'generating';
|
|
21
|
+
else if (rawStatus === 'completed' || rawStatus === 'idle' || !rawStatus) status = 'idle';
|
|
22
|
+
|
|
23
|
+
// Detect approval dialogs
|
|
24
|
+
let activeModal = null;
|
|
25
|
+
|
|
26
|
+
// Primary signal: Cursor uses .run-command-review-active on conversations container
|
|
27
|
+
const reviewActive = !!document.querySelector('.run-command-review-active');
|
|
28
|
+
|
|
29
|
+
// Also check clickable elements (Cursor uses divs with cursor-pointer, not buttons)
|
|
30
|
+
// Note: Cursor concatenates button text with shortcut key labels (e.g. "SkipEsc", "Run⏎")
|
|
31
|
+
const clickableEls = [...document.querySelectorAll('button, [role="button"], .cursor-pointer')].filter(b =>
|
|
32
|
+
b.offsetWidth > 0 && /^(accept|reject|approve|deny|run|skip|allow|cancel)/i.test((b.textContent || b.getAttribute('aria-label') || '').trim())
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (reviewActive || clickableEls.length > 0) {
|
|
36
|
+
status = 'waiting_approval';
|
|
37
|
+
const reviewContainer = document.querySelector('.run-command-review-active');
|
|
38
|
+
const renderedMsgs = reviewContainer?.querySelectorAll('.composer-rendered-message');
|
|
39
|
+
const lastRendered = renderedMsgs?.length ? renderedMsgs[renderedMsgs.length - 1] : null;
|
|
40
|
+
const toolMsg = lastRendered || reviewContainer?.querySelector('.composer-tool-former-message:last-of-type');
|
|
41
|
+
activeModal = {
|
|
42
|
+
message: toolMsg?.textContent?.trim()?.substring(0, 200) || 'Command approval required',
|
|
43
|
+
buttons: clickableEls.map(b => b.textContent.trim().replace(/[⏎↵]/g, '').trim()).filter(Boolean),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const msgs = [];
|
|
48
|
+
document.querySelectorAll('.composer-human-ai-pair-container').forEach((p, i) => {
|
|
49
|
+
if (p.children.length === 0) return; // skip virtual-scroll placeholders
|
|
50
|
+
const h = p.querySelector('.composer-human-message');
|
|
51
|
+
if (h) {
|
|
52
|
+
const userText = (h.innerText || '').trim().substring(0, 6000);
|
|
53
|
+
if (userText) msgs.push({ role: 'user', content: userText, index: msgs.length });
|
|
54
|
+
}
|
|
55
|
+
// Iterate direct children after the first (user message block)
|
|
56
|
+
for (let ci = 1; ci < p.children.length; ci++) {
|
|
57
|
+
const b = p.children[ci];
|
|
58
|
+
if ((b.className || '').includes('opacity-50')) continue;
|
|
59
|
+
const t = (b.innerText || '').trim();
|
|
60
|
+
if (t.length < 2) continue;
|
|
61
|
+
// Filter noise: "Thought for Xs", "Explored N files", step group timing headers (e.g. "Crafting a minimal response\n1s")
|
|
62
|
+
if (/^Thought\nfor \d+s?$/i.test(t) || /^Explored\n/i.test(t)) continue;
|
|
63
|
+
if (/^.{1,80}\n\d+s$/.test(t) && !t.includes('\n\n')) continue; // step-group collapsible header with timing
|
|
64
|
+
// Skip step-group collapsible headers (e.g. "Crafting a minimal response\n1s")
|
|
65
|
+
if (b.querySelector('.ui-step-group-collapsible, .ui-collapsible-header') &&
|
|
66
|
+
!b.querySelector('.composer-rendered-message, .composer-tool-former-message, .composer-diff-block')) continue;
|
|
67
|
+
const hasTool = b.querySelector('.composer-tool-former-message, .composer-diff-block, .composer-code-block-container');
|
|
68
|
+
msgs.push({ role: hasTool ? 'tool' : 'assistant', content: t.substring(0, 6000), index: msgs.length });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
const inputEl = document.querySelector('.aislash-editor-input[contenteditable="true"]');
|
|
72
|
+
const inputContent = inputEl?.textContent?.trim() || '';
|
|
73
|
+
const titleParts = document.title.split(' — ');
|
|
74
|
+
const projectTitle = (titleParts.length >= 2 ? titleParts[titleParts.length - 2] : titleParts[0] || '').trim();
|
|
75
|
+
return JSON.stringify({ id, status, title: projectTitle, messages: msgs, inputContent, activeModal });
|
|
76
|
+
} catch(e) {
|
|
77
|
+
return JSON.stringify({ id: '', status: 'error', messages: [] });
|
|
78
|
+
}
|
|
79
|
+
})()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor — resolve_action
|
|
3
|
+
*
|
|
4
|
+
* 승인/거부 버튼 찾기 + 클릭:
|
|
5
|
+
* button, [role="button"], .cursor-pointer 중 텍스트 매칭
|
|
6
|
+
* Cursor는 단축키 라벨을 붙임 (e.g. "Run⏎", "SkipEsc") → ⏎↵ 제거 후 비교
|
|
7
|
+
*
|
|
8
|
+
* params.buttonText: string — 찾을 버튼 텍스트
|
|
9
|
+
* → { resolved: true/false, clicked?, available? }
|
|
10
|
+
*/
|
|
11
|
+
(params) => {
|
|
12
|
+
try {
|
|
13
|
+
const btns = [...document.querySelectorAll('button, [role="button"], .cursor-pointer')].filter(b => b.offsetWidth > 0);
|
|
14
|
+
const searchText = (params.buttonText || '').toLowerCase();
|
|
15
|
+
const target = btns.find(b => (b.textContent||'').trim().replace(/[⏎↵]/g, '').trim().toLowerCase().includes(searchText));
|
|
16
|
+
if (target) { target.click(); return JSON.stringify({ resolved: true, clicked: target.textContent.trim() }); }
|
|
17
|
+
return JSON.stringify({ resolved: false, available: btns.map(b => b.textContent.trim()).filter(Boolean).slice(0, 15) });
|
|
18
|
+
} catch(e) { return JSON.stringify({ resolved: false, error: e.message }); }
|
|
19
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor CDP Scripts — Router
|
|
3
|
+
*
|
|
4
|
+
* Version routing is handled by ProviderLoader:
|
|
5
|
+
* - provider.json "compatibility" field declares script directory overrides
|
|
6
|
+
* - VersionArchive detects installed Cursor version at daemon startup
|
|
7
|
+
* - resolve() picks the appropriate scripts/ directory
|
|
8
|
+
*
|
|
9
|
+
* Pattern:
|
|
10
|
+
* - Scripts WITHOUT params: loaded as-is (self-invoking IIFE)
|
|
11
|
+
* - Scripts WITH params: loaded as function expression, invoked with params JSON
|
|
12
|
+
* e.g. `(${script})(${JSON.stringify(params)})`
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
function load(name) {
|
|
21
|
+
try { return fs.readFileSync(path.join(__dirname, name), 'utf-8'); }
|
|
22
|
+
catch { return null; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Wrap a function-expression script with params invocation */
|
|
26
|
+
function withParams(name, params) {
|
|
27
|
+
const script = load(name);
|
|
28
|
+
if (!script) return null;
|
|
29
|
+
return `(${script})(${JSON.stringify(params)})`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ─── Core (no params — IIFE) ───
|
|
33
|
+
|
|
34
|
+
module.exports.readChat = () => load('read_chat.js');
|
|
35
|
+
module.exports.sendMessage = () => load('send_message.js');
|
|
36
|
+
module.exports.listSessions = () => load('list_sessions.js');
|
|
37
|
+
module.exports.newSession = () => load('new_session.js');
|
|
38
|
+
module.exports.focusEditor = () => load('focus_editor.js');
|
|
39
|
+
module.exports.openPanel = () => load('open_panel.js');
|
|
40
|
+
module.exports.listModels = () => load('list_models.js');
|
|
41
|
+
module.exports.listModes = () => load('list_modes.js');
|
|
42
|
+
|
|
43
|
+
// ─── With params (function expression) ───
|
|
44
|
+
|
|
45
|
+
module.exports.switchSession = (params) => {
|
|
46
|
+
const index = typeof params === 'number' ? params : params?.index ?? 0;
|
|
47
|
+
const title = typeof params === 'string' ? params : params?.title || null;
|
|
48
|
+
return withParams('switch_session.js', { index, title });
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
module.exports.resolveAction = (params) => {
|
|
52
|
+
const action = typeof params === 'string' ? params : params?.action || 'approve';
|
|
53
|
+
const buttonText = params?.button || params?.buttonText
|
|
54
|
+
|| (action === 'approve' ? 'Run' : action === 'reject' ? 'Skip' : action);
|
|
55
|
+
return withParams('resolve_action.js', { buttonText });
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
module.exports.listNotifications = (params) => {
|
|
59
|
+
const filter = typeof params === 'string' ? params : params?.message || null;
|
|
60
|
+
return withParams('list_notifications.js', { filter });
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
module.exports.dismissNotification = (params) => {
|
|
64
|
+
const index = typeof params === 'number' ? params : params?.index ?? 0;
|
|
65
|
+
const button = typeof params === 'string' ? params : params?.button || null;
|
|
66
|
+
const message = params?.message || null;
|
|
67
|
+
return withParams('dismiss_notification.js', { index, button, message });
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
module.exports.setModel = (params) => {
|
|
71
|
+
const model = typeof params === 'string' ? params : params?.model;
|
|
72
|
+
return withParams('set_model.js', { model });
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
module.exports.setMode = (params) => {
|
|
76
|
+
const mode = typeof params === 'string' ? params : params?.mode;
|
|
77
|
+
return withParams('set_mode.js', { mode });
|
|
78
|
+
};
|