adhdev 0.2.0 → 0.4.0

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 (71) hide show
  1. package/dist/cli-entrypoint.js +18164 -1101
  2. package/dist/cli-entrypoint.js.map +1 -1
  3. package/dist/index.js +18304 -1262
  4. package/dist/index.js.map +1 -1
  5. package/package.json +1 -1
  6. package/providers/_builtin/COMPATIBILITY.md +217 -0
  7. package/providers/_builtin/acp/agentpool/provider.json +7 -0
  8. package/providers/_builtin/acp/amp/provider.json +7 -0
  9. package/providers/_builtin/acp/auggie/provider.json +7 -0
  10. package/providers/_builtin/acp/autodev/provider.json +7 -0
  11. package/providers/_builtin/acp/autohand/provider.json +7 -0
  12. package/providers/_builtin/acp/blackbox-ai/provider.json +7 -0
  13. package/providers/_builtin/acp/claude-agent/provider.json +7 -0
  14. package/providers/_builtin/acp/cline-acp/provider.json +7 -0
  15. package/providers/_builtin/acp/codebuddy/provider.json +7 -0
  16. package/providers/_builtin/acp/codex-cli/provider.json +7 -0
  17. package/providers/_builtin/acp/corust-agent/provider.json +7 -0
  18. package/providers/_builtin/acp/crow-cli/provider.json +7 -0
  19. package/providers/_builtin/acp/cursor-acp/provider.json +7 -0
  20. package/providers/_builtin/acp/deepagents/provider.json +7 -0
  21. package/providers/_builtin/acp/dimcode/provider.json +7 -0
  22. package/providers/_builtin/acp/docker-cagent/provider.json +7 -0
  23. package/providers/_builtin/acp/factory-droid/provider.json +7 -0
  24. package/providers/_builtin/acp/fast-agent/provider.json +7 -0
  25. package/providers/_builtin/acp/gemini-cli/provider.json +7 -0
  26. package/providers/_builtin/acp/github-copilot/provider.json +7 -0
  27. package/providers/_builtin/acp/goose/provider.json +7 -0
  28. package/providers/_builtin/acp/junie/provider.json +7 -0
  29. package/providers/_builtin/acp/kilo/provider.json +10 -1
  30. package/providers/_builtin/acp/kimi-cli/provider.json +7 -0
  31. package/providers/_builtin/acp/minion-code/provider.json +7 -0
  32. package/providers/_builtin/acp/mistral-vibe/provider.json +7 -0
  33. package/providers/_builtin/acp/nova/provider.json +7 -0
  34. package/providers/_builtin/acp/openclaw/provider.json +7 -0
  35. package/providers/_builtin/acp/opencode/provider.json +7 -0
  36. package/providers/_builtin/acp/openhands/provider.json +7 -0
  37. package/providers/_builtin/acp/pi-acp/provider.json +7 -0
  38. package/providers/_builtin/acp/qoder/provider.json +7 -0
  39. package/providers/_builtin/acp/qwen-code/provider.json +7 -0
  40. package/providers/_builtin/acp/stakpak/provider.json +7 -0
  41. package/providers/_builtin/acp/vtcode/provider.json +7 -0
  42. package/providers/_builtin/cli/claude-cli/provider.json +22 -0
  43. package/providers/_builtin/cli/codex-cli/provider.json +29 -0
  44. package/providers/_builtin/cli/gemini-cli/provider.json +29 -0
  45. package/providers/_builtin/docs/CDP_SELECTOR_GUIDE.md +370 -0
  46. package/providers/_builtin/docs/PROVIDER_GUIDE.md +916 -0
  47. package/providers/_builtin/extension/cline/provider.json +24 -0
  48. package/providers/_builtin/extension/roo-code/provider.json +24 -0
  49. package/providers/_builtin/ide/antigravity/provider.json +32 -1
  50. package/providers/_builtin/ide/antigravity/scripts/legacy/list_models.js +38 -0
  51. package/providers/_builtin/ide/antigravity/scripts/legacy/list_modes.js +48 -0
  52. package/providers/_builtin/ide/antigravity/scripts/legacy/scripts.js +64 -0
  53. package/providers/_builtin/ide/antigravity/scripts/legacy/set_mode.js +34 -0
  54. package/providers/_builtin/ide/antigravity/scripts/legacy/set_model.js +47 -0
  55. package/providers/_builtin/ide/antigravity/scripts/list_models.js +31 -8
  56. package/providers/_builtin/ide/antigravity/scripts/list_modes.js +37 -13
  57. package/providers/_builtin/ide/antigravity/scripts/set_mode.js +49 -16
  58. package/providers/_builtin/ide/antigravity/scripts/set_model.js +47 -22
  59. package/providers/_builtin/ide/antigravity/scripts.js +54 -60
  60. package/providers/_builtin/ide/cursor/provider.json +24 -0
  61. package/providers/_builtin/ide/cursor/scripts.js +16 -10
  62. package/providers/_builtin/ide/kiro/provider.json +25 -1
  63. package/providers/_builtin/ide/pearai/provider.json +25 -1
  64. package/providers/_builtin/ide/trae/provider.json +25 -1
  65. package/providers/_builtin/ide/vscode/provider.json +25 -1
  66. package/providers/_builtin/ide/vscode-insiders/provider.json +25 -1
  67. package/providers/_builtin/ide/vscodium/provider.json +25 -1
  68. package/providers/_builtin/ide/windsurf/provider.json +25 -1
  69. package/providers/_builtin/acp/code-assistant/provider.json +0 -47
  70. package/providers/_builtin/acp/fount/provider.json +0 -47
  71. package/providers/_builtin/acp/kiro-cli/provider.json +0 -47
