nstantpage-agent 0.4.1 → 0.4.2
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/cli.d.ts +2 -0
- package/dist/cli.js +10 -0
- package/dist/commands/service.d.ts +17 -0
- package/dist/commands/service.js +215 -0
- package/dist/localServer.d.ts +4 -0
- package/dist/localServer.js +147 -0
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* nstantpage start [dir] — Start agent for a project directory
|
|
8
8
|
* nstantpage stop — Stop running agent
|
|
9
9
|
* nstantpage status — Show agent status
|
|
10
|
+
* nstantpage service — Install as background service (auto-start on boot)
|
|
11
|
+
* nstantpage service --uninstall — Remove background service
|
|
10
12
|
*
|
|
11
13
|
* The agent replaces Docker containers — your project runs natively on your
|
|
12
14
|
* machine with full performance, and the cloud editor connects through a tunnel.
|
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* nstantpage start [dir] — Start agent for a project directory
|
|
8
8
|
* nstantpage stop — Stop running agent
|
|
9
9
|
* nstantpage status — Show agent status
|
|
10
|
+
* nstantpage service — Install as background service (auto-start on boot)
|
|
11
|
+
* nstantpage service --uninstall — Remove background service
|
|
10
12
|
*
|
|
11
13
|
* The agent replaces Docker containers — your project runs natively on your
|
|
12
14
|
* machine with full performance, and the cloud editor connects through a tunnel.
|
|
@@ -16,6 +18,7 @@ import chalk from 'chalk';
|
|
|
16
18
|
import { loginCommand } from './commands/login.js';
|
|
17
19
|
import { startCommand } from './commands/start.js';
|
|
18
20
|
import { statusCommand } from './commands/status.js';
|
|
21
|
+
import { serviceCommand } from './commands/service.js';
|
|
19
22
|
const program = new Command();
|
|
20
23
|
program
|
|
21
24
|
.name('nstantpage')
|
|
@@ -66,5 +69,12 @@ program
|
|
|
66
69
|
.command('status')
|
|
67
70
|
.description('Show agent status')
|
|
68
71
|
.action(statusCommand);
|
|
72
|
+
program
|
|
73
|
+
.command('service')
|
|
74
|
+
.description('Install/uninstall agent as a background service (starts on boot)')
|
|
75
|
+
.option('--project-id <id>', 'Project ID to serve')
|
|
76
|
+
.option('--gateway <url>', 'Gateway URL', 'wss://webprev.live')
|
|
77
|
+
.option('--uninstall', 'Remove the background service')
|
|
78
|
+
.action(serviceCommand);
|
|
69
79
|
program.parse();
|
|
70
80
|
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service command — install/uninstall the nstantpage agent as a background service.
|
|
3
|
+
*
|
|
4
|
+
* macOS: Uses launchd (~/Library/LaunchAgents/com.nstantpage.agent.plist)
|
|
5
|
+
* Linux: Uses systemd (--user mode: ~/.config/systemd/user/nstantpage-agent.service)
|
|
6
|
+
* Windows: Uses node-windows / Task Scheduler (future)
|
|
7
|
+
*
|
|
8
|
+
* This allows the agent to run on boot, always ready to accept connections
|
|
9
|
+
* from the web editor without manually starting it each time.
|
|
10
|
+
*/
|
|
11
|
+
interface ServiceOptions {
|
|
12
|
+
projectId?: string;
|
|
13
|
+
gateway?: string;
|
|
14
|
+
uninstall?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function serviceCommand(options: ServiceOptions): Promise<void>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service command — install/uninstall the nstantpage agent as a background service.
|
|
3
|
+
*
|
|
4
|
+
* macOS: Uses launchd (~/Library/LaunchAgents/com.nstantpage.agent.plist)
|
|
5
|
+
* Linux: Uses systemd (--user mode: ~/.config/systemd/user/nstantpage-agent.service)
|
|
6
|
+
* Windows: Uses node-windows / Task Scheduler (future)
|
|
7
|
+
*
|
|
8
|
+
* This allows the agent to run on boot, always ready to accept connections
|
|
9
|
+
* from the web editor without manually starting it each time.
|
|
10
|
+
*/
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import os from 'os';
|
|
15
|
+
import { execSync } from 'child_process';
|
|
16
|
+
import { getConfig } from '../config.js';
|
|
17
|
+
const PLIST_LABEL = 'com.nstantpage.agent';
|
|
18
|
+
const SYSTEMD_SERVICE = 'nstantpage-agent';
|
|
19
|
+
export async function serviceCommand(options) {
|
|
20
|
+
if (options.uninstall) {
|
|
21
|
+
return uninstallService();
|
|
22
|
+
}
|
|
23
|
+
return installService(options);
|
|
24
|
+
}
|
|
25
|
+
function getAgentBinPath() {
|
|
26
|
+
// Find the globally installed nstantpage binary
|
|
27
|
+
try {
|
|
28
|
+
const resolved = execSync('which nstantpage 2>/dev/null || which nstantpage-agent 2>/dev/null', { encoding: 'utf-8' }).trim();
|
|
29
|
+
if (resolved)
|
|
30
|
+
return resolved;
|
|
31
|
+
}
|
|
32
|
+
catch { }
|
|
33
|
+
// Fallback: try npm root -g
|
|
34
|
+
try {
|
|
35
|
+
const npmRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim();
|
|
36
|
+
const bin = path.join(npmRoot, '.bin', 'nstantpage');
|
|
37
|
+
if (fs.existsSync(bin))
|
|
38
|
+
return bin;
|
|
39
|
+
}
|
|
40
|
+
catch { }
|
|
41
|
+
throw new Error('Could not find nstantpage binary. Install globally first: npm install -g nstantpage-agent');
|
|
42
|
+
}
|
|
43
|
+
function getNodePath() {
|
|
44
|
+
try {
|
|
45
|
+
return execSync('which node', { encoding: 'utf-8' }).trim();
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return '/usr/local/bin/node';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function installService(options) {
|
|
52
|
+
const conf = getConfig();
|
|
53
|
+
const token = conf.get('token');
|
|
54
|
+
if (!token) {
|
|
55
|
+
console.log(chalk.red('✗ Not authenticated. Run "nstantpage login" first.'));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
const projectId = options.projectId;
|
|
59
|
+
if (!projectId) {
|
|
60
|
+
console.log(chalk.red('✗ --project-id is required for service mode.'));
|
|
61
|
+
console.log(chalk.gray(' Example: nstantpage service --project-id 1234'));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
const gateway = options.gateway || 'wss://webprev.live';
|
|
65
|
+
const platform = os.platform();
|
|
66
|
+
if (platform === 'darwin') {
|
|
67
|
+
await installLaunchd(projectId, gateway, token);
|
|
68
|
+
}
|
|
69
|
+
else if (platform === 'linux') {
|
|
70
|
+
await installSystemd(projectId, gateway, token);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
console.log(chalk.yellow('⚠ Background service not yet supported on this platform.'));
|
|
74
|
+
console.log(chalk.gray(' Use "nstantpage start" to run manually.'));
|
|
75
|
+
console.log(chalk.gray(' Windows support coming soon.'));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function installLaunchd(projectId, gateway, token) {
|
|
80
|
+
const binPath = getAgentBinPath();
|
|
81
|
+
const nodePath = getNodePath();
|
|
82
|
+
const plistDir = path.join(os.homedir(), 'Library', 'LaunchAgents');
|
|
83
|
+
const plistPath = path.join(plistDir, `${PLIST_LABEL}.plist`);
|
|
84
|
+
const logPath = path.join(os.homedir(), '.nstantpage', 'agent.log');
|
|
85
|
+
const errPath = path.join(os.homedir(), '.nstantpage', 'agent.err.log');
|
|
86
|
+
// Ensure dirs exist
|
|
87
|
+
fs.mkdirSync(plistDir, { recursive: true });
|
|
88
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
89
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
90
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
91
|
+
<plist version="1.0">
|
|
92
|
+
<dict>
|
|
93
|
+
<key>Label</key>
|
|
94
|
+
<string>${PLIST_LABEL}</string>
|
|
95
|
+
<key>ProgramArguments</key>
|
|
96
|
+
<array>
|
|
97
|
+
<string>${nodePath}</string>
|
|
98
|
+
<string>${binPath}</string>
|
|
99
|
+
<string>start</string>
|
|
100
|
+
<string>--project-id</string>
|
|
101
|
+
<string>${projectId}</string>
|
|
102
|
+
<string>--gateway</string>
|
|
103
|
+
<string>${gateway}</string>
|
|
104
|
+
<string>--token</string>
|
|
105
|
+
<string>${token}</string>
|
|
106
|
+
</array>
|
|
107
|
+
<key>RunAtLoad</key>
|
|
108
|
+
<true/>
|
|
109
|
+
<key>KeepAlive</key>
|
|
110
|
+
<dict>
|
|
111
|
+
<key>SuccessfulExit</key>
|
|
112
|
+
<false/>
|
|
113
|
+
</dict>
|
|
114
|
+
<key>StandardOutPath</key>
|
|
115
|
+
<string>${logPath}</string>
|
|
116
|
+
<key>StandardErrorPath</key>
|
|
117
|
+
<string>${errPath}</string>
|
|
118
|
+
<key>EnvironmentVariables</key>
|
|
119
|
+
<dict>
|
|
120
|
+
<key>PATH</key>
|
|
121
|
+
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
122
|
+
</dict>
|
|
123
|
+
<key>ThrottleInterval</key>
|
|
124
|
+
<integer>10</integer>
|
|
125
|
+
</dict>
|
|
126
|
+
</plist>`;
|
|
127
|
+
fs.writeFileSync(plistPath, plist, 'utf-8');
|
|
128
|
+
// Unload if already running
|
|
129
|
+
try {
|
|
130
|
+
execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { encoding: 'utf-8' });
|
|
131
|
+
}
|
|
132
|
+
catch { }
|
|
133
|
+
// Load
|
|
134
|
+
execSync(`launchctl load "${plistPath}"`, { encoding: 'utf-8' });
|
|
135
|
+
console.log(chalk.green('\n ✓ Agent installed as background service (launchd)\n'));
|
|
136
|
+
console.log(chalk.gray(` Plist: ${plistPath}`));
|
|
137
|
+
console.log(chalk.gray(` Project: ${projectId}`));
|
|
138
|
+
console.log(chalk.gray(` Log: ${logPath}`));
|
|
139
|
+
console.log(chalk.gray(` Status: launchctl list | grep nstantpage`));
|
|
140
|
+
console.log(chalk.gray(` Stop: nstantpage service --uninstall\n`));
|
|
141
|
+
console.log(chalk.blue(' The agent will now start automatically on login.'));
|
|
142
|
+
}
|
|
143
|
+
async function installSystemd(projectId, gateway, token) {
|
|
144
|
+
const binPath = getAgentBinPath();
|
|
145
|
+
const serviceDir = path.join(os.homedir(), '.config', 'systemd', 'user');
|
|
146
|
+
const servicePath = path.join(serviceDir, `${SYSTEMD_SERVICE}.service`);
|
|
147
|
+
fs.mkdirSync(serviceDir, { recursive: true });
|
|
148
|
+
const unit = `[Unit]
|
|
149
|
+
Description=nstantpage Local Development Agent
|
|
150
|
+
After=network-online.target
|
|
151
|
+
Wants=network-online.target
|
|
152
|
+
|
|
153
|
+
[Service]
|
|
154
|
+
Type=simple
|
|
155
|
+
ExecStart=${binPath} start --project-id ${projectId} --gateway ${gateway} --token ${token}
|
|
156
|
+
Restart=on-failure
|
|
157
|
+
RestartSec=10
|
|
158
|
+
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
|
159
|
+
Environment=HOME=${os.homedir()}
|
|
160
|
+
|
|
161
|
+
[Install]
|
|
162
|
+
WantedBy=default.target
|
|
163
|
+
`;
|
|
164
|
+
fs.writeFileSync(servicePath, unit, 'utf-8');
|
|
165
|
+
try {
|
|
166
|
+
execSync('systemctl --user daemon-reload', { encoding: 'utf-8' });
|
|
167
|
+
execSync(`systemctl --user enable ${SYSTEMD_SERVICE}`, { encoding: 'utf-8' });
|
|
168
|
+
execSync(`systemctl --user start ${SYSTEMD_SERVICE}`, { encoding: 'utf-8' });
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
console.log(chalk.yellow(` ⚠ systemctl commands failed: ${err.message}`));
|
|
172
|
+
console.log(chalk.gray(' You may need to reload manually: systemctl --user daemon-reload'));
|
|
173
|
+
}
|
|
174
|
+
console.log(chalk.green('\n ✓ Agent installed as background service (systemd --user)\n'));
|
|
175
|
+
console.log(chalk.gray(` Unit: ${servicePath}`));
|
|
176
|
+
console.log(chalk.gray(` Project: ${projectId}`));
|
|
177
|
+
console.log(chalk.gray(` Status: systemctl --user status ${SYSTEMD_SERVICE}`));
|
|
178
|
+
console.log(chalk.gray(` Logs: journalctl --user -u ${SYSTEMD_SERVICE} -f`));
|
|
179
|
+
console.log(chalk.gray(` Stop: nstantpage service --uninstall\n`));
|
|
180
|
+
console.log(chalk.blue(' The agent will now start automatically on login.'));
|
|
181
|
+
}
|
|
182
|
+
async function uninstallService() {
|
|
183
|
+
const platform = os.platform();
|
|
184
|
+
if (platform === 'darwin') {
|
|
185
|
+
const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', `${PLIST_LABEL}.plist`);
|
|
186
|
+
try {
|
|
187
|
+
execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { encoding: 'utf-8' });
|
|
188
|
+
}
|
|
189
|
+
catch { }
|
|
190
|
+
if (fs.existsSync(plistPath)) {
|
|
191
|
+
fs.unlinkSync(plistPath);
|
|
192
|
+
}
|
|
193
|
+
console.log(chalk.green(' ✓ Agent service uninstalled (launchd)'));
|
|
194
|
+
}
|
|
195
|
+
else if (platform === 'linux') {
|
|
196
|
+
try {
|
|
197
|
+
execSync(`systemctl --user stop ${SYSTEMD_SERVICE} 2>/dev/null`, { encoding: 'utf-8' });
|
|
198
|
+
execSync(`systemctl --user disable ${SYSTEMD_SERVICE} 2>/dev/null`, { encoding: 'utf-8' });
|
|
199
|
+
}
|
|
200
|
+
catch { }
|
|
201
|
+
const servicePath = path.join(os.homedir(), '.config', 'systemd', 'user', `${SYSTEMD_SERVICE}.service`);
|
|
202
|
+
if (fs.existsSync(servicePath)) {
|
|
203
|
+
fs.unlinkSync(servicePath);
|
|
204
|
+
try {
|
|
205
|
+
execSync('systemctl --user daemon-reload', { encoding: 'utf-8' });
|
|
206
|
+
}
|
|
207
|
+
catch { }
|
|
208
|
+
}
|
|
209
|
+
console.log(chalk.green(' ✓ Agent service uninstalled (systemd)'));
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
console.log(chalk.yellow(' ⚠ Service uninstall not supported on this platform'));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=service.js.map
|
package/dist/localServer.d.ts
CHANGED
|
@@ -46,6 +46,10 @@ export declare class LocalServer {
|
|
|
46
46
|
private handleInstall;
|
|
47
47
|
private handleExec;
|
|
48
48
|
private handleTerminal;
|
|
49
|
+
private handleTerminalSessions;
|
|
50
|
+
private handleTerminalWrite;
|
|
51
|
+
private handleTerminalResize;
|
|
52
|
+
private handleAgentStatus;
|
|
49
53
|
private handleContainerStatus;
|
|
50
54
|
private handleContainerStats;
|
|
51
55
|
private handleLogs;
|
package/dist/localServer.js
CHANGED
|
@@ -14,11 +14,16 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import http from 'http';
|
|
16
16
|
import os from 'os';
|
|
17
|
+
import { spawn } from 'child_process';
|
|
17
18
|
import { DevServer } from './devServer.js';
|
|
18
19
|
import { FileManager } from './fileManager.js';
|
|
19
20
|
import { Checker } from './checker.js';
|
|
20
21
|
import { ErrorStore, structuredErrorToString } from './errorStore.js';
|
|
21
22
|
import { PackageInstaller } from './packageInstaller.js';
|
|
23
|
+
const terminalSessions = new Map();
|
|
24
|
+
function generateSessionId() {
|
|
25
|
+
return Math.random().toString(16).slice(2, 10);
|
|
26
|
+
}
|
|
22
27
|
export class LocalServer {
|
|
23
28
|
server = null;
|
|
24
29
|
options;
|
|
@@ -113,6 +118,10 @@ export class LocalServer {
|
|
|
113
118
|
'/live/install': this.handleInstall,
|
|
114
119
|
'/live/exec': this.handleExec,
|
|
115
120
|
'/live/terminal': this.handleTerminal,
|
|
121
|
+
'/live/terminal/sessions': this.handleTerminalSessions,
|
|
122
|
+
'/live/terminal/write': this.handleTerminalWrite,
|
|
123
|
+
'/live/terminal/resize': this.handleTerminalResize,
|
|
124
|
+
'/live/agent-status': this.handleAgentStatus,
|
|
116
125
|
'/live/container-status': this.handleContainerStatus,
|
|
117
126
|
'/live/container-stats': this.handleContainerStats,
|
|
118
127
|
'/live/logs': this.handleLogs,
|
|
@@ -286,6 +295,144 @@ export class LocalServer {
|
|
|
286
295
|
stderr: result.stderr,
|
|
287
296
|
});
|
|
288
297
|
}
|
|
298
|
+
// ─── /live/terminal/sessions ─────────────────────────────────
|
|
299
|
+
async handleTerminalSessions(req, res, body, url) {
|
|
300
|
+
const method = req.method?.toUpperCase() || 'GET';
|
|
301
|
+
// GET → list sessions for this project
|
|
302
|
+
if (method === 'GET') {
|
|
303
|
+
const sessions = Array.from(terminalSessions.values())
|
|
304
|
+
.filter(s => s.projectId === this.options.projectId)
|
|
305
|
+
.map(s => ({
|
|
306
|
+
sessionId: s.id,
|
|
307
|
+
projectId: s.projectId,
|
|
308
|
+
createdAt: s.createdAt,
|
|
309
|
+
lastActivity: s.lastActivity,
|
|
310
|
+
cols: s.cols,
|
|
311
|
+
rows: s.rows,
|
|
312
|
+
isAiSession: s.isAiSession,
|
|
313
|
+
}));
|
|
314
|
+
this.json(res, { success: true, sessions });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
// POST → create a new terminal session
|
|
318
|
+
if (method !== 'POST') {
|
|
319
|
+
res.statusCode = 405;
|
|
320
|
+
this.json(res, { error: 'Method not allowed' });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
let parsed = {};
|
|
324
|
+
try {
|
|
325
|
+
parsed = body ? JSON.parse(body) : {};
|
|
326
|
+
}
|
|
327
|
+
catch { }
|
|
328
|
+
const sessionId = generateSessionId();
|
|
329
|
+
const cols = parsed.cols || 120;
|
|
330
|
+
const rows = parsed.rows || 30;
|
|
331
|
+
const isAiSession = parsed.isAiSession || false;
|
|
332
|
+
// Determine shell
|
|
333
|
+
const shellCmd = process.platform === 'win32' ? 'cmd.exe' : (process.env.SHELL || '/bin/bash');
|
|
334
|
+
const shell = spawn(shellCmd, [], {
|
|
335
|
+
cwd: this.options.projectDir,
|
|
336
|
+
env: { ...process.env, TERM: 'xterm-256color', COLUMNS: String(cols), LINES: String(rows) },
|
|
337
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
338
|
+
});
|
|
339
|
+
const session = {
|
|
340
|
+
id: sessionId,
|
|
341
|
+
projectId: this.options.projectId,
|
|
342
|
+
shell,
|
|
343
|
+
outputBuffer: [],
|
|
344
|
+
createdAt: Date.now(),
|
|
345
|
+
lastActivity: Date.now(),
|
|
346
|
+
cols,
|
|
347
|
+
rows,
|
|
348
|
+
isAiSession,
|
|
349
|
+
};
|
|
350
|
+
shell.stdout?.on('data', (data) => {
|
|
351
|
+
session.outputBuffer.push(data.toString('utf-8'));
|
|
352
|
+
session.lastActivity = Date.now();
|
|
353
|
+
// Keep buffer capped at ~100KB
|
|
354
|
+
while (session.outputBuffer.length > 500)
|
|
355
|
+
session.outputBuffer.shift();
|
|
356
|
+
});
|
|
357
|
+
shell.stderr?.on('data', (data) => {
|
|
358
|
+
session.outputBuffer.push(data.toString('utf-8'));
|
|
359
|
+
session.lastActivity = Date.now();
|
|
360
|
+
while (session.outputBuffer.length > 500)
|
|
361
|
+
session.outputBuffer.shift();
|
|
362
|
+
});
|
|
363
|
+
shell.on('exit', () => {
|
|
364
|
+
terminalSessions.delete(sessionId);
|
|
365
|
+
});
|
|
366
|
+
terminalSessions.set(sessionId, session);
|
|
367
|
+
this.json(res, {
|
|
368
|
+
success: true,
|
|
369
|
+
sessionId,
|
|
370
|
+
projectId: this.options.projectId,
|
|
371
|
+
cols,
|
|
372
|
+
rows,
|
|
373
|
+
isAiSession,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
// ─── /live/terminal/write ────────────────────────────────────
|
|
377
|
+
async handleTerminalWrite(_req, res, body) {
|
|
378
|
+
let parsed = {};
|
|
379
|
+
try {
|
|
380
|
+
parsed = JSON.parse(body);
|
|
381
|
+
}
|
|
382
|
+
catch { }
|
|
383
|
+
const { sessionId, data: inputData } = parsed;
|
|
384
|
+
if (!sessionId || !inputData) {
|
|
385
|
+
res.statusCode = 400;
|
|
386
|
+
this.json(res, { success: false, error: 'sessionId and data required' });
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const session = terminalSessions.get(sessionId);
|
|
390
|
+
if (!session) {
|
|
391
|
+
res.statusCode = 404;
|
|
392
|
+
this.json(res, { success: false, error: 'Session not found' });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
session.shell.stdin?.write(inputData);
|
|
396
|
+
session.lastActivity = Date.now();
|
|
397
|
+
this.json(res, { success: true });
|
|
398
|
+
}
|
|
399
|
+
// ─── /live/terminal/resize ───────────────────────────────────
|
|
400
|
+
async handleTerminalResize(_req, res, body) {
|
|
401
|
+
let parsed = {};
|
|
402
|
+
try {
|
|
403
|
+
parsed = JSON.parse(body);
|
|
404
|
+
}
|
|
405
|
+
catch { }
|
|
406
|
+
const { sessionId, cols, rows } = parsed;
|
|
407
|
+
if (!sessionId) {
|
|
408
|
+
res.statusCode = 400;
|
|
409
|
+
this.json(res, { success: false, error: 'sessionId required' });
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const session = terminalSessions.get(sessionId);
|
|
413
|
+
if (!session) {
|
|
414
|
+
res.statusCode = 404;
|
|
415
|
+
this.json(res, { success: false, error: 'Session not found' });
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
session.cols = cols || session.cols;
|
|
419
|
+
session.rows = rows || session.rows;
|
|
420
|
+
// For real pty we'd call pty.resize(cols, rows), but for child_process
|
|
421
|
+
// we just update the env (takes effect on next data)
|
|
422
|
+
this.json(res, { success: true, cols: session.cols, rows: session.rows });
|
|
423
|
+
}
|
|
424
|
+
// ─── /live/agent-status ──────────────────────────────────────
|
|
425
|
+
async handleAgentStatus(_req, res) {
|
|
426
|
+
this.json(res, {
|
|
427
|
+
connected: true,
|
|
428
|
+
projectId: this.options.projectId,
|
|
429
|
+
agent: {
|
|
430
|
+
version: '0.4.1',
|
|
431
|
+
hostname: os.hostname(),
|
|
432
|
+
platform: `${os.platform()} ${os.arch()}`,
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
}
|
|
289
436
|
// ─── /live/container-status ──────────────────────────────────
|
|
290
437
|
async handleContainerStatus(_req, res, _body, url) {
|
|
291
438
|
this.json(res, {
|
package/package.json
CHANGED