nebula-notebook 0.1.0
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/README.md +222 -0
- package/bin/nebula-notebook.js +33 -0
- package/dist/assets/index-C1h_sArD.css +32 -0
- package/dist/assets/index-CDSTBon8.js +658 -0
- package/dist/favicon.svg +11 -0
- package/dist/index.html +73 -0
- package/node-server/dist/app.d.ts +5 -0
- package/node-server/dist/app.js +38 -0
- package/node-server/dist/auth/auth-middleware.d.ts +24 -0
- package/node-server/dist/auth/auth-middleware.js +276 -0
- package/node-server/dist/auth/auth-service.d.ts +84 -0
- package/node-server/dist/auth/auth-service.js +265 -0
- package/node-server/dist/auth/index.d.ts +3 -0
- package/node-server/dist/auth/index.js +8 -0
- package/node-server/dist/cluster/client-registration.d.ts +43 -0
- package/node-server/dist/cluster/client-registration.js +217 -0
- package/node-server/dist/cluster/cluster-secret.d.ts +11 -0
- package/node-server/dist/cluster/cluster-secret.js +90 -0
- package/node-server/dist/cluster/kernel-proxy.d.ts +100 -0
- package/node-server/dist/cluster/kernel-proxy.js +361 -0
- package/node-server/dist/cluster/server-registry.d.ts +109 -0
- package/node-server/dist/cluster/server-registry.js +217 -0
- package/node-server/dist/config/output-limits.d.ts +7 -0
- package/node-server/dist/config/output-limits.js +10 -0
- package/node-server/dist/discovery/discovery-service.d.ts +198 -0
- package/node-server/dist/discovery/discovery-service.js +811 -0
- package/node-server/dist/discovery/index.d.ts +5 -0
- package/node-server/dist/discovery/index.js +21 -0
- package/node-server/dist/discovery/types.d.ts +48 -0
- package/node-server/dist/discovery/types.js +24 -0
- package/node-server/dist/fs/fs-service.d.ts +218 -0
- package/node-server/dist/fs/fs-service.js +1422 -0
- package/node-server/dist/fs/index.d.ts +5 -0
- package/node-server/dist/fs/index.js +21 -0
- package/node-server/dist/fs/types.d.ts +132 -0
- package/node-server/dist/fs/types.js +5 -0
- package/node-server/dist/index.d.ts +13 -0
- package/node-server/dist/index.js +556 -0
- package/node-server/dist/kernel/default-kernel.d.ts +5 -0
- package/node-server/dist/kernel/default-kernel.js +138 -0
- package/node-server/dist/kernel/index.d.ts +7 -0
- package/node-server/dist/kernel/index.js +23 -0
- package/node-server/dist/kernel/kernel-service.d.ts +290 -0
- package/node-server/dist/kernel/kernel-service.js +1714 -0
- package/node-server/dist/kernel/kernelspec.d.ts +29 -0
- package/node-server/dist/kernel/kernelspec.js +210 -0
- package/node-server/dist/kernel/session-store.d.ts +87 -0
- package/node-server/dist/kernel/session-store.js +303 -0
- package/node-server/dist/kernel/types.d.ts +143 -0
- package/node-server/dist/kernel/types.js +17 -0
- package/node-server/dist/llm/index.d.ts +5 -0
- package/node-server/dist/llm/index.js +21 -0
- package/node-server/dist/llm/llm-service.d.ts +77 -0
- package/node-server/dist/llm/llm-service.js +454 -0
- package/node-server/dist/llm/types.d.ts +40 -0
- package/node-server/dist/llm/types.js +15 -0
- package/node-server/dist/notebook/cell-metadata.d.ts +27 -0
- package/node-server/dist/notebook/cell-metadata.js +76 -0
- package/node-server/dist/notebook/headless-handler.d.ts +127 -0
- package/node-server/dist/notebook/headless-handler.js +1530 -0
- package/node-server/dist/notebook/notebook-websocket.d.ts +12 -0
- package/node-server/dist/notebook/notebook-websocket.js +103 -0
- package/node-server/dist/notebook/operation-router.d.ts +115 -0
- package/node-server/dist/notebook/operation-router.js +641 -0
- package/node-server/dist/notebook/undoRedoManager.d.ts +194 -0
- package/node-server/dist/notebook/undoRedoManager.js +558 -0
- package/node-server/dist/output/display-data.d.ts +14 -0
- package/node-server/dist/output/display-data.js +134 -0
- package/node-server/dist/resources/resource-service.d.ts +69 -0
- package/node-server/dist/resources/resource-service.js +363 -0
- package/node-server/dist/routes/auth.d.ts +5 -0
- package/node-server/dist/routes/auth.js +61 -0
- package/node-server/dist/routes/cluster.d.ts +7 -0
- package/node-server/dist/routes/cluster.js +94 -0
- package/node-server/dist/routes/fs.d.ts +7 -0
- package/node-server/dist/routes/fs.js +392 -0
- package/node-server/dist/routes/kernel.d.ts +13 -0
- package/node-server/dist/routes/kernel.js +637 -0
- package/node-server/dist/routes/llm.d.ts +8 -0
- package/node-server/dist/routes/llm.js +105 -0
- package/node-server/dist/routes/notebook.d.ts +10 -0
- package/node-server/dist/routes/notebook.js +335 -0
- package/node-server/dist/routes/python.d.ts +8 -0
- package/node-server/dist/routes/python.js +187 -0
- package/node-server/dist/routes/resources.d.ts +7 -0
- package/node-server/dist/routes/resources.js +77 -0
- package/node-server/dist/scripts/show-auth-qr.d.ts +1 -0
- package/node-server/dist/scripts/show-auth-qr.js +81 -0
- package/node-server/dist/terminal/pty-manager.d.ts +100 -0
- package/node-server/dist/terminal/pty-manager.js +246 -0
- package/node-server/dist/terminal/server.d.ts +19 -0
- package/node-server/dist/terminal/server.js +254 -0
- package/node-server/dist/terminal/types.d.ts +50 -0
- package/node-server/dist/terminal/types.js +9 -0
- package/node-server/package.json +45 -0
- package/package.json +99 -0
- package/scripts/postinstall.cjs +26 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Resources API Routes
|
|
4
|
+
*
|
|
5
|
+
* Provides endpoints for system resource monitoring (RAM, GPU).
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.default = resourceRoutes;
|
|
9
|
+
const resource_service_1 = require("../resources/resource-service");
|
|
10
|
+
const server_registry_1 = require("../cluster/server-registry");
|
|
11
|
+
const resourceService = (0, resource_service_1.getResourceService)();
|
|
12
|
+
async function resourceRoutes(fastify) {
|
|
13
|
+
/**
|
|
14
|
+
* GET /
|
|
15
|
+
*
|
|
16
|
+
* Get server resources (RAM, GPU).
|
|
17
|
+
* If server_id is provided and not local, fetches from remote server.
|
|
18
|
+
* Returns cached data - never blocks.
|
|
19
|
+
*/
|
|
20
|
+
fastify.get('/', async (request, reply) => {
|
|
21
|
+
try {
|
|
22
|
+
const serverId = request.query.server_id;
|
|
23
|
+
const localServerId = server_registry_1.serverRegistry.getLocalServerId();
|
|
24
|
+
// Check if we need to fetch from a remote server
|
|
25
|
+
if (serverId && serverId !== localServerId && serverId !== 'local') {
|
|
26
|
+
const server = server_registry_1.serverRegistry.getServer(serverId);
|
|
27
|
+
if (!server) {
|
|
28
|
+
return reply.code(404).send({ error: `Server not found: ${serverId}` });
|
|
29
|
+
}
|
|
30
|
+
if (server.status !== 'online') {
|
|
31
|
+
return reply.code(503).send({ error: `Server is offline: ${serverId}` });
|
|
32
|
+
}
|
|
33
|
+
// Fetch from remote server
|
|
34
|
+
const response = await fetch(`${server.url}/api/resources`, {
|
|
35
|
+
headers: process.env.NEBULA_CLUSTER_SECRET
|
|
36
|
+
? { 'X-Nebula-Cluster-Secret': process.env.NEBULA_CLUSTER_SECRET }
|
|
37
|
+
: undefined,
|
|
38
|
+
});
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
41
|
+
return reply.code(response.status).send({ error: error.error || 'Failed to fetch resources' });
|
|
42
|
+
}
|
|
43
|
+
const data = await response.json();
|
|
44
|
+
return reply.send(data);
|
|
45
|
+
}
|
|
46
|
+
// Return local resources
|
|
47
|
+
const resources = resourceService.getResources();
|
|
48
|
+
return reply.send({
|
|
49
|
+
...resources,
|
|
50
|
+
isStale: resourceService.isStale(),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
55
|
+
return reply.code(500).send({ error: message });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* POST /refresh
|
|
60
|
+
*
|
|
61
|
+
* Force refresh resources (still has timeout protection).
|
|
62
|
+
* Use sparingly - normally cached data is sufficient.
|
|
63
|
+
*/
|
|
64
|
+
fastify.post('/refresh', async (_request, reply) => {
|
|
65
|
+
try {
|
|
66
|
+
const resources = await resourceService.refreshResources();
|
|
67
|
+
return reply.send({
|
|
68
|
+
...resources,
|
|
69
|
+
isStale: false,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
74
|
+
return reply.code(500).send({ error: message });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const fs_1 = __importDefault(require("fs"));
|
|
40
|
+
const os_1 = __importDefault(require("os"));
|
|
41
|
+
const path_1 = __importDefault(require("path"));
|
|
42
|
+
const otplib_1 = require("otplib");
|
|
43
|
+
const qrcode = __importStar(require("qrcode-terminal"));
|
|
44
|
+
const AUTH_CONFIG_FILE = path_1.default.join(os_1.default.homedir(), '.nebula', 'auth.json');
|
|
45
|
+
const ISSUER = 'NebulaNotebook';
|
|
46
|
+
const ACCOUNT_NAME = 'local';
|
|
47
|
+
function fail(message) {
|
|
48
|
+
console.error(message);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
function readAuthConfig() {
|
|
52
|
+
if (!fs_1.default.existsSync(AUTH_CONFIG_FILE)) {
|
|
53
|
+
fail(`[Auth] Config not found: ${AUTH_CONFIG_FILE}\n[Auth] Start the Nebula server once to initialize 2FA.`);
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const raw = fs_1.default.readFileSync(AUTH_CONFIG_FILE, 'utf-8');
|
|
57
|
+
return JSON.parse(raw);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
61
|
+
fail(`[Auth] Failed to read ${AUTH_CONFIG_FILE}: ${detail}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function main() {
|
|
65
|
+
const config = readAuthConfig();
|
|
66
|
+
const secret = typeof config.totpSecret === 'string' ? config.totpSecret.trim() : '';
|
|
67
|
+
if (!secret) {
|
|
68
|
+
fail(`[Auth] Missing \"totpSecret\" in ${AUTH_CONFIG_FILE}`);
|
|
69
|
+
}
|
|
70
|
+
const otpAuthUrl = otplib_1.authenticator.keyuri(ACCOUNT_NAME, ISSUER, secret);
|
|
71
|
+
console.log('\n' + '='.repeat(60));
|
|
72
|
+
console.log(' NEBULA NOTEBOOK - 2FA QR');
|
|
73
|
+
console.log('='.repeat(60));
|
|
74
|
+
console.log('\nScan this QR code with your authenticator app:\n');
|
|
75
|
+
qrcode.generate(otpAuthUrl, { small: true });
|
|
76
|
+
console.log('\nOr enter this key manually: ' + secret);
|
|
77
|
+
console.log(`\nConfig: ${AUTH_CONFIG_FILE}`);
|
|
78
|
+
console.log(`[Auth] setupComplete=${config.setupComplete === true ? 'true' : 'false'}`);
|
|
79
|
+
console.log('='.repeat(60) + '\n');
|
|
80
|
+
}
|
|
81
|
+
main();
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PTY Manager - Manages terminal sessions using node-pty
|
|
3
|
+
*/
|
|
4
|
+
import * as pty from '@homebridge/node-pty-prebuilt-multiarch';
|
|
5
|
+
import { TerminalInfo } from './types';
|
|
6
|
+
interface TerminalSession {
|
|
7
|
+
id: string;
|
|
8
|
+
pty: pty.IPty;
|
|
9
|
+
cwd: string;
|
|
10
|
+
shell: string;
|
|
11
|
+
cols: number;
|
|
12
|
+
rows: number;
|
|
13
|
+
created: number;
|
|
14
|
+
lastActivity: number;
|
|
15
|
+
outputBuffer: string;
|
|
16
|
+
onData: ((data: string) => void) | null;
|
|
17
|
+
onExit: ((code: number) => void) | null;
|
|
18
|
+
}
|
|
19
|
+
export declare class PtyManager {
|
|
20
|
+
private sessions;
|
|
21
|
+
/**
|
|
22
|
+
* Get the default shell for the current platform
|
|
23
|
+
*/
|
|
24
|
+
private getDefaultShell;
|
|
25
|
+
/**
|
|
26
|
+
* Normalize a terminal name to a valid ID
|
|
27
|
+
* - Lowercase
|
|
28
|
+
* - Only alphanumeric, hyphens, underscores
|
|
29
|
+
* - Max 32 chars
|
|
30
|
+
* - Falls back to 'default' if empty/invalid
|
|
31
|
+
*/
|
|
32
|
+
normalizeTerminalName(name: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Create a new terminal session
|
|
35
|
+
*/
|
|
36
|
+
create(options?: {
|
|
37
|
+
id?: string;
|
|
38
|
+
cwd?: string;
|
|
39
|
+
shell?: string;
|
|
40
|
+
cols?: number;
|
|
41
|
+
rows?: number;
|
|
42
|
+
}): TerminalInfo;
|
|
43
|
+
/**
|
|
44
|
+
* Get terminal info from session
|
|
45
|
+
*/
|
|
46
|
+
private getInfo;
|
|
47
|
+
/**
|
|
48
|
+
* Get a terminal session by ID
|
|
49
|
+
*/
|
|
50
|
+
get(id: string): TerminalSession | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Get terminal info by ID
|
|
53
|
+
*/
|
|
54
|
+
getTerminalInfo(id: string): TerminalInfo | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* List all active terminals
|
|
57
|
+
*/
|
|
58
|
+
list(): TerminalInfo[];
|
|
59
|
+
/**
|
|
60
|
+
* Write data to a terminal
|
|
61
|
+
*/
|
|
62
|
+
write(id: string, data: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Resize a terminal
|
|
65
|
+
*/
|
|
66
|
+
resize(id: string, cols: number, rows: number): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Get or create a terminal by name
|
|
69
|
+
* If a terminal with the normalized name exists, return it
|
|
70
|
+
* Otherwise create a new one with that name
|
|
71
|
+
*/
|
|
72
|
+
getOrCreate(name: string, options?: {
|
|
73
|
+
cwd?: string;
|
|
74
|
+
shell?: string;
|
|
75
|
+
cols?: number;
|
|
76
|
+
rows?: number;
|
|
77
|
+
}): TerminalInfo;
|
|
78
|
+
/**
|
|
79
|
+
* Kill a terminal session
|
|
80
|
+
*/
|
|
81
|
+
kill(id: string): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Set data listener for a terminal (for WebSocket connection)
|
|
84
|
+
*/
|
|
85
|
+
setOnData(id: string, callback: ((data: string) => void) | null): void;
|
|
86
|
+
/**
|
|
87
|
+
* Set exit listener for a terminal
|
|
88
|
+
*/
|
|
89
|
+
setOnExit(id: string, callback: ((code: number) => void) | null): void;
|
|
90
|
+
/**
|
|
91
|
+
* Get buffered output for reconnection
|
|
92
|
+
*/
|
|
93
|
+
getOutputBuffer(id: string): string;
|
|
94
|
+
/**
|
|
95
|
+
* Kill all terminal sessions (for cleanup)
|
|
96
|
+
*/
|
|
97
|
+
killAll(): void;
|
|
98
|
+
}
|
|
99
|
+
export declare const ptyManager: PtyManager;
|
|
100
|
+
export {};
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PTY Manager - Manages terminal sessions using node-pty
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.ptyManager = exports.PtyManager = void 0;
|
|
40
|
+
const pty = __importStar(require("@homebridge/node-pty-prebuilt-multiarch"));
|
|
41
|
+
const uuid_1 = require("uuid");
|
|
42
|
+
const types_1 = require("./types");
|
|
43
|
+
class PtyManager {
|
|
44
|
+
sessions = new Map();
|
|
45
|
+
/**
|
|
46
|
+
* Get the default shell for the current platform
|
|
47
|
+
*/
|
|
48
|
+
getDefaultShell() {
|
|
49
|
+
if (process.platform === 'win32') {
|
|
50
|
+
return process.env.COMSPEC || 'cmd.exe';
|
|
51
|
+
}
|
|
52
|
+
return process.env.SHELL || '/bin/bash';
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Normalize a terminal name to a valid ID
|
|
56
|
+
* - Lowercase
|
|
57
|
+
* - Only alphanumeric, hyphens, underscores
|
|
58
|
+
* - Max 32 chars
|
|
59
|
+
* - Falls back to 'default' if empty/invalid
|
|
60
|
+
*/
|
|
61
|
+
normalizeTerminalName(name) {
|
|
62
|
+
const normalized = name
|
|
63
|
+
.toLowerCase()
|
|
64
|
+
.replace(/[^a-z0-9_-]/g, '-')
|
|
65
|
+
.replace(/-+/g, '-')
|
|
66
|
+
.replace(/^-|-$/g, '')
|
|
67
|
+
.slice(0, 32);
|
|
68
|
+
return normalized || 'default';
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Create a new terminal session
|
|
72
|
+
*/
|
|
73
|
+
create(options) {
|
|
74
|
+
const id = options?.id ? this.normalizeTerminalName(options.id) : (0, uuid_1.v4)();
|
|
75
|
+
const shell = options?.shell || this.getDefaultShell();
|
|
76
|
+
const cwd = options?.cwd || process.cwd();
|
|
77
|
+
const cols = options?.cols || 80;
|
|
78
|
+
const rows = options?.rows || 24;
|
|
79
|
+
// Spawn PTY process
|
|
80
|
+
const ptyProcess = pty.spawn(shell, [], {
|
|
81
|
+
name: 'xterm-256color',
|
|
82
|
+
cols,
|
|
83
|
+
rows,
|
|
84
|
+
cwd,
|
|
85
|
+
env: {
|
|
86
|
+
...process.env,
|
|
87
|
+
TERM: 'xterm-256color',
|
|
88
|
+
COLORTERM: 'truecolor',
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
const session = {
|
|
92
|
+
id,
|
|
93
|
+
pty: ptyProcess,
|
|
94
|
+
cwd,
|
|
95
|
+
shell,
|
|
96
|
+
cols,
|
|
97
|
+
rows,
|
|
98
|
+
created: Date.now(),
|
|
99
|
+
lastActivity: Date.now(),
|
|
100
|
+
outputBuffer: '',
|
|
101
|
+
onData: null,
|
|
102
|
+
onExit: null,
|
|
103
|
+
};
|
|
104
|
+
// Handle PTY output
|
|
105
|
+
ptyProcess.onData((data) => {
|
|
106
|
+
session.lastActivity = Date.now();
|
|
107
|
+
// Append to output buffer for reconnection
|
|
108
|
+
session.outputBuffer += data;
|
|
109
|
+
// Trim buffer if too large
|
|
110
|
+
if (session.outputBuffer.length > types_1.OUTPUT_BUFFER_MAX_SIZE) {
|
|
111
|
+
session.outputBuffer = session.outputBuffer.slice(-types_1.OUTPUT_BUFFER_TRIM_SIZE);
|
|
112
|
+
}
|
|
113
|
+
// Forward to listener if connected
|
|
114
|
+
if (session.onData) {
|
|
115
|
+
session.onData(data);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
// Handle PTY exit
|
|
119
|
+
ptyProcess.onExit(({ exitCode }) => {
|
|
120
|
+
if (session.onExit) {
|
|
121
|
+
session.onExit(exitCode);
|
|
122
|
+
}
|
|
123
|
+
this.sessions.delete(id);
|
|
124
|
+
});
|
|
125
|
+
this.sessions.set(id, session);
|
|
126
|
+
return this.getInfo(session);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get terminal info from session
|
|
130
|
+
*/
|
|
131
|
+
getInfo(session) {
|
|
132
|
+
return {
|
|
133
|
+
id: session.id,
|
|
134
|
+
pid: session.pty.pid,
|
|
135
|
+
cwd: session.cwd,
|
|
136
|
+
shell: session.shell,
|
|
137
|
+
cols: session.cols,
|
|
138
|
+
rows: session.rows,
|
|
139
|
+
created: session.created,
|
|
140
|
+
lastActivity: session.lastActivity,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get a terminal session by ID
|
|
145
|
+
*/
|
|
146
|
+
get(id) {
|
|
147
|
+
return this.sessions.get(id);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get terminal info by ID
|
|
151
|
+
*/
|
|
152
|
+
getTerminalInfo(id) {
|
|
153
|
+
const session = this.sessions.get(id);
|
|
154
|
+
return session ? this.getInfo(session) : undefined;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* List all active terminals
|
|
158
|
+
*/
|
|
159
|
+
list() {
|
|
160
|
+
return Array.from(this.sessions.values()).map((s) => this.getInfo(s));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Write data to a terminal
|
|
164
|
+
*/
|
|
165
|
+
write(id, data) {
|
|
166
|
+
const session = this.sessions.get(id);
|
|
167
|
+
if (!session)
|
|
168
|
+
return false;
|
|
169
|
+
session.pty.write(data);
|
|
170
|
+
session.lastActivity = Date.now();
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Resize a terminal
|
|
175
|
+
*/
|
|
176
|
+
resize(id, cols, rows) {
|
|
177
|
+
const session = this.sessions.get(id);
|
|
178
|
+
if (!session)
|
|
179
|
+
return false;
|
|
180
|
+
session.pty.resize(cols, rows);
|
|
181
|
+
session.cols = cols;
|
|
182
|
+
session.rows = rows;
|
|
183
|
+
session.lastActivity = Date.now();
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get or create a terminal by name
|
|
188
|
+
* If a terminal with the normalized name exists, return it
|
|
189
|
+
* Otherwise create a new one with that name
|
|
190
|
+
*/
|
|
191
|
+
getOrCreate(name, options) {
|
|
192
|
+
const normalizedId = this.normalizeTerminalName(name);
|
|
193
|
+
const existing = this.sessions.get(normalizedId);
|
|
194
|
+
if (existing) {
|
|
195
|
+
return this.getInfo(existing);
|
|
196
|
+
}
|
|
197
|
+
return this.create({ ...options, id: normalizedId });
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Kill a terminal session
|
|
201
|
+
*/
|
|
202
|
+
kill(id) {
|
|
203
|
+
const session = this.sessions.get(id);
|
|
204
|
+
if (!session)
|
|
205
|
+
return false;
|
|
206
|
+
session.pty.kill();
|
|
207
|
+
this.sessions.delete(id);
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Set data listener for a terminal (for WebSocket connection)
|
|
212
|
+
*/
|
|
213
|
+
setOnData(id, callback) {
|
|
214
|
+
const session = this.sessions.get(id);
|
|
215
|
+
if (session) {
|
|
216
|
+
session.onData = callback;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Set exit listener for a terminal
|
|
221
|
+
*/
|
|
222
|
+
setOnExit(id, callback) {
|
|
223
|
+
const session = this.sessions.get(id);
|
|
224
|
+
if (session) {
|
|
225
|
+
session.onExit = callback;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get buffered output for reconnection
|
|
230
|
+
*/
|
|
231
|
+
getOutputBuffer(id) {
|
|
232
|
+
const session = this.sessions.get(id);
|
|
233
|
+
return session?.outputBuffer || '';
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Kill all terminal sessions (for cleanup)
|
|
237
|
+
*/
|
|
238
|
+
killAll() {
|
|
239
|
+
for (const [id] of this.sessions) {
|
|
240
|
+
this.kill(id);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
exports.PtyManager = PtyManager;
|
|
245
|
+
// Singleton instance
|
|
246
|
+
exports.ptyManager = new PtyManager();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal Server - Routes and WebSocket setup for PTY management
|
|
3
|
+
*/
|
|
4
|
+
import { FastifyInstance } from 'fastify';
|
|
5
|
+
import { WebSocketServer } from 'ws';
|
|
6
|
+
import { Server as HttpServer } from 'http';
|
|
7
|
+
/**
|
|
8
|
+
* Setup terminal REST routes as a Fastify plugin
|
|
9
|
+
*/
|
|
10
|
+
export declare function setupTerminalRoutes(fastify: FastifyInstance): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Setup terminal WebSocket server with noServer mode
|
|
13
|
+
* Returns the WSS so the upgrade can be handled externally
|
|
14
|
+
*/
|
|
15
|
+
export declare function setupTerminalWebSocket(server: HttpServer): WebSocketServer;
|
|
16
|
+
/**
|
|
17
|
+
* Cleanup all terminals (call on shutdown)
|
|
18
|
+
*/
|
|
19
|
+
export declare function cleanupTerminals(): void;
|