@@ -7,5 +7,29 @@
7
7
  "extensionIdPattern_flags": "i",
8
8
  "vscodeCommands": {
9
9
  "focusPanel": "claude-dev.SidebarProvider.focus"
10
+ },
11
+ "settings": {
12
+ "approvalAlert": {
13
+ "type": "boolean",
14
+ "default": true,
15
+ "public": true,
16
+ "label": "Approval Notifications",
17
+ "description": "Show notification when approval is needed"
18
+ },
19
+ "longGeneratingAlert": {
20
+ "type": "boolean",
21
+ "default": true,
22
+ "public": true,
23
+ "label": "Long Generation Alert",
24
+ "description": "Alert when generation takes too long"
25
+ },
26
+ "longGeneratingThresholdSec": {
27
+ "type": "number",
28
+ "default": 180,
29
+ "public": true,
30
+ "label": "Long Generation Threshold (sec)",
31
+ "min": 30,
32
+ "max": 600
33
+ }
10
34
  }
11
35
  }
@@ -7,5 +7,29 @@
7
7
  "extensionIdPattern_flags": "i",
8
8
  "vscodeCommands": {
9
9
  "focusPanel": "roo-cline.SidebarProvider.focus"
10
+ },
11
+ "settings": {
12
+ "approvalAlert": {
13
+ "type": "boolean",
14
+ "default": true,
15
+ "public": true,
16
+ "label": "Approval Notifications",
17
+ "description": "Show notification when approval is needed"
18
+ },
19
+ "longGeneratingAlert": {
20
+ "type": "boolean",
21
+ "default": true,
22
+ "public": true,
23
+ "label": "Long Generation Alert",
24
+ "description": "Alert when generation takes too long"
25
+ },
26
+ "longGeneratingThresholdSec": {
27
+ "type": "number",
28
+ "default": 180,
29
+ "public": true,
30
+ "label": "Long Generation Threshold (sec)",
31
+ "min": 30,
32
+ "max": 600
33
+ }
10
34
  }
11
35
  }
@@ -28,5 +28,36 @@
28
28
  ]
29
29
  },
30
30
  "inputMethod": "cdp-type-and-send",
