adhdev 0.1.46 → 0.1.48

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adhdev",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "description": "ADHDev CLI — Detect, install and configure your IDE + AI agent extensions",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -73,5 +73,27 @@ module.exports = {
73
73
  openPanel() {
74
74
  return loadScript('open_panel.js');
75
75
  },
76
+
77
+ listModels() {
78
+ return loadScript('list_models.js');
79
+ },
80
+
81
+ setModel(params) {
82
+ const model = params?.model || params;
83
+ const script = loadScript('set_model.js');
84
+ if (!script) return null;
85
+ return script.replace(/\$\{\s*MODEL\s*\}/g, JSON.stringify(model));
86
+ },
87
+
88
+ listModes() {
89
+ return loadScript('list_modes.js');
90
+ },
91
+
92
+ setMode(params) {
93
+ const mode = params?.mode || params;
94
+ const script = loadScript('set_mode.js');
95
+ if (!script) return null;
96
+ return script.replace(/\$\{\s*MODE\s*\}/g, JSON.stringify(mode));
97
+ },
76
98
  },
77
99
  };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Cline — list_models
3
+ * 드롭다운에서 사용 가능한 모델 목록 + 현재 선택된 모델 반환
4
+ * → { models: string[], current: string }
5
+ */
6
+ (async () => {
7
+ try {
8
+ const inner = document.querySelector('iframe');
9
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
10
+ if (!doc) return JSON.stringify({ models: [], current: '', error: 'no doc' });
11
+
12
+ // 현재 모델: mode-switch 또는 model selector에서 읽기
13
+ let current = '';
14
+ const modeSwitch = doc.querySelector('[data-testid="mode-switch"]');
15
+ if (modeSwitch) current = (modeSwitch.textContent || '').trim();
16
+ if (!current) {
17
+ const modelSel = doc.querySelector('[data-testid*="model"], [aria-label*="model" i]');
18
+ if (modelSel) current = (modelSel.textContent || '').trim();
19
+ }
20
+
21
+ // 드롭다운 트리거 찾기
22
+ const trigger = doc.querySelector('[data-testid="model-selector"], [data-testid*="model-dropdown"], [data-testid="dropdown-trigger"]');
23
+ if (!trigger) return JSON.stringify({ models: [], current, error: 'no model trigger' });
24
+
25
+ // 드롭다운 열기
26
+ trigger.click();
27
+ await new Promise(r => setTimeout(r, 300));
28
+
29
+ // 옵션 수집
30
+ const options = doc.querySelectorAll('[data-testid*="dropdown-option"], [role="option"], [class*="dropdown"] [class*="item"], [class*="listbox"] [class*="option"]');
31
+ const models = [];
32
+ for (const opt of options) {
33
+ const text = (opt.textContent || '').trim();
34
+ if (text && text.length > 1 && text.length < 100) models.push(text);
35
+ }
36
+
37
+ // 닫기
38
+ doc.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
39
+ if (trigger.click) trigger.click(); // fallback close
40
+
41
+ return JSON.stringify({ models: [...new Set(models)], current });
42
+ } catch (e) { return JSON.stringify({ models: [], current: '', error: e.message }); }
43
+ })()
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Cline — list_modes
3
+ * 모드 selector에서 사용 가능한 모드 목록 + 현재 모드 반환
4
+ * → { modes: string[], current: string }
5
+ */
6
+ (async () => {
7
+ try {
8
+ const inner = document.querySelector('iframe');
9
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
10
+ if (!doc) return JSON.stringify({ modes: [], current: '', error: 'no doc' });
11
+
12
+ // 현재 모드
13
+ const trigger = doc.querySelector('[data-testid="mode-selector-trigger"], [data-testid="mode-switch"]');
14
+ if (!trigger) return JSON.stringify({ modes: [], current: '', error: 'no mode trigger' });
15
+ const current = (trigger.textContent || '').trim();
16
+
17
+ // 드롭다운 열기
18
+ trigger.click();
19
+ await new Promise(r => setTimeout(r, 300));
20
+
21
+ // 옵션 수집
22
+ const options = doc.querySelectorAll('[data-testid*="mode-option"], [role="option"], [class*="dropdown"] [class*="item"], [class*="listbox"] [class*="option"]');
23
+ const modes = [];
24
+ for (const opt of options) {
25
+ const text = (opt.textContent || '').trim();
26
+ if (text && text.length > 1 && text.length < 50) modes.push(text);
27
+ }
28
+
29
+ // 닫기
30
+ doc.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
31
+ if (trigger.click) trigger.click(); // fallback close
32
+
33
+ return JSON.stringify({ modes: [...new Set(modes)], current });
34
+ } catch (e) { return JSON.stringify({ modes: [], current: '', error: e.message }); }
35
+ })()
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Cline — set_mode
3
+ * 모드 selector에서 지정된 모드 선택
4
+ * ${MODE} → JSON.stringify(modeName)
5
+ * → { success: boolean }
6
+ */
7
+ (async () => {
8
+ try {
9
+ const inner = document.querySelector('iframe');
10
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
11
+ if (!doc) return JSON.stringify({ success: false, error: 'no doc' });
12
+
13
+ const target = ${MODE};
14
+ const trigger = doc.querySelector('[data-testid="mode-selector-trigger"], [data-testid="mode-switch"]');
15
+ if (!trigger) return JSON.stringify({ success: false, error: 'no mode trigger' });
16
+
17
+ // 드롭다운 열기
18
+ trigger.click();
19
+ await new Promise(r => setTimeout(r, 300));
20
+
21
+ // 옵션에서 타겟 모드 찾기
22
+ const options = doc.querySelectorAll('[data-testid*="mode-option"], [role="option"], [class*="dropdown"] [class*="item"], [class*="listbox"] [class*="option"]');
23
+ for (const opt of options) {
24
+ const text = (opt.textContent || '').trim();
25
+ if (text === target || text.toLowerCase().includes(target.toLowerCase())) {
26
+ opt.click();
27
+ await new Promise(r => setTimeout(r, 200));
28
+ return JSON.stringify({ success: true, mode: text });
29
+ }
30
+ }
31
+
32
+ // 닫기
33
+ doc.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
34
+ return JSON.stringify({ success: false, error: 'mode not found: ' + target });
35
+ } catch (e) { return JSON.stringify({ success: false, error: e.message }); }
36
+ })()
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Cline — set_model
3
+ * 드롭다운에서 지정된 모델 선택
4
+ * ${MODEL} → JSON.stringify(modelName)
5
+ * → { success: boolean }
6
+ */
7
+ (async () => {
8
+ try {
9
+ const inner = document.querySelector('iframe');
10
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
11
+ if (!doc) return JSON.stringify({ success: false, error: 'no doc' });
12
+
13
+ const target = ${MODEL};
14
+ const trigger = doc.querySelector('[data-testid="model-selector"], [data-testid*="model-dropdown"], [data-testid="dropdown-trigger"]');
15
+ if (!trigger) return JSON.stringify({ success: false, error: 'no model trigger' });
16
+
17
+ // 드롭다운 열기
18
+ trigger.click();
19
+ await new Promise(r => setTimeout(r, 300));
20
+
21
+ // 옵션에서 타겟 모델 찾기
22
+ const options = doc.querySelectorAll('[data-testid*="dropdown-option"], [role="option"], [class*="dropdown"] [class*="item"], [class*="listbox"] [class*="option"]');
23
+ for (const opt of options) {
24
+ const text = (opt.textContent || '').trim();
25
+ if (text === target || text.includes(target)) {
26
+ opt.click();
27
+ await new Promise(r => setTimeout(r, 200));
28
+ return JSON.stringify({ success: true, model: text });
29
+ }
30
+ }
31
+
32
+ // 닫기
33
+ doc.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
34
+ return JSON.stringify({ success: false, error: 'model not found: ' + target });
35
+ } catch (e) { return JSON.stringify({ success: false, error: e.message }); }
36
+ })()
@@ -572,6 +572,157 @@ module.exports = {
572
572
  if (root && root.offsetHeight > 0) return 'visible';
573
573
  return 'panel_hidden';
574
574
  } catch (e) { return 'error: ' + e.message; }
