nstantpage-agent 0.8.8 → 0.8.10
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/dist/agentSync.d.ts +6 -60
- package/dist/agentSync.js +6 -211
- package/dist/localServer.d.ts +0 -5
- package/dist/localServer.js +0 -97
- package/package.json +1 -1
package/dist/agentSync.d.ts
CHANGED
|
@@ -1,80 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* agentSync.ts —
|
|
2
|
+
* agentSync.ts — File watcher for HMR notifications.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 1. POST /api/projects/{id}/sync — new version + clear ALL FullFiles
|
|
8
|
-
* 2. Collect ALL disk files
|
|
9
|
-
* 3. POST /api/sandbox/push-files — push ALL files in batches
|
|
10
|
-
*
|
|
11
|
-
* No checksums, no comparisons, no diffs. Disk wins. Always.
|
|
4
|
+
* Watches disk for file changes and fires onSyncDirty callback
|
|
5
|
+
* so the gateway can trigger HMR reload in the browser preview.
|
|
6
|
+
* That's it. No sync, no push, no pull, no status, no diffs.
|
|
12
7
|
*/
|
|
13
|
-
export type SyncDirection = 'in-sync' | 'backend-ahead' | 'disk-ahead' | 'diverged';
|
|
14
|
-
export interface SyncStatusResult {
|
|
15
|
-
inSync: boolean;
|
|
16
|
-
direction: SyncDirection;
|
|
17
|
-
diskChecksum: string | null;
|
|
18
|
-
backendChecksum: string | null;
|
|
19
|
-
diskFileCount: number;
|
|
20
|
-
backendFileCount: number;
|
|
21
|
-
modifiedFiles: string[];
|
|
22
|
-
diskOnlyFiles: string[];
|
|
23
|
-
backendOnlyFiles: string[];
|
|
24
|
-
lastSyncedVersionId: string | null;
|
|
25
|
-
lastSyncedAt: number | null;
|
|
26
|
-
diskDirty: boolean;
|
|
27
|
-
computedAt: number;
|
|
28
|
-
}
|
|
29
|
-
export interface PerFileStatus {
|
|
30
|
-
path: string;
|
|
31
|
-
status: 'same' | 'modified' | 'disk-only' | 'backend-only';
|
|
32
|
-
diskSha?: string;
|
|
33
|
-
backendSha?: string;
|
|
34
|
-
}
|
|
35
8
|
export interface AgentSyncOptions {
|
|
36
9
|
projectDir: string;
|
|
37
10
|
projectId: string;
|
|
38
11
|
backendUrl: string;
|
|
39
|
-
/** Called when file watcher detects changes —
|
|
12
|
+
/** Called when file watcher detects changes — triggers HMR via tunnel */
|
|
40
13
|
onSyncDirty?: (projectId: string) => void;
|
|
41
14
|
}
|
|
42
15
|
export declare class AgentSync {
|
|
43
16
|
private projectDir;
|
|
44
17
|
private projectId;
|
|
45
|
-
private backendUrl;
|
|
46
18
|
private onSyncDirty?;
|
|
47
19
|
private fileWatcher;
|
|
48
20
|
private debounceTimer;
|
|
49
|
-
private syncing;
|
|
50
|
-
private lastVersionId;
|
|
51
|
-
private lastSyncAt;
|
|
52
21
|
constructor(options: AgentSyncOptions);
|
|
53
22
|
startFileWatcher(): void;
|
|
54
23
|
stopFileWatcher(): void;
|
|
55
|
-
|
|
56
|
-
getPerFileStatus(): Promise<PerFileStatus[]>;
|
|
57
|
-
pushToBackend(): Promise<{
|
|
58
|
-
success: boolean;
|
|
59
|
-
filesPushed: number;
|
|
60
|
-
filesDeleted: number;
|
|
61
|
-
modifiedFiles: string[];
|
|
62
|
-
addedFiles: string[];
|
|
63
|
-
deletedFiles: string[];
|
|
64
|
-
versionId?: string;
|
|
65
|
-
message?: string;
|
|
66
|
-
}>;
|
|
67
|
-
markSynced(versionId: string): void;
|
|
68
|
-
/** Read a single file from disk (for diff view) */
|
|
69
|
-
readDiskFile(filePath: string): Promise<string | null>;
|
|
70
|
-
/**
|
|
71
|
-
* Pull files from the backend DB and write them to disk.
|
|
72
|
-
* After writing, clears pending changes (the writes would trigger the watcher).
|
|
73
|
-
*/
|
|
74
|
-
pullFromBackend(): Promise<{
|
|
75
|
-
success: boolean;
|
|
76
|
-
filesWritten: number;
|
|
77
|
-
versionId?: string;
|
|
78
|
-
}>;
|
|
24
|
+
markSynced(_versionId: string): void;
|
|
79
25
|
destroy(): void;
|
|
80
26
|
}
|
package/dist/agentSync.js
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* agentSync.ts —
|
|
2
|
+
* agentSync.ts — File watcher for HMR notifications.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 1. POST /api/projects/{id}/sync — new version + clear ALL FullFiles
|
|
8
|
-
* 2. Collect ALL disk files
|
|
9
|
-
* 3. POST /api/sandbox/push-files — push ALL files in batches
|
|
10
|
-
*
|
|
11
|
-
* No checksums, no comparisons, no diffs. Disk wins. Always.
|
|
4
|
+
* Watches disk for file changes and fires onSyncDirty callback
|
|
5
|
+
* so the gateway can trigger HMR reload in the browser preview.
|
|
6
|
+
* That's it. No sync, no push, no pull, no status, no diffs.
|
|
12
7
|
*/
|
|
13
8
|
import path from 'path';
|
|
14
|
-
import fs from 'fs/promises';
|
|
15
9
|
import { watch, existsSync } from 'fs';
|
|
16
10
|
// ─── Skip Patterns ────────────────────────────────────────────
|
|
17
11
|
const SKIP_DIRS = new Set([
|
|
@@ -33,8 +27,6 @@ const SKIP_EXTENSIONS = new Set([
|
|
|
33
27
|
'.sqlite', '.db',
|
|
34
28
|
'.svg',
|
|
35
29
|
]);
|
|
36
|
-
/** Max file size to sync (1MB) */
|
|
37
|
-
const MAX_FILE_SIZE = 1_048_576;
|
|
38
30
|
function shouldSkip(name, isDir) {
|
|
39
31
|
if (isDir)
|
|
40
32
|
return SKIP_DIRS.has(name) || name.startsWith('.');
|
|
@@ -45,7 +37,6 @@ function shouldSkip(name, isDir) {
|
|
|
45
37
|
const ext = path.extname(name).toLowerCase();
|
|
46
38
|
return SKIP_EXTENSIONS.has(ext);
|
|
47
39
|
}
|
|
48
|
-
/** Check if a relative path should be skipped (checks every path component) */
|
|
49
40
|
function shouldSkipPath(relPath) {
|
|
50
41
|
const parts = relPath.split(/[\/\\]/);
|
|
51
42
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
@@ -54,60 +45,17 @@ function shouldSkipPath(relPath) {
|
|
|
54
45
|
}
|
|
55
46
|
return shouldSkip(parts[parts.length - 1], false);
|
|
56
47
|
}
|
|
57
|
-
// ─── Collect All Files (same as sync.ts) ──────────────────────
|
|
58
|
-
async function collectAllFiles(baseDir, currentDir) {
|
|
59
|
-
const files = [];
|
|
60
|
-
let entries;
|
|
61
|
-
try {
|
|
62
|
-
entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return files;
|
|
66
|
-
}
|
|
67
|
-
for (const entry of entries) {
|
|
68
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
69
|
-
if (entry.isDirectory()) {
|
|
70
|
-
if (shouldSkip(entry.name, true))
|
|
71
|
-
continue;
|
|
72
|
-
const sub = await collectAllFiles(baseDir, fullPath);
|
|
73
|
-
files.push(...sub);
|
|
74
|
-
}
|
|
75
|
-
else if (entry.isFile()) {
|
|
76
|
-
if (shouldSkip(entry.name, false))
|
|
77
|
-
continue;
|
|
78
|
-
try {
|
|
79
|
-
const stat = await fs.stat(fullPath);
|
|
80
|
-
if (stat.size > MAX_FILE_SIZE)
|
|
81
|
-
continue;
|
|
82
|
-
const content = await fs.readFile(fullPath, 'utf-8');
|
|
83
|
-
if (content.includes('\0'))
|
|
84
|
-
continue; // binary
|
|
85
|
-
const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
|
|
86
|
-
files.push({ relativePath, content });
|
|
87
|
-
}
|
|
88
|
-
catch { /* unreadable — skip */ }
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return files;
|
|
92
|
-
}
|
|
93
48
|
export class AgentSync {
|
|
94
49
|
projectDir;
|
|
95
50
|
projectId;
|
|
96
|
-
backendUrl;
|
|
97
51
|
onSyncDirty;
|
|
98
|
-
// State
|
|
99
52
|
fileWatcher = null;
|
|
100
53
|
debounceTimer = null;
|
|
101
|
-
syncing = false;
|
|
102
|
-
lastVersionId = null;
|
|
103
|
-
lastSyncAt = null;
|
|
104
54
|
constructor(options) {
|
|
105
55
|
this.projectDir = options.projectDir;
|
|
106
56
|
this.projectId = options.projectId;
|
|
107
|
-
this.backendUrl = options.backendUrl;
|
|
108
57
|
this.onSyncDirty = options.onSyncDirty;
|
|
109
58
|
}
|
|
110
|
-
// ─── File Watcher ─────────────────────────────────────────
|
|
111
59
|
startFileWatcher() {
|
|
112
60
|
if (this.fileWatcher)
|
|
113
61
|
return;
|
|
@@ -119,7 +67,6 @@ export class AgentSync {
|
|
|
119
67
|
return;
|
|
120
68
|
if (shouldSkipPath(filename))
|
|
121
69
|
return;
|
|
122
|
-
// Debounce — just fire HMR notification, no file tracking
|
|
123
70
|
if (this.debounceTimer)
|
|
124
71
|
clearTimeout(this.debounceTimer);
|
|
125
72
|
this.debounceTimer = setTimeout(() => {
|
|
@@ -149,160 +96,8 @@ export class AgentSync {
|
|
|
149
96
|
this.debounceTimer = null;
|
|
150
97
|
}
|
|
151
98
|
}
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
inSync: true,
|
|
156
|
-
direction: 'in-sync',
|
|
157
|
-
diskChecksum: null,
|
|
158
|
-
backendChecksum: null,
|
|
159
|
-
diskFileCount: 0,
|
|
160
|
-
backendFileCount: 0,
|
|
161
|
-
modifiedFiles: [],
|
|
162
|
-
diskOnlyFiles: [],
|
|
163
|
-
backendOnlyFiles: [],
|
|
164
|
-
lastSyncedVersionId: this.lastVersionId,
|
|
165
|
-
lastSyncedAt: this.lastSyncAt,
|
|
166
|
-
diskDirty: false,
|
|
167
|
-
computedAt: Date.now(),
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
// ─── Per-File Status ──────────────────────────────────────
|
|
171
|
-
async getPerFileStatus() {
|
|
172
|
-
return [];
|
|
173
|
-
}
|
|
174
|
-
// ─── Push to Backend (Full Sync) ──────────────────────────
|
|
175
|
-
//
|
|
176
|
-
// Does EXACTLY what `nstantpage sync` does:
|
|
177
|
-
// 1. POST /api/projects/{id}/sync — new version + clear ALL FullFiles
|
|
178
|
-
// 2. Collect ALL files from disk
|
|
179
|
-
// 3. POST /api/sandbox/push-files — push ALL files in batches of 100
|
|
180
|
-
async pushToBackend() {
|
|
181
|
-
if (this.syncing) {
|
|
182
|
-
return {
|
|
183
|
-
success: true, filesPushed: 0, filesDeleted: 0,
|
|
184
|
-
modifiedFiles: [], addedFiles: [], deletedFiles: [],
|
|
185
|
-
message: 'Sync already in progress',
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
this.syncing = true;
|
|
189
|
-
try {
|
|
190
|
-
// Step 1: Create new version + clear all FullFiles
|
|
191
|
-
const syncRes = await fetch(`${this.backendUrl}/api/projects/${this.projectId}/sync`, {
|
|
192
|
-
method: 'POST',
|
|
193
|
-
headers: { 'Content-Type': 'application/json' },
|
|
194
|
-
});
|
|
195
|
-
if (!syncRes.ok) {
|
|
196
|
-
const text = await syncRes.text().catch(() => '');
|
|
197
|
-
throw new Error(`Sync init failed (${syncRes.status}): ${text}`);
|
|
198
|
-
}
|
|
199
|
-
const syncData = await syncRes.json();
|
|
200
|
-
console.log(` [AgentSync] New version ${syncData.versionNumber} created (old files cleared)`);
|
|
201
|
-
// Step 2: Collect ALL files from disk
|
|
202
|
-
const allFiles = await collectAllFiles(this.projectDir, this.projectDir);
|
|
203
|
-
// Step 3: Push in batches of 100
|
|
204
|
-
const BATCH_SIZE = 100;
|
|
205
|
-
let totalPushed = 0;
|
|
206
|
-
for (let i = 0; i < allFiles.length; i += BATCH_SIZE) {
|
|
207
|
-
const batch = allFiles.slice(i, i + BATCH_SIZE);
|
|
208
|
-
const filesMap = {};
|
|
209
|
-
for (const f of batch) {
|
|
210
|
-
filesMap[f.relativePath] = f.content;
|
|
211
|
-
}
|
|
212
|
-
const pushRes = await fetch(`${this.backendUrl}/api/sandbox/push-files`, {
|
|
213
|
-
method: 'POST',
|
|
214
|
-
headers: { 'Content-Type': 'application/json' },
|
|
215
|
-
body: JSON.stringify({
|
|
216
|
-
projectId: parseInt(this.projectId, 10),
|
|
217
|
-
files: filesMap,
|
|
218
|
-
}),
|
|
219
|
-
});
|
|
220
|
-
if (!pushRes.ok) {
|
|
221
|
-
const text = await pushRes.text().catch(() => '');
|
|
222
|
-
throw new Error(`Push batch failed (${pushRes.status}): ${text}`);
|
|
223
|
-
}
|
|
224
|
-
totalPushed += batch.length;
|
|
225
|
-
}
|
|
226
|
-
// Done
|
|
227
|
-
this.lastVersionId = String(syncData.versionId);
|
|
228
|
-
this.lastSyncAt = Date.now();
|
|
229
|
-
const fileNames = allFiles.map(f => f.relativePath);
|
|
230
|
-
console.log(` [AgentSync] Full sync complete: ${totalPushed} files pushed (version ${syncData.versionNumber})`);
|
|
231
|
-
return {
|
|
232
|
-
success: true,
|
|
233
|
-
filesPushed: totalPushed,
|
|
234
|
-
filesDeleted: 0,
|
|
235
|
-
modifiedFiles: fileNames,
|
|
236
|
-
addedFiles: [],
|
|
237
|
-
deletedFiles: [],
|
|
238
|
-
versionId: String(syncData.versionId),
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
catch (err) {
|
|
242
|
-
console.error(` [AgentSync] Sync failed:`, err.message);
|
|
243
|
-
throw err;
|
|
244
|
-
}
|
|
245
|
-
finally {
|
|
246
|
-
this.syncing = false;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
// ─── Sync Markers (for compatibility with start.ts) ───────
|
|
250
|
-
markSynced(versionId) {
|
|
251
|
-
this.lastVersionId = versionId;
|
|
252
|
-
this.lastSyncAt = Date.now();
|
|
253
|
-
console.log(` [AgentSync] Marked synced at version ${versionId}`);
|
|
254
|
-
}
|
|
255
|
-
/** Read a single file from disk (for diff view) */
|
|
256
|
-
async readDiskFile(filePath) {
|
|
257
|
-
try {
|
|
258
|
-
const normalized = path.normalize(filePath).replace(/\.\.\//g, '');
|
|
259
|
-
const fullPath = path.join(this.projectDir, normalized);
|
|
260
|
-
if (!fullPath.startsWith(this.projectDir))
|
|
261
|
-
return null;
|
|
262
|
-
return await fs.readFile(fullPath, 'utf-8');
|
|
263
|
-
}
|
|
264
|
-
catch {
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Pull files from the backend DB and write them to disk.
|
|
270
|
-
* After writing, clears pending changes (the writes would trigger the watcher).
|
|
271
|
-
*/
|
|
272
|
-
async pullFromBackend() {
|
|
273
|
-
const url = `${this.backendUrl}/api/sandbox/files?projectId=${this.projectId}`;
|
|
274
|
-
const response = await fetch(url);
|
|
275
|
-
if (!response.ok) {
|
|
276
|
-
throw new Error(`Failed to fetch files from backend: ${response.status}`);
|
|
277
|
-
}
|
|
278
|
-
const data = await response.json();
|
|
279
|
-
if (!data.files || data.files.length === 0) {
|
|
280
|
-
return { success: true, filesWritten: 0 };
|
|
281
|
-
}
|
|
282
|
-
const dirsToCreate = new Set();
|
|
283
|
-
for (const file of data.files) {
|
|
284
|
-
const fp = path.join(this.projectDir, file.path);
|
|
285
|
-
dirsToCreate.add(path.dirname(fp));
|
|
286
|
-
}
|
|
287
|
-
for (const dir of dirsToCreate) {
|
|
288
|
-
await fs.mkdir(dir, { recursive: true }).catch(() => { });
|
|
289
|
-
}
|
|
290
|
-
const BATCH_SIZE = 50;
|
|
291
|
-
let written = 0;
|
|
292
|
-
for (let i = 0; i < data.files.length; i += BATCH_SIZE) {
|
|
293
|
-
const batch = data.files.slice(i, i + BATCH_SIZE);
|
|
294
|
-
await Promise.all(batch.map(async (file) => {
|
|
295
|
-
const fp = path.join(this.projectDir, file.path);
|
|
296
|
-
await fs.writeFile(fp, file.content, 'utf-8');
|
|
297
|
-
}));
|
|
298
|
-
written += batch.length;
|
|
299
|
-
}
|
|
300
|
-
this.lastVersionId = String(data.versionId);
|
|
301
|
-
this.lastSyncAt = Date.now();
|
|
302
|
-
console.log(` [AgentSync] Pulled ${written} files from backend (version ${data.versionId})`);
|
|
303
|
-
return { success: true, filesWritten: written, versionId: String(data.versionId) };
|
|
304
|
-
}
|
|
305
|
-
// ─── Cleanup ──────────────────────────────────────────────
|
|
99
|
+
// Stubs kept for API compat so localServer.ts doesn't break
|
|
100
|
+
markSynced(_versionId) { }
|
|
306
101
|
destroy() {
|
|
307
102
|
this.stopFileWatcher();
|
|
308
103
|
}
|
package/dist/localServer.d.ts
CHANGED
|
@@ -124,11 +124,6 @@ export declare class LocalServer {
|
|
|
124
124
|
private handleStats;
|
|
125
125
|
private handleUsage;
|
|
126
126
|
private handleBuild;
|
|
127
|
-
private handleSyncStatus;
|
|
128
|
-
private handleSyncDiff;
|
|
129
|
-
private handlePushToBackend;
|
|
130
|
-
private handlePullFromBackend;
|
|
131
|
-
private handleDiskFile;
|
|
132
127
|
private handleTree;
|
|
133
128
|
private handleFileContent;
|
|
134
129
|
private handleSaveFile;
|
package/dist/localServer.js
CHANGED
|
@@ -401,11 +401,6 @@ export class LocalServer {
|
|
|
401
401
|
'/live/db/query': this.handleDbQuery,
|
|
402
402
|
'/live/db/create': this.handleDbCreate,
|
|
403
403
|
'/live/build': this.handleBuild,
|
|
404
|
-
'/live/sync-status': this.handleSyncStatus,
|
|
405
|
-
'/live/sync-diff': this.handleSyncDiff,
|
|
406
|
-
'/live/push-to-backend': this.handlePushToBackend,
|
|
407
|
-
'/live/pull-from-backend': this.handlePullFromBackend,
|
|
408
|
-
'/live/disk-file': this.handleDiskFile,
|
|
409
404
|
'/live/tree': this.handleTree,
|
|
410
405
|
'/live/file-content': this.handleFileContent,
|
|
411
406
|
'/live/save-file': this.handleSaveFile,
|
|
@@ -1186,98 +1181,6 @@ export class LocalServer {
|
|
|
1186
1181
|
this.json(res, { success: false, error: err.message });
|
|
1187
1182
|
}
|
|
1188
1183
|
}
|
|
1189
|
-
// ─── /live/sync-status ────────────────────────────────────────
|
|
1190
|
-
async handleSyncStatus(_req, res, _body, url) {
|
|
1191
|
-
if (!this.agentSync) {
|
|
1192
|
-
this.json(res, { success: false, error: 'Sync not available (no backendUrl configured)' }, 503);
|
|
1193
|
-
return;
|
|
1194
|
-
}
|
|
1195
|
-
try {
|
|
1196
|
-
const status = await this.agentSync.getSyncStatus();
|
|
1197
|
-
this.json(res, { success: true, ...status });
|
|
1198
|
-
}
|
|
1199
|
-
catch (error) {
|
|
1200
|
-
console.error(` [LocalServer] sync-status error:`, error.message);
|
|
1201
|
-
this.json(res, { success: false, error: error.message }, 500);
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
// ─── /live/sync-diff ─────────────────────────────────────────
|
|
1205
|
-
async handleSyncDiff(_req, res, _body, url) {
|
|
1206
|
-
if (!this.agentSync) {
|
|
1207
|
-
this.json(res, { success: false, error: 'Sync not available (no backendUrl configured)' }, 503);
|
|
1208
|
-
return;
|
|
1209
|
-
}
|
|
1210
|
-
try {
|
|
1211
|
-
const files = await this.agentSync.getPerFileStatus();
|
|
1212
|
-
const changed = files.filter(f => f.status !== 'same');
|
|
1213
|
-
this.json(res, {
|
|
1214
|
-
success: true,
|
|
1215
|
-
totalFiles: files.length,
|
|
1216
|
-
changedCount: changed.length,
|
|
1217
|
-
files: changed,
|
|
1218
|
-
});
|
|
1219
|
-
}
|
|
1220
|
-
catch (error) {
|
|
1221
|
-
console.error(` [LocalServer] sync-diff error:`, error.message);
|
|
1222
|
-
this.json(res, { success: false, error: error.message }, 500);
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
// ─── /live/push-to-backend ───────────────────────────────────
|
|
1226
|
-
async handlePushToBackend(_req, res, _body) {
|
|
1227
|
-
if (!this.agentSync) {
|
|
1228
|
-
this.json(res, { success: false, error: 'Sync not available (no backendUrl configured)' }, 503);
|
|
1229
|
-
return;
|
|
1230
|
-
}
|
|
1231
|
-
try {
|
|
1232
|
-
const result = await this.agentSync.pushToBackend();
|
|
1233
|
-
this.json(res, result);
|
|
1234
|
-
}
|
|
1235
|
-
catch (error) {
|
|
1236
|
-
console.error(` [LocalServer] push-to-backend error:`, error.message);
|
|
1237
|
-
this.json(res, { success: false, error: error.message }, 500);
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
// ─── /live/pull-from-backend ──────────────────────────────────
|
|
1241
|
-
async handlePullFromBackend(_req, res) {
|
|
1242
|
-
if (!this.agentSync) {
|
|
1243
|
-
this.json(res, { success: false, error: 'Sync not available (no backendUrl configured)' }, 503);
|
|
1244
|
-
return;
|
|
1245
|
-
}
|
|
1246
|
-
try {
|
|
1247
|
-
const result = await this.agentSync.pullFromBackend();
|
|
1248
|
-
this.json(res, result);
|
|
1249
|
-
}
|
|
1250
|
-
catch (error) {
|
|
1251
|
-
console.error(` [LocalServer] pull-from-backend error:`, error.message);
|
|
1252
|
-
this.json(res, { success: false, error: error.message }, 500);
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
// ─── /live/disk-file ─────────────────────────────────────────
|
|
1256
|
-
async handleDiskFile(_req, res, _body, url) {
|
|
1257
|
-
const filePath = url.searchParams.get('path');
|
|
1258
|
-
if (!filePath) {
|
|
1259
|
-
this.json(res, { error: 'Missing path parameter' }, 400);
|
|
1260
|
-
return;
|
|
1261
|
-
}
|
|
1262
|
-
if (!this.agentSync) {
|
|
1263
|
-
this.json(res, { success: false, error: 'Sync not available' }, 503);
|
|
1264
|
-
return;
|
|
1265
|
-
}
|
|
1266
|
-
try {
|
|
1267
|
-
const content = await this.agentSync.readDiskFile(filePath);
|
|
1268
|
-
if (content === null) {
|
|
1269
|
-
this.json(res, { error: 'File not found' }, 404);
|
|
1270
|
-
return;
|
|
1271
|
-
}
|
|
1272
|
-
res.statusCode = 200;
|
|
1273
|
-
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
1274
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
1275
|
-
res.end(content);
|
|
1276
|
-
}
|
|
1277
|
-
catch (error) {
|
|
1278
|
-
this.json(res, { success: false, error: error.message }, 500);
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
1184
|
// ─── /live/tree ──────────────────────────────────────────────
|
|
1282
1185
|
async handleTree(_req, res) {
|
|
1283
1186
|
try {
|
package/package.json
CHANGED