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
|
@@ -1,89 +1,140 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
|
+
import { ensureCdpConnection } from '@/lib/init';
|
|
2
3
|
import ctx from '@/lib/context';
|
|
3
4
|
import fs from 'fs';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
import os from 'os';
|
|
7
|
+
import { getIdeArtifacts } from '@/lib/scraper/ide-artifacts';
|
|
6
8
|
|
|
7
9
|
export const dynamic = 'force-dynamic';
|
|
8
10
|
|
|
9
11
|
const BRAIN_DIR = path.join(os.homedir(), '.gemini', 'antigravity', 'brain');
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
+
* Scan a brain conversation directory recursively for artifact files.
|
|
15
|
+
* Returns .md files, skipping hidden directories like .system_generated.
|
|
14
16
|
*/
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const entries = fs.readdirSync(BRAIN_DIR, { withFileTypes: true });
|
|
19
|
-
let latest: { id: string; mtime: number } | null = null;
|
|
17
|
+
function scanBrainDir(convId: string) {
|
|
18
|
+
const convDir = path.join(BRAIN_DIR, convId);
|
|
19
|
+
if (!fs.existsSync(convDir)) return [];
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
23
|
-
const dirPath = path.join(BRAIN_DIR, entry.name);
|
|
24
|
-
const stat = fs.statSync(dirPath);
|
|
25
|
-
if (!latest || stat.mtimeMs > latest.mtime) {
|
|
26
|
-
latest = { id: entry.name, mtime: stat.mtimeMs };
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return latest?.id ?? null;
|
|
30
|
-
} catch {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* GET /api/v1/artifacts/active — list files in the active conversation directory.
|
|
37
|
-
* Auto-detects active conversation on first load if none is set.
|
|
38
|
-
*/
|
|
39
|
-
export async function GET() {
|
|
21
|
+
const files: any[] = [];
|
|
40
22
|
try {
|
|
41
|
-
// Auto-detect on first load if no conversation has been selected yet
|
|
42
|
-
if (!ctx.activeConversationId && !ctx.activeTitle) {
|
|
43
|
-
ctx.activeConversationId = autoDetectActiveConversation();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!ctx.activeConversationId) {
|
|
47
|
-
return NextResponse.json({ files: [] }); // Graceful empty state
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const convDir = path.join(BRAIN_DIR, ctx.activeConversationId);
|
|
51
|
-
if (!fs.existsSync(convDir)) {
|
|
52
|
-
return NextResponse.json({ files: [] });
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const files: any[] = [];
|
|
56
23
|
const entries = fs.readdirSync(convDir, { recursive: true, withFileTypes: true });
|
|
57
|
-
|
|
58
24
|
for (const d of entries) {
|
|
59
25
|
if (!d.isFile() || !d.name.endsWith('.md')) continue;
|
|
60
|
-
|
|
61
|
-
// Node 20+ uses parentPath, older Node might use path. Fallback to convDir just in case
|
|
62
|
-
// @ts-ignore
|
|
26
|
+
// @ts-ignore - Node 20+ parentPath
|
|
63
27
|
const parentDir = d.parentPath || d.path || convDir;
|
|
64
28
|
const fullPath = path.join(parentDir, d.name);
|
|
65
29
|
const relPath = path.relative(convDir, fullPath).replace(/\\/g, '/');
|
|
66
|
-
|
|
30
|
+
|
|
67
31
|
// Skip hidden folders like .system_generated
|
|
68
32
|
if (relPath.split('/').some(p => p.startsWith('.'))) continue;
|
|
69
|
-
|
|
33
|
+
|
|
70
34
|
try {
|
|
71
35
|
const stats = fs.statSync(fullPath);
|
|
72
36
|
files.push({
|
|
73
|
-
name: relPath,
|
|
37
|
+
name: relPath,
|
|
74
38
|
size: stats.size,
|
|
75
39
|
mtime: stats.mtime.toISOString(),
|
|
76
40
|
});
|
|
41
|
+
} catch { continue; }
|
|
42
|
+
}
|
|
43
|
+
files.sort((a, b) => new Date(b.mtime).getTime() - new Date(a.mtime).getTime());
|
|
44
|
+
} catch { /* ignore */ }
|
|
45
|
+
return files;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* GET /api/v1/artifacts/active — list artifacts for the active conversation.
|
|
50
|
+
*
|
|
51
|
+
* PRIMARY: Scrapes the IDE's artifact panel for the exact list of artifacts
|
|
52
|
+
* the IDE knows about for the current conversation.
|
|
53
|
+
*
|
|
54
|
+
* FALLBACK: If CDP isn't available or scraping fails, falls back to scanning
|
|
55
|
+
* the brain directory for the active conversation.
|
|
56
|
+
*/
|
|
57
|
+
export async function GET() {
|
|
58
|
+
try {
|
|
59
|
+
await ensureCdpConnection();
|
|
60
|
+
|
|
61
|
+
// 1. Try the IDE scraper first — this gives us the correct conversation's artifacts
|
|
62
|
+
if (ctx.workbenchPage) {
|
|
63
|
+
try {
|
|
64
|
+
const ideResult = await getIdeArtifacts(ctx);
|
|
65
|
+
|
|
66
|
+
if (ideResult.artifacts.length > 0) {
|
|
67
|
+
// Map IDE artifacts to the response format
|
|
68
|
+
const files = ideResult.artifacts.map((a, idx) => ({
|
|
69
|
+
name: a.name,
|
|
70
|
+
size: 0, // Not available from IDE — will be populated if we match to brain
|
|
71
|
+
mtime: a.lastUpdated || new Date().toISOString(),
|
|
72
|
+
isFile: a.isFile,
|
|
73
|
+
source: 'ide' as const,
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
// Try to enrich with brain directory data if we can match the conversation
|
|
77
|
+
if (ctx.activeConversationId) {
|
|
78
|
+
const brainFiles = scanBrainDir(ctx.activeConversationId);
|
|
79
|
+
for (const f of files) {
|
|
80
|
+
const match = brainFiles.find(
|
|
81
|
+
bf => bf.name === f.name || bf.name.endsWith('/' + f.name) || f.name.endsWith(bf.name)
|
|
82
|
+
);
|
|
83
|
+
if (match) {
|
|
84
|
+
f.size = match.size;
|
|
85
|
+
f.mtime = match.mtime;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return NextResponse.json({
|
|
91
|
+
files,
|
|
92
|
+
source: 'ide',
|
|
93
|
+
conversationTitle: ideResult.conversationTitle,
|
|
94
|
+
totalCount: ideResult.totalCount,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
77
97
|
} catch {
|
|
78
|
-
|
|
98
|
+
// IDE scraping failed — fall through to brain directory
|
|
79
99
|
}
|
|
80
100
|
}
|
|
81
101
|
|
|
82
|
-
//
|
|
83
|
-
|
|
102
|
+
// 2. Fallback: scan brain directory for the active conversation
|
|
103
|
+
if (!ctx.activeConversationId) {
|
|
104
|
+
// Auto-detect from most recently modified conversation dir
|
|
105
|
+
ctx.activeConversationId = autoDetectActiveConversation();
|
|
106
|
+
}
|
|
84
107
|
|
|
85
|
-
|
|
108
|
+
if (!ctx.activeConversationId) {
|
|
109
|
+
return NextResponse.json({ files: [], source: 'none' });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const files = scanBrainDir(ctx.activeConversationId);
|
|
113
|
+
return NextResponse.json({ files, source: 'brain' });
|
|
86
114
|
} catch (err: any) {
|
|
87
115
|
return NextResponse.json({ error: err.message }, { status: 500 });
|
|
88
116
|
}
|
|
89
117
|
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Auto-detect the most recently modified conversation directory.
|
|
121
|
+
*/
|
|
122
|
+
function autoDetectActiveConversation(): string | null {
|
|
123
|
+
try {
|
|
124
|
+
if (!fs.existsSync(BRAIN_DIR)) return null;
|
|
125
|
+
const entries = fs.readdirSync(BRAIN_DIR, { withFileTypes: true });
|
|
126
|
+
let latest: { id: string; mtime: number } | null = null;
|
|
127
|
+
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
130
|
+
const dirPath = path.join(BRAIN_DIR, entry.name);
|
|
131
|
+
const stat = fs.statSync(dirPath);
|
|
132
|
+
if (!latest || stat.mtimeMs > latest.mtime) {
|
|
133
|
+
latest = { id: entry.name, mtime: stat.mtimeMs };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return latest?.id ?? null;
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { ensureCdpConnection } from '@/lib/init';
|
|
3
|
+
import ctx from '@/lib/context';
|
|
4
|
+
import { getIdeChanges } from '@/lib/scraper/ide-changes';
|
|
5
|
+
|
|
6
|
+
export const dynamic = 'force-dynamic';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* GET /api/v1/changes/active — list file changes for the active conversation.
|
|
10
|
+
*
|
|
11
|
+
* Scrapes the IDE's "Changes Overview" section to get the list of files
|
|
12
|
+
* modified/created/deleted in the current conversation with diff stats.
|
|
13
|
+
*/
|
|
14
|
+
export async function GET() {
|
|
15
|
+
try {
|
|
16
|
+
await ensureCdpConnection();
|
|
17
|
+
|
|
18
|
+
if (!ctx.workbenchPage) {
|
|
19
|
+
return NextResponse.json({ changes: [], totalCount: 0, error: 'No CDP connection' });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const result = await getIdeChanges(ctx);
|
|
23
|
+
return NextResponse.json(result);
|
|
24
|
+
} catch (err: any) {
|
|
25
|
+
return NextResponse.json({ error: err.message }, { status: 500 });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* GET /api/v1/changes/diff?filepath=…
|
|
9
|
+
*
|
|
10
|
+
* Returns the git diff for a specific file. Tries multiple strategies:
|
|
11
|
+
* 1. Unstaged working-tree diff: `git diff -- <path>`
|
|
12
|
+
* 2. Staged diff: `git diff --cached -- <path>`
|
|
13
|
+
* 3. Last commit diff: `git diff HEAD~1 HEAD -- <path>`
|
|
14
|
+
* 4. If all empty, returns full file content as "new file"
|
|
15
|
+
*
|
|
16
|
+
* The `filepath` is relative to the workspace root or can be the
|
|
17
|
+
* path as reported by the IDE's Changes Overview (e.g.,
|
|
18
|
+
* "antigravity-chat-proxy/components/artifact-panel.tsx").
|
|
19
|
+
*/
|
|
20
|
+
export async function GET(request: NextRequest) {
|
|
21
|
+
const filepath = request.nextUrl.searchParams.get('filepath');
|
|
22
|
+
if (!filepath) {
|
|
23
|
+
return NextResponse.json({ error: 'Missing filepath parameter' }, { status: 400 });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Security: block directory traversal
|
|
27
|
+
if (filepath.includes('..')) {
|
|
28
|
+
return NextResponse.json({ error: 'Invalid path' }, { status: 403 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Determine git repo root. The server runs from the project directory.
|
|
32
|
+
const cwd = process.cwd();
|
|
33
|
+
|
|
34
|
+
// Try to find the file path relative to git root
|
|
35
|
+
let gitRoot: string;
|
|
36
|
+
try {
|
|
37
|
+
gitRoot = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim();
|
|
38
|
+
} catch {
|
|
39
|
+
return NextResponse.json({ error: 'Not a git repository' }, { status: 500 });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// The IDE reports paths like "antigravity-chat-proxy/components/artifact-panel.tsx"
|
|
43
|
+
// or absolute paths like "/tmp/scrape-changes.js".
|
|
44
|
+
// We need to resolve relative to git root.
|
|
45
|
+
let resolvedPath: string;
|
|
46
|
+
if (path.isAbsolute(filepath)) {
|
|
47
|
+
// Absolute path — try to make it relative to gitRoot
|
|
48
|
+
if (filepath.startsWith(gitRoot)) {
|
|
49
|
+
resolvedPath = path.relative(gitRoot, filepath);
|
|
50
|
+
} else {
|
|
51
|
+
// File outside git — can't diff
|
|
52
|
+
return NextResponse.json({ diff: '', filename: path.basename(filepath), message: 'File is outside git repository' });
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
// The IDE path might include the project folder name as prefix
|
|
56
|
+
// e.g. "antigravity-chat-proxy/components/artifact-panel.tsx"
|
|
57
|
+
// The gitRoot is /home/belal/repos/ide_agent/antigravity-chat-proxy
|
|
58
|
+
// So the relative path is "components/artifact-panel.tsx"
|
|
59
|
+
const gitRootBasename = path.basename(gitRoot);
|
|
60
|
+
if (filepath.startsWith(gitRootBasename + '/')) {
|
|
61
|
+
resolvedPath = filepath.substring(gitRootBasename.length + 1);
|
|
62
|
+
} else {
|
|
63
|
+
resolvedPath = filepath;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const filename = path.basename(resolvedPath);
|
|
68
|
+
|
|
69
|
+
// Try different diff strategies
|
|
70
|
+
const strategies = [
|
|
71
|
+
// 1. Unstaged changes
|
|
72
|
+
`git diff -- "${resolvedPath}"`,
|
|
73
|
+
// 2. Staged changes
|
|
74
|
+
`git diff --cached -- "${resolvedPath}"`,
|
|
75
|
+
// 3. Last commit
|
|
76
|
+
`git diff HEAD~1 HEAD -- "${resolvedPath}"`,
|
|
77
|
+
// 4. Last 2 commits
|
|
78
|
+
`git diff HEAD~2 HEAD -- "${resolvedPath}"`,
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
for (const cmd of strategies) {
|
|
82
|
+
try {
|
|
83
|
+
const diff = execSync(cmd, { cwd: gitRoot, encoding: 'utf-8', maxBuffer: 5 * 1024 * 1024 });
|
|
84
|
+
if (diff.trim()) {
|
|
85
|
+
return NextResponse.json({ diff, filename });
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
// Strategy failed, try next
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 5. Fallback: show as new file if it exists (untracked)
|
|
93
|
+
try {
|
|
94
|
+
const fullPath = path.resolve(gitRoot, resolvedPath);
|
|
95
|
+
// Security check
|
|
96
|
+
if (!fullPath.startsWith(gitRoot)) {
|
|
97
|
+
return NextResponse.json({ error: 'Path outside repository' }, { status: 403 });
|
|
98
|
+
}
|
|
99
|
+
const fs = require('fs');
|
|
100
|
+
if (fs.existsSync(fullPath)) {
|
|
101
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
102
|
+
// Format as unified diff for new file
|
|
103
|
+
const lines = content.split('\n');
|
|
104
|
+
const diffLines = [
|
|
105
|
+
`diff --git a/${resolvedPath} b/${resolvedPath}`,
|
|
106
|
+
'new file mode 100644',
|
|
107
|
+
`--- /dev/null`,
|
|
108
|
+
`+++ b/${resolvedPath}`,
|
|
109
|
+
`@@ -0,0 +1,${lines.length} @@`,
|
|
110
|
+
...lines.map((l: string) => `+${l}`),
|
|
111
|
+
];
|
|
112
|
+
return NextResponse.json({ diff: diffLines.join('\n'), filename });
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
// Ignore
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return NextResponse.json({ diff: '', filename, message: 'No changes found' });
|
|
119
|
+
}
|