575
+ })()`;
576
+ },
577
+
578
+ /**
579
+ * listModels → { models: string[], current: string }
580
+ * dropdown-trigger 클릭 → 옵션 읽기 → Escape로 닫기
581
+ */
582
+ listModels() {
583
+ return `(async () => {
584
+ try {
585
+ const inner = document.querySelector('iframe');
586
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
587
+ if (!doc) return JSON.stringify({ models: [], current: '', error: 'no doc' });
588
+
589
+ // 현재 모델 읽기
590
+ const trigger = doc.querySelector('[data-testid="dropdown-trigger"]');
591
+ if (!trigger) return JSON.stringify({ models: [], current: '', error: 'no model trigger' });
592
+ const current = (trigger.textContent || '').trim();
593
+
594
+ // 드롭다운 열기
595
+ trigger.click();
596
+ await new Promise(r => setTimeout(r, 300));
597
+
598
+ // 옵션 수집
599
+ const options = doc.querySelectorAll('[data-testid*="dropdown-option"], [role="option"], [class*="dropdown"] [class*="item"], [class*="listbox"] [class*="option"]');
600
+ const models = [];
601
+ for (const opt of options) {
602
+ const text = (opt.textContent || '').trim();
603
+ if (text && text.length > 1 && text.length < 100) models.push(text);
604
+ }
605
+
606
+ // 닫기
607
+ doc.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
608
+ trigger.click(); // fallback close
609
+
610
+ return JSON.stringify({ models: [...new Set(models)], current });
611
+ } catch (e) { return JSON.stringify({ models: [], current: '', error: e.message }); }
612
+ })()`;
613
+ },
614
+
615
+ /**
616
+ * setModel(params) → { success: boolean }
617
+ * params.model: 선택할 모델 이름
618
+ */
619
+ setModel(params) {
620
+ const model = params?.model || params;
621
+ const escaped = JSON.stringify(model);
622
+ return `(async () => {
623
+ try {
624
+ const inner = document.querySelector('iframe');
625
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
626
+ if (!doc) return JSON.stringify({ success: false, error: 'no doc' });
627
+
628
+ const target = ${escaped};
629
+ const trigger = doc.querySelector('[data-testid="dropdown-trigger"]');
630
+ if (!trigger) return JSON.stringify({ success: false, error: 'no model trigger' });
631
+
632
+ // 드롭다운 열기
633
+ trigger.click();
634
+ await new Promise(r => setTimeout(r, 300));
635
+
636
+ // 옵션에서 타겟 모델 찾기
637
+ const options = doc.querySelectorAll('[data-testid*="dropdown-option"], [role="option"], [class*="dropdown"] [class*="item"], [class*="listbox"] [class*="option"]');
638
+ for (const opt of options) {
639
+ const text = (opt.textContent || '').trim();
640
+ if (text === target || text.includes(target)) {
641
+ opt.click();
642
+ await new Promise(r => setTimeout(r, 200));
643
+ return JSON.stringify({ success: true, model: text });
644
+ }
645
+ }
646
+
647
+ // 닫기
648
+ doc.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
649
+ return JSON.stringify({ success: false, error: 'model not found: ' + target });
650
+ } catch (e) { return JSON.stringify({ success: false, error: e.message }); }
651
+ })()`;
652
+ },
653
+
654
+ /**
655
+ * listModes → { modes: string[], current: string }
656
+ * mode-selector-trigger 클릭 → 옵션 읽기
657
+ */
658
+ listModes() {
659
+ return `(async () => {
660
+ try {
661
+ const inner = document.querySelector('iframe');
662
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
663
+ if (!doc) return JSON.stringify({ modes: [], current: '', error: 'no doc' });
664
+
665
+ const trigger = doc.querySelector('[data-testid="mode-selector-trigger"]');
666
+ if (!trigger) return JSON.stringify({ modes: [], current: '', error: 'no mode trigger' });
667
+ const current = (trigger.textContent || '').trim();
668
+
669
+ // 드롭다운 열기
670
+ trigger.click();
671
+ await new Promise(r => setTimeout(r, 300));
672
+
673
+ // 옵션 수집
674
+ const options = doc.querySelectorAll('[data-testid*="mode-option"], [role="option"], [class*="dropdown"] [class*="item"], [class*="listbox"] [class*="option"]');
675
+ const modes = [];
676
+ for (const opt of options) {
677
+ const text = (opt.textContent || '').trim();
678
+ if (text && text.length > 1 && text.length < 50) modes.push(text);
679
+ }
680
+
681
+ // 닫기
682
+ doc.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
683
+ trigger.click(); // fallback close
684
+
685
+ return JSON.stringify({ modes: [...new Set(modes)], current });
686
+ } catch (e) { return JSON.stringify({ modes: [], current: '', error: e.message }); }
687
+ })()`;
688
+ },
689
+
690
+ /**
691
+ * setMode(params) → { success: boolean }
692
+ * params.mode: 선택할 모드 이름
693
+ */
694
+ setMode(params) {
695
+ const mode = params?.mode || params;
696
+ const escaped = JSON.stringify(mode);
697
+ return `(async () => {
698
+ try {
699
+ const inner = document.querySelector('iframe');
700
+ const doc = inner?.contentDocument || inner?.contentWindow?.document;
701
+ if (!doc) return JSON.stringify({ success: false, error: 'no doc' });
702
+
703
+ const target = ${escaped};
704
+ const trigger = doc.querySelector('[data-testid="mode-selector-trigger"]');
705
+ if (!trigger) return JSON.stringify({ success: false, error: 'no mode trigger' });
706
+
707
+ // 드롭다운 열기
708
+ trigger.click();
709
+ await new Promise(r => setTimeout(r, 300));
710
+
711
+ // 옵션에서 타겟 모드 찾기
712
+ const options = doc.querySelectorAll('[data-testid*="mode-option"], [role="option"], [class*="dropdown"] [class*="item"], [class*="listbox"] [class*="option"]');
713
+ for (const opt of options) {
714
+ const text = (opt.textContent || '').trim();
715
+ if (text === target || text.toLowerCase().includes(target.toLowerCase())) {
716
+ opt.click();
717
+ await new Promise(r => setTimeout(r, 200));
718
+ return JSON.stringify({ success: true, mode: text });
719
+ }
720
+ }
721
+
722
+ // 닫기
723
+ doc.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
724
+ return JSON.stringify({ success: false, error: 'mode not found: ' + target });
725
+ } catch (e) { return JSON.stringify({ success: false, error: e.message }); }
575
726
  })()`;
576
727
  },
577
728
  },
@@ -88,5 +88,27 @@ module.exports = {
88
88
  focusEditor() {
89
89
  return loadScript('focus_editor.js');
90
90
  },
91
+
92
+ listModels() {
93
+ return loadScript('list_models.js');
94
+ },
95
+
96
+ setModel(params) {
97
+ const model = typeof params === 'string' ? params : params?.model;
98
+ const script = loadScript('set_model.js');
99
+ if (!script) return null;
100
+ return script.replace(/\$\{\s*MODEL\s*\}/g, JSON.stringify(model));
101
+ },
102
+
103
+ listModes() {
104
+ return loadScript('list_modes.js');
105
+ },
106
+
107
+ setMode(params) {
108
+ const mode = typeof params === 'string' ? params : params?.mode;
109
+ const script = loadScript('set_mode.js');
110
+ if (!script) return null;
111
+ return script.replace(/\$\{\s*MODE\s*\}/g, JSON.stringify(mode));
112
+ },
91
113
  },
92
114
  };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Antigravity — list_models
3
+ * antigravity-agent-side-panel 내부 모델 드롭다운에서 목록 + 현재 모델 추출
4
+ * → { models: string[], current: string }
5
+ */
6
+ (() => {
7
+ try {
8
+ const models = [];
9
+ let current = '';
10
+
11
+ // 1. 모델 항목에서 목록 추출
12
+ // 셀렉터: .px-2.py-1.flex.items-center.justify-between.cursor-pointer
13
+ const items = document.querySelectorAll('.px-2.py-1.flex.items-center.justify-between.cursor-pointer');
14
+ for (const item of items) {
15
+ const label = item.querySelector('.text-xs.font-medium');
16
+ const text = (label || item).textContent?.trim();
17
+ if (!text || text.length > 60) continue;
18
+ // 모델명 검증 (Claude, Gemini, GPT, etc.)
19
+ models.push(text);
20
+ // 선택된 항목: bg-gray-500/20
21
+ if ((item.className || '').includes('bg-gray-500/20')) {
22
+ current = text;
23
+ }
24
+ }
25
+
26
+ // 2. 모델 목록이 없으면 (드롭다운 닫힘) → 트리거 버튼에서 현재 모델만
27
+ if (models.length === 0) {
28
+ const trigger = document.querySelector('.flex.min-w-0.max-w-full.cursor-pointer.items-center');
29
+ if (trigger) {
30
+ current = trigger.textContent?.trim() || '';
31
+ }
32
+ }
33
+
34
+ return JSON.stringify({ models, current });
35
+ } catch (e) {
36
+ return JSON.stringify({ models: [], current: '', error: e.message });
37
+ }
38
+ })()
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Antigravity — list_modes
3
+ * Conversation mode: Planning / Fast 세그먼트 컨트롤
4
+ * inputBox 근처의 "Conversation mode" 패널에서 읽기
5
+ * → { modes: string[], current: string }
6
+ */
7
+ (() => {
8
+ try {
9
+ const modes = [];
10
+ let current = '';
11
+
12
+ // "Conversation mode" 헤더를 포함하는 패널 찾기
13
+ const headers = document.querySelectorAll('.text-xs.px-2.pb-1.opacity-80');
14
+ for (const header of headers) {
15
+ if (header.textContent?.trim() === 'Conversation mode') {
16
+ // 형제 요소들에서 모드 항목 추출
17
+ const parent = header.parentElement;
18
+ if (!parent) continue;
19
+ const items = parent.querySelectorAll('.font-medium');
20
+ for (const item of items) {
21
+ const text = item.textContent?.trim();
22
+ if (text && text.length < 20) {
23
+ modes.push(text);
24
+ }
25
+ }
26
+ break;
27
+ }
28
+ }
29
+
30
+ // 현재 모드: Fast 버튼의 텍스트 (현재 활성 모드 표시)
31
+ const modeBtn = [...document.querySelectorAll('button')].find(b => {
32
+ const cls = b.className || '';
33
+ return cls.includes('py-1') && cls.includes('pl-1') && cls.includes('pr-2') && b.offsetWidth > 0;
34
+ });
35
+ if (modeBtn) {
36
+ current = modeBtn.textContent?.trim() || '';
37
+ }
38
+
39
+ // modes가 비어있으면 기본값
40
+ if (modes.length === 0) {
41
+ modes.push('Planning', 'Fast');
42
+ }
43
+
44
+ return JSON.stringify({ modes, current });
45
+ } catch (e) {
46
+ return JSON.stringify({ modes: [], current: '', error: e.message });
47
+ }
48
+ })()
@@ -1,64 +1,68 @@
1
1
  /**
2
2
  * Antigravity v1 — resolve_action
3
3
  *
4
- * 승인/거부 버튼 클릭 (파라미터: ${BUTTON_TEXT})
5
- * Antigravity는 모달이 아닌 인라인 버튼(Run⌥⏎, Reject 등)을 사용하므로,
6
- * DOM 전체에서 startsWith 매칭으로 탐색.
4
+ * 버튼 찾기 + 좌표 반환 (CDP Input.dispatchMouseEvent로 클릭)
5
+ * 파라미터: ${BUTTON_TEXT}
6
+ *
7
+ * 핵심: viewport 안에 보이는 버튼 중 마지막(최신) 매칭 우선
7
8
  */
8
9
  (() => {
9
10
  const want = ${ BUTTON_TEXT };
11
+ const wantNorm = (want || '').replace(/\s+/g, ' ').trim().toLowerCase();
12
+
10
13
  function norm(t) { return (t || '').replace(/\s+/g, ' ').trim().toLowerCase(); }
14
+
11
15
  function matches(el) {
12
- const t = norm(el.textContent);
13
- if (t.length > 80) return false;
14
- if (t === want) return true;
15
- if (t.indexOf(want) === 0) return true; // startsWith: "run⌥⏎" starts with "run"
16
- if (want === 'run' && (/^run\s*/.test(t) || t === 'enter' || t === '⏎')) return true;
17
- if (want === 'skip' && t.indexOf('skip') >= 0) return true;
18
- if (want === 'reject' && t.indexOf('reject') >= 0) return true;
19
- if (want === 'accept' && t.indexOf('accept') >= 0) return true;
16
+ const raw = (el.textContent || '').trim();
17
+ const t = norm(raw);
18
+ if (!t || t.length > 80) return false;
19
+ if (t === wantNorm) return true;
20
+ if (t.indexOf(wantNorm) === 0) return true;
21
+ if (wantNorm.indexOf(t) >= 0 && t.length > 2) return true;
22
+ if (t.indexOf(wantNorm) >= 0) return true;
23
+ if (/^(run|approve|allow|accept|always|yes)\b/.test(wantNorm)) {
24
+ if (/^run\b/.test(t)) return true;
25
+ if (/^allow\b/.test(t)) return true;
26
+ if (/^accept\b/.test(t) && !t.includes('changes')) return true;
27
+ if (/^always\b/.test(t)) return true;
28
+ }
29
+ if (/^(reject|deny|no|abort)\b/.test(wantNorm)) {
30
+ if (/^reject\b/.test(t) && !t.includes('changes')) return true;
31
+ if (/^deny\b/.test(t)) return true;
32
+ }
33
+ if (/^(skip|cancel)\b/.test(wantNorm)) {
34
+ if (/^skip\b/.test(t) || /^cancel\b/.test(t)) return true;
35
+ }
20
36
  return false;
21
37
  }
22
- function doClick(el) {
23
- if (!el) return;
24
- el.focus && el.focus();
25
- el.click && el.click();
26
- try {
27
- el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window }));
28
- el.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true, view: window }));
29
- el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
30
- } catch (e) { }
31
- }
32
- const sel = 'button, [role="button"], .monaco-button, .solid-dropdown-toggle';
33
- // 1. Try run-command-review container first
34
- const runReview = document.querySelector('[class*="run-command-review"]');
35
- if (runReview) {
36
- const btns = Array.from(runReview.querySelectorAll(sel)).filter(b => b.offsetWidth > 0);
37
- for (const b of btns) { if (matches(b)) { doClick(b); return true; } }
38
- }
39
- // 2. Try overlay containers
40
- const overlay = document.querySelector('.quick-agent-overlay-container, [class*="overlay-container"]');
41
- if (overlay) {
42
- const btns = Array.from(overlay.querySelectorAll(sel)).filter(b => b.offsetWidth > 0);
43
- for (const b of btns) { if (matches(b)) { doClick(b); return true; } }
44
- }
45
- // 3. Try dialog boxes
46
- const dialog = document.querySelector('.monaco-dialog-box, .monaco-modal-block, [role="dialog"]');
47
- if (dialog) {
48
- const btns = Array.from(dialog.querySelectorAll(sel)).filter(b => b.offsetWidth > 0);
49
- for (const b of btns) { if (matches(b)) { doClick(b); return true; } }
50
- }
51
- // 4. Global search — find any visible matching button
52
- const allBtns = Array.from(document.querySelectorAll(sel)).filter(b => b.offsetWidth > 0 && b.getBoundingClientRect().height > 0);
53
- for (const b of allBtns) { if (matches(b)) { doClick(b); return true; } }
54
- // 5. If Run was requested, try Enter key on focused element
55
- if (want === 'run') {
56
- const focused = document.activeElement;
57
- if (focused) {
58
- focused.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
59
- focused.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
60
- return true;
38
+
39
+ const sel = 'button, [role="button"]';
40
+ const allBtns = [...document.querySelectorAll(sel)].filter(b => {
41
+ if (!b.offsetWidth || !b.getBoundingClientRect().height) return false;
42
+ const rect = b.getBoundingClientRect();
43
+ // viewport 안에 보이는 것만 (y > 0, y < window.innerHeight)
44
+ return rect.y > 0 && rect.y < window.innerHeight;
45
+ });
46
+
47
+ // 마지막(최신) 매칭 우선 — 역순 검색
48
+ let found = null;
49
+ for (let i = allBtns.length - 1; i >= 0; i--) {
50
+ if (matches(allBtns[i])) {
51
+ found = allBtns[i];
52
+ break;
61
53
  }
62
54
  }
63
- return false;
55
+
56
+ if (found) {
57
+ const rect = found.getBoundingClientRect();
58
+ return JSON.stringify({
59
+ found: true,
60
+ text: found.textContent?.trim()?.substring(0, 40),
61
+ x: Math.round(rect.x + rect.width / 2),
62
+ y: Math.round(rect.y + rect.height / 2),
63
+ w: Math.round(rect.width),
64
+ h: Math.round(rect.height)
65
+ });
66
+ }
67
+ return JSON.stringify({ found: false, want: wantNorm });
64
68
  })()
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Antigravity — set_mode
3
+ * Conversation mode 패널에서 Planning/Fast 클릭
4
+ * ${MODE} → JSON.stringify(modeName)
5
+ * → { success: boolean, mode?: string }
6
+ */
7
+ (async () => {
8
+ try {
9
+ const target = ${MODE};
10
+
11
+ // "Conversation mode" 헤더의 부모에서 .font-medium 항목 찾기
12
+ const headers = document.querySelectorAll('.text-xs.px-2.pb-1.opacity-80');
13
+ for (const header of headers) {
14
+ if (header.textContent?.trim() === 'Conversation mode') {
15
+ const parent = header.parentElement;
16
+ if (!parent) continue;
17
+ const items = parent.querySelectorAll('.font-medium');
18
+ for (const item of items) {
19
+ const text = item.textContent?.trim();
20
+ if (text && text.toLowerCase() === target.toLowerCase()) {
21
+ item.click();
22
+ await new Promise(r => setTimeout(r, 300));
23
+ return JSON.stringify({ success: true, mode: text });
24
+ }
25
+ }
26
+ break;
27
+ }
28
+ }
29
+
30
+ return JSON.stringify({ success: false, error: 'mode not found: ' + target });
31
+ } catch (e) {
32
+ return JSON.stringify({ success: false, error: e.message });
33
+ }
34
+ })()