31
- "inputSelector": "[contenteditable=\"true\"][role=\"textbox\"]"
31
+ "inputSelector": "[contenteditable=\"true\"][role=\"textbox\"]",
32
+ "versionCommand": "antigravity --version",
33
+ "testedVersions": ["1.107.0"],
34
+ "versions": {
35
+ "< 1.107.0": {
36
+ "__dir": "scripts/legacy"
37
+ }
38
+ },
39
+ "settings": {
40
+ "approvalAlert": {
41
+ "type": "boolean",
42
+ "default": true,
43
+ "public": true,
44
+ "label": "Approval Notifications",
45
+ "description": "Show notification when approval is needed"
46
+ },
47
+ "longGeneratingAlert": {
48
+ "type": "boolean",
49
+ "default": true,
50
+ "public": true,
51
+ "label": "Long Generation Alert",
52
+ "description": "Alert when generation takes too long"
53
+ },
54
+ "longGeneratingThresholdSec": {
55
+ "type": "number",
56
+ "default": 180,
57
+ "public": true,
58
+ "label": "Long Generation Threshold (sec)",
59
+ "min": 30,
60
+ "max": 600
61
+ }
62
+ }
32
63
  }
@@ -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
+ })()
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Antigravity CDP Scripts — legacy (< 1.107.0)
3
+ * DOM uses exact CSS class selectors (original Tailwind classes without arbitrary values)
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const DIR = __dirname; // scripts/legacy/
11
+
12
+ function load(name) {
13
+ try { return fs.readFileSync(path.join(DIR, name), 'utf-8'); }
14
+ catch { return null; }
15
+ }
16
+
17
+ // Non-model/mode scripts fall back to parent scripts/
18
+ const PARENT_DIR = path.join(DIR, '..');
19
+ function loadParent(name) {
20
+ try { return fs.readFileSync(path.join(PARENT_DIR, name), 'utf-8'); }
21
+ catch { return null; }
22
+ }
23
+
24
+ module.exports.readChat = () => loadParent('read_chat.js');
25
+ module.exports.focusEditor = () => loadParent('focus_editor.js');
26
+ module.exports.listSessions = () => loadParent('list_chats.js');
27
+ module.exports.newSession = () => loadParent('new_session.js');
28
+ module.exports.listModels = () => load('list_models.js');
29
+ module.exports.listModes = () => load('list_modes.js');
30
+
31
+ module.exports.sendMessage = (text) => {
32
+ const script = loadParent('send_message.js');
33
+ if (!script) return null;
34
+ return script.replace(/\$\{\s*MESSAGE\s*\}/g, JSON.stringify(text));
35
+ };
36
+
37
+ module.exports.switchSession = (sessionId) => {
38
+ const script = loadParent('switch_session.js');
39
+ if (!script) return null;
40
+ return script.replace(/\$\{\s*SESSION_ID\s*\}/g, JSON.stringify(sessionId));
41
+ };
42
+
43
+ module.exports.resolveAction = (params) => {
44
+ const action = typeof params === 'string' ? params : params?.action || 'approve';
45
+ const buttonText = params?.button || params?.buttonText
46
+ || (action === 'approve' ? 'Accept' : action === 'reject' ? 'Reject' : action);
47
+ const script = loadParent('resolve_action.js');
48
+ if (!script) return null;
49
+ return script.replace(/\$\{\s*BUTTON_TEXT\s*\}/g, JSON.stringify(buttonText));
50
+ };
51
+
52
+ module.exports.setModel = (params) => {
53
+ const model = typeof params === 'string' ? params : params?.model;
54
+ const script = load('set_model.js');
55
+ if (!script) return null;
56
+ return script.replace(/\$\{\s*MODEL\s*\}/g, JSON.stringify(model));
57
+ };
58
+
59
+ module.exports.setMode = (params) => {
60
+ const mode = typeof params === 'string' ? params : params?.mode;
61
+ const script = load('set_mode.js');
62
+ if (!script) return null;
63
+ return script.replace(/\$\{\s*MODE\s*\}/g, JSON.stringify(mode));
64
+ };
@@ -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
+ })()
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Antigravity — set_model
3
+ * antigravity-agent-side-panel 모델 드롭다운에서 모델 선택
4
+ * ${MODEL} → JSON.stringify(modelName)
5
+ * → { success: boolean, model?: string }
6
+ */
7
+ (async () => {
8
+ try {
9
+ const target = ${MODEL};
10
+
11
+ // 1. 모델 드롭다운이 열려 있는 경우 → 직접 선택
12
+ const items = document.querySelectorAll('.px-2.py-1.flex.items-center.justify-between.cursor-pointer');
13
+ for (const item of items) {
14
+ const label = item.querySelector('.text-xs.font-medium');
15
+ const text = (label || item).textContent?.trim();
16
+ if (text && (text === target || text.toLowerCase().includes(target.toLowerCase()))) {
17
+ item.click();
18
+ await new Promise(r => setTimeout(r, 200));
19
+ return JSON.stringify({ success: true, model: text });
20
+ }
21
+ }
22
+
23
+ // 2. 드롭다운이 닫혀 있으면 → 트리거 버튼 클릭해서 열기
24
+ const trigger = document.querySelector('.flex.min-w-0.max-w-full.cursor-pointer.items-center');
25
+ if (trigger) {
26
+ trigger.click();
27
+ await new Promise(r => setTimeout(r, 400));
28
+
29
+ // 다시 항목 탐색
30
+ const newItems = document.querySelectorAll('.px-2.py-1.flex.items-center.justify-between.cursor-pointer');
31
+ for (const item of newItems) {
32
+ const label = item.querySelector('.text-xs.font-medium');
33
+ const text = (label || item).textContent?.trim();
34
+ if (text && (text === target || text.toLowerCase().includes(target.toLowerCase()))) {
35
+ item.click();
36
+ return JSON.stringify({ success: true, model: text });
37
+ }
38
+ }
39
+ // 못 찾으면 드롭다운 닫기
40
+ trigger.click();
41
+ }
42
+
43
+ return JSON.stringify({ success: false, error: 'model not found: ' + target });
44
+ } catch (e) {
45
+ return JSON.stringify({ success: false, error: e.message });
46
+ }
47
+ })()
@@ -1,6 +1,12 @@
1
1
  /**
2
2
  * Antigravity — list_models
3
- * antigravity-agent-side-panel 내부 모델 드롭다운에서 목록 + 현재 모델 추출
3
+ * 모델 드롭다운 트리거 버튼 + 항목 목록 추출
4
+ *
5
+ * Version compatibility:
6
+ * v0 (old): .flex.min-w-0.max-w-full.cursor-pointer.items-center (exact CSS selector works)
7
+ * v1 (new): Tailwind arbitrary values (pl-[0.125rem]) — must use partial class matching
8
+ * Both: dropdown items use .px-2.py-1.flex.items-center.justify-between.cursor-pointer
9
+ *
4
10
  * → { models: string[], current: string }
5
11
  */
