antigravity-mobile-proxy 0.1.6 → 0.1.7
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/.next/standalone/.next/app-path-routes-manifest.json +2 -0
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/routes-manifest.json +12 -0
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page.js +5 -6
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/artifacts/active/route.js +5 -1
- package/.next/standalone/.next/server/app/api/v1/artifacts/active/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/changes/active/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route.js +11 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/v1/changes/active/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js +7 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/v1/changes/diff/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/v1/chat/stream/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/chat/stream/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/windows/cdp-start/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/windows/cdp-status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/windows/close/route.js +1 -1
- package/.next/standalone/.next/server/app/api/v1/windows/close/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/windows/open/route.js +2 -2
- package/.next/standalone/.next/server/app/api/v1/windows/open/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/debug/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/debug/page.js +5 -5
- package/.next/standalone/.next/server/app/debug/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/debug/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page.js +5 -5
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +2 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__26662154._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__53c4f34d._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__851f6b5a._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__94275f7f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__9a1969e6._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c34d50c8._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c696771d._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d13bbe3c._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d172e6aa._.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_v1_changes_active_route_actions_1bb9fc18.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_v1_changes_diff_route_actions_65d9ee16.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__d080cb3d._.js → [root-of-the-server]__012405ac._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__a457c799._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__f62d412e._.js → [root-of-the-server]__b9356576._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__52af585c._.js → [root-of-the-server]__ce78239f._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__ae6d24d9._.js → [root-of-the-server]__f47dc36d._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_524b2348._.js → _657ecbe9._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{_fe4475aa._.js → _939145a4._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/app_layout_tsx_271801d7._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_not-found_tsx_ef35050a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/components_chat-container_tsx_fcbc457f._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_ece394eb.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_39f173ba.js → node_modules_next_dist_esm_build_templates_app-page_7f45f9bf.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_f183c70b._.js → node_modules_next_dist_f21d913a._.js} +2 -2
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/500.html +2 -2
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/app/api/v1/artifacts/active/[filename]/route.ts +73 -23
- package/.next/standalone/app/api/v1/artifacts/active/route.ts +103 -52
- package/.next/standalone/app/api/v1/changes/active/route.ts +27 -0
- package/.next/standalone/app/api/v1/changes/diff/route.ts +119 -0
- package/.next/standalone/app/globals.css +424 -0
- package/.next/standalone/app/layout.tsx +3 -3
- package/.next/standalone/app/not-found.tsx +14 -23
- package/.next/standalone/components/artifact-panel.tsx +57 -13
- package/.next/standalone/components/changes-panel.tsx +178 -0
- package/.next/standalone/components/chat-container.tsx +44 -3
- package/.next/standalone/components/chat-input.tsx +44 -0
- package/.next/standalone/components/header.tsx +1 -13
- package/.next/standalone/hooks/use-changes.ts +61 -0
- package/.next/standalone/hooks/use-chat.ts +21 -3
- package/.next/standalone/hooks/use-conversations.ts +19 -11
- package/.next/standalone/lib/scraper/agent-state.ts +89 -54
- package/.next/standalone/lib/scraper/chat-history.ts +215 -85
- package/.next/standalone/lib/scraper/ide-artifacts.ts +212 -0
- package/.next/standalone/lib/scraper/ide-changes.ts +172 -0
- package/.next/standalone/lib/types.ts +11 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +2 -2
- package/.next/standalone/scripts/check-send-button.js +126 -0
- package/.next/standalone/scripts/find-send-btn.js +65 -0
- package/.next/standalone/tsconfig.tsbuildinfo +1 -1
- package/.next/static/chunks/09dc2aa5c698c324.css +1 -0
- package/.next/static/chunks/{f7c83373e6561461.js → b9a0fabf54a78ef2.js} +1 -1
- package/.next/static/chunks/e2ccf5908cad5a88.js +5 -0
- package/.next/static/chunks/f7cc8fe5822bbc01.js +1 -0
- package/.next/static/chunks/{turbopack-3f34081d758747ed.js → turbopack-7b5dc393c5d3964b.js} +1 -1
- package/package.json +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__151eca3a._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ec32b318._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__f77eb371._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_9170b7a0._.js +0 -3
- package/.next/standalone/app/global-error.tsx +0 -42
- package/.next/static/chunks/2317ab948a7d90a4.js +0 -5
- package/.next/static/chunks/2d277a81099566c3.js +0 -1
- package/.next/static/chunks/ad1121f40e497811.css +0 -1
- package/.next/static/chunks/d5d4abede4bc89fd.js +0 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrapes the artifact list directly from the Antigravity IDE's conversation panel.
|
|
3
|
+
*
|
|
4
|
+
* The IDE maintains a "tooltip-artifacts" toggle button in the input toolbar.
|
|
5
|
+
* Clicking it reveals a section headed "Artifacts (N Files for Conversation)"
|
|
6
|
+
* with rows showing artifact names and last-updated timestamps.
|
|
7
|
+
*
|
|
8
|
+
* By scraping this section we get the EXACT artifacts for the current
|
|
9
|
+
* conversation — no more cross-conversation mixing.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ProxyContext } from '../types';
|
|
13
|
+
import { logger } from '../logger';
|
|
14
|
+
|
|
15
|
+
export interface IdeArtifact {
|
|
16
|
+
/** Display name, e.g. "task.md" or "Pricing Blue Cards" */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Raw timestamp text from the IDE, e.g. "Mar 10 11:21 PM" */
|
|
19
|
+
lastUpdated: string | null;
|
|
20
|
+
/** Whether this looks like a file (has an extension) */
|
|
21
|
+
isFile: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface IdeArtifactResult {
|
|
25
|
+
/** Total count stated in the section header */
|
|
26
|
+
totalCount: number;
|
|
27
|
+
/** Scraped artifact entries */
|
|
28
|
+
artifacts: IdeArtifact[];
|
|
29
|
+
/** The conversation title at the time of scraping */
|
|
30
|
+
conversationTitle: string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get the list of artifacts from the IDE's conversation panel.
|
|
35
|
+
*
|
|
36
|
+
* Strategy:
|
|
37
|
+
* 1. Check whether the artifact section is already visible.
|
|
38
|
+
* 2. If not, click `[data-tooltip-id="tooltip-artifacts"]` to toggle it open.
|
|
39
|
+
* 3. Parse the rows to extract artifact names and timestamps.
|
|
40
|
+
* 4. Close the section again (toggle) to leave the IDE clean.
|
|
41
|
+
*/
|
|
42
|
+
export async function getIdeArtifacts(ctx: ProxyContext): Promise<IdeArtifactResult> {
|
|
43
|
+
if (!ctx.workbenchPage) {
|
|
44
|
+
logger.info('[IdeArtifacts] No active workbench page.');
|
|
45
|
+
return { totalCount: 0, artifacts: [], conversationTitle: null };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const result = await ctx.workbenchPage.evaluate(async () => {
|
|
50
|
+
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
51
|
+
if (!panel) return { error: 'No agent panel found' };
|
|
52
|
+
|
|
53
|
+
// Get current conversation title
|
|
54
|
+
const titleEl = panel.querySelector('span.font-semibold.text-ide-text-color');
|
|
55
|
+
const conversationTitle = titleEl?.textContent?.trim() || null;
|
|
56
|
+
|
|
57
|
+
// Check whether the artifact section is already open
|
|
58
|
+
const findArtifactHeader = (): Element | null => {
|
|
59
|
+
for (const el of panel.querySelectorAll('*')) {
|
|
60
|
+
const t = (el.textContent || '').trim();
|
|
61
|
+
if (
|
|
62
|
+
t.startsWith('Artifacts (') &&
|
|
63
|
+
t.includes('Files') &&
|
|
64
|
+
el.children.length <= 3
|
|
65
|
+
) {
|
|
66
|
+
return el;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
let header = findArtifactHeader();
|
|
73
|
+
let didOpen = false;
|
|
74
|
+
|
|
75
|
+
if (!header) {
|
|
76
|
+
// Click the artifact button to open the section
|
|
77
|
+
const btn = panel.querySelector('[data-tooltip-id="tooltip-artifacts"]');
|
|
78
|
+
if (!btn) return { error: 'No artifact button (tooltip-artifacts) found' };
|
|
79
|
+
|
|
80
|
+
(btn as HTMLElement).click();
|
|
81
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
82
|
+
header = findArtifactHeader();
|
|
83
|
+
didOpen = true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!header) {
|
|
87
|
+
return { error: 'Artifact section did not appear after clicking' };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Parse total count from header like "Artifacts (15 Files for Conversation)"
|
|
91
|
+
const headerText = header.textContent?.trim() || '';
|
|
92
|
+
const countMatch = headerText.match(/Artifacts\s*\((\d+)\s*Files?/);
|
|
93
|
+
const totalCount = countMatch ? parseInt(countMatch[1], 10) : 0;
|
|
94
|
+
|
|
95
|
+
// Find the section container — walk up until we find the container
|
|
96
|
+
// that holds the header AND the file rows
|
|
97
|
+
let section = header as HTMLElement;
|
|
98
|
+
for (let i = 0; i < 8; i++) {
|
|
99
|
+
if (!section.parentElement) break;
|
|
100
|
+
section = section.parentElement;
|
|
101
|
+
const text = section.textContent || '';
|
|
102
|
+
if (text.includes('Artifact Name') && text.includes('Last Updated')) {
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Extract artifact rows — each row has a name and a date
|
|
108
|
+
// Strategy: find all visible row-like flex containers within the section
|
|
109
|
+
const rows = section.querySelectorAll(
|
|
110
|
+
'.flex.w-full.flex-row.items-center.justify-between'
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const artifacts: Array<{ name: string; lastUpdated: string | null }> = [];
|
|
114
|
+
|
|
115
|
+
for (const row of rows) {
|
|
116
|
+
const cells = row.children;
|
|
117
|
+
if (cells.length < 2) continue;
|
|
118
|
+
|
|
119
|
+
const nameText = (cells[0].textContent || '').trim();
|
|
120
|
+
const dateText = (cells[1].textContent || '').trim();
|
|
121
|
+
|
|
122
|
+
// Skip the header row itself
|
|
123
|
+
if (nameText === 'Artifact Name' || !nameText) continue;
|
|
124
|
+
|
|
125
|
+
artifacts.push({ name: nameText, lastUpdated: dateText || null });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Fallback: if no rows found via flex layout, try extracting from text nodes
|
|
129
|
+
if (artifacts.length === 0) {
|
|
130
|
+
// Use TreeWalker to find file-name and date text nodes
|
|
131
|
+
const walker = document.createTreeWalker(section, NodeFilter.SHOW_TEXT);
|
|
132
|
+
const allTexts: string[] = [];
|
|
133
|
+
while (walker.nextNode()) {
|
|
134
|
+
const t = walker.currentNode.textContent?.trim();
|
|
135
|
+
if (t && t.length > 0) allTexts.push(t);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Parse pairs: artifact name followed by date
|
|
139
|
+
for (let i = 0; i < allTexts.length; i++) {
|
|
140
|
+
const text = allTexts[i];
|
|
141
|
+
// Skip header/control texts
|
|
142
|
+
if (
|
|
143
|
+
text === 'Artifact Name' ||
|
|
144
|
+
text === 'Last Updated' ||
|
|
145
|
+
text.startsWith('Artifacts (') ||
|
|
146
|
+
text === 'Review Changes'
|
|
147
|
+
) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Check if this has a date pattern like "Mar 10 11:21 PM"
|
|
152
|
+
const dateMatch = text.match(
|
|
153
|
+
/\(([A-Z][a-z]{2}\s+\d{1,2}\s+\d{1,2}:\d{2}\s*(?:AM|PM)?)\)$/
|
|
154
|
+
);
|
|
155
|
+
if (dateMatch) {
|
|
156
|
+
const name = text.replace(dateMatch[0], '').trim();
|
|
157
|
+
artifacts.push({ name, lastUpdated: dateMatch[1] });
|
|
158
|
+
} else if (
|
|
159
|
+
text.match(
|
|
160
|
+
/\.(md|json|txt|ts|tsx|js|jsx|css|html|yaml|yml|py|sh)$/
|
|
161
|
+
)
|
|
162
|
+
) {
|
|
163
|
+
// It's a file name — check if next text is a date
|
|
164
|
+
const nextText = allTexts[i + 1] || '';
|
|
165
|
+
const nextDate = nextText.match(
|
|
166
|
+
/^[A-Z][a-z]{2}\s+\d{1,2}\s+\d{1,2}:\d{2}/
|
|
167
|
+
);
|
|
168
|
+
artifacts.push({
|
|
169
|
+
name: text,
|
|
170
|
+
lastUpdated: nextDate ? nextText : null,
|
|
171
|
+
});
|
|
172
|
+
if (nextDate) i++; // Skip the date we already consumed
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Close the section if we opened it (toggle back)
|
|
178
|
+
if (didOpen) {
|
|
179
|
+
const btn = panel.querySelector('[data-tooltip-id="tooltip-artifacts"]');
|
|
180
|
+
if (btn) (btn as HTMLElement).click();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return { conversationTitle, totalCount, artifacts };
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (result && 'error' in result) {
|
|
187
|
+
logger.error(`[IdeArtifacts] ${result.error}`);
|
|
188
|
+
return { totalCount: 0, artifacts: [], conversationTitle: null };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const artifacts: IdeArtifact[] = (result?.artifacts || []).map(
|
|
192
|
+
(a: { name: string; lastUpdated: string | null }) => ({
|
|
193
|
+
name: a.name,
|
|
194
|
+
lastUpdated: a.lastUpdated,
|
|
195
|
+
isFile: /\.\w{1,5}$/.test(a.name),
|
|
196
|
+
})
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
logger.info(
|
|
200
|
+
`[IdeArtifacts] Scraped ${artifacts.length}/${result?.totalCount || 0} artifacts for "${result?.conversationTitle}"`
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
totalCount: result?.totalCount || 0,
|
|
205
|
+
artifacts,
|
|
206
|
+
conversationTitle: result?.conversationTitle || null,
|
|
207
|
+
};
|
|
208
|
+
} catch (err: any) {
|
|
209
|
+
logger.error(`[IdeArtifacts] Error scraping: ${err.message}`);
|
|
210
|
+
return { totalCount: 0, artifacts: [], conversationTitle: null };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IDE Changes Overview Scraper
|
|
3
|
+
*
|
|
4
|
+
* Scrapes the "Changes Overview" section from the IDE's agent panel.
|
|
5
|
+
* This section lists files that have been modified/created/deleted
|
|
6
|
+
* during the current conversation, with +additions/-deletions counts.
|
|
7
|
+
*
|
|
8
|
+
* DOM structure (when open):
|
|
9
|
+
* .flex.grow.flex-col.justify-start.gap-8
|
|
10
|
+
* └ div.relative.flex.flex-col.mb-2
|
|
11
|
+
* └ div.px-2 (header bar)
|
|
12
|
+
* └ div (the outline container)
|
|
13
|
+
* ├ span "N Files With Changes"
|
|
14
|
+
* ├ "Reject all" / "Accept all"
|
|
15
|
+
* └ div.pointer-events-none.absolute.bottom-full (dropdown)
|
|
16
|
+
* └ div.flex.flex-col (rows container)
|
|
17
|
+
* └ each row: [+][N][-][M] [filename] [filepath]
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ProxyContext } from '../types';
|
|
21
|
+
|
|
22
|
+
export interface ChangeFile {
|
|
23
|
+
filename: string;
|
|
24
|
+
filepath: string;
|
|
25
|
+
additions: number;
|
|
26
|
+
deletions: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface IdeChangesResult {
|
|
30
|
+
changes: ChangeFile[];
|
|
31
|
+
totalCount: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function getIdeChanges(ctx: ProxyContext): Promise<IdeChangesResult> {
|
|
35
|
+
const page = ctx.workbenchPage;
|
|
36
|
+
if (!page) return { changes: [], totalCount: 0 };
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const result = await page.evaluate(async () => {
|
|
40
|
+
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
41
|
+
if (!panel) return { changes: [] as any[], totalCount: 0, error: 'no panel' };
|
|
42
|
+
|
|
43
|
+
// Find the gap-8 container that holds the changes section
|
|
44
|
+
const gapContainer = panel.querySelector('.flex.grow.flex-col.justify-start.gap-8');
|
|
45
|
+
if (!gapContainer) return { changes: [] as any[], totalCount: 0, error: 'no gap container' };
|
|
46
|
+
|
|
47
|
+
// Check if the changes section is already visible
|
|
48
|
+
let section = Array.from(gapContainer.children).find(c =>
|
|
49
|
+
(c.textContent || '').includes('Files With Changes')
|
|
50
|
+
) as HTMLElement | undefined;
|
|
51
|
+
|
|
52
|
+
let didOpen = false;
|
|
53
|
+
|
|
54
|
+
if (!section) {
|
|
55
|
+
// Toggle the changesOverview button to open it
|
|
56
|
+
const btn = panel.querySelector('[data-tooltip-id="tooltip-changesOverview"]') as HTMLElement;
|
|
57
|
+
if (!btn) return { changes: [] as any[], totalCount: 0, error: 'no changesOverview button' };
|
|
58
|
+
|
|
59
|
+
btn.click();
|
|
60
|
+
// Wait for the section to appear
|
|
61
|
+
for (let i = 0; i < 10; i++) {
|
|
62
|
+
await new Promise(r => setTimeout(r, 300));
|
|
63
|
+
section = Array.from(gapContainer.children).find(c =>
|
|
64
|
+
(c.textContent || '').includes('Files With Changes')
|
|
65
|
+
) as HTMLElement | undefined;
|
|
66
|
+
if (section) break;
|
|
67
|
+
}
|
|
68
|
+
didOpen = true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!section) {
|
|
72
|
+
// Close if we opened but nothing appeared
|
|
73
|
+
if (didOpen) {
|
|
74
|
+
const btn = panel.querySelector('[data-tooltip-id="tooltip-changesOverview"]') as HTMLElement;
|
|
75
|
+
if (btn) btn.click();
|
|
76
|
+
}
|
|
77
|
+
return { changes: [] as any[], totalCount: 0 };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Find the dropdown containing file rows
|
|
81
|
+
const dropdown = section.querySelector(
|
|
82
|
+
'.pointer-events-none.absolute.bottom-full .flex.flex-col'
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const changes: any[] = [];
|
|
86
|
+
|
|
87
|
+
if (dropdown) {
|
|
88
|
+
const rows = Array.from(dropdown.children);
|
|
89
|
+
for (const row of rows) {
|
|
90
|
+
const walker = document.createTreeWalker(row, NodeFilter.SHOW_TEXT);
|
|
91
|
+
const texts: string[] = [];
|
|
92
|
+
while (walker.nextNode()) {
|
|
93
|
+
const t = (walker.currentNode.textContent || '').trim();
|
|
94
|
+
if (t) texts.push(t);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Parse: ["+"] ["N"] ["-"] ["M"] ["filename.ext"] ["path/to/filename.ext"]
|
|
98
|
+
// The + and number may be separate text nodes
|
|
99
|
+
let additions = 0, deletions = 0;
|
|
100
|
+
let filename = '', filepath = '';
|
|
101
|
+
|
|
102
|
+
let plusSeen = false, minusSeen = false;
|
|
103
|
+
for (let i = 0; i < texts.length; i++) {
|
|
104
|
+
const t = texts[i];
|
|
105
|
+
|
|
106
|
+
// Handle "+N" as single token
|
|
107
|
+
const addSingle = t.match(/^\+(\d+)$/);
|
|
108
|
+
if (addSingle) { additions = parseInt(addSingle[1]); continue; }
|
|
109
|
+
|
|
110
|
+
// Handle "-N" as single token
|
|
111
|
+
const delSingle = t.match(/^-(\d+)$/);
|
|
112
|
+
if (delSingle) { deletions = parseInt(delSingle[1]); continue; }
|
|
113
|
+
|
|
114
|
+
// Handle "+" and number as separate tokens
|
|
115
|
+
if (t === '+') { plusSeen = true; continue; }
|
|
116
|
+
if (t === '-') { minusSeen = true; continue; }
|
|
117
|
+
|
|
118
|
+
// Number after +/-
|
|
119
|
+
if (plusSeen && t.match(/^\d+$/)) {
|
|
120
|
+
additions = parseInt(t);
|
|
121
|
+
plusSeen = false;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (minusSeen && t.match(/^\d+$/)) {
|
|
125
|
+
deletions = parseInt(t);
|
|
126
|
+
minusSeen = false;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// File name (has extension)
|
|
131
|
+
if (!filename && t.match(/\.\w{1,5}$/) && t.length < 100) {
|
|
132
|
+
filename = t;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// File path (contains /)
|
|
137
|
+
if (filename && !filepath && t.includes('/')) {
|
|
138
|
+
filepath = t;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (filename) {
|
|
143
|
+
changes.push({ filename, filepath: filepath || filename, additions, deletions });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Get total count from header
|
|
149
|
+
const headerSpan = section.querySelector('span');
|
|
150
|
+
const headerText = headerSpan ? (headerSpan.textContent || '').trim() : '';
|
|
151
|
+
const totalMatch = headerText.match(/^(\d+)/);
|
|
152
|
+
const totalCount = totalMatch ? parseInt(totalMatch[1]) : changes.length;
|
|
153
|
+
|
|
154
|
+
// Close the section if we opened it
|
|
155
|
+
if (didOpen) {
|
|
156
|
+
const btn = panel.querySelector('[data-tooltip-id="tooltip-changesOverview"]') as HTMLElement;
|
|
157
|
+
if (btn) btn.click();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { changes, totalCount };
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (result.changes.length > 0) {
|
|
164
|
+
console.log(`[INFO] [IdeChanges] Scraped ${result.changes.length}/${result.totalCount} file changes`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return result;
|
|
168
|
+
} catch (err: any) {
|
|
169
|
+
console.error(`[ERROR] [IdeChanges] ${err.message}`);
|
|
170
|
+
return { changes: [], totalCount: 0 };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -132,6 +132,17 @@ export interface ArtifactFile {
|
|
|
132
132
|
name: string;
|
|
133
133
|
size: number;
|
|
134
134
|
mtime: string;
|
|
135
|
+
/** Whether this is a file with an extension (can be opened/viewed) or a named IDE artifact */
|
|
136
|
+
isFile?: boolean;
|
|
137
|
+
/** Where this artifact was detected from */
|
|
138
|
+
source?: 'ide' | 'brain' | 'none';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface ChangeFile {
|
|
142
|
+
filename: string;
|
|
143
|
+
filepath: string;
|
|
144
|
+
additions: number;
|
|
145
|
+
deletions: number;
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
export interface ChatMessage {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "antigravity-mobile-proxy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "antigravity-mobile-proxy",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.7",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "antigravity-mobile-proxy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "A web chat proxy for Antigravity IDE with ngrok OAuth tunnel support",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antigravity",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
24
|
"dev": "next dev -p 5555",
|
|
25
|
-
"build": "next build",
|
|
25
|
+
"build": "NODE_ENV=production next build",
|
|
26
26
|
"postbuild": "node scripts/patch-next.js",
|
|
27
27
|
"start": "next start -p 5555",
|
|
28
28
|
"tunnel": "node bin/cli.js",
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostic: Check the send/stop button state in both idle and running states.
|
|
3
|
+
* Run: node scripts/check-send-button.js
|
|
4
|
+
*/
|
|
5
|
+
const puppeteer = require('puppeteer-core');
|
|
6
|
+
const CDP_PORT = process.env.CDP_PORT || '9223';
|
|
7
|
+
|
|
8
|
+
async function run() {
|
|
9
|
+
const browser = await puppeteer.connect({ browserURL: `http://127.0.0.1:${CDP_PORT}` });
|
|
10
|
+
const pages = await browser.pages();
|
|
11
|
+
const page = pages.find(p => p.url().includes('workbench.html'));
|
|
12
|
+
|
|
13
|
+
if (!page) {
|
|
14
|
+
console.log('No workbench page found.');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const result = await page.evaluate(() => {
|
|
19
|
+
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
20
|
+
if (!panel) return { error: 'No panel' };
|
|
21
|
+
|
|
22
|
+
const inputArea = document.querySelector('#antigravity\\.agentSidePanelInputBox') ||
|
|
23
|
+
panel.querySelector('[id*="InputBox"]');
|
|
24
|
+
if (!inputArea) return { error: 'No input box' };
|
|
25
|
+
|
|
26
|
+
const wrapper = inputArea.closest('.flex') || inputArea.parentElement?.parentElement || inputArea.parentElement;
|
|
27
|
+
if (!wrapper) return { error: 'No wrapper' };
|
|
28
|
+
|
|
29
|
+
const inputBtns = wrapper.querySelectorAll('button');
|
|
30
|
+
const buttons = [];
|
|
31
|
+
|
|
32
|
+
for (const btn of inputBtns) {
|
|
33
|
+
const html = btn.innerHTML || '';
|
|
34
|
+
const ariaLabel = (btn.getAttribute('aria-label') || '');
|
|
35
|
+
const text = (btn.textContent || '').trim();
|
|
36
|
+
const tooltipId = (btn.getAttribute('data-tooltip-id') || '');
|
|
37
|
+
const cls = (btn.className || '');
|
|
38
|
+
|
|
39
|
+
// Check what icons are present
|
|
40
|
+
const svgClasses = [...btn.querySelectorAll('svg')].map(s => s.getAttribute('class') || '');
|
|
41
|
+
|
|
42
|
+
buttons.push({
|
|
43
|
+
text,
|
|
44
|
+
ariaLabel,
|
|
45
|
+
tooltipId,
|
|
46
|
+
cls: cls.substring(0, 200),
|
|
47
|
+
svgClasses,
|
|
48
|
+
htmlSnippet: html.substring(0, 300),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Also check the spinner state
|
|
53
|
+
const spinners = panel.querySelectorAll('.animate-spin');
|
|
54
|
+
let visibleSpinners = 0;
|
|
55
|
+
for (const s of spinners) {
|
|
56
|
+
let el = s, hidden = false;
|
|
57
|
+
while (el) {
|
|
58
|
+
const c = (el.getAttribute && el.getAttribute('class')) || '';
|
|
59
|
+
if (c.includes('invisible') || c.includes('opacity-0')) { hidden = true; break; }
|
|
60
|
+
el = el.parentElement;
|
|
61
|
+
}
|
|
62
|
+
if (!hidden) visibleSpinners++;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
totalButtons: inputBtns.length,
|
|
67
|
+
buttons,
|
|
68
|
+
visibleSpinners,
|
|
69
|
+
wrapperTag: wrapper.tagName,
|
|
70
|
+
wrapperCls: (wrapper.className || '').substring(0, 200),
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
console.log(JSON.stringify(result, null, 2));
|
|
75
|
+
|
|
76
|
+
// Analyze
|
|
77
|
+
if (result.buttons) {
|
|
78
|
+
console.log('\n--- ANALYSIS ---');
|
|
79
|
+
let hasStop = false, hasSend = false;
|
|
80
|
+
for (const btn of result.buttons) {
|
|
81
|
+
const tooltipLower = btn.tooltipId.toLowerCase();
|
|
82
|
+
const ariaLower = btn.ariaLabel.toLowerCase();
|
|
83
|
+
const textLower = btn.text.toLowerCase();
|
|
84
|
+
const html = btn.htmlSnippet.toLowerCase();
|
|
85
|
+
|
|
86
|
+
const isStopIcon = html.match(/lucide-square(?:[^a-z0-9-]|$)/i) ||
|
|
87
|
+
html.includes('lucide-circle-stop') ||
|
|
88
|
+
html.includes('lucide-octagon');
|
|
89
|
+
|
|
90
|
+
const isStop = isStopIcon ||
|
|
91
|
+
ariaLower.includes('stop') ||
|
|
92
|
+
ariaLower.includes('cancel') ||
|
|
93
|
+
textLower === 'stop' ||
|
|
94
|
+
tooltipLower.includes('stop');
|
|
95
|
+
|
|
96
|
+
const isSend = html.includes('lucide-send') ||
|
|
97
|
+
html.includes('lucide-arrow-up') ||
|
|
98
|
+
html.includes('lucide-arrow-right') ||
|
|
99
|
+
html.includes('codicon-send') ||
|
|
100
|
+
html.includes('lucide-corner-down-left') ||
|
|
101
|
+
ariaLower.includes('send') ||
|
|
102
|
+
ariaLower.includes('submit') ||
|
|
103
|
+
textLower === 'send' ||
|
|
104
|
+
tooltipLower.includes('send');
|
|
105
|
+
|
|
106
|
+
if (isStop) hasStop = true;
|
|
107
|
+
if (isSend) hasSend = true;
|
|
108
|
+
|
|
109
|
+
console.log(` Button "${btn.text || btn.ariaLabel || btn.tooltipId}": stop=${isStop}, send=${isSend}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`\n hasStop=${hasStop}, hasSend=${hasSend}`);
|
|
113
|
+
if (hasStop) {
|
|
114
|
+
console.log(' => isRunning = TRUE (stop found)');
|
|
115
|
+
} else if (hasSend) {
|
|
116
|
+
console.log(' => isRunning = FALSE (send found, no stop)');
|
|
117
|
+
} else {
|
|
118
|
+
console.log(' => isRunning = TRUE (no send or stop — send button removed from DOM)');
|
|
119
|
+
}
|
|
120
|
+
console.log(` Visible spinners: ${result.visibleSpinners}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
await browser.disconnect();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
run().catch(e => { console.error('Error:', e.message); process.exit(1); });
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find exactly where the send button lives relative to the input box.
|
|
3
|
+
*/
|
|
4
|
+
const puppeteer = require('puppeteer-core');
|
|
5
|
+
|
|
6
|
+
async function run() {
|
|
7
|
+
const browser = await puppeteer.connect({ browserURL: 'http://127.0.0.1:9223' });
|
|
8
|
+
const pages = await browser.pages();
|
|
9
|
+
const page = pages.find(p => p.url().includes('workbench.html'));
|
|
10
|
+
if (!page) { console.log('No workbench'); process.exit(1); }
|
|
11
|
+
|
|
12
|
+
const result = await page.evaluate(() => {
|
|
13
|
+
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
14
|
+
if (!panel) return { error: 'No panel' };
|
|
15
|
+
|
|
16
|
+
// Find the send/cancel button by tooltip
|
|
17
|
+
const sendBtn = panel.querySelector('[data-tooltip-id*="send"]') ||
|
|
18
|
+
panel.querySelector('[data-tooltip-id*="cancel"]');
|
|
19
|
+
|
|
20
|
+
// Find all buttons with send-like tooltips
|
|
21
|
+
const sendBtns = [...panel.querySelectorAll('[data-tooltip-id]')]
|
|
22
|
+
.filter(el => {
|
|
23
|
+
const tip = el.getAttribute('data-tooltip-id') || '';
|
|
24
|
+
return tip.includes('send') || tip.includes('cancel');
|
|
25
|
+
})
|
|
26
|
+
.map(el => ({
|
|
27
|
+
tag: el.tagName,
|
|
28
|
+
tooltipId: el.getAttribute('data-tooltip-id'),
|
|
29
|
+
innerHTML: el.innerHTML?.substring(0, 300) || '',
|
|
30
|
+
cls: (el.className || '').substring(0, 200),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
// The input box
|
|
34
|
+
const inputBox = document.querySelector('#antigravity\\.agentSidePanelInputBox');
|
|
35
|
+
if (!inputBox) return { error: 'No input box', sendBtns };
|
|
36
|
+
|
|
37
|
+
// Current wrapper that `.closest('.flex')` finds
|
|
38
|
+
const currentWrapper = inputBox.closest('.flex');
|
|
39
|
+
|
|
40
|
+
// Walk up and list all ancestors with their button counts
|
|
41
|
+
const ancestors = [];
|
|
42
|
+
let el = inputBox;
|
|
43
|
+
for (let i = 0; i < 10 && el; i++) {
|
|
44
|
+
const btns = el.querySelectorAll('button');
|
|
45
|
+
const sendInside = el.querySelector('[data-tooltip-id*="send"]');
|
|
46
|
+
ancestors.push({
|
|
47
|
+
level: i,
|
|
48
|
+
tag: el.tagName,
|
|
49
|
+
id: el.id || '',
|
|
50
|
+
cls: (el.className || '').substring(0, 150),
|
|
51
|
+
buttonCount: btns.length,
|
|
52
|
+
hasSendBtn: !!sendInside,
|
|
53
|
+
isCurrentWrapper: el === currentWrapper,
|
|
54
|
+
});
|
|
55
|
+
el = el.parentElement;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { sendBtns, ancestors };
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
console.log(JSON.stringify(result, null, 2));
|
|
62
|
+
await browser.disconnect();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
run().catch(e => { console.error(e.message); process.exit(1); });
|