tabminal 1.1.17 → 1.1.18

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tabminal",
3
- "version": "1.1.17",
3
+ "version": "1.1.18",
4
4
  "description": "A modern, persistent web terminal with multi-tab support and real-time system monitoring.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,6 +12,7 @@
12
12
  "dev": "node --watch src/server.mjs",
13
13
  "build": "node build.mjs",
14
14
  "test": "node --test",
15
+ "updep": "npx npm-check-updates -u && npm install",
15
16
  "test:watch": "node --test --watch",
16
17
  "lint": "eslint ."
17
18
  },
@@ -32,7 +33,7 @@
32
33
  },
33
34
  "dependencies": {
34
35
  "@fontsource/monaspace-neon": "^5.2.5",
35
- "@koa/router": "^14.0.0",
36
+ "@koa/router": "^15.0.0",
36
37
  "@mozilla/readability": "^0.6.0",
37
38
  "js-tiktoken": "^1.0.21",
38
39
  "jsdom": "^27.2.0",
@@ -41,8 +42,8 @@
41
42
  "koa-static": "^5.0.0",
42
43
  "node-ansiparser": "^2.2.1",
43
44
  "node-pty": "^1.0.0",
44
- "openai": "^6.9.1",
45
- "utilitas": "^2000.3.3",
45
+ "openai": "^6.10.0",
46
+ "utilitas": "^2000.3.26",
46
47
  "ws": "^8.18.3"
47
48
  },
48
49
  "repository": {
@@ -8,7 +8,6 @@ import { config } from './config.mjs';
8
8
  const execAsync = promisify(exec);
9
9
  const WS_STATE_OPEN = 1;
10
10
  const DEFAULT_HISTORY_LIMIT = 512 * 1024; // chars
11
- const PROMPT_MARKER = '\u001b]1337;TabminalPrompt\u0007';
12
11
  const OSC_SEQUENCE_REGEX =
13
12
  /\u001b\]1337;(ExitCode=(\d+);CommandB64=([a-zA-Z0-9+/=]+)|TabminalPrompt)\u0007/g;