6
12
  (() => {
@@ -8,26 +14,43 @@
8
14
  const models = [];
9
15
  let current = '';
10
16
 
11
- // 1. 모델 항목에서 목록 추출
12
- // 셀렉터: .px-2.py-1.flex.items-center.justify-between.cursor-pointer
17
+ // ── Step 1: Dropdown open state — extract item list ──────────────────
18
+ // Selector works for both v0 and v1 (hover class changed but cursor-pointer persists)
13
19
  const items = document.querySelectorAll('.px-2.py-1.flex.items-center.justify-between.cursor-pointer');
14
20
  for (const item of items) {
15
21
  const label = item.querySelector('.text-xs.font-medium');
16
22
  const text = (label || item).textContent?.trim();
17
23
  if (!text || text.length > 60) continue;
18
- // 모델명 검증 (Claude, Gemini, GPT, etc.)
19
24
  models.push(text);
20
- // 선택된 항목: bg-gray-500/20
25
+ // Selected item: bg-gray-500/20 (both v0 & v1)
21
26
  if ((item.className || '').includes('bg-gray-500/20')) {
22
27
  current = text;
23
28
  }
24
29
  }
25
30
 
26
- // 2. 모델 목록이 없으면 (드롭다운 닫힘) 트리거 버튼에서 현재 모델만
31
+ // ── Step 2: Dropdown closed extract current model from trigger ──────
27
32
  if (models.length === 0) {
28
- const trigger = document.querySelector('.flex.min-w-0.max-w-full.cursor-pointer.items-center');
33
+ // v0: exact class match works
34
+ let trigger = document.querySelector('.flex.min-w-0.max-w-full.cursor-pointer.items-center');
35
+
36
+ // v1: arbitrary Tailwind classes → partial matching
37
+ if (!trigger || trigger.offsetWidth === 0) {
38
+ trigger = [...document.querySelectorAll('div, button')].find(e => {
39
+ const cls = e.className || '';
40
+ return cls.includes('min-w-0') &&
41
+ cls.includes('max-w-full') &&
42
+ cls.includes('cursor-pointer') &&
43
+ cls.includes('items-center') &&
44
+ e.offsetWidth > 0;
45
+ }) || null;
46
+ }
47
+
29
48
  if (trigger) {
30
- current = trigger.textContent?.trim() || '';
49
+ // v0: .text-xs.font-medium span / v1: .text-xs span with opacity-70
50
+ const span = trigger.querySelector('.text-xs.font-medium') ||
51
+ trigger.querySelector('span.text-xs') ||
52
+ trigger;
53
+ current = span.textContent?.trim() || '';
31
54
  }
32
55
  }
33
56
 
@@ -1,7 +1,12 @@
1
1
  /**
2
2
  * Antigravity — list_modes
3
- * Conversation mode: Planning / Fast 세그먼트 컨트롤
4
- * inputBox 근처의 "Conversation mode" 패널에서 읽기
3
+ * 현재 모드 + 사용 가능한 모드 목록 추출
4
+ *
5
+ * Version compatibility:
6
+ * v0 (old): span-based mode button (plain text, no opacity class)
7
+ * v1 (new): BUTTON element with py-1 pl-1 pr-2 opacity-70 classes
8
+ * Both: "Conversation mode" panel with .font-medium items (when dropdown open)
9
+ *
5
10
  * → { modes: string[], current: string }
6
11
  */
7
12
  (() => {
@@ -9,34 +14,53 @@
9
14
  const modes = [];
10
15
  let current = '';
11
16
 
12
- // "Conversation mode" 헤더를 포함하는 패널 찾기
17
+ // ── Helper: find mode trigger button (v0 + v1 compat) ────────────────
18
+ function findModeTrigger() {
19
+ // v1: BUTTON with opacity-70 + py-1 pl-1 pr-2
20
+ const v1 = [...document.querySelectorAll('button')].find(b => {
21
+ const cls = b.className || '';
22
+ return cls.includes('py-1') &&
23
+ cls.includes('pl-1') &&
24
+ cls.includes('pr-2') &&
25
+ cls.includes('opacity-70') &&
26
+ b.offsetWidth > 0;
27
+ });
28
+ if (v1) return v1;
29
+
30
+ // v0: span/button with py-1 pl-1 pr-2 (without opacity-70)
31
+ return [...document.querySelectorAll('button, span')].find(b => {
32
+ const cls = b.className || '';
33
+ return cls.includes('py-1') &&
34
+ cls.includes('pl-1') &&
35
+ cls.includes('pr-2') &&
36
+ b.offsetWidth > 0 &&
37
+ // ensure it's in the model/mode area (not some other button)
38
+ (b.textContent?.trim() === 'Fast' || b.textContent?.trim() === 'Planning' || b.textContent?.trim() === 'Normal');
39
+ }) || null;
40
+ }
41
+
42
+ // ── Step 1: "Conversation mode" panel open — extract items ───────────
13
43
  const headers = document.querySelectorAll('.text-xs.px-2.pb-1.opacity-80');
14
44
  for (const header of headers) {
15
45
  if (header.textContent?.trim() === 'Conversation mode') {
16
- // 형제 요소들에서 모드 항목 추출
17
46
  const parent = header.parentElement;
18
47
  if (!parent) continue;
19
48
  const items = parent.querySelectorAll('.font-medium');
20
49
  for (const item of items) {
21
50
  const text = item.textContent?.trim();
22
- if (text && text.length < 20) {
23
- modes.push(text);
24
- }
51
+ if (text && text.length < 20) modes.push(text);
25
52
  }
26
53
  break;
27
54
  }
28
55
  }
29
56
 
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
- });
57
+ // ── Step 2: Read current mode from trigger button ─────────────────────
58
+ const modeBtn = findModeTrigger();
35
59
  if (modeBtn) {
36
60
  current = modeBtn.textContent?.trim() || '';
37
61
  }
38
62
 
39
- // modes 비어있으면 기본값
63
+ // ── Fallback: default modes ───────────────────────────────────────────
40
64
  if (modes.length === 0) {
41
65
  modes.push('Planning', 'Fast');
42
66
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Antigravity — set_mode
3
- * Conversation mode 패널에서 Planning/Fast 클릭
2
+ * Antigravity — set_mode [>= 1.107.0]
3
+ * mode trigger: BUTTON with py-1 pl-1 pr-2 opacity-70
4
4
  * ${MODE} → JSON.stringify(modeName)
5
5
  * → { success: boolean, mode?: string }
6
6
  */
@@ -8,23 +8,56 @@
8
8
  try {
9
9
  const target = ${MODE};
10
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 });
11
+ // ── Helper: find mode button ──────────────────────────────────────────
12
+ function findModeBtn() {
13
+ return [...document.querySelectorAll('button')].find(b => {
14
+ const cls = b.className || '';
15
+ return cls.includes('py-1') &&
16
+ cls.includes('pl-1') &&
17
+ cls.includes('pr-2') &&
18
+ cls.includes('opacity-70') &&
19
+ b.offsetWidth > 0;
20
+ }) || null;
21
+ }
22
+
23
+ // ── Helper: click item in open "Conversation mode" panel ─────────────
24
+ function clickModeItem(targetName) {
25
+ const headers = document.querySelectorAll('.text-xs.px-2.pb-1.opacity-80');
26
+ for (const header of headers) {
27
+ if (header.textContent?.trim() === 'Conversation mode') {
28
+ const parent = header.parentElement;
29
+ if (!parent) continue;
30
+ for (const item of parent.querySelectorAll('.font-medium')) {
31
+ const text = item.textContent?.trim();
32
+ if (text && text.toLowerCase() === targetName.toLowerCase()) {
33
+ item.click();
34
+ return text;
35
+ }
24
36
  }
37
+ break;
25
38
  }
26
- break;
27
39
  }
40
+ return null;
41
+ }
42
+
43
+ // ── Step 1: Panel already open — direct click ─────────────────────────
44
+ const direct = clickModeItem(target);
45
+ if (direct) {
46
+ await new Promise(r => setTimeout(r, 300));
47
+ return JSON.stringify({ success: true, mode: direct });
48
+ }
49
+
50
+ // ── Step 2: Open panel via mode button, then click ───────────────────
51
+ const modeBtn = findModeBtn();
52
+ if (modeBtn) {
53
+ modeBtn.click();
54
+ await new Promise(r => setTimeout(r, 400));
55
+
56
+ const hit = clickModeItem(target);
57
+ if (hit) {
58
+ return JSON.stringify({ success: true, mode: hit });
59
+ }
60
+ modeBtn.click(); // Close if not found
28
61
  }
29
62
 
30
63
  return JSON.stringify({ success: false, error: 'mode not found: ' + target });
@@ -1,6 +1,12 @@
1
1
  /**
2
2
  * Antigravity — set_model
3
- * antigravity-agent-side-panel 모델 드롭다운에서 모델 선택
3
+ * 모델 드롭다운에서 모델 선택
4
+ *
5
+ * Version compatibility:
6
+ * v0 (old): .flex.min-w-0.max-w-full.cursor-pointer.items-center trigger
7
+ * v1 (new): Tailwind arbitrary values → partial matching required
8
+ * Both: dropdown items .px-2.py-1.flex.items-center.justify-between.cursor-pointer
9
+ *
4
10
  * ${MODEL} → JSON.stringify(modelName)
5
11
  * → { success: boolean, model?: string }
6
12
  */
@@ -8,35 +14,54 @@
8
14
  try {
9
15
  const target = ${MODEL};
10
16
 
11
- // 1. 모델 드롭다운이 열려 있는 경우 직접 선택
12
- const items = document.querySelectorAll('.px-2.py-1.flex.items-center.justify-between.cursor-pointer');
13
- for (const item of items) {
14
- const label = item.querySelector('.text-xs.font-medium');
15
- const text = (label || item).textContent?.trim();
16
- if (text && (text === target || text.toLowerCase().includes(target.toLowerCase()))) {
17
- item.click();
18
- await new Promise(r => setTimeout(r, 200));
19
- return JSON.stringify({ success: true, model: text });
17
+ // ── Helper: find model trigger button (v0 + v1 compat) ───────────────
18
+ function findModelTrigger() {
19
+ // v0: exact selector
20
+ const v0 = document.querySelector('.flex.min-w-0.max-w-full.cursor-pointer.items-center');
21
+ if (v0 && v0.offsetWidth > 0) return v0;
22
+ // v1: partial matching
23
+ return [...document.querySelectorAll('div, button')].find(e => {
24
+ const cls = e.className || '';
25
+ return cls.includes('min-w-0') &&
26
+ cls.includes('max-w-full') &&
27
+ cls.includes('cursor-pointer') &&
28
+ cls.includes('items-center') &&
29
+ e.offsetWidth > 0;
30
+ }) || null;
31
+ }
32
+
33
+ // ── Helper: find and click a model from open dropdown ────────────────
34
+ function clickModelItem(targetName) {
35
+ const items = document.querySelectorAll('.px-2.py-1.flex.items-center.justify-between.cursor-pointer');
36
+ for (const item of items) {
37
+ const label = item.querySelector('.text-xs.font-medium');
38
+ const text = (label || item).textContent?.trim();
39
+ if (text && (text === targetName || text.toLowerCase().includes(targetName.toLowerCase()))) {
40
+ item.click();
41
+ return text;
42
+ }
20
43
  }
44
+ return null;
21
45
  }
22
46
 
23
- // 2. 드롭다운이 닫혀 있으면 트리거 버튼 클릭해서 열기
24
- const trigger = document.querySelector('.flex.min-w-0.max-w-full.cursor-pointer.items-center');
47
+ // ── Step 1: Dropdown already open try direct click ─────────────────
48
+ const directHit = clickModelItem(target);
49
+ if (directHit) {
50
+ await new Promise(r => setTimeout(r, 200));
51
+ return JSON.stringify({ success: true, model: directHit });
52
+ }
53
+
54
+ // ── Step 2: Dropdown closed — open trigger, then select ──────────────
55
+ const trigger = findModelTrigger();
25
56
  if (trigger) {
26
57
  trigger.click();
27
58
  await new Promise(r => setTimeout(r, 400));
28
59
 
29
- // 다시 항목 탐색
30
- const newItems = document.querySelectorAll('.px-2.py-1.flex.items-center.justify-between.cursor-pointer');
31
- for (const item of newItems) {
32
- const label = item.querySelector('.text-xs.font-medium');
33
- const text = (label || item).textContent?.trim();
34
- if (text && (text === target || text.toLowerCase().includes(target.toLowerCase()))) {
35
- item.click();
36
- return JSON.stringify({ success: true, model: text });
37
- }
60
+ const hit = clickModelItem(target);
61
+ if (hit) {
62
+ return JSON.stringify({ success: true, model: hit });
38
63
  }
39
- // 찾으면 드롭다운 닫기
64
+ // Close dropdown if target not found
40
65
  trigger.click();
41
66
  }
42
67