ai-cli-online 2.5.0 → 2.6.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/package.json +1 -1
- package/server/dist/db.d.ts +6 -0
- package/server/dist/db.js +35 -0
- package/server/dist/files.d.ts +2 -0
- package/server/dist/files.js +14 -0
- package/server/dist/index.js +151 -10
- package/server/dist/websocket.js +3 -11
- package/server/package.json +1 -1
- package/shared/dist/types.d.ts +9 -0
- package/shared/dist/types.js +9 -1
- package/web/dist/assets/index-CmWFgqt3.js +31 -0
- package/web/dist/assets/index-CtLiFTts.css +32 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-ClJzpPT-.css +0 -32
- package/web/dist/assets/index-zFa9lx6P.js +0 -33
- package/web/dist/assets/pdf-Tk4_4Bu3.js +0 -12
- package/web/dist/assets/pdf.worker-BA9kU3Pw.mjs +0 -61080
package/package.json
CHANGED
package/server/dist/db.d.ts
CHANGED
|
@@ -4,4 +4,10 @@ export declare function getDraft(sessionName: string): string;
|
|
|
4
4
|
export declare function saveDraft(sessionName: string, content: string): void;
|
|
5
5
|
export declare function deleteDraft(sessionName: string): void;
|
|
6
6
|
export declare function cleanupOldDrafts(maxAgeDays?: number): number;
|
|
7
|
+
export declare function getAnnotation(sessionName: string, filePath: string): {
|
|
8
|
+
content: string;
|
|
9
|
+
updatedAt: number;
|
|
10
|
+
} | null;
|
|
11
|
+
export declare function saveAnnotation(sessionName: string, filePath: string, content: string, updatedAt: number): void;
|
|
12
|
+
export declare function cleanupOldAnnotations(maxAgeDays?: number): number;
|
|
7
13
|
export declare function closeDb(): void;
|
package/server/dist/db.js
CHANGED
|
@@ -26,6 +26,23 @@ db.exec(`
|
|
|
26
26
|
PRIMARY KEY (token_hash, key)
|
|
27
27
|
)
|
|
28
28
|
`);
|
|
29
|
+
db.exec(`
|
|
30
|
+
CREATE TABLE IF NOT EXISTS annotations (
|
|
31
|
+
session_name TEXT NOT NULL,
|
|
32
|
+
file_path TEXT NOT NULL,
|
|
33
|
+
content TEXT NOT NULL DEFAULT '{}',
|
|
34
|
+
updated_at INTEGER NOT NULL,
|
|
35
|
+
PRIMARY KEY (session_name, file_path)
|
|
36
|
+
)
|
|
37
|
+
`);
|
|
38
|
+
// --- Annotations statements ---
|
|
39
|
+
const stmtAnnGet = db.prepare('SELECT content, updated_at FROM annotations WHERE session_name = ? AND file_path = ?');
|
|
40
|
+
const stmtAnnUpsert = db.prepare(`
|
|
41
|
+
INSERT INTO annotations (session_name, file_path, content, updated_at) VALUES (?, ?, ?, ?)
|
|
42
|
+
ON CONFLICT(session_name, file_path) DO UPDATE SET content = excluded.content, updated_at = excluded.updated_at
|
|
43
|
+
`);
|
|
44
|
+
const stmtAnnDelete = db.prepare('DELETE FROM annotations WHERE session_name = ? AND file_path = ?');
|
|
45
|
+
const stmtAnnCleanup = db.prepare('DELETE FROM annotations WHERE updated_at < ?');
|
|
29
46
|
// --- Drafts statements ---
|
|
30
47
|
const stmtGet = db.prepare('SELECT content FROM drafts WHERE session_name = ?');
|
|
31
48
|
const stmtUpsert = db.prepare(`
|
|
@@ -68,6 +85,24 @@ export function cleanupOldDrafts(maxAgeDays = 7) {
|
|
|
68
85
|
const result = stmtCleanup.run(cutoff);
|
|
69
86
|
return result.changes;
|
|
70
87
|
}
|
|
88
|
+
// --- Annotation functions ---
|
|
89
|
+
export function getAnnotation(sessionName, filePath) {
|
|
90
|
+
const row = stmtAnnGet.get(sessionName, filePath);
|
|
91
|
+
return row ? { content: row.content, updatedAt: row.updated_at } : null;
|
|
92
|
+
}
|
|
93
|
+
export function saveAnnotation(sessionName, filePath, content, updatedAt) {
|
|
94
|
+
if (!content || content === '{}' || content === '{"additions":[],"deletions":[]}') {
|
|
95
|
+
stmtAnnDelete.run(sessionName, filePath);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
stmtAnnUpsert.run(sessionName, filePath, content, updatedAt);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export function cleanupOldAnnotations(maxAgeDays = 7) {
|
|
102
|
+
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
|
|
103
|
+
const result = stmtAnnCleanup.run(cutoff);
|
|
104
|
+
return result.changes;
|
|
105
|
+
}
|
|
71
106
|
export function closeDb() {
|
|
72
107
|
db.close();
|
|
73
108
|
}
|
package/server/dist/files.d.ts
CHANGED
|
@@ -17,3 +17,5 @@ export declare function listFiles(dirPath: string): Promise<ListFilesResult>;
|
|
|
17
17
|
* We resolve the path and ensure it's an absolute path that exists.
|
|
18
18
|
*/
|
|
19
19
|
export declare function validatePath(requested: string, baseCwd: string): Promise<string | null>;
|
|
20
|
+
/** Validate a path that may not exist yet (for touch/mkdir). Uses realpath on baseCwd only. */
|
|
21
|
+
export declare function validateNewPath(requested: string, baseCwd: string): Promise<string | null>;
|
package/server/dist/files.js
CHANGED
|
@@ -59,3 +59,17 @@ export async function validatePath(requested, baseCwd) {
|
|
|
59
59
|
return null;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
/** Validate a path that may not exist yet (for touch/mkdir). Uses realpath on baseCwd only. */
|
|
63
|
+
export async function validateNewPath(requested, baseCwd) {
|
|
64
|
+
try {
|
|
65
|
+
const realBase = await realpath(baseCwd);
|
|
66
|
+
const resolved = resolve(realBase, requested);
|
|
67
|
+
if (resolved !== realBase && !resolved.startsWith(realBase + '/')) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return resolved;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
package/server/dist/index.js
CHANGED
|
@@ -8,14 +8,15 @@ import rateLimit from 'express-rate-limit';
|
|
|
8
8
|
import multer from 'multer';
|
|
9
9
|
import { config } from 'dotenv';
|
|
10
10
|
import { existsSync, readFileSync, createReadStream } from 'fs';
|
|
11
|
-
import {
|
|
11
|
+
import { spawn } from 'child_process';
|
|
12
|
+
import { copyFile, unlink, stat, mkdir, readFile, writeFile, rm } from 'fs/promises';
|
|
12
13
|
import { join, dirname, basename, extname } from 'path';
|
|
13
14
|
import { fileURLToPath } from 'url';
|
|
14
15
|
import { createHash } from 'crypto';
|
|
15
16
|
import { setupWebSocket, getActiveSessionNames, clearWsIntervals } from './websocket.js';
|
|
16
17
|
import { isTmuxAvailable, listSessions, buildSessionName, killSession, isValidSessionId, cleanupStaleSessions, getCwd, getPaneCommand } from './tmux.js';
|
|
17
|
-
import { listFiles, validatePath, MAX_DOWNLOAD_SIZE, MAX_UPLOAD_SIZE } from './files.js';
|
|
18
|
-
import { getDraft, saveDraft as saveDraftDb, deleteDraft, cleanupOldDrafts, getSetting, saveSetting, closeDb } from './db.js';
|
|
18
|
+
import { listFiles, validatePath, validateNewPath, MAX_DOWNLOAD_SIZE, MAX_UPLOAD_SIZE } from './files.js';
|
|
19
|
+
import { getDraft, saveDraft as saveDraftDb, deleteDraft, cleanupOldDrafts, getSetting, saveSetting, getAnnotation, saveAnnotation, cleanupOldAnnotations, closeDb } from './db.js';
|
|
19
20
|
import { safeTokenCompare } from './auth.js';
|
|
20
21
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
22
|
config();
|
|
@@ -58,7 +59,7 @@ async function main() {
|
|
|
58
59
|
defaultSrc: ["'self'"],
|
|
59
60
|
scriptSrc: ["'self'", "https://cdn.jsdelivr.net", "https://unpkg.com"],
|
|
60
61
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
61
|
-
imgSrc: ["'self'", "https:", "data:"],
|
|
62
|
+
imgSrc: ["'self'", "https:", "data:", "blob:"],
|
|
62
63
|
connectSrc: ["'self'", "wss:", "ws:"],
|
|
63
64
|
},
|
|
64
65
|
},
|
|
@@ -178,13 +179,24 @@ async function main() {
|
|
|
178
179
|
try {
|
|
179
180
|
const cwd = await getCwd(sessionName);
|
|
180
181
|
const subPath = req.query.path || '';
|
|
181
|
-
|
|
182
|
+
let targetDir = null;
|
|
183
|
+
if (subPath) {
|
|
184
|
+
targetDir = await validatePath(subPath, cwd);
|
|
185
|
+
// Fallback: allow absolute paths under HOME (e.g., ~/.claude/commands)
|
|
186
|
+
if (!targetDir) {
|
|
187
|
+
const home = process.env.HOME || '/root';
|
|
188
|
+
targetDir = await validatePath(subPath, home);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
targetDir = cwd;
|
|
193
|
+
}
|
|
182
194
|
if (!targetDir) {
|
|
183
195
|
res.status(400).json({ error: 'Invalid path' });
|
|
184
196
|
return;
|
|
185
197
|
}
|
|
186
198
|
const { files, truncated } = await listFiles(targetDir);
|
|
187
|
-
res.json({ cwd: targetDir, files, truncated });
|
|
199
|
+
res.json({ cwd: targetDir, home: process.env.HOME || '/root', files, truncated });
|
|
188
200
|
}
|
|
189
201
|
catch (err) {
|
|
190
202
|
console.error(`[api:files] ${sessionName}:`, err);
|
|
@@ -266,6 +278,36 @@ async function main() {
|
|
|
266
278
|
res.status(404).json({ error: 'File not found' });
|
|
267
279
|
}
|
|
268
280
|
});
|
|
281
|
+
// Download CWD as tar.gz
|
|
282
|
+
app.get('/api/sessions/:sessionId/download-cwd', async (req, res) => {
|
|
283
|
+
const sessionName = resolveSession(req, res);
|
|
284
|
+
if (!sessionName)
|
|
285
|
+
return;
|
|
286
|
+
try {
|
|
287
|
+
const cwd = await getCwd(sessionName);
|
|
288
|
+
const dirName = basename(cwd);
|
|
289
|
+
res.setHeader('Content-Type', 'application/gzip');
|
|
290
|
+
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(dirName)}.tar.gz"`);
|
|
291
|
+
const tar = spawn('tar', ['czf', '-', '-C', cwd, '.'], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
292
|
+
tar.stdout.pipe(res);
|
|
293
|
+
tar.stderr.on('data', (data) => console.error(`[tar stderr] ${data}`));
|
|
294
|
+
tar.on('error', (err) => {
|
|
295
|
+
console.error('[api:download-cwd] tar error:', err);
|
|
296
|
+
if (!res.headersSent)
|
|
297
|
+
res.status(500).json({ error: 'Failed to create archive' });
|
|
298
|
+
});
|
|
299
|
+
tar.on('close', (code) => {
|
|
300
|
+
if (code !== 0 && !res.headersSent) {
|
|
301
|
+
res.status(500).json({ error: 'Archive creation failed' });
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
catch (err) {
|
|
306
|
+
console.error(`[api:download-cwd] ${sessionName}:`, err);
|
|
307
|
+
if (!res.headersSent)
|
|
308
|
+
res.status(500).json({ error: 'Failed to download' });
|
|
309
|
+
}
|
|
310
|
+
});
|
|
269
311
|
// --- Draft API ---
|
|
270
312
|
// Get draft for a session
|
|
271
313
|
app.get('/api/sessions/:sessionId/draft', (req, res) => {
|
|
@@ -288,6 +330,37 @@ async function main() {
|
|
|
288
330
|
saveDraftDb(sessionName, content);
|
|
289
331
|
res.json({ ok: true });
|
|
290
332
|
});
|
|
333
|
+
// --- Annotations API ---
|
|
334
|
+
// Get annotation for a file
|
|
335
|
+
app.get('/api/sessions/:sessionId/annotations', (req, res) => {
|
|
336
|
+
const sessionName = resolveSession(req, res);
|
|
337
|
+
if (!sessionName)
|
|
338
|
+
return;
|
|
339
|
+
const filePath = req.query.path;
|
|
340
|
+
if (!filePath) {
|
|
341
|
+
res.status(400).json({ error: 'path query parameter required' });
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const result = getAnnotation(sessionName, filePath);
|
|
345
|
+
res.json(result || { content: null, updatedAt: 0 });
|
|
346
|
+
});
|
|
347
|
+
// Save (upsert) annotation for a file
|
|
348
|
+
app.put('/api/sessions/:sessionId/annotations', (req, res) => {
|
|
349
|
+
const sessionName = resolveSession(req, res);
|
|
350
|
+
if (!sessionName)
|
|
351
|
+
return;
|
|
352
|
+
const { path: filePath, content, updatedAt } = req.body;
|
|
353
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
354
|
+
res.status(400).json({ error: 'path must be a string' });
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (typeof content !== 'string') {
|
|
358
|
+
res.status(400).json({ error: 'content must be a string' });
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
saveAnnotation(sessionName, filePath, content, updatedAt || Date.now());
|
|
362
|
+
res.json({ ok: true });
|
|
363
|
+
});
|
|
291
364
|
// --- Pane command API ---
|
|
292
365
|
// Get current pane command (to detect if claude is running)
|
|
293
366
|
app.get('/api/sessions/:sessionId/pane-command', async (req, res) => {
|
|
@@ -309,14 +382,20 @@ async function main() {
|
|
|
309
382
|
return;
|
|
310
383
|
try {
|
|
311
384
|
const { name } = req.body;
|
|
312
|
-
if (!name || typeof name !== 'string' || name.includes('
|
|
385
|
+
if (!name || typeof name !== 'string' || name.includes('..')) {
|
|
313
386
|
res.status(400).json({ error: 'Invalid filename' });
|
|
314
387
|
return;
|
|
315
388
|
}
|
|
316
389
|
const cwd = await getCwd(sessionName);
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
390
|
+
const resolved = await validateNewPath(join(cwd, name), cwd);
|
|
391
|
+
if (!resolved) {
|
|
392
|
+
res.status(400).json({ error: 'Invalid path' });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
// Ensure parent directory exists (supports paths like "PLAN/INDEX.md")
|
|
396
|
+
await mkdir(dirname(resolved), { recursive: true });
|
|
397
|
+
await writeFile(resolved, '', { flag: 'wx' }); // create exclusively
|
|
398
|
+
res.json({ ok: true, path: resolved });
|
|
320
399
|
}
|
|
321
400
|
catch (err) {
|
|
322
401
|
if (err && typeof err === 'object' && 'code' in err && err.code === 'EEXIST') {
|
|
@@ -329,6 +408,62 @@ async function main() {
|
|
|
329
408
|
}
|
|
330
409
|
}
|
|
331
410
|
});
|
|
411
|
+
// --- Mkdir (create directory) API ---
|
|
412
|
+
app.post('/api/sessions/:sessionId/mkdir', async (req, res) => {
|
|
413
|
+
const sessionName = resolveSession(req, res);
|
|
414
|
+
if (!sessionName)
|
|
415
|
+
return;
|
|
416
|
+
try {
|
|
417
|
+
const { path: dirPath } = req.body;
|
|
418
|
+
if (!dirPath || typeof dirPath !== 'string' || dirPath.includes('..')) {
|
|
419
|
+
res.status(400).json({ error: 'Invalid path' });
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const cwd = await getCwd(sessionName);
|
|
423
|
+
const resolved = await validateNewPath(join(cwd, dirPath), cwd);
|
|
424
|
+
if (!resolved) {
|
|
425
|
+
res.status(400).json({ error: 'Invalid path' });
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
await mkdir(resolved, { recursive: true });
|
|
429
|
+
res.json({ ok: true, path: resolved });
|
|
430
|
+
}
|
|
431
|
+
catch (err) {
|
|
432
|
+
console.error(`[api:mkdir] ${sessionName}:`, err);
|
|
433
|
+
res.status(500).json({ error: 'Failed to create directory' });
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
// --- Delete file/directory API ---
|
|
437
|
+
app.delete('/api/sessions/:sessionId/rm', async (req, res) => {
|
|
438
|
+
const sessionName = resolveSession(req, res);
|
|
439
|
+
if (!sessionName)
|
|
440
|
+
return;
|
|
441
|
+
try {
|
|
442
|
+
const { path: rmPath } = req.body;
|
|
443
|
+
if (!rmPath || typeof rmPath !== 'string' || rmPath.includes('..')) {
|
|
444
|
+
res.status(400).json({ error: 'Invalid path' });
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
const cwd = await getCwd(sessionName);
|
|
448
|
+
const resolved = await validatePath(rmPath, cwd);
|
|
449
|
+
if (!resolved) {
|
|
450
|
+
res.status(400).json({ error: 'Invalid path' });
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const fileStat = await stat(resolved);
|
|
454
|
+
if (fileStat.isDirectory()) {
|
|
455
|
+
await rm(resolved, { recursive: true });
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
await unlink(resolved);
|
|
459
|
+
}
|
|
460
|
+
res.json({ ok: true });
|
|
461
|
+
}
|
|
462
|
+
catch (err) {
|
|
463
|
+
console.error(`[api:rm] ${sessionName}:`, err);
|
|
464
|
+
res.status(500).json({ error: 'Failed to delete' });
|
|
465
|
+
}
|
|
466
|
+
});
|
|
332
467
|
// --- Settings API ---
|
|
333
468
|
/** Hash token for settings storage (same prefix as tmux session names) */
|
|
334
469
|
function tokenHash(token) {
|
|
@@ -530,6 +665,12 @@ async function main() {
|
|
|
530
665
|
catch (e) {
|
|
531
666
|
console.error('[cleanup:drafts]', e);
|
|
532
667
|
}
|
|
668
|
+
try {
|
|
669
|
+
cleanupOldAnnotations(7);
|
|
670
|
+
}
|
|
671
|
+
catch (e) {
|
|
672
|
+
console.error('[cleanup:annotations]', e);
|
|
673
|
+
}
|
|
533
674
|
}, CLEANUP_INTERVAL);
|
|
534
675
|
console.log(`Session TTL: ${SESSION_TTL_HOURS}h (cleanup every hour)`);
|
|
535
676
|
}
|
package/server/dist/websocket.js
CHANGED
|
@@ -4,16 +4,7 @@ import { validatePath } from './files.js';
|
|
|
4
4
|
import { createReadStream } from 'fs';
|
|
5
5
|
import { stat as fsStat } from 'fs/promises';
|
|
6
6
|
import { PtySession } from './pty.js';
|
|
7
|
-
|
|
8
|
-
* Binary protocol for hot-path messages (output/input/scrollback).
|
|
9
|
-
* Format: [1-byte type prefix][raw UTF-8 payload]
|
|
10
|
-
* JSON is kept for low-frequency control messages.
|
|
11
|
-
*/
|
|
12
|
-
const BIN_TYPE_OUTPUT = 0x01;
|
|
13
|
-
const BIN_TYPE_INPUT = 0x02;
|
|
14
|
-
const BIN_TYPE_SCROLLBACK = 0x03;
|
|
15
|
-
const BIN_TYPE_SCROLLBACK_CONTENT = 0x04;
|
|
16
|
-
const BIN_TYPE_FILE_CHUNK = 0x05;
|
|
7
|
+
import { BIN_TYPE_OUTPUT, BIN_TYPE_INPUT, BIN_TYPE_SCROLLBACK, BIN_TYPE_SCROLLBACK_CONTENT, BIN_TYPE_FILE_CHUNK } from 'ai-cli-online-shared';
|
|
17
8
|
const MAX_STREAM_SIZE = 50 * 1024 * 1024; // 50MB
|
|
18
9
|
const STREAM_CHUNK_SIZE = 64 * 1024; // 64KB highWaterMark
|
|
19
10
|
const STREAM_HIGH_WATER = 1024 * 1024; // 1MB backpressure threshold
|
|
@@ -131,6 +122,7 @@ export function setupWebSocket(wss, authToken, defaultCwd, tokenCompare, maxConn
|
|
|
131
122
|
const rows = 24;
|
|
132
123
|
const rawSessionId = url.searchParams.get('sessionId') || undefined;
|
|
133
124
|
const sessionId = rawSessionId && isValidSessionId(rawSessionId) ? rawSessionId : undefined;
|
|
125
|
+
const clientCwd = url.searchParams.get('cwd') || undefined;
|
|
134
126
|
if (rawSessionId && !sessionId) {
|
|
135
127
|
console.log(`[WS] Invalid sessionId rejected: ${rawSessionId}`);
|
|
136
128
|
ws.close(4004, 'Invalid sessionId');
|
|
@@ -179,7 +171,7 @@ export function setupWebSocket(wss, authToken, defaultCwd, tokenCompare, maxConn
|
|
|
179
171
|
// Check or create tmux session
|
|
180
172
|
const resumed = await hasSession(sessionName);
|
|
181
173
|
if (!resumed) {
|
|
182
|
-
await createSession(sessionName, cols, rows, defaultCwd);
|
|
174
|
+
await createSession(sessionName, cols, rows, clientCwd || defaultCwd);
|
|
183
175
|
}
|
|
184
176
|
else {
|
|
185
177
|
// resizeSession, captureScrollback, and configureSession are independent — run in parallel
|
package/server/package.json
CHANGED
package/shared/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary protocol type prefixes for hot-path WebSocket messages.
|
|
3
|
+
* Format: [1-byte type prefix][raw UTF-8 payload]
|
|
4
|
+
*/
|
|
5
|
+
export declare const BIN_TYPE_OUTPUT = 1;
|
|
6
|
+
export declare const BIN_TYPE_INPUT = 2;
|
|
7
|
+
export declare const BIN_TYPE_SCROLLBACK = 3;
|
|
8
|
+
export declare const BIN_TYPE_SCROLLBACK_CONTENT = 4;
|
|
9
|
+
export declare const BIN_TYPE_FILE_CHUNK = 5;
|
|
1
10
|
export interface FileEntry {
|
|
2
11
|
name: string;
|
|
3
12
|
type: 'file' | 'directory';
|
package/shared/dist/types.js
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Binary protocol type prefixes for hot-path WebSocket messages.
|
|
3
|
+
* Format: [1-byte type prefix][raw UTF-8 payload]
|
|
4
|
+
*/
|
|
5
|
+
export const BIN_TYPE_OUTPUT = 0x01;
|
|
6
|
+
export const BIN_TYPE_INPUT = 0x02;
|
|
7
|
+
export const BIN_TYPE_SCROLLBACK = 0x03;
|
|
8
|
+
export const BIN_TYPE_SCROLLBACK_CONTENT = 0x04;
|
|
9
|
+
export const BIN_TYPE_FILE_CHUNK = 0x05;
|