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,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cluster Management API Routes
|
|
4
|
+
*
|
|
5
|
+
* Endpoints for server registration and cluster management.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.default = clusterRoutes;
|
|
9
|
+
const server_registry_1 = require("../cluster/server-registry");
|
|
10
|
+
const resource_service_1 = require("../resources/resource-service");
|
|
11
|
+
const resourceService = (0, resource_service_1.getResourceService)();
|
|
12
|
+
async function clusterRoutes(fastify) {
|
|
13
|
+
/**
|
|
14
|
+
* GET /servers
|
|
15
|
+
* List all servers in the cluster (local + registered peers)
|
|
16
|
+
* Includes resource info for all servers
|
|
17
|
+
*/
|
|
18
|
+
fastify.get('/servers', async (_request, reply) => {
|
|
19
|
+
// Get local resources (cached, never blocks)
|
|
20
|
+
const localResources = resourceService.getResources();
|
|
21
|
+
const clusterInfo = server_registry_1.serverRegistry.getClusterInfo(localResources);
|
|
22
|
+
return reply.send(clusterInfo);
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* POST /servers/register
|
|
26
|
+
* Register a peer server with this server
|
|
27
|
+
* Body can include { resources } for initial resource info
|
|
28
|
+
*/
|
|
29
|
+
fastify.post('/servers/register', async (request, reply) => {
|
|
30
|
+
const { host, port, name, secret, resources } = request.body;
|
|
31
|
+
if (!host || !port) {
|
|
32
|
+
return reply.code(400).send({ error: 'host and port are required' });
|
|
33
|
+
}
|
|
34
|
+
const result = server_registry_1.serverRegistry.register({ host, port, name, secret, resources });
|
|
35
|
+
if (!result.success) {
|
|
36
|
+
return reply.code(403).send({ error: result.error });
|
|
37
|
+
}
|
|
38
|
+
return reply.send({
|
|
39
|
+
registered: true,
|
|
40
|
+
serverId: result.serverId,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* DELETE /servers/:serverId
|
|
45
|
+
* Unregister a peer server
|
|
46
|
+
*/
|
|
47
|
+
fastify.delete('/servers/:serverId', async (request, reply) => {
|
|
48
|
+
const { serverId } = request.params;
|
|
49
|
+
const { secret } = request.body;
|
|
50
|
+
// Validate secret if configured
|
|
51
|
+
const clusterSecret = process.env.NEBULA_CLUSTER_SECRET;
|
|
52
|
+
if (clusterSecret && secret !== clusterSecret) {
|
|
53
|
+
return reply.code(403).send({ error: 'Invalid cluster secret' });
|
|
54
|
+
}
|
|
55
|
+
const removed = server_registry_1.serverRegistry.unregister(serverId);
|
|
56
|
+
if (!removed) {
|
|
57
|
+
return reply.code(404).send({ error: 'Server not found' });
|
|
58
|
+
}
|
|
59
|
+
return reply.send({ unregistered: true });
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* POST /servers/:serverId/heartbeat
|
|
63
|
+
* Record heartbeat from a peer server
|
|
64
|
+
* Body can include { resources } for system resource info
|
|
65
|
+
*/
|
|
66
|
+
fastify.post('/servers/:serverId/heartbeat', async (request, reply) => {
|
|
67
|
+
const { serverId } = request.params;
|
|
68
|
+
const { resources } = request.body;
|
|
69
|
+
const success = server_registry_1.serverRegistry.heartbeat(serverId, resources);
|
|
70
|
+
if (!success) {
|
|
71
|
+
return reply.code(404).send({ error: 'Server not found' });
|
|
72
|
+
}
|
|
73
|
+
return reply.send({ ok: true });
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* GET /servers/:serverId
|
|
77
|
+
* Get info about a specific server
|
|
78
|
+
*/
|
|
79
|
+
fastify.get('/servers/:serverId', async (request, reply) => {
|
|
80
|
+
const { serverId } = request.params;
|
|
81
|
+
const server = server_registry_1.serverRegistry.getServer(serverId);
|
|
82
|
+
if (!server) {
|
|
83
|
+
return reply.code(404).send({ error: 'Server not found' });
|
|
84
|
+
}
|
|
85
|
+
return reply.send({
|
|
86
|
+
id: server.id,
|
|
87
|
+
name: server.name,
|
|
88
|
+
url: server.url,
|
|
89
|
+
status: server.status,
|
|
90
|
+
registeredAt: server.registeredAt,
|
|
91
|
+
lastHeartbeat: server.lastHeartbeat,
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Filesystem API Routes
|
|
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.fsService = void 0;
|
|
40
|
+
exports.default = fsRoutes;
|
|
41
|
+
const fs_service_1 = require("../fs/fs-service");
|
|
42
|
+
Object.defineProperty(exports, "fsService", { enumerable: true, get: function () { return fs_service_1.fsService; } });
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const os = __importStar(require("os"));
|
|
45
|
+
const nodeFs = __importStar(require("fs"));
|
|
46
|
+
async function fsRoutes(fastify) {
|
|
47
|
+
/**
|
|
48
|
+
* List directory contents
|
|
49
|
+
*/
|
|
50
|
+
fastify.get('/fs/list', async (request, reply) => {
|
|
51
|
+
try {
|
|
52
|
+
const dirPath = request.query.path || '~';
|
|
53
|
+
const result = fs_service_1.fsService.listDirectory(dirPath);
|
|
54
|
+
return reply.send(result);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
if (err instanceof Error) {
|
|
58
|
+
if (err.message.includes('not found') || err.message.includes('ENOENT')) {
|
|
59
|
+
return reply.code(404).send({ detail: err.message });
|
|
60
|
+
}
|
|
61
|
+
else if (err.message.includes('permission') || err.message.includes('EACCES')) {
|
|
62
|
+
return reply.code(403).send({ detail: err.message });
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
return reply.code(500).send({ detail: err.message });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
return reply.code(500).send({ detail: 'Unknown error' });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
/**
|
|
74
|
+
* Get current server root directory
|
|
75
|
+
*/
|
|
76
|
+
fastify.get('/fs/root', async (_request, reply) => {
|
|
77
|
+
return reply.send({ root: fs_service_1.fsService.getRootDirectory() });
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* Set server root directory
|
|
81
|
+
*/
|
|
82
|
+
fastify.post('/fs/root', async (request, reply) => {
|
|
83
|
+
const { root } = request.body || {};
|
|
84
|
+
if (!root || typeof root !== 'string') {
|
|
85
|
+
return reply.code(400).send({ detail: 'root is required' });
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const updated = fs_service_1.fsService.setRootDirectory(root);
|
|
89
|
+
return reply.send({ root: updated });
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
const message = err instanceof Error ? err.message : 'Failed to set root';
|
|
93
|
+
return reply.code(400).send({ detail: message });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* Get directory modification time
|
|
98
|
+
*/
|
|
99
|
+
fastify.get('/fs/mtime', async (request, reply) => {
|
|
100
|
+
try {
|
|
101
|
+
const dirPath = request.query.path || '~';
|
|
102
|
+
const result = fs_service_1.fsService.getDirectoryMtime(dirPath);
|
|
103
|
+
return reply.send(result);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
if (err instanceof Error && err.message.includes('not found')) {
|
|
107
|
+
return reply.code(404).send({ detail: err.message });
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
111
|
+
return reply.code(500).send({ detail: message });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
/**
|
|
116
|
+
* Get file modification time
|
|
117
|
+
*/
|
|
118
|
+
fastify.get('/fs/file-mtime', async (request, reply) => {
|
|
119
|
+
try {
|
|
120
|
+
const filePath = request.query.path;
|
|
121
|
+
if (!filePath) {
|
|
122
|
+
return reply.code(400).send({ detail: 'path query parameter is required' });
|
|
123
|
+
}
|
|
124
|
+
const result = fs_service_1.fsService.getFileMtime(filePath);
|
|
125
|
+
return reply.send(result);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
if (err instanceof Error && err.message.includes('not found')) {
|
|
129
|
+
return reply.code(404).send({ detail: err.message });
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
133
|
+
return reply.code(500).send({ detail: message });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
/**
|
|
138
|
+
* Read file contents
|
|
139
|
+
*/
|
|
140
|
+
fastify.get('/fs/read', async (request, reply) => {
|
|
141
|
+
try {
|
|
142
|
+
const filePath = request.query.path;
|
|
143
|
+
if (!filePath) {
|
|
144
|
+
return reply.code(400).send({ detail: 'path query parameter is required' });
|
|
145
|
+
}
|
|
146
|
+
const result = fs_service_1.fsService.readFile(filePath);
|
|
147
|
+
return reply.send(result);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
if (err instanceof Error) {
|
|
151
|
+
if (err.message.includes('not found') || err.message.includes('ENOENT')) {
|
|
152
|
+
return reply.code(404).send({ detail: err.message });
|
|
153
|
+
}
|
|
154
|
+
else if (err.message.includes('directory') || err.message.includes('EISDIR')) {
|
|
155
|
+
return reply.code(400).send({ detail: err.message });
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
return reply.code(500).send({ detail: err.message });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
return reply.code(500).send({ detail: 'Unknown error' });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
/**
|
|
167
|
+
* Write content to a file
|
|
168
|
+
*/
|
|
169
|
+
fastify.post('/fs/write', async (request, reply) => {
|
|
170
|
+
try {
|
|
171
|
+
const { path: filePath, content, file_type = 'text' } = request.body;
|
|
172
|
+
if (!filePath) {
|
|
173
|
+
return reply.code(400).send({ detail: 'path is required' });
|
|
174
|
+
}
|
|
175
|
+
fs_service_1.fsService.writeFile(filePath, content, file_type);
|
|
176
|
+
return reply.send({ status: 'ok', path: filePath });
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
180
|
+
return reply.code(500).send({ detail: message });
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
/**
|
|
184
|
+
* Create a new file or directory
|
|
185
|
+
*/
|
|
186
|
+
fastify.post('/fs/create', async (request, reply) => {
|
|
187
|
+
try {
|
|
188
|
+
const { path: filePath, is_directory = false } = request.body;
|
|
189
|
+
if (!filePath) {
|
|
190
|
+
return reply.code(400).send({ detail: 'path is required' });
|
|
191
|
+
}
|
|
192
|
+
const info = fs_service_1.fsService.createFile(filePath, is_directory);
|
|
193
|
+
return reply.send({ status: 'ok', file: info });
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
if (err instanceof Error && err.message.includes('exists')) {
|
|
197
|
+
return reply.code(409).send({ detail: err.message });
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
201
|
+
return reply.code(500).send({ detail: message });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
/**
|
|
206
|
+
* Delete a file or directory
|
|
207
|
+
*/
|
|
208
|
+
fastify.delete('/fs/delete', async (request, reply) => {
|
|
209
|
+
try {
|
|
210
|
+
const filePath = request.query.path;
|
|
211
|
+
if (!filePath) {
|
|
212
|
+
return reply.code(400).send({ detail: 'path query parameter is required' });
|
|
213
|
+
}
|
|
214
|
+
fs_service_1.fsService.deleteFile(filePath);
|
|
215
|
+
return reply.send({ status: 'ok' });
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
if (err instanceof Error && err.message.includes('not found')) {
|
|
219
|
+
return reply.code(404).send({ detail: err.message });
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
223
|
+
return reply.code(500).send({ detail: message });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
/**
|
|
228
|
+
* Rename/move a file or directory
|
|
229
|
+
*/
|
|
230
|
+
fastify.post('/fs/rename', async (request, reply) => {
|
|
231
|
+
try {
|
|
232
|
+
const { old_path, new_path } = request.body;
|
|
233
|
+
if (!old_path || !new_path) {
|
|
234
|
+
return reply.code(400).send({ detail: 'old_path and new_path are required' });
|
|
235
|
+
}
|
|
236
|
+
const info = fs_service_1.fsService.renameFile(old_path, new_path);
|
|
237
|
+
return reply.send({ status: 'ok', file: info });
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
if (err instanceof Error) {
|
|
241
|
+
if (err.message.includes('not found')) {
|
|
242
|
+
return reply.code(404).send({ detail: err.message });
|
|
243
|
+
}
|
|
244
|
+
else if (err.message.includes('exists')) {
|
|
245
|
+
return reply.code(409).send({ detail: err.message });
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
return reply.code(500).send({ detail: err.message });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
return reply.code(500).send({ detail: 'Unknown error' });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
/**
|
|
257
|
+
* Duplicate a file or directory
|
|
258
|
+
*/
|
|
259
|
+
fastify.post('/fs/duplicate', async (request, reply) => {
|
|
260
|
+
try {
|
|
261
|
+
const { path: filePath } = request.body;
|
|
262
|
+
if (!filePath) {
|
|
263
|
+
return reply.code(400).send({ detail: 'path is required' });
|
|
264
|
+
}
|
|
265
|
+
const info = fs_service_1.fsService.duplicateFile(filePath);
|
|
266
|
+
return reply.send({ status: 'ok', file: info });
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
if (err instanceof Error) {
|
|
270
|
+
if (err.message.includes('not found')) {
|
|
271
|
+
return reply.code(404).send({ detail: err.message });
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
return reply.code(500).send({ detail: err.message });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
return reply.code(500).send({ detail: 'Unknown error' });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
/**
|
|
283
|
+
* Download a file (raw stream with proper headers)
|
|
284
|
+
*/
|
|
285
|
+
fastify.get('/fs/download', async (request, reply) => {
|
|
286
|
+
try {
|
|
287
|
+
const filePath = request.query.path;
|
|
288
|
+
if (!filePath) {
|
|
289
|
+
return reply.code(400).send({ detail: 'path query parameter is required' });
|
|
290
|
+
}
|
|
291
|
+
const normalizedPath = fs_service_1.fsService.normalizePath(filePath);
|
|
292
|
+
if (!nodeFs.existsSync(normalizedPath)) {
|
|
293
|
+
return reply.code(404).send({ detail: `File not found: ${normalizedPath}` });
|
|
294
|
+
}
|
|
295
|
+
const stat = nodeFs.statSync(normalizedPath);
|
|
296
|
+
if (stat.isDirectory()) {
|
|
297
|
+
return reply.code(400).send({ detail: 'Cannot download a directory' });
|
|
298
|
+
}
|
|
299
|
+
const filename = path.basename(normalizedPath);
|
|
300
|
+
const extension = path.extname(normalizedPath).toLowerCase();
|
|
301
|
+
// Set content type based on extension
|
|
302
|
+
const mimeTypes = {
|
|
303
|
+
'.txt': 'text/plain',
|
|
304
|
+
'.json': 'application/json',
|
|
305
|
+
'.ipynb': 'application/json',
|
|
306
|
+
'.py': 'text/x-python',
|
|
307
|
+
'.js': 'application/javascript',
|
|
308
|
+
'.ts': 'application/typescript',
|
|
309
|
+
'.html': 'text/html',
|
|
310
|
+
'.css': 'text/css',
|
|
311
|
+
'.csv': 'text/csv',
|
|
312
|
+
'.md': 'text/markdown',
|
|
313
|
+
'.png': 'image/png',
|
|
314
|
+
'.jpg': 'image/jpeg',
|
|
315
|
+
'.jpeg': 'image/jpeg',
|
|
316
|
+
'.gif': 'image/gif',
|
|
317
|
+
'.svg': 'image/svg+xml',
|
|
318
|
+
'.pdf': 'application/pdf',
|
|
319
|
+
'.zip': 'application/zip',
|
|
320
|
+
};
|
|
321
|
+
const contentType = mimeTypes[extension] || 'application/octet-stream';
|
|
322
|
+
const stream = nodeFs.createReadStream(normalizedPath);
|
|
323
|
+
return reply
|
|
324
|
+
.header('Content-Type', contentType)
|
|
325
|
+
.header('Content-Disposition', `attachment; filename="${filename}"`)
|
|
326
|
+
.header('Content-Length', stat.size)
|
|
327
|
+
.send(stream);
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
if (err instanceof Error) {
|
|
331
|
+
if (err.message.includes('not found') || err.message.includes('ENOENT')) {
|
|
332
|
+
return reply.code(404).send({ detail: err.message });
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
return reply.code(500).send({ detail: err.message });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
return reply.code(500).send({ detail: 'Unknown error' });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
/**
|
|
344
|
+
* Upload a file
|
|
345
|
+
* Uses @fastify/multipart (registered in index.ts)
|
|
346
|
+
*/
|
|
347
|
+
fastify.post('/fs/upload', async (request, reply) => {
|
|
348
|
+
try {
|
|
349
|
+
const data = await request.file();
|
|
350
|
+
if (!data) {
|
|
351
|
+
return reply.code(400).send({ detail: 'file is required' });
|
|
352
|
+
}
|
|
353
|
+
// Read the dest path from the fields
|
|
354
|
+
const destPathField = data.fields.path;
|
|
355
|
+
const destPath = destPathField && 'value' in destPathField ? destPathField.value : undefined;
|
|
356
|
+
if (!destPath) {
|
|
357
|
+
return reply.code(400).send({ detail: 'path is required' });
|
|
358
|
+
}
|
|
359
|
+
// Save the uploaded file to a temp location first
|
|
360
|
+
const tmpPath = path.join(os.tmpdir(), `upload-${Date.now()}-${data.filename}`);
|
|
361
|
+
const writeStream = nodeFs.createWriteStream(tmpPath);
|
|
362
|
+
await new Promise((resolve, reject) => {
|
|
363
|
+
data.file.pipe(writeStream);
|
|
364
|
+
writeStream.on('finish', resolve);
|
|
365
|
+
writeStream.on('error', reject);
|
|
366
|
+
});
|
|
367
|
+
const info = await fs_service_1.fsService.uploadFile(destPath, tmpPath, data.filename);
|
|
368
|
+
// Clean up temp file
|
|
369
|
+
try {
|
|
370
|
+
nodeFs.unlinkSync(tmpPath);
|
|
371
|
+
}
|
|
372
|
+
catch { /* ignore */ }
|
|
373
|
+
return reply.send({ status: 'ok', file: info });
|
|
374
|
+
}
|
|
375
|
+
catch (err) {
|
|
376
|
+
if (err instanceof Error) {
|
|
377
|
+
if (err.message.includes('not found')) {
|
|
378
|
+
return reply.code(404).send({ detail: err.message });
|
|
379
|
+
}
|
|
380
|
+
else if (err.message.includes('permission')) {
|
|
381
|
+
return reply.code(403).send({ detail: err.message });
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
return reply.code(500).send({ detail: err.message });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
return reply.code(500).send({ detail: 'Unknown error' });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kernel API Routes
|
|
3
|
+
*/
|
|
4
|
+
import { FastifyInstance } from 'fastify';
|
|
5
|
+
import { WebSocketServer } from 'ws';
|
|
6
|
+
import { KernelService } from '../kernel/kernel-service';
|
|
7
|
+
declare const kernelService: KernelService;
|
|
8
|
+
/**
|
|
9
|
+
* Setup WebSocket handler for kernel execution
|
|
10
|
+
*/
|
|
11
|
+
export declare function setupKernelWebSocket(wss: WebSocketServer): void;
|
|
12
|
+
export default function kernelRoutes(fastify: FastifyInstance): Promise<void>;
|
|
13
|
+
export { kernelService };
|