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.
Files changed (113) hide show
  1. package/.next/standalone/.next/app-path-routes-manifest.json +2 -0
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/routes-manifest.json +12 -0
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  6. package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  9. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  10. package/.next/standalone/.next/server/app/_not-found/page.js +5 -6
  11. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  12. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/standalone/.next/server/app/api/v1/artifacts/active/route.js +5 -1
  14. package/.next/standalone/.next/server/app/api/v1/artifacts/active/route.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/api/v1/changes/active/route/app-paths-manifest.json +3 -0
  16. package/.next/standalone/.next/server/app/api/v1/changes/active/route/build-manifest.json +11 -0
  17. package/.next/standalone/.next/server/app/api/v1/changes/active/route/server-reference-manifest.json +4 -0
  18. package/.next/standalone/.next/server/app/api/v1/changes/active/route.js +11 -0
  19. package/.next/standalone/.next/server/app/api/v1/changes/active/route.js.map +5 -0
  20. package/.next/standalone/.next/server/app/api/v1/changes/active/route.js.nft.json +1 -0
  21. package/.next/standalone/.next/server/app/api/v1/changes/active/route_client-reference-manifest.js +2 -0
  22. package/.next/standalone/.next/server/app/api/v1/changes/diff/route/app-paths-manifest.json +3 -0
  23. package/.next/standalone/.next/server/app/api/v1/changes/diff/route/build-manifest.json +11 -0
  24. package/.next/standalone/.next/server/app/api/v1/changes/diff/route/server-reference-manifest.json +4 -0
  25. package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js +7 -0
  26. package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js.map +5 -0
  27. package/.next/standalone/.next/server/app/api/v1/changes/diff/route.js.nft.json +1 -0
  28. package/.next/standalone/.next/server/app/api/v1/changes/diff/route_client-reference-manifest.js +2 -0
  29. package/.next/standalone/.next/server/app/api/v1/chat/stream/route.js +1 -1
  30. package/.next/standalone/.next/server/app/api/v1/chat/stream/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/v1/windows/cdp-start/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/v1/windows/cdp-status/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/v1/windows/close/route.js +1 -1
  34. package/.next/standalone/.next/server/app/api/v1/windows/close/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/v1/windows/open/route.js +2 -2
  36. package/.next/standalone/.next/server/app/api/v1/windows/open/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/debug/page/build-manifest.json +3 -3
  38. package/.next/standalone/.next/server/app/debug/page.js +5 -5
  39. package/.next/standalone/.next/server/app/debug/page.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/debug/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  42. package/.next/standalone/.next/server/app/page.js +5 -5
  43. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app-paths-manifest.json +2 -0
  46. package/.next/standalone/.next/server/chunks/[root-of-the-server]__26662154._.js +1 -1
  47. package/.next/standalone/.next/server/chunks/[root-of-the-server]__53c4f34d._.js +1 -1
  48. package/.next/standalone/.next/server/chunks/[root-of-the-server]__851f6b5a._.js +3 -0
  49. package/.next/standalone/.next/server/chunks/[root-of-the-server]__94275f7f._.js +1 -1
  50. package/.next/standalone/.next/server/chunks/[root-of-the-server]__9a1969e6._.js +3 -0
  51. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c34d50c8._.js +1 -1
  52. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c696771d._.js +1 -1
  53. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d13bbe3c._.js +3 -0
  54. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d172e6aa._.js +3 -0
  55. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_v1_changes_active_route_actions_1bb9fc18.js +3 -0
  56. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_v1_changes_diff_route_actions_65d9ee16.js +3 -0
  57. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__d080cb3d._.js → [root-of-the-server]__012405ac._.js} +2 -2
  58. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__a457c799._.js +3 -0
  59. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__f62d412e._.js → [root-of-the-server]__b9356576._.js} +2 -2
  60. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__52af585c._.js → [root-of-the-server]__ce78239f._.js} +2 -2
  61. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__ae6d24d9._.js → [root-of-the-server]__f47dc36d._.js} +2 -2
  62. package/.next/standalone/.next/server/chunks/ssr/{_524b2348._.js → _657ecbe9._.js} +3 -3
  63. package/.next/standalone/.next/server/chunks/ssr/{_fe4475aa._.js → _939145a4._.js} +3 -3
  64. package/.next/standalone/.next/server/chunks/ssr/app_layout_tsx_271801d7._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/ssr/app_not-found_tsx_ef35050a._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/ssr/components_chat-container_tsx_fcbc457f._.js +1 -1
  67. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_ece394eb.js +3 -0
  68. 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
  69. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_f183c70b._.js → node_modules_next_dist_f21d913a._.js} +2 -2
  70. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  71. package/.next/standalone/.next/server/pages/500.html +2 -2
  72. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  73. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  74. package/.next/standalone/app/api/v1/artifacts/active/[filename]/route.ts +73 -23
  75. package/.next/standalone/app/api/v1/artifacts/active/route.ts +103 -52
  76. package/.next/standalone/app/api/v1/changes/active/route.ts +27 -0
  77. package/.next/standalone/app/api/v1/changes/diff/route.ts +119 -0
  78. package/.next/standalone/app/globals.css +424 -0
  79. package/.next/standalone/app/layout.tsx +3 -3
  80. package/.next/standalone/app/not-found.tsx +14 -23
  81. package/.next/standalone/components/artifact-panel.tsx +57 -13
  82. package/.next/standalone/components/changes-panel.tsx +178 -0
  83. package/.next/standalone/components/chat-container.tsx +44 -3
  84. package/.next/standalone/components/chat-input.tsx +44 -0
  85. package/.next/standalone/components/header.tsx +1 -13
  86. package/.next/standalone/hooks/use-changes.ts +61 -0
  87. package/.next/standalone/hooks/use-chat.ts +21 -3
  88. package/.next/standalone/hooks/use-conversations.ts +19 -11
  89. package/.next/standalone/lib/scraper/agent-state.ts +89 -54
  90. package/.next/standalone/lib/scraper/chat-history.ts +215 -85
  91. package/.next/standalone/lib/scraper/ide-artifacts.ts +212 -0
  92. package/.next/standalone/lib/scraper/ide-changes.ts +172 -0
  93. package/.next/standalone/lib/types.ts +11 -0
  94. package/.next/standalone/package-lock.json +2 -2
  95. package/.next/standalone/package.json +2 -2
  96. package/.next/standalone/scripts/check-send-button.js +126 -0
  97. package/.next/standalone/scripts/find-send-btn.js +65 -0
  98. package/.next/standalone/tsconfig.tsbuildinfo +1 -1
  99. package/.next/static/chunks/09dc2aa5c698c324.css +1 -0
  100. package/.next/static/chunks/{f7c83373e6561461.js → b9a0fabf54a78ef2.js} +1 -1
  101. package/.next/static/chunks/e2ccf5908cad5a88.js +5 -0
  102. package/.next/static/chunks/f7cc8fe5822bbc01.js +1 -0
  103. package/.next/static/chunks/{turbopack-3f34081d758747ed.js → turbopack-7b5dc393c5d3964b.js} +1 -1
  104. package/package.json +2 -2
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__151eca3a._.js +0 -3
  106. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ec32b318._.js +0 -3
  107. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__f77eb371._.js +0 -3
  108. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_9170b7a0._.js +0 -3
  109. package/.next/standalone/app/global-error.tsx +0 -42
  110. package/.next/static/chunks/2317ab948a7d90a4.js +0 -5
  111. package/.next/static/chunks/2d277a81099566c3.js +0 -1
  112. package/.next/static/chunks/ad1121f40e497811.css +0 -1
  113. 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
- * Auto-detect the most recently modified conversation directory.
13
- * Ensures the artifact panel works even before a conversation is explicitly selected.
14
+ * Scan a brain conversation directory recursively for artifact files.
15
+ * Returns .md files, skipping hidden directories like .system_generated.
14
16
  */
15
- function autoDetectActiveConversation(): string | null {
16
- try {
17
- if (!fs.existsSync(BRAIN_DIR)) return null;
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
- for (const entry of entries) {
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, // Return the relative path, e.g. "browser/scratchpad.md"
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
- continue;
98
+ // IDE scraping failed — fall through to brain directory
79
99
  }
80
100
  }
81
101
 
82
- // Sort newest first
83
- files.sort((a: any, b: any) => new Date(b.mtime).getTime() - new Date(a.mtime).getTime());
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
- return NextResponse.json({ files });
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
+ }