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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termify-agent",
3
- "version": "1.0.25",
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>
@@ -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