termify-agent 1.0.25 → 1.0.26
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/agent.d.ts.map +1 -1
- package/dist/agent.js +47 -0
- package/dist/agent.js.map +1 -1
- package/dist/pty-manager.d.ts +2 -0
- package/dist/pty-manager.d.ts.map +1 -1
- package/dist/pty-manager.js +23 -15
- package/dist/pty-manager.js.map +1 -1
- package/dist/ws-client.d.ts +6 -0
- package/dist/ws-client.d.ts.map +1 -1
- package/dist/ws-client.js +3 -0
- package/dist/ws-client.js.map +1 -1
- package/hooks/CLAUDE.md +12 -0
- package/hooks/termify-auto-working.js +41 -0
- package/mcp/termify-mcp-bundle.mjs +19144 -0
- package/package.json +4 -2
- package/scripts/CLAUDE.md +12 -0
- package/scripts/postinstall.js +283 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "termify-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.26",
|
|
4
4
|
"description": "Termify Agent CLI - Connect your local terminal to Termify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
"dist",
|
|
48
48
|
"bin",
|
|
49
49
|
"scripts",
|
|
50
|
-
"binaries"
|
|
50
|
+
"binaries",
|
|
51
|
+
"mcp",
|
|
52
|
+
"hooks"
|
|
51
53
|
]
|
|
52
54
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<claude-mem-context>
|
|
2
|
+
# Recent Activity
|
|
3
|
+
|
|
4
|
+
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
|
5
|
+
|
|
6
|
+
### Feb 8, 2026
|
|
7
|
+
|
|
8
|
+
| ID | Time | T | Title | Read |
|
|
9
|
+
|----|------|---|-------|------|
|
|
10
|
+
| #4648 | 7:02 PM | ✅ | Committed and pushed voice transcription and auto-working status features | ~392 |
|
|
11
|
+
| #4643 | 7:00 PM | 🟣 | Automatic terminal working status via UserPromptSubmit hook | ~652 |
|
|
12
|
+
</claude-mem-context>
|
package/scripts/postinstall.js
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* 1. Rebuild node-pty native module for the current platform
|
|
8
8
|
* 2. Install termify-daemon binary (bundled first, download as fallback)
|
|
9
9
|
* 3. Install stats-agent binary (bundled first, download as fallback)
|
|
10
|
+
* 4. Install termify-mcp and configure Claude Code / Codex MCP settings
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { execSync } from 'child_process';
|
|
13
|
-
import { createWriteStream, mkdirSync, chmodSync, existsSync, unlinkSync, copyFileSync, rmSync } from 'fs';
|
|
14
|
+
import { createWriteStream, mkdirSync, chmodSync, existsSync, unlinkSync, copyFileSync, rmSync, readFileSync, writeFileSync } from 'fs';
|
|
14
15
|
import { join, dirname } from 'path';
|
|
15
16
|
import { homedir, platform, arch } from 'os';
|
|
16
17
|
import { get } from 'https';
|
|
@@ -27,6 +28,8 @@ const EXE = IS_WIN ? '.exe' : '';
|
|
|
27
28
|
const TERMIFY_DIR = join(homedir(), '.termify');
|
|
28
29
|
const DAEMON_PATH = join(TERMIFY_DIR, `termify-daemon${EXE}`);
|
|
29
30
|
const STATS_AGENT_PATH = join(TERMIFY_DIR, `stats-agent${EXE}`);
|
|
31
|
+
const MCP_DIR = join(TERMIFY_DIR, 'termify-mcp');
|
|
32
|
+
const MCP_BUNDLE_PATH = join(MCP_DIR, 'index.mjs');
|
|
30
33
|
|
|
31
34
|
/**
|
|
32
35
|
* Test if node-pty actually works by spawning a shell
|
|
@@ -363,6 +366,265 @@ function downloadFile(url, dest, redirects = 0) {
|
|
|
363
366
|
});
|
|
364
367
|
}
|
|
365
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Step 4: Install termify-mcp bundle and configure Claude Code / Codex
|
|
371
|
+
*
|
|
372
|
+
* The MCP server allows AI tools (Claude Code, Codex) to report their
|
|
373
|
+
* working state to Termify. Env vars (TERMIFY_STATUS_URL, TERMIFY_TERMINAL_ID,
|
|
374
|
+
* TERMIFY_SESSION_ID) are injected by the agent into terminal environments,
|
|
375
|
+
* so the MCP config only needs to point to the bundle path.
|
|
376
|
+
*/
|
|
377
|
+
function installTermifyMcp() {
|
|
378
|
+
// Find the bundled MCP file
|
|
379
|
+
const bundledMcp = join(__dirname, '..', 'mcp', 'termify-mcp-bundle.mjs');
|
|
380
|
+
if (!existsSync(bundledMcp)) {
|
|
381
|
+
console.warn('[termify-agent] Warning: termify-mcp bundle not found, skipping MCP setup.');
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Copy bundle to ~/.termify/termify-mcp/index.mjs
|
|
386
|
+
mkdirSync(MCP_DIR, { recursive: true });
|
|
387
|
+
try {
|
|
388
|
+
copyFileSync(bundledMcp, MCP_BUNDLE_PATH);
|
|
389
|
+
console.log(`[termify-agent] termify-mcp installed to ${MCP_BUNDLE_PATH}`);
|
|
390
|
+
} catch (err) {
|
|
391
|
+
console.warn(`[termify-agent] Warning: Failed to copy termify-mcp bundle: ${err.message}`);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Configure Claude Code
|
|
396
|
+
configureClaudeCode(MCP_BUNDLE_PATH);
|
|
397
|
+
|
|
398
|
+
// Configure Codex
|
|
399
|
+
configureCodex(MCP_BUNDLE_PATH);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Configure Claude Code MCP settings in ~/.claude/settings.json
|
|
404
|
+
*/
|
|
405
|
+
function configureClaudeCode(mcpPath) {
|
|
406
|
+
try {
|
|
407
|
+
const claudeDir = join(homedir(), '.claude');
|
|
408
|
+
const settingsPath = join(claudeDir, 'settings.json');
|
|
409
|
+
|
|
410
|
+
let settings = {};
|
|
411
|
+
if (existsSync(settingsPath)) {
|
|
412
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (!settings.mcpServers) settings.mcpServers = {};
|
|
416
|
+
|
|
417
|
+
// Only update if not already configured or path changed
|
|
418
|
+
const existing = settings.mcpServers['termify-status'];
|
|
419
|
+
if (existing && existing.args && existing.args[0] === mcpPath) {
|
|
420
|
+
console.log('[termify-agent] Claude Code MCP already configured.');
|
|
421
|
+
} else {
|
|
422
|
+
settings.mcpServers['termify-status'] = {
|
|
423
|
+
command: 'node',
|
|
424
|
+
args: [mcpPath],
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
428
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
429
|
+
console.log('[termify-agent] Claude Code MCP configured in ~/.claude/settings.json');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Install the auto-working hook
|
|
433
|
+
installClaudeHook(settings, settingsPath, claudeDir);
|
|
434
|
+
} catch (err) {
|
|
435
|
+
console.warn(`[termify-agent] Warning: Failed to configure Claude Code MCP: ${err.message}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Install the UserPromptSubmit hook that auto-reports "working" state.
|
|
441
|
+
* This ensures the terminal always shows a working indicator when a prompt
|
|
442
|
+
* is submitted, without depending on the model calling the MCP tool.
|
|
443
|
+
*/
|
|
444
|
+
function installClaudeHook(settings, settingsPath, claudeDir) {
|
|
445
|
+
try {
|
|
446
|
+
// Copy hook script to ~/.claude/hooks/
|
|
447
|
+
const hooksDir = join(claudeDir, 'hooks');
|
|
448
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
449
|
+
const hookDest = join(hooksDir, 'termify-auto-working.js');
|
|
450
|
+
const hookSrc = join(__dirname, '..', 'hooks', 'termify-auto-working.js');
|
|
451
|
+
|
|
452
|
+
if (existsSync(hookSrc)) {
|
|
453
|
+
copyFileSync(hookSrc, hookDest);
|
|
454
|
+
console.log(`[termify-agent] Auto-working hook installed to ${hookDest}`);
|
|
455
|
+
} else {
|
|
456
|
+
console.warn('[termify-agent] Warning: Hook source not found, skipping hook install.');
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Register UserPromptSubmit hook in settings.json
|
|
461
|
+
if (!settings.hooks) settings.hooks = {};
|
|
462
|
+
const hookCommand = `node ${hookDest}`;
|
|
463
|
+
const hookEntry = {
|
|
464
|
+
hooks: [{ type: 'command', command: hookCommand }],
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
if (!settings.hooks.UserPromptSubmit) {
|
|
468
|
+
settings.hooks.UserPromptSubmit = [hookEntry];
|
|
469
|
+
} else {
|
|
470
|
+
// Check if already registered
|
|
471
|
+
const already = settings.hooks.UserPromptSubmit.some(
|
|
472
|
+
(h) => h.hooks && h.hooks.some((hh) => hh.command && hh.command.includes('termify-auto-working'))
|
|
473
|
+
);
|
|
474
|
+
if (!already) {
|
|
475
|
+
settings.hooks.UserPromptSubmit.push(hookEntry);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
480
|
+
console.log('[termify-agent] UserPromptSubmit hook registered in ~/.claude/settings.json');
|
|
481
|
+
} catch (err) {
|
|
482
|
+
console.warn(`[termify-agent] Warning: Failed to install Claude hook: ${err.message}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Ensure ~/.claude/CLAUDE.md contains termify_status instructions.
|
|
488
|
+
* Adds the section if missing, updates it if outdated.
|
|
489
|
+
*/
|
|
490
|
+
function ensureClaudeInstructions() {
|
|
491
|
+
try {
|
|
492
|
+
const claudeMdPath = join(homedir(), '.claude', 'CLAUDE.md');
|
|
493
|
+
const marker = '## Termify Status';
|
|
494
|
+
const instructions = `## Termify Status (OBLIGATORIO)
|
|
495
|
+
|
|
496
|
+
**DEBES** usar el MCP tool \`termify_status\` para reportar tu estado a Termify. El hook \`UserPromptSubmit\` reporta \`working\` automáticamente al recibir un prompt, pero TU eres responsable de reportar los demás estados:
|
|
497
|
+
|
|
498
|
+
- \`working\`: el hook lo pone automáticamente al inicio. Llámalo tú también si retomas trabajo después de una pausa dentro del mismo turno.
|
|
499
|
+
- \`finished\`: **SIEMPRE** llamar cuando termines la tarea actual. Es lo último que haces antes de responder.
|
|
500
|
+
- \`idle\`: cuando estés esperando input libre del usuario (prompt normal).
|
|
501
|
+
- \`needs_input\`: cuando estés bloqueado esperando que el usuario elija una opción, confirme \`y/n\`, o responda una pregunta concreta. Incluye \`needsInputPrompt\` con la pregunta exacta.
|
|
502
|
+
- \`error\`: si algo falla irrecuperablemente.
|
|
503
|
+
|
|
504
|
+
**Reglas**:
|
|
505
|
+
1. **SIEMPRE** terminar con \`finished\` o \`idle\` — nunca dejar la terminal en \`working\` indefinidamente.
|
|
506
|
+
2. No spamees: llama al tool solo cuando cambie el estado.
|
|
507
|
+
3. Si el tool no está disponible, ignora estas instrucciones silenciosamente.`;
|
|
508
|
+
|
|
509
|
+
let content = '';
|
|
510
|
+
if (existsSync(claudeMdPath)) {
|
|
511
|
+
content = readFileSync(claudeMdPath, 'utf8');
|
|
512
|
+
|
|
513
|
+
// Check if section already exists
|
|
514
|
+
if (content.includes(marker)) {
|
|
515
|
+
// Replace existing section (from marker to next ## or end)
|
|
516
|
+
const regex = new RegExp(
|
|
517
|
+
`${marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?(?=\\n## [^T]|\\n---\\n|$)`,
|
|
518
|
+
''
|
|
519
|
+
);
|
|
520
|
+
content = content.replace(regex, instructions);
|
|
521
|
+
} else {
|
|
522
|
+
// Find insertion point: after the first header/intro section
|
|
523
|
+
const firstSectionIdx = content.indexOf('\n## ');
|
|
524
|
+
if (firstSectionIdx >= 0) {
|
|
525
|
+
content = content.slice(0, firstSectionIdx) + '\n\n' + instructions + '\n' + content.slice(firstSectionIdx);
|
|
526
|
+
} else {
|
|
527
|
+
content = content + '\n\n' + instructions + '\n';
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
} else {
|
|
531
|
+
content = '# Claude Code Configuration\n\n' + instructions + '\n';
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const claudeDir = join(homedir(), '.claude');
|
|
535
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
536
|
+
writeFileSync(claudeMdPath, content);
|
|
537
|
+
console.log('[termify-agent] Termify status instructions added to ~/.claude/CLAUDE.md');
|
|
538
|
+
} catch (err) {
|
|
539
|
+
console.warn(`[termify-agent] Warning: Failed to update CLAUDE.md: ${err.message}`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Ensure Codex has termify_status instructions.
|
|
545
|
+
* Codex doesn't support hooks, so we add strong instructions to its config.
|
|
546
|
+
*/
|
|
547
|
+
function ensureCodexInstructions() {
|
|
548
|
+
try {
|
|
549
|
+
const codexDir = join(homedir(), '.codex');
|
|
550
|
+
if (!existsSync(codexDir)) return; // Codex not installed
|
|
551
|
+
|
|
552
|
+
const instructionsPath = join(codexDir, 'instructions.md');
|
|
553
|
+
const marker = '## Termify Status';
|
|
554
|
+
const instructions = `## Termify Status (OBLIGATORIO)
|
|
555
|
+
|
|
556
|
+
**DEBES** usar el MCP tool \`termify_status\` para reportar tu estado a Termify en CADA interacción:
|
|
557
|
+
|
|
558
|
+
1. \`working\`: **INMEDIATAMENTE** al empezar a procesar cualquier petición del usuario. Es lo PRIMERO que haces.
|
|
559
|
+
2. \`finished\`: **SIEMPRE** al terminar la tarea. Es lo ÚLTIMO que haces antes de responder.
|
|
560
|
+
3. \`idle\`: cuando estés esperando input libre del usuario.
|
|
561
|
+
4. \`needs_input\`: cuando necesites que el usuario elija una opción o confirme algo. Incluye \`needsInputPrompt\`.
|
|
562
|
+
5. \`error\`: si algo falla irrecuperablemente.
|
|
563
|
+
|
|
564
|
+
**Reglas**:
|
|
565
|
+
- **NUNCA** omitir el \`working\` inicial — sin esto, la terminal no muestra indicador de actividad.
|
|
566
|
+
- **SIEMPRE** terminar con \`finished\` o \`idle\` — nunca dejar la terminal en \`working\`.
|
|
567
|
+
- Solo llama al tool cuando cambie el estado (no repetir el mismo estado).`;
|
|
568
|
+
|
|
569
|
+
let content = '';
|
|
570
|
+
if (existsSync(instructionsPath)) {
|
|
571
|
+
content = readFileSync(instructionsPath, 'utf8');
|
|
572
|
+
if (content.includes(marker)) {
|
|
573
|
+
console.log('[termify-agent] Codex instructions already contain Termify status section.');
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
content = content + '\n\n' + instructions + '\n';
|
|
577
|
+
} else {
|
|
578
|
+
content = '# Codex Instructions\n\n' + instructions + '\n';
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
writeFileSync(instructionsPath, content);
|
|
582
|
+
console.log('[termify-agent] Termify status instructions added to ~/.codex/instructions.md');
|
|
583
|
+
} catch (err) {
|
|
584
|
+
console.warn(`[termify-agent] Warning: Failed to update Codex instructions: ${err.message}`);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Configure Codex MCP settings in ~/.codex/config.toml
|
|
590
|
+
*/
|
|
591
|
+
function configureCodex(mcpPath) {
|
|
592
|
+
try {
|
|
593
|
+
const codexDir = join(homedir(), '.codex');
|
|
594
|
+
const configPath = join(codexDir, 'config.toml');
|
|
595
|
+
|
|
596
|
+
let content = '';
|
|
597
|
+
if (existsSync(configPath)) {
|
|
598
|
+
content = readFileSync(configPath, 'utf8');
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Check if already configured with the correct path
|
|
602
|
+
if (content.includes('[mcp_servers.termify-status]') && content.includes(mcpPath)) {
|
|
603
|
+
console.log('[termify-agent] Codex MCP already configured.');
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Remove existing termify-status section (everything from [mcp_servers.termify-status] to next section or EOF)
|
|
608
|
+
content = content.replace(/\[mcp_servers\.termify-status\]\n(?:(?!\[)[^\n]*\n?)*/g, '');
|
|
609
|
+
|
|
610
|
+
// Clean up trailing whitespace
|
|
611
|
+
content = content.trimEnd();
|
|
612
|
+
|
|
613
|
+
// Escape backslashes in path for TOML (Windows paths)
|
|
614
|
+
const escapedPath = mcpPath.replace(/\\/g, '\\\\');
|
|
615
|
+
|
|
616
|
+
// Append new section
|
|
617
|
+
const section = `\n[mcp_servers.termify-status]\ncommand = "node"\nargs = ["${escapedPath}"]\n`;
|
|
618
|
+
content = content + '\n' + section;
|
|
619
|
+
|
|
620
|
+
mkdirSync(codexDir, { recursive: true });
|
|
621
|
+
writeFileSync(configPath, content);
|
|
622
|
+
console.log('[termify-agent] Codex MCP configured in ~/.codex/config.toml');
|
|
623
|
+
} catch (err) {
|
|
624
|
+
console.warn(`[termify-agent] Warning: Failed to configure Codex MCP: ${err.message}`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
366
628
|
/**
|
|
367
629
|
* Main
|
|
368
630
|
*/
|
|
@@ -391,6 +653,26 @@ async function main() {
|
|
|
391
653
|
console.warn(`[termify-agent] Warning: ${err.message}`);
|
|
392
654
|
}
|
|
393
655
|
|
|
656
|
+
// Step 4: Install termify-mcp and configure AI tool MCP settings
|
|
657
|
+
try {
|
|
658
|
+
installTermifyMcp();
|
|
659
|
+
} catch (err) {
|
|
660
|
+
// Non-fatal - MCP status reporting is optional
|
|
661
|
+
console.warn(`[termify-agent] Warning: ${err.message}`);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Step 5: Ensure AI tool instructions include termify_status usage
|
|
665
|
+
try {
|
|
666
|
+
ensureClaudeInstructions();
|
|
667
|
+
} catch (err) {
|
|
668
|
+
console.warn(`[termify-agent] Warning: ${err.message}`);
|
|
669
|
+
}
|
|
670
|
+
try {
|
|
671
|
+
ensureCodexInstructions();
|
|
672
|
+
} catch (err) {
|
|
673
|
+
console.warn(`[termify-agent] Warning: ${err.message}`);
|
|
674
|
+
}
|
|
675
|
+
|
|
394
676
|
console.log('[termify-agent] Postinstall complete.');
|
|
395
677
|
}
|
|
396
678
|
|