14
13
  const CSI_SEQUENCE_REGEX = /\u001b\[[0-9;?]*[ -\/]*[@-~]/g;
@@ -31,16 +30,16 @@ export class TerminalSession {
31
30
  this.createdAt = options.createdAt ?? new Date();
32
31
  this.shell = options.shell;
33
32
  this.initialCwd = options.initialCwd;
34
-
33
+
35
34
  this.title = this.shell ? this.shell.split('/').pop() : 'Terminal';
36
35
  this.cwd = this.initialCwd;
37
36
  this.inputBuffer = '';
38
-
37
+
39
38
  // Format the initial environment object into a static string
40
39
  this.env = Object.entries(options.env || {})
41
40
  .map(([key, value]) => `${key}=${value}`)
42
41
  .join('\n');
43
-
42
+
44
43
  this.editorState = options.editorState || {};
45
44
  this.executions = options.executions || [];
46
45
 
@@ -136,7 +135,6 @@ export class TerminalSession {
136
135
 
137
136
  this.dataSubscription = this.pty.onData(this._handleData);
138
137
  this.exitSubscription = this.pty.onExit(this._handleExit);
139
-
140
138
  this.startTitlePolling();
141
139
  }
142
140
 
@@ -177,14 +175,14 @@ export class TerminalSession {
177
175
  const rawLine = lines.slice(1).join(' ');
178
176
  const cmdAndArgs = (await execAsync(`ps -o args= -p ${currentPid}`)).stdout.trim();
179
177
  const envBlock = rawLine.substring(rawLine.indexOf(cmdAndArgs) + cmdAndArgs.length).trim();
180
-
178
+
181
179
  const regex = /([A-Z_][A-Z0-9_]*=)/g;
182
180
  const indices = [];
183
181
  let match;
184
182
  while ((match = regex.exec(envBlock)) !== null) {
185
183
  indices.push(match.index);
186
184
  }
187
-
185
+
188
186
  if (indices.length > 0) {
189
187
  const envs = [];
190
188
  for (let i = 0; i < indices.length; i++) {
@@ -311,7 +309,7 @@ export class TerminalSession {
311
309
  // Ignore prefix if it only contains whitespace or terminal control artifacts (CPR)
312
310
  const idx = this.inputBuffer.indexOf('#');
313
311
  let line = null;
314
-
312
+
315
313
  if (idx !== -1) {
316
314
  const prefix = this.inputBuffer.substring(0, idx);
317
315
  // Allow whitespace, ESC, [, digits, ;, R (typical CPR response)
@@ -319,10 +317,10 @@ export class TerminalSession {
319
317
  line = this.inputBuffer.substring(idx);
320
318
  }
321
319
  }
322
-
320
+
323
321
  if (line) {
324
322
  // --- HIJACK DETECTED ---
325
-
323
+
326
324
  // 1. Write pending data BEFORE this char to pty
327
325
  if (i > startIndex) {
328
326
  this.pty.write(data.substring(startIndex, i));
@@ -331,13 +329,13 @@ export class TerminalSession {
331
329
  // 2. Execute Hijack Logic
332
330
  const prompt = line.substring(1).trim();
333
331
  this.inputBuffer = ''; // Reset buffer
334
-
332
+
335
333
  // Send Ctrl+U to pty to clear the visual line (since user typed it)
336
334
  this.pty.write('\x15');
337
-
335
+
338
336
  // Suppress PTY echo (like the Ctrl+U echo) to prevent race conditions with AI stream
339
337
  this.suppressPtyOutput = true;
340
-
338
+
341
339
  // Send newline and reset cursor to User (visual only)
342
340
  this._writeToLogAndBroadcast('\r\n\r\x1b[K');
343
341
 
@@ -360,7 +358,7 @@ export class TerminalSession {
360
358
  }
361
359
  // Handle Control Chars (Reset buffer to be safe, except Tab)
362
360
  else if (char < ' ' && char !== '\t') {
363
- this.inputBuffer = '';
361
+ this.inputBuffer = '';
364
362
  }
365
363
  // Normal Text
366
364
  else {
@@ -382,9 +380,9 @@ export class TerminalSession {
382
380
  if (entry.command === 'ai' && entry.exitCode === 0 && entry.output) {
383
381
  // Case A: Successful AI Interaction -> Flush pending history into this turn
384
382
  const userContent = (pendingShellHistory ? pendingShellHistory.trim() + '\n\n' : '') + entry.input;
385
-
383
+
386
384
  conversationHistory.push({ request: userContent, response: entry.output });
387
-
385
+
388
386
  pendingShellHistory = ''; // Reset buffer
389
387
  } else {
390
388
  // Case B: Shell Command or Failed AI -> Accumulate history
@@ -393,127 +391,58 @@ export class TerminalSession {
393
391
  pendingShellHistory += record + '\n';
394
392
  }
395
393
  }
396
-
394
+
397
395
  return { conversationHistory, pendingShellHistory };
398
396
  }
399
397
 
400
- async _handleAiCommand(prompt, options = {}) {
401
-
402
- // Prevent duplicate logging from shell integration
403
-
404
- this.skipNextShellLog = true;
405
-
406
-
407
-
408
- // Ensure clean line start and set Cyan color (No prefix yet)
409
-
410
- this._writeToLogAndBroadcast('\r\x1b[K\x1b[36m');
411
-
412
-
413
-
414
- // Gather Context (Current Session Only)
415
-
416
- const cleanHistory = (this.executions && this.executions.length > 0) ? this.executions : [];
417
-
418
-
419
-
420
- // Build Context
421
-
422
- const { conversationHistory, pendingShellHistory } = this._buildAiContext(cleanHistory);
423
-
424
-
425
-
426
- // Construct Current Prompt
427
-
428
-
429
-
430
- const currentContext = `Recent Shell History:\n${pendingShellHistory}\nEnvironment:\n${this.env}\nCurrent Path: ${this.cwd}`;
431
-
432
-
433
-
434
- const finalPrompt = `${currentContext}\n\nQuestion: ${prompt}`;
435
-
436
-
437
-
438
-
439
-
440
-
441
-
442
- if (config.debug) {
443
-
444
-
445
-
446
- console.log('[AI Context Build]');
447
-
448
-
449
-
450
- console.log('History:', JSON.stringify(conversationHistory, null, 2));
451
-
452
-
453
-
454
- console.log('Current Prompt Preview:', JSON.stringify(finalPrompt, null, 2));
455
-
456
-
457
-
458
- }
459
-
460
-
461
-
462
-
463
-
464
-
465
-
466
- const startTime = new Date();
467
-
468
- let fullResponse = '';
469
-
470
- let isFirstChunk = true;
471
-
472
-
473
-
474
- try {
475
-
476
- const streamCallback = (chunk) => {
477
-
478
- if (chunk && chunk.text) {
479
-
480
- let text = chunk.text;
481
-
482
- // Normalize newlines for terminal
483
-
484
- text = text.replace(/\n/g, '\r\n');
485
-
486
-
487
-
488
- if (isFirstChunk) {
489
-
490
- const prefix = '\n\nTabminal:\n\n';
491
-
492
- text = prefix + text;
493
-
494
- isFirstChunk = false;
495
-
496
- }
497
-
498
-
499
-
500
- this._writeToLogAndBroadcast(text);
501
-
398
+ async _handleAiCommand(prompt, options = {}) {
399
+ // Prevent duplicate logging from shell integration
400
+ this.skipNextShellLog = true;
401
+ // Ensure clean line start and set Cyan color (No prefix yet)
402
+ this._writeToLogAndBroadcast('\r\x1b[K\x1b[36m');
403
+ // Gather Context (Current Session Only)
404
+ const cleanHistory = (this.executions && this.executions.length > 0) ? this.executions : [];
405
+ // Build Context
406
+ const { conversationHistory, pendingShellHistory } = this._buildAiContext(cleanHistory);
407
+ // Construct Current Prompt
408
+ const currentContext = `Recent Shell History:\n${pendingShellHistory}\nEnvironment:\n${this.env}\nCurrent Path: ${this.cwd}`;
409
+ const finalPrompt = `${currentContext}\n\nQuestion: ${prompt}`;
410
+ if (config.debug) {
411
+ console.log('[AI Context Build]');
412
+ console.log('History:', JSON.stringify(conversationHistory, null, 2));
413
+ console.log('Current Prompt Preview:', JSON.stringify(finalPrompt, null, 2));
414
+ }
415
+ const startTime = new Date();
416
+ let fullResponse = '';
417
+ let isFirstChunk = true;
418
+ try {
419
+ const streamCallback = (chunk) => {
420
+ // console.log('Chunk Received:');
421
+ // console.log(chunk);
422
+ if (chunk && chunk.text) {
423
+ let text = chunk.text;
424
+ // Normalize newlines for terminal
425
+ text = text.replace(/\n/g, '\r\n');
426
+ if (isFirstChunk) {
427
+ const prefix = '\n\nTabminal:\n\n';
428
+ text = prefix + text;
429
+ isFirstChunk = false;
502
430
  }
503
-
504
- };
505
-
506
- const result = await alan.prompt(finalPrompt, {
431
+ this._writeToLogAndBroadcast(text);
432
+ }
433
+ };
434
+ // console.log('Start AI Prompt...');
435
+ const result = await alan.prompt(finalPrompt, {
507
436
  stream: streamCallback,
508
437
  delta: true,
509
438
  messages: conversationHistory,
510
439
  trimBeginning: true
511
440
  });
512
-
441
+
513
442
  if (result && result.text) {
514
443
  fullResponse = result.text;
515
444
  }
516
-
445
+
517
446
  // End color and new line
518
447
  this._writeToLogAndBroadcast('\x1b[0m\r\n');
519
448
 
@@ -529,7 +458,7 @@ export class TerminalSession {
529
458
 
530
459
  } catch (e) {
531
460
  this._writeToLogAndBroadcast(`\x1b[31mAI Error: ${e.message}\x1b[0m\r\n`);
532
-
461
+
533
462
  this._logCommandExecution({
534
463
  command: 'ai',
535
464
  exitCode: 1,
@@ -539,10 +468,10 @@ export class TerminalSession {
539
468
  completedAt: new Date()
540
469
  });
541
470
  }
542
-
471
+
543
472
  // Resume PTY output
544
473
  this.suppressPtyOutput = false;
545
-
474
+
546
475
  // Restore prompt by sending \r to pty (empty command)
547
476
  this.pty.write('\r');
548
477
  }
@@ -930,7 +859,7 @@ export class TerminalSession {
930
859
  entry.startedAt && entry.completedAt
931
860
  ? entry.completedAt.getTime() - entry.startedAt.getTime()
932
861
  : null;
933
-
862
+
934
863
  const record = {
935
864
  command: entry.command ?? null,
936
865
  exitCode: entry.exitCode ?? null,