nstantpage-agent 0.8.18 → 0.8.19
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/commands/sync.d.ts +6 -7
- package/dist/commands/sync.js +14 -147
- package/dist/fileManager.d.ts +1 -1
- package/dist/fileManager.js +6 -6
- package/dist/localServer.d.ts +6 -0
- package/dist/localServer.js +107 -8
- package/package.json +1 -1
package/dist/commands/sync.d.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sync command —
|
|
2
|
+
* Sync command — link a local directory to an nstantpage project.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* nstantpage sync —
|
|
6
|
-
* nstantpage sync /path/to/dir —
|
|
5
|
+
* nstantpage sync — Link current directory to nstantpage project
|
|
6
|
+
* nstantpage sync /path/to/dir — Link a specific directory
|
|
7
7
|
*
|
|
8
8
|
* What it does:
|
|
9
|
-
* 1.
|
|
10
|
-
* 2.
|
|
11
|
-
* 3.
|
|
12
|
-
* 4. Starts the agent (equivalent to `nstantpage start`)
|
|
9
|
+
* 1. Creates a LocalRepo project if one doesn't exist (name = folder name)
|
|
10
|
+
* 2. Links the project to this device (deviceId + deviceName)
|
|
11
|
+
* 3. Files stay on disk — no file pushing to the DB
|
|
13
12
|
*/
|
|
14
13
|
interface SyncOptions {
|
|
15
14
|
local?: boolean | string;
|
package/dist/commands/sync.js
CHANGED
|
@@ -1,41 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sync command —
|
|
2
|
+
* Sync command — link a local directory to an nstantpage project.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* nstantpage sync —
|
|
6
|
-
* nstantpage sync /path/to/dir —
|
|
5
|
+
* nstantpage sync — Link current directory to nstantpage project
|
|
6
|
+
* nstantpage sync /path/to/dir — Link a specific directory
|
|
7
7
|
*
|
|
8
8
|
* What it does:
|
|
9
|
-
* 1.
|
|
10
|
-
* 2.
|
|
11
|
-
* 3.
|
|
12
|
-
* 4. Starts the agent (equivalent to `nstantpage start`)
|
|
9
|
+
* 1. Creates a LocalRepo project if one doesn't exist (name = folder name)
|
|
10
|
+
* 2. Links the project to this device (deviceId + deviceName)
|
|
11
|
+
* 3. Files stay on disk — no file pushing to the DB
|
|
13
12
|
*/
|
|
14
13
|
import chalk from 'chalk';
|
|
14
|
+
import os from 'os';
|
|
15
15
|
import path from 'path';
|
|
16
16
|
import fs from 'fs';
|
|
17
17
|
import { getConfig, getDeviceId } from '../config.js';
|
|
18
|
-
const SKIP_DIRS = new Set([
|
|
19
|
-
'node_modules', 'dist', '.git', '.vite-cache', '.next',
|
|
20
|
-
'__pycache__', '.turbo', '.cache', 'build', 'out',
|
|
21
|
-
'.svelte-kit', '.nuxt', '.output', '.vercel',
|
|
22
|
-
'.angular', '.parcel-cache', '.rollup.cache',
|
|
23
|
-
]);
|
|
24
|
-
const SKIP_FILES = new Set([
|
|
25
|
-
'.DS_Store', 'Thumbs.db', '.env.local',
|
|
26
|
-
]);
|
|
27
|
-
/** Max file size to sync (1MB — skip large binaries) */
|
|
28
|
-
const MAX_FILE_SIZE = 1_048_576;
|
|
29
|
-
/** Binary file extensions to skip */
|
|
30
|
-
const BINARY_EXTENSIONS = new Set([
|
|
31
|
-
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.bmp', '.tiff',
|
|
32
|
-
'.mp4', '.mp3', '.wav', '.ogg', '.webm', '.avi',
|
|
33
|
-
'.zip', '.tar', '.gz', '.rar', '.7z',
|
|
34
|
-
'.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
35
|
-
'.pdf', '.doc', '.docx', '.xls', '.xlsx',
|
|
36
|
-
'.exe', '.dll', '.so', '.dylib',
|
|
37
|
-
'.sqlite', '.db',
|
|
38
|
-
]);
|
|
39
18
|
/**
|
|
40
19
|
* Resolve the backend API base URL (same logic as start.ts).
|
|
41
20
|
*/
|
|
@@ -45,51 +24,6 @@ function resolveBackendUrl(gateway, backend) {
|
|
|
45
24
|
const isLocal = /^wss?:\/\/(localhost|127\.0\.0\.1)/.test(gateway);
|
|
46
25
|
return isLocal ? 'http://localhost:5001' : 'https://nstantpage.com';
|
|
47
26
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Recursively collect all source files in a directory.
|
|
50
|
-
* Returns array of { relativePath, content }.
|
|
51
|
-
*/
|
|
52
|
-
async function collectFiles(baseDir, currentDir) {
|
|
53
|
-
const files = [];
|
|
54
|
-
let entries;
|
|
55
|
-
try {
|
|
56
|
-
entries = await fs.promises.readdir(currentDir, { withFileTypes: true });
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
return files;
|
|
60
|
-
}
|
|
61
|
-
for (const entry of entries) {
|
|
62
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
63
|
-
const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
|
|
64
|
-
if (entry.isDirectory()) {
|
|
65
|
-
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith('.nstantpage'))
|
|
66
|
-
continue;
|
|
67
|
-
const subFiles = await collectFiles(baseDir, fullPath);
|
|
68
|
-
files.push(...subFiles);
|
|
69
|
-
}
|
|
70
|
-
else if (entry.isFile()) {
|
|
71
|
-
if (SKIP_FILES.has(entry.name))
|
|
72
|
-
continue;
|
|
73
|
-
const ext = path.extname(entry.name).toLowerCase();
|
|
74
|
-
if (BINARY_EXTENSIONS.has(ext))
|
|
75
|
-
continue;
|
|
76
|
-
try {
|
|
77
|
-
const stat = await fs.promises.stat(fullPath);
|
|
78
|
-
if (stat.size > MAX_FILE_SIZE)
|
|
79
|
-
continue;
|
|
80
|
-
const content = await fs.promises.readFile(fullPath, 'utf-8');
|
|
81
|
-
// Skip files with null bytes (binary)
|
|
82
|
-
if (content.includes('\0'))
|
|
83
|
-
continue;
|
|
84
|
-
files.push({ relativePath, content });
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
// Can't read — skip
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return files;
|
|
92
|
-
}
|
|
93
27
|
export async function syncCommand(directory, options) {
|
|
94
28
|
const conf = getConfig();
|
|
95
29
|
// Resolve directory
|
|
@@ -117,26 +51,19 @@ export async function syncCommand(directory, options) {
|
|
|
117
51
|
}
|
|
118
52
|
const backendUrl = resolveBackendUrl(gateway, options.backend);
|
|
119
53
|
const deviceId = getDeviceId();
|
|
54
|
+
const deviceName = os.hostname();
|
|
120
55
|
const folderName = path.basename(projectDir);
|
|
121
56
|
console.log(chalk.blue(`\n 📂 nstantpage sync\n`));
|
|
122
57
|
console.log(chalk.gray(` Directory: ${projectDir}`));
|
|
58
|
+
console.log(chalk.gray(` Device: ${deviceName}`));
|
|
123
59
|
console.log(chalk.gray(` Backend: ${backendUrl}\n`));
|
|
124
|
-
// 1.
|
|
125
|
-
console.log(chalk.gray(' Scanning files...'));
|
|
126
|
-
const files = await collectFiles(projectDir, projectDir);
|
|
127
|
-
if (files.length === 0) {
|
|
128
|
-
console.log(chalk.yellow(' ⚠ No source files found in directory'));
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
console.log(chalk.green(` ✓ Found ${files.length} files`));
|
|
132
|
-
// 2. Get user info from token
|
|
60
|
+
// 1. Get user info from token
|
|
133
61
|
let userId;
|
|
134
62
|
if (token && token !== 'local-dev') {
|
|
135
63
|
try {
|
|
136
64
|
const parts = token.split('.');
|
|
137
65
|
if (parts.length === 3) {
|
|
138
66
|
const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));
|
|
139
|
-
// .NET uses full claim type URIs; also check standard 'sub' claim
|
|
140
67
|
userId = payload['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier']
|
|
141
68
|
|| payload['sub']
|
|
142
69
|
|| payload['nameid'];
|
|
@@ -144,8 +71,8 @@ export async function syncCommand(directory, options) {
|
|
|
144
71
|
}
|
|
145
72
|
catch { }
|
|
146
73
|
}
|
|
147
|
-
//
|
|
148
|
-
console.log(chalk.gray('
|
|
74
|
+
// 2. Create or find LocalRepo project (linked to this device)
|
|
75
|
+
console.log(chalk.gray(' Linking project...'));
|
|
149
76
|
let projectId;
|
|
150
77
|
try {
|
|
151
78
|
const createRes = await fetch(`${backendUrl}/api/projects`, {
|
|
@@ -160,6 +87,7 @@ export async function syncCommand(directory, options) {
|
|
|
160
87
|
localFolderPath: projectDir,
|
|
161
88
|
userId,
|
|
162
89
|
deviceId,
|
|
90
|
+
deviceName,
|
|
163
91
|
}),
|
|
164
92
|
});
|
|
165
93
|
if (!createRes.ok) {
|
|
@@ -174,68 +102,7 @@ export async function syncCommand(directory, options) {
|
|
|
174
102
|
console.log(chalk.red(` ✗ ${err.message}`));
|
|
175
103
|
process.exit(1);
|
|
176
104
|
}
|
|
177
|
-
|
|
178
|
-
console.log(chalk.gray(' Creating new version (clearing old files)...'));
|
|
179
|
-
try {
|
|
180
|
-
const syncRes = await fetch(`${backendUrl}/api/projects/${projectId}/sync`, {
|
|
181
|
-
method: 'POST',
|
|
182
|
-
headers: {
|
|
183
|
-
'Authorization': `Bearer ${token}`,
|
|
184
|
-
'Content-Type': 'application/json',
|
|
185
|
-
},
|
|
186
|
-
});
|
|
187
|
-
if (!syncRes.ok) {
|
|
188
|
-
const text = await syncRes.text().catch(() => '');
|
|
189
|
-
throw new Error(`Sync init failed (${syncRes.status}): ${text}`);
|
|
190
|
-
}
|
|
191
|
-
const syncData = await syncRes.json();
|
|
192
|
-
console.log(chalk.green(` ✓ Version ${syncData.versionNumber} created (old files cleared)`));
|
|
193
|
-
}
|
|
194
|
-
catch (err) {
|
|
195
|
-
console.log(chalk.red(` ✗ ${err.message}`));
|
|
196
|
-
process.exit(1);
|
|
197
|
-
}
|
|
198
|
-
// 5. Push all files to DB
|
|
199
|
-
console.log(chalk.gray(` Pushing ${files.length} files to database...`));
|
|
200
|
-
// Push in batches of 100 to avoid huge payloads
|
|
201
|
-
const BATCH_SIZE = 100;
|
|
202
|
-
let totalPushed = 0;
|
|
203
|
-
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
204
|
-
const batch = files.slice(i, i + BATCH_SIZE);
|
|
205
|
-
const filesMap = {};
|
|
206
|
-
for (const f of batch) {
|
|
207
|
-
filesMap[f.relativePath] = f.content;
|
|
208
|
-
}
|
|
209
|
-
try {
|
|
210
|
-
const pushRes = await fetch(`${backendUrl}/api/sandbox/push-files`, {
|
|
211
|
-
method: 'POST',
|
|
212
|
-
headers: {
|
|
213
|
-
'Authorization': `Bearer ${token}`,
|
|
214
|
-
'Content-Type': 'application/json',
|
|
215
|
-
},
|
|
216
|
-
body: JSON.stringify({
|
|
217
|
-
projectId: parseInt(projectId, 10),
|
|
218
|
-
files: filesMap,
|
|
219
|
-
}),
|
|
220
|
-
});
|
|
221
|
-
if (!pushRes.ok) {
|
|
222
|
-
const text = await pushRes.text().catch(() => '');
|
|
223
|
-
throw new Error(`Push failed (${pushRes.status}): ${text}`);
|
|
224
|
-
}
|
|
225
|
-
totalPushed += batch.length;
|
|
226
|
-
if (files.length > BATCH_SIZE) {
|
|
227
|
-
process.stdout.write(`\r Pushed ${totalPushed}/${files.length} files...`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
catch (err) {
|
|
231
|
-
console.log(chalk.red(`\n ✗ ${err.message}`));
|
|
232
|
-
process.exit(1);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
if (files.length > BATCH_SIZE)
|
|
236
|
-
process.stdout.write('\n');
|
|
237
|
-
console.log(chalk.green(` ✓ ${totalPushed} files synced to database`));
|
|
238
|
-
console.log(chalk.blue(`\n ✓ Sync complete! Project ID: ${projectId}`));
|
|
105
|
+
console.log(chalk.blue(`\n ✓ Project linked to device "${deviceName}"! Project ID: ${projectId}`));
|
|
239
106
|
console.log(chalk.gray(`\n View on web: https://nstantpage.com/project/${projectId}`));
|
|
240
107
|
console.log(chalk.gray(` Start agent: nstantpage start --project-id ${projectId} --dir "${projectDir}"\n`));
|
|
241
108
|
}
|
package/dist/fileManager.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export declare class FileManager {
|
|
|
40
40
|
* Build a recursive file tree of the project directory.
|
|
41
41
|
* Returns a structure matching the backend's /api/projects/{id}/tree format.
|
|
42
42
|
*/
|
|
43
|
-
getFileTree(): Promise<{
|
|
43
|
+
getFileTree(showHidden?: boolean): Promise<{
|
|
44
44
|
name: string;
|
|
45
45
|
path: string;
|
|
46
46
|
isDirectory: boolean;
|
package/dist/fileManager.js
CHANGED
|
@@ -105,7 +105,7 @@ export class FileManager {
|
|
|
105
105
|
* Build a recursive file tree of the project directory.
|
|
106
106
|
* Returns a structure matching the backend's /api/projects/{id}/tree format.
|
|
107
107
|
*/
|
|
108
|
-
async getFileTree() {
|
|
108
|
+
async getFileTree(showHidden = false) {
|
|
109
109
|
const root = {
|
|
110
110
|
name: path.basename(this.projectDir),
|
|
111
111
|
path: '',
|
|
@@ -113,11 +113,11 @@ export class FileManager {
|
|
|
113
113
|
children: [],
|
|
114
114
|
};
|
|
115
115
|
if (existsSync(this.projectDir)) {
|
|
116
|
-
root.children = await this.buildTree(this.projectDir, '');
|
|
116
|
+
root.children = await this.buildTree(this.projectDir, '', showHidden);
|
|
117
117
|
}
|
|
118
118
|
return root;
|
|
119
119
|
}
|
|
120
|
-
async buildTree(dir, relativePath) {
|
|
120
|
+
async buildTree(dir, relativePath, showHidden = false) {
|
|
121
121
|
const SKIP_DIRS = new Set(['node_modules', 'dist', '.git', '.vite-cache', '.next', '__pycache__', '.turbo', '.cache']);
|
|
122
122
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
123
123
|
const result = [];
|
|
@@ -130,13 +130,13 @@ export class FileManager {
|
|
|
130
130
|
return a.name.localeCompare(b.name);
|
|
131
131
|
});
|
|
132
132
|
for (const entry of sorted) {
|
|
133
|
-
if (entry.name.startsWith('.') && entry.name !== '.env')
|
|
133
|
+
if (!showHidden && entry.name.startsWith('.') && entry.name !== '.env')
|
|
134
134
|
continue;
|
|
135
135
|
const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
136
136
|
if (entry.isDirectory()) {
|
|
137
|
-
if (SKIP_DIRS.has(entry.name))
|
|
137
|
+
if (!showHidden && SKIP_DIRS.has(entry.name))
|
|
138
138
|
continue;
|
|
139
|
-
const children = await this.buildTree(path.join(dir, entry.name), entryRelPath);
|
|
139
|
+
const children = await this.buildTree(path.join(dir, entry.name), entryRelPath, showHidden);
|
|
140
140
|
result.push({ name: entry.name, path: entryRelPath, isDirectory: true, children });
|
|
141
141
|
}
|
|
142
142
|
else {
|
package/dist/localServer.d.ts
CHANGED
|
@@ -104,6 +104,12 @@ export declare class LocalServer {
|
|
|
104
104
|
private handleExec;
|
|
105
105
|
private handleTerminal;
|
|
106
106
|
private handleTerminalSessions;
|
|
107
|
+
private handleTerminalDestroy;
|
|
108
|
+
private handleTerminalDestroyAll;
|
|
109
|
+
/**
|
|
110
|
+
* Force-kill a terminal session: send signals, notify listeners, remove from map.
|
|
111
|
+
*/
|
|
112
|
+
private killSession;
|
|
107
113
|
private handleTerminalWrite;
|
|
108
114
|
private handleTerminalResize;
|
|
109
115
|
private handleAgentStatus;
|
package/dist/localServer.js
CHANGED
|
@@ -375,6 +375,8 @@ export class LocalServer {
|
|
|
375
375
|
'/live/terminal/sessions': this.handleTerminalSessions,
|
|
376
376
|
'/live/terminal/write': this.handleTerminalWrite,
|
|
377
377
|
'/live/terminal/resize': this.handleTerminalResize,
|
|
378
|
+
'/live/terminal/destroy': this.handleTerminalDestroy,
|
|
379
|
+
'/live/terminal/destroy-all': this.handleTerminalDestroyAll,
|
|
378
380
|
'/live/agent-status': this.handleAgentStatus,
|
|
379
381
|
'/live/container-status': this.handleContainerStatus,
|
|
380
382
|
'/live/container-stats': this.handleContainerStats,
|
|
@@ -416,12 +418,17 @@ export class LocalServer {
|
|
|
416
418
|
};
|
|
417
419
|
if (handlers[path])
|
|
418
420
|
return handlers[path];
|
|
419
|
-
// Prefix matches (e.g., /live/
|
|
421
|
+
// Prefix matches — find the LONGEST matching prefix (e.g., /live/terminal/sessions
|
|
422
|
+
// must not match /live/terminal when /live/terminal/sessions is available)
|
|
423
|
+
let bestMatch = null;
|
|
424
|
+
let bestLen = 0;
|
|
420
425
|
for (const [route, handler] of Object.entries(handlers)) {
|
|
421
|
-
if (path.startsWith(route + '/'))
|
|
422
|
-
|
|
426
|
+
if (path.startsWith(route + '/') && route.length > bestLen) {
|
|
427
|
+
bestMatch = handler;
|
|
428
|
+
bestLen = route.length;
|
|
429
|
+
}
|
|
423
430
|
}
|
|
424
|
-
return
|
|
431
|
+
return bestMatch;
|
|
425
432
|
}
|
|
426
433
|
// ─── /live/sync ──────────────────────────────────────────────
|
|
427
434
|
async handleSync(_req, res, body) {
|
|
@@ -591,6 +598,24 @@ export class LocalServer {
|
|
|
591
598
|
cols: s.cols,
|
|
592
599
|
rows: s.rows,
|
|
593
600
|
}));
|
|
601
|
+
// Include dev server process as a virtual session entry
|
|
602
|
+
if (this.devServer.isRunning) {
|
|
603
|
+
const stats = this.devServer.getStats();
|
|
604
|
+
sessions.unshift({
|
|
605
|
+
id: `devserver-${this.options.projectId}`,
|
|
606
|
+
projectId: this.options.projectId,
|
|
607
|
+
isAiSession: false,
|
|
608
|
+
label: 'Dev Server',
|
|
609
|
+
createdAt: Date.now() - this.devServer.uptime,
|
|
610
|
+
lastActivity: Date.now(),
|
|
611
|
+
exited: false,
|
|
612
|
+
exitCode: null,
|
|
613
|
+
devServerReady: true,
|
|
614
|
+
devServerPort: this.devServer.port,
|
|
615
|
+
pid: stats.pid,
|
|
616
|
+
isDevServer: true,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
594
619
|
this.json(res, { success: true, sessions });
|
|
595
620
|
return;
|
|
596
621
|
}
|
|
@@ -786,6 +811,78 @@ export class LocalServer {
|
|
|
786
811
|
},
|
|
787
812
|
});
|
|
788
813
|
}
|
|
814
|
+
// ─── /live/terminal/destroy ──────────────────────────────────
|
|
815
|
+
async handleTerminalDestroy(_req, res, body) {
|
|
816
|
+
let parsed = {};
|
|
817
|
+
try {
|
|
818
|
+
parsed = JSON.parse(body);
|
|
819
|
+
}
|
|
820
|
+
catch { }
|
|
821
|
+
const { sessionId } = parsed;
|
|
822
|
+
if (!sessionId) {
|
|
823
|
+
res.statusCode = 400;
|
|
824
|
+
this.json(res, { success: false, error: 'sessionId required' });
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
// Handle dev server virtual session
|
|
828
|
+
if (sessionId.startsWith('devserver-')) {
|
|
829
|
+
await this.devServer.stop();
|
|
830
|
+
this.json(res, { success: true });
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
const session = terminalSessions.get(sessionId);
|
|
834
|
+
if (!session) {
|
|
835
|
+
// Already gone — treat as success
|
|
836
|
+
this.json(res, { success: true });
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
this.killSession(sessionId, session);
|
|
840
|
+
this.json(res, { success: true });
|
|
841
|
+
}
|
|
842
|
+
// ─── /live/terminal/destroy-all ──────────────────────────────
|
|
843
|
+
async handleTerminalDestroyAll(_req, res) {
|
|
844
|
+
let count = 0;
|
|
845
|
+
for (const [sessionId, session] of Array.from(terminalSessions.entries())) {
|
|
846
|
+
this.killSession(sessionId, session);
|
|
847
|
+
count++;
|
|
848
|
+
}
|
|
849
|
+
// Also stop the dev server
|
|
850
|
+
if (this.devServer.isRunning) {
|
|
851
|
+
await this.devServer.stop();
|
|
852
|
+
count++;
|
|
853
|
+
}
|
|
854
|
+
this.json(res, { success: true, destroyed: count });
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Force-kill a terminal session: send signals, notify listeners, remove from map.
|
|
858
|
+
*/
|
|
859
|
+
killSession(sessionId, session) {
|
|
860
|
+
if (!session.exited) {
|
|
861
|
+
if (session.ptyProcess) {
|
|
862
|
+
try {
|
|
863
|
+
session.ptyProcess.kill();
|
|
864
|
+
}
|
|
865
|
+
catch { }
|
|
866
|
+
}
|
|
867
|
+
else if (session.shell) {
|
|
868
|
+
try {
|
|
869
|
+
session.shell.kill('SIGKILL');
|
|
870
|
+
}
|
|
871
|
+
catch { }
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
session.exited = true;
|
|
875
|
+
session.exitCode = null;
|
|
876
|
+
for (const listener of session.exitListeners) {
|
|
877
|
+
try {
|
|
878
|
+
listener(null);
|
|
879
|
+
}
|
|
880
|
+
catch { }
|
|
881
|
+
}
|
|
882
|
+
session.dataListeners.clear();
|
|
883
|
+
session.exitListeners.clear();
|
|
884
|
+
terminalSessions.delete(sessionId);
|
|
885
|
+
}
|
|
789
886
|
// ─── /live/terminal/write ────────────────────────────────────
|
|
790
887
|
async handleTerminalWrite(_req, res, body) {
|
|
791
888
|
let parsed = {};
|
|
@@ -1190,9 +1287,10 @@ export class LocalServer {
|
|
|
1190
1287
|
}
|
|
1191
1288
|
}
|
|
1192
1289
|
// ─── /live/tree ──────────────────────────────────────────────
|
|
1193
|
-
async handleTree(_req, res) {
|
|
1290
|
+
async handleTree(_req, res, _body, url) {
|
|
1194
1291
|
try {
|
|
1195
|
-
const
|
|
1292
|
+
const showHidden = url.searchParams.get('showHidden') === 'true';
|
|
1293
|
+
const tree = await this.fileManager.getFileTree(showHidden);
|
|
1196
1294
|
this.json(res, tree);
|
|
1197
1295
|
}
|
|
1198
1296
|
catch (error) {
|
|
@@ -1319,9 +1417,10 @@ export class LocalServer {
|
|
|
1319
1417
|
this.json(res, { error: error.message }, 500);
|
|
1320
1418
|
}
|
|
1321
1419
|
}
|
|
1322
|
-
async handleBrowseTree(_req, res) {
|
|
1420
|
+
async handleBrowseTree(_req, res, _body, url) {
|
|
1323
1421
|
try {
|
|
1324
|
-
const
|
|
1422
|
+
const showHidden = url.searchParams.get('showHidden') === 'true';
|
|
1423
|
+
const tree = await this.fileManager.getFileTree(showHidden);
|
|
1325
1424
|
this.json(res, tree);
|
|
1326
1425
|
}
|
|
1327
1426
|
catch (error) {
|
package/package.json
CHANGED