agentvibes 5.6.9 → 5.7.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.
Files changed (95) hide show
  1. package/.agentvibes/config.json +3 -38
  2. package/.claude/commands/agent-vibes/provider.md +0 -0
  3. package/.claude/config/audio-effects.cfg +1 -1
  4. package/.claude/config/background-music-position.txt +6 -8
  5. package/.claude/config/reverb-level.txt +0 -0
  6. package/.claude/github-star-reminder.txt +1 -1
  7. package/.claude/hooks/bmad-tts-injector.sh +49 -21
  8. package/.claude/hooks/provider-commands.sh +16 -4
  9. package/.claude/hooks/provider-manager.sh +38 -0
  10. package/.claude/hooks/stop.sh +2 -27
  11. package/.claude/hooks/voice-manager.sh +50 -2
  12. package/.claude/hooks-windows/play-tts.ps1 +34 -1
  13. package/.claude/hooks-windows/tts-watcher.ps1 +122 -0
  14. package/.claude/piper-voices-dir.txt +1 -1
  15. package/.mcp.json +13 -33
  16. package/README.md +6 -8
  17. package/RELEASE_NOTES.md +32 -0
  18. package/bin/agent-vibes +39 -39
  19. package/package.json +1 -1
  20. package/src/bmad-detector.js +85 -71
  21. package/src/cli/list-personalities.js +110 -110
  22. package/src/cli/list-voices.js +114 -114
  23. package/src/commands/bmad-voices.js +394 -394
  24. package/src/commands/install-mcp.js +476 -476
  25. package/src/console/brand-colors.js +13 -13
  26. package/src/console/constants/personalities.js +44 -44
  27. package/src/console/tabs/help-tab.js +314 -314
  28. package/src/console/tabs/readme-tab.js +272 -272
  29. package/src/console/widgets/destroy-list.js +25 -25
  30. package/src/console/widgets/notice.js +55 -55
  31. package/src/console/widgets/personality-picker.js +213 -213
  32. package/src/i18n/de.js +202 -202
  33. package/src/i18n/es.js +202 -202
  34. package/src/i18n/fr.js +202 -202
  35. package/src/i18n/hi.js +202 -202
  36. package/src/i18n/ja.js +202 -202
  37. package/src/i18n/ko.js +202 -202
  38. package/src/i18n/pt.js +202 -202
  39. package/src/i18n/strings.js +54 -54
  40. package/src/i18n/zh-CN.js +202 -202
  41. package/src/installer/language-screen.js +31 -31
  42. package/src/installer/music-file-input.js +304 -304
  43. package/src/installer.js +70 -7
  44. package/src/services/agent-voice-store.js +59 -12
  45. package/src/services/config-service.js +264 -264
  46. package/src/services/language-service.js +47 -47
  47. package/src/services/provider-service.js +143 -143
  48. package/src/utils/audio-duration-validator.js +298 -298
  49. package/src/utils/audio-format-validator.js +277 -277
  50. package/src/utils/dependency-checker.js +469 -469
  51. package/src/utils/file-ownership-verifier.js +358 -358
  52. package/src/utils/list-formatter.js +194 -194
  53. package/src/utils/music-file-validator.js +285 -285
  54. package/src/utils/preview-list-prompt.js +136 -136
  55. package/src/utils/secure-music-storage.js +412 -412
  56. package/.agentvibes/LITE-MODE.md +0 -236
  57. package/.agentvibes/README.md +0 -136
  58. package/.agentvibes/backup/session-start-tts.sh.20251210_212814 +0 -141
  59. package/.agentvibes/backups/agents/analyst_20260204_144958.md +0 -78
  60. package/.agentvibes/backups/agents/architect_20260204_144958.md +0 -72
  61. package/.agentvibes/backups/agents/dev_20260204_144958.md +0 -74
  62. package/.agentvibes/backups/agents/pm_20260204_144958.md +0 -72
  63. package/.agentvibes/backups/agents/quick-flow-solo-dev_20260204_144958.md +0 -64
  64. package/.agentvibes/backups/agents/sm_20260204_144958.md +0 -87
  65. package/.agentvibes/backups/agents/tea_20260204_144958.md +0 -79
  66. package/.agentvibes/backups/agents/tech-writer_20260204_144958.md +0 -82
  67. package/.agentvibes/backups/agents/ux-designer_20260204_144958.md +0 -80
  68. package/.agentvibes/config/README-personality-defaults.md +0 -162
  69. package/.agentvibes/config/agentvibes.json +0 -1
  70. package/.agentvibes/config/mode.txt +0 -1
  71. package/.agentvibes/config/personality-voice-defaults.default.json +0 -21
  72. package/.agentvibes/config/save-audio.txt +0 -1
  73. package/.agentvibes/config/voice-metadata.json +0 -160
  74. package/.agentvibes/hooks/help.sh +0 -191
  75. package/.agentvibes/hooks/post-tool-use-lite.sh +0 -111
  76. package/.agentvibes/hooks/save-audio-manager.sh +0 -162
  77. package/.agentvibes/hooks/session-start-full-optimized.sh +0 -102
  78. package/.agentvibes/hooks/session-start-full.sh +0 -142
  79. package/.agentvibes/hooks/session-start-lite-v2.sh +0 -34
  80. package/.agentvibes/hooks/session-start-lite.sh +0 -29
  81. package/.agentvibes/hooks/stop-lite.sh +0 -115
  82. package/.agentvibes/hooks/switch-mode.sh +0 -215
  83. package/.agentvibes/output-styles/audio-summary.md +0 -30
  84. package/.claude/audio/voice-samples/piper/alan.wav +0 -0
  85. package/.claude/audio/voice-samples/piper/amy.wav +0 -0
  86. package/.claude/audio/voice-samples/piper/charlotte.wav +0 -0
  87. package/.claude/audio/voice-samples/piper/joe.wav +0 -0
  88. package/.claude/audio/voice-samples/piper/john.wav +0 -0
  89. package/.claude/audio/voice-samples/piper/katherine.wav +0 -0
  90. package/.claude/audio/voice-samples/piper/kristin.wav +0 -0
  91. package/.claude/audio/voice-samples/piper/linda.wav +0 -0
  92. package/.claude/audio/voice-samples/piper/marcus.wav +0 -0
  93. package/.claude/audio/voice-samples/piper/ryan.wav +0 -0
  94. package/.claude/hooks/post-response.sh +0 -41
  95. package/bin/ensure-soprano-running.sh +0 -43
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  [![Publish](https://github.com/paulpreibisch/AgentVibes/actions/workflows/publish.yml/badge.svg)](https://github.com/paulpreibisch/AgentVibes/actions/workflows/publish.yml)
12
12
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
13
13
 
14
- **Author**: Paul Preibisch ([@997Fire](https://x.com/997Fire)) | **Version**: v5.6.9
14
+ **Author**: Paul Preibisch ([@997Fire](https://x.com/997Fire)) | **Version**: v5.7.0
15
15
 
16
16
  ---
17
17
 
@@ -40,17 +40,15 @@ Whether you're coding in Claude Code, chatting in Claude Desktop, using Warp Ter
40
40
 
41
41
  ---
42
42
 
43
- ## 🌟 NEW IN v5.6.9Reverb & Background Music Silent in NPX Installs
43
+ ## 🌟 NEW IN v5.7.0BMAD v6.6 Support + Windows Auto-Restart Watcher
44
44
 
45
- **NPX users:** Reverb and background music were silently broken for all `npx`-installed users. Hook files extracted from the npm tarball lacked execute bits (644), causing `audio-processor.sh` to exit with code 126. Fixed via `bash` prefix in the caller and a postinstall `chmod 755` step.
45
+ **BMAD v6.6.0:** AgentVibes now detects the new `.claude/skills/*/agents/` agent structure, correctly handles globally-installed BMAD at `~/_bmad`, and gracefully skips v6.6+ plain-Markdown agents during TTS injection instead of erroring. The BMAD tab now shows detection correctly for global installs.
46
46
 
47
- **Voice Browser:** The Preview button now applies your configured reverb and background musicit was playing raw audio with no effects.
47
+ **Windows watcher:** `tts-watcher.ps1` is now a standalone file at `~/.agentvibes/tts-watcher.ps1`. Running `npx agentvibes update` now copies the latest watcher **and** restarts it automatically both the file and the process are updated in one step, no manual restart needed.
48
48
 
49
- **MCP tool:** `text_to_speech` now returns the correct audio file path (no trailing emoji garbage) and includes the voice name in its response.
49
+ **Windows provider:** `play-tts.ps1` now respects the `ProviderOverride` from the Linux server config when receiving remote audio.
50
50
 
51
- **Background music toggle:** Enabling music in the TUI now actually enables it the flag file read by bash hooks is now kept in sync.
52
-
53
- ## v5.6.8 — WSL Voice Routing Fixed + Session Lifecycle Reliability
51
+ ## v5.6.9Reverb & Background Music Silent in NPX Installs
54
52
 
55
53
  **WSL users:** AgentVibes was playing `en_US-lessac-medium` regardless of your configured voice. Fixed — Piper is now found in non-interactive shells by explicitly prepending `~/.local/bin` to `PATH` before the binary check.
56
54
 
package/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # AgentVibes Release Notes
2
2
 
3
+ ## 🎭 v5.7.0 — BMAD v6.6 Support + Windows Auto-Restart Watcher
4
+
5
+ **Released:** 2026-05-11
6
+
7
+ ### 🆕 BMAD v6.6.0 Compatibility
8
+
9
+ BMAD v6.6 restructured where agents live — they moved from `_bmad/bmm/agents/` to `.claude/skills/*/agents/`. AgentVibes now detects and scans these new paths correctly.
10
+
11
+ **TTS injection** gracefully skips v6.6+ agents (which use plain Markdown without XML/YAML activation sections) instead of throwing errors. The install summary now clearly reports how many agents were skipped vs. modified.
12
+
13
+ **BMAD tab detection** now finds globally-installed BMAD at `~/_bmad` (home-dir install) in addition to project-local installs. Previously the BMAD tab showed "Not detected" even when BMAD was installed globally.
14
+
15
+ **Security:** The installer's path validation now correctly permits BMAD paths under the user's home directory, fixing a false-positive "Invalid BMAD path" error for global installs.
16
+
17
+ ### 🆕 Windows TTS Watcher — Standalone File + Auto-Restart
18
+
19
+ `tts-watcher.ps1` is now extracted to `~/.agentvibes/tts-watcher.ps1` as a standalone file. Running `npx agentvibes update` now copies the latest watcher to that location **and** automatically restarts it — so both the watcher script itself and its running process are updated in one step. No manual file replacement or restart needed after updates.
20
+
21
+ ### 🐛 Windows Provider Override Respected on Laptop
22
+
23
+ `play-tts.ps1` now reads the `ProviderOverride` setting from the Linux-side config when receiving audio via SSH. Previously the laptop always used its locally-configured provider even if the server specified a different one.
24
+
25
+ ### 🐛 Sample Command Added to Voice Manager
26
+
27
+ `voice-manager.sh sample` was missing its handler — calling it fell through to the usage/exit path silently. Fixed.
28
+
29
+ ### 🐛 Preview SSH Routing Detects Correct Endpoint
30
+
31
+ `provider-manager.sh` now includes `detect_routing_llm()` which checks `AGENTVIBES_LLM_KEY` then `transport-config.json` for the first `mode=remote` entry, so preview audio reaches the correct SSH host.
32
+
33
+ ---
34
+
3
35
  ## 🔇 v5.6.9 — Reverb & Background Music Silent in NPX Installs
4
36
 
5
37
  **Released:** 2026-05-09
package/bin/agent-vibes CHANGED
@@ -1,40 +1,40 @@
1
1
  #!/usr/bin/env node
2
-
3
- /**
4
- * AgentVibes - Beautiful ElevenLabs TTS voice commands for Claude Code
5
- * This file ensures proper execution when run via npx
6
- */
7
-
8
- import { execFileSync } from 'node:child_process';
9
- import path from 'node:path';
10
- import fs from 'node:fs';
11
- import { fileURLToPath } from 'node:url';
12
-
13
- const __filename = fileURLToPath(import.meta.url);
14
- const __dirname = path.dirname(__filename);
15
-
16
- // Check if we're running in an npx temporary directory
17
- const isNpxExecution = __dirname.includes('_npx') || __dirname.includes('.npm');
18
-
19
- // Get CLI arguments
20
- const arguments_ = process.argv.slice(2);
21
-
22
- // Route through the TUI console (agentvibes.js) which handles install/config/etc
23
- const installerPath = path.join(__dirname, 'agentvibes.js');
24
-
25
- if (!fs.existsSync(installerPath)) {
26
- console.error('Error: Could not find installer.js at', installerPath);
27
- console.error('Current directory:', __dirname);
28
- process.exit(1);
29
- }
30
-
31
- try {
32
- // Security: Use execFileSync with array args to prevent command injection
33
- // Arguments are passed as array elements, not string interpolation
34
- execFileSync('node', [installerPath, ...arguments_], {
35
- stdio: 'inherit',
36
- cwd: path.dirname(__dirname),
37
- });
38
- } catch (error) {
39
- process.exit(error.status || 1);
40
- }
2
+
3
+ /**
4
+ * AgentVibes - Beautiful ElevenLabs TTS voice commands for Claude Code
5
+ * This file ensures proper execution when run via npx
6
+ */
7
+
8
+ import { execFileSync } from 'node:child_process';
9
+ import path from 'node:path';
10
+ import fs from 'node:fs';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ // Check if we're running in an npx temporary directory
17
+ const isNpxExecution = __dirname.includes('_npx') || __dirname.includes('.npm');
18
+
19
+ // Get CLI arguments
20
+ const arguments_ = process.argv.slice(2);
21
+
22
+ // Route through the TUI console (agentvibes.js) which handles install/config/etc
23
+ const installerPath = path.join(__dirname, 'agentvibes.js');
24
+
25
+ if (!fs.existsSync(installerPath)) {
26
+ console.error('Error: Could not find installer.js at', installerPath);
27
+ console.error('Current directory:', __dirname);
28
+ process.exit(1);
29
+ }
30
+
31
+ try {
32
+ // Security: Use execFileSync with array args to prevent command injection
33
+ // Arguments are passed as array elements, not string interpolation
34
+ execFileSync('node', [installerPath, ...arguments_], {
35
+ stdio: 'inherit',
36
+ cwd: path.dirname(__dirname),
37
+ });
38
+ } catch (error) {
39
+ process.exit(error.status || 1);
40
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "agentvibes",
4
- "version": "5.6.9",
4
+ "version": "5.7.0",
5
5
  "description": "Now your AI Agents can finally talk back! Professional TTS voice for Claude Code, Claude Desktop (via MCP), and Clawdbot with multi-provider support.",
6
6
  "homepage": "https://agentvibes.org",
7
7
  "keywords": [
@@ -1,71 +1,85 @@
1
- import path from 'node:path';
2
- import fs from 'node:fs/promises';
3
- import yaml from 'js-yaml';
4
-
5
- /**
6
- * Detect BMAD installation and version
7
- * @param {string} targetDir - Directory to check
8
- * @returns {Promise<Object>} Detection result with version info
9
- */
10
- export async function detectBMAD(targetDir) {
11
- // Check v6 first (newer version) - support both .bmad and bmad paths
12
- // Try .bmad (standard path with dot prefix) first
13
- let v6Manifest = path.join(targetDir, '.bmad/_cfg/manifest.yaml');
14
- let bmadPath = '.bmad';
15
-
16
- try {
17
- await fs.access(v6Manifest);
18
- } catch {
19
- // Try bmad (alternative path without dot prefix)
20
- v6Manifest = path.join(targetDir, 'bmad/_cfg/manifest.yaml');
21
- bmadPath = 'bmad';
22
- try {
23
- await fs.access(v6Manifest);
24
- } catch {
25
- // Neither path found, continue to v4 check
26
- bmadPath = null;
27
- }
28
- }
29
-
30
- if (bmadPath) {
31
- try {
32
- const manifestContent = await fs.readFile(v6Manifest, 'utf8');
33
- const manifest = yaml.load(manifestContent);
34
-
35
- return {
36
- version: 6,
37
- detailedVersion: manifest.installation?.version || '6.0.0-alpha.x',
38
- manifestPath: v6Manifest,
39
- configPath: path.join(targetDir, bmadPath, 'core/config.yaml'),
40
- bmadPath: path.join(targetDir, bmadPath),
41
- installed: true
42
- };
43
- } catch {}
44
- }
45
-
46
- // Check v4 (legacy)
47
- const v4Manifest = path.join(targetDir, '.bmad-core/install-manifest.yaml');
48
- try {
49
- await fs.access(v4Manifest);
50
- return {
51
- version: 4,
52
- detailedVersion: '4.x',
53
- manifestPath: v4Manifest,
54
- configPath: path.join(targetDir, '.bmad-core/config.yaml'),
55
- bmadPath: path.join(targetDir, '.bmad-core'),
56
- installed: true
57
- };
58
- } catch {}
59
-
60
- // Not installed
61
- return { version: null, installed: false };
62
- }
63
-
64
- /**
65
- * Get BMAD configuration file path for detected version
66
- * @param {Object} detection - Result from detectBMAD()
67
- * @returns {string|null} Path to config.yaml or null
68
- */
69
- export function getBMADConfigPath(detection) {
70
- return detection.installed ? detection.configPath : null;
71
- }
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import yaml from 'js-yaml';
5
+
6
+ /**
7
+ * Detect BMAD installation and version
8
+ * @param {string} targetDir - Directory to check
9
+ * @returns {Promise<Object>} Detection result with version info
10
+ */
11
+ export async function detectBMAD(targetDir) {
12
+ // Check v6 first (newer version).
13
+ // Search order: project-local variants first, then home-dir (_bmad is the current BMAD installer default)
14
+ const homeDir = os.homedir();
15
+ const v6Candidates = [
16
+ // Project-local checks first
17
+ { manifest: path.join(targetDir, '.bmad/_cfg/manifest.yaml'), bmadPath: '.bmad' },
18
+ { manifest: path.join(targetDir, 'bmad/_cfg/manifest.yaml'), bmadPath: 'bmad' },
19
+ { manifest: path.join(targetDir, '_bmad/_cfg/manifest.yaml'), bmadPath: '_bmad' },
20
+ { manifest: path.join(targetDir, '_bmad/_config/manifest.yaml'), bmadPath: '_bmad' },
21
+ // Home-dir installs (global BMAD not inside a project) — detected but NOT injected
22
+ { manifest: path.join(homeDir, '_bmad/_config/manifest.yaml'), bmadPath: '_bmad', root: homeDir, isGlobal: true },
23
+ { manifest: path.join(homeDir, '_bmad/_cfg/manifest.yaml'), bmadPath: '_bmad', root: homeDir, isGlobal: true },
24
+ { manifest: path.join(homeDir, '.bmad/_cfg/manifest.yaml'), bmadPath: '.bmad', root: homeDir, isGlobal: true },
25
+ ];
26
+
27
+ let v6Manifest = null;
28
+ let bmadPath = null;
29
+ let bmadRoot = targetDir;
30
+ let isGlobal = false;
31
+
32
+ for (const candidate of v6Candidates) {
33
+ try {
34
+ await fs.access(candidate.manifest);
35
+ v6Manifest = candidate.manifest;
36
+ bmadPath = candidate.bmadPath;
37
+ bmadRoot = candidate.root ?? targetDir;
38
+ isGlobal = candidate.isGlobal ?? false;
39
+ break;
40
+ } catch { /* try next */ }
41
+ }
42
+
43
+ if (bmadPath) {
44
+ try {
45
+ const manifestContent = await fs.readFile(v6Manifest, 'utf8');
46
+ const manifest = yaml.load(manifestContent);
47
+
48
+ return {
49
+ version: 6,
50
+ detailedVersion: manifest.installation?.version || '6.0.0-alpha.x',
51
+ manifestPath: v6Manifest,
52
+ configPath: path.join(bmadRoot, bmadPath, 'core/config.yaml'),
53
+ bmadPath: path.join(bmadRoot, bmadPath),
54
+ installed: true,
55
+ isGlobal,
56
+ };
57
+ } catch {}
58
+ }
59
+
60
+ // Check v4 (legacy)
61
+ const v4Manifest = path.join(targetDir, '.bmad-core/install-manifest.yaml');
62
+ try {
63
+ await fs.access(v4Manifest);
64
+ return {
65
+ version: 4,
66
+ detailedVersion: '4.x',
67
+ manifestPath: v4Manifest,
68
+ configPath: path.join(targetDir, '.bmad-core/config.yaml'),
69
+ bmadPath: path.join(targetDir, '.bmad-core'),
70
+ installed: true
71
+ };
72
+ } catch {}
73
+
74
+ // Not installed
75
+ return { version: null, installed: false };
76
+ }
77
+
78
+ /**
79
+ * Get BMAD configuration file path for detected version
80
+ * @param {Object} detection - Result from detectBMAD()
81
+ * @returns {string|null} Path to config.yaml or null
82
+ */
83
+ export function getBMADConfigPath(detection) {
84
+ return detection.installed ? detection.configPath : null;
85
+ }
@@ -1,110 +1,110 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Personality List Display - Beautiful multi-column personality listing
4
- * Called by personality-manager.sh to display personalities with boxen formatting
5
- */
6
-
7
- import { formatPersonalitiesList } from '../utils/list-formatter.js';
8
- import fs from 'fs';
9
- import path from 'path';
10
- import os from 'os';
11
-
12
- /**
13
- * Get personality description from markdown file
14
- */
15
- function getPersonalityDescription(filePath) {
16
- try {
17
- const content = fs.readFileSync(filePath, 'utf8');
18
-
19
- // Try to extract description from frontmatter or first paragraph
20
- const descMatch = content.match(/description:\s*(.+)/i);
21
- if (descMatch) {
22
- return descMatch[1].trim();
23
- }
24
-
25
- // Try to get first line after frontmatter
26
- const lines = content.split('\n');
27
- let inFrontmatter = false;
28
- let frontmatterCount = 0;
29
-
30
- for (const line of lines) {
31
- if (line.trim() === '---') {
32
- frontmatterCount++;
33
- inFrontmatter = frontmatterCount === 1;
34
- continue;
35
- }
36
-
37
- if (!inFrontmatter && frontmatterCount >= 2 && line.trim()) {
38
- // First non-empty line after frontmatter
39
- return line.trim().replace(/^#+\s*/, '').substring(0, 50);
40
- }
41
- }
42
-
43
- return '';
44
- } catch (error) {
45
- return '';
46
- }
47
- }
48
-
49
- /**
50
- * Get all personalities from directory
51
- */
52
- function getPersonalities(personalitiesDir, currentPersonality) {
53
- const personalities = [];
54
-
55
- if (!fs.existsSync(personalitiesDir)) {
56
- return personalities;
57
- }
58
-
59
- const files = fs.readdirSync(personalitiesDir);
60
- for (const file of files) {
61
- if (file.endsWith('.md')) {
62
- const name = path.basename(file, '.md');
63
- const filePath = path.join(personalitiesDir, file);
64
- const description = getPersonalityDescription(filePath);
65
-
66
- personalities.push({
67
- name,
68
- description,
69
- current: name === currentPersonality
70
- });
71
- }
72
- }
73
-
74
- // Add special 'random' option
75
- personalities.push({
76
- name: 'random',
77
- description: 'Picks randomly each time',
78
- current: currentPersonality === 'random'
79
- });
80
-
81
- return personalities.sort((a, b) => {
82
- // Keep 'random' at the end
83
- if (a.name === 'random') return 1;
84
- if (b.name === 'random') return -1;
85
- return a.name.localeCompare(b.name);
86
- });
87
- }
88
-
89
- /**
90
- * Main function
91
- */
92
- function main() {
93
- const args = process.argv.slice(2);
94
-
95
- // Parse arguments
96
- const personalitiesDir = args[0] || path.join(os.homedir(), '.claude', 'personalities');
97
- const currentPersonality = args[1] || 'normal';
98
-
99
- const personalities = getPersonalities(personalitiesDir, currentPersonality);
100
-
101
- // Display with boxen
102
- const output = formatPersonalitiesList(personalities, {
103
- columns: 2,
104
- showUsage: true
105
- });
106
-
107
- console.log(output);
108
- }
109
-
110
- main();
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Personality List Display - Beautiful multi-column personality listing
4
+ * Called by personality-manager.sh to display personalities with boxen formatting
5
+ */
6
+
7
+ import { formatPersonalitiesList } from '../utils/list-formatter.js';
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import os from 'os';
11
+
12
+ /**
13
+ * Get personality description from markdown file
14
+ */
15
+ function getPersonalityDescription(filePath) {
16
+ try {
17
+ const content = fs.readFileSync(filePath, 'utf8');
18
+
19
+ // Try to extract description from frontmatter or first paragraph
20
+ const descMatch = content.match(/description:\s*(.+)/i);
21
+ if (descMatch) {
22
+ return descMatch[1].trim();
23
+ }
24
+
25
+ // Try to get first line after frontmatter
26
+ const lines = content.split('\n');
27
+ let inFrontmatter = false;
28
+ let frontmatterCount = 0;
29
+
30
+ for (const line of lines) {
31
+ if (line.trim() === '---') {
32
+ frontmatterCount++;
33
+ inFrontmatter = frontmatterCount === 1;
34
+ continue;
35
+ }
36
+
37
+ if (!inFrontmatter && frontmatterCount >= 2 && line.trim()) {
38
+ // First non-empty line after frontmatter
39
+ return line.trim().replace(/^#+\s*/, '').substring(0, 50);
40
+ }
41
+ }
42
+
43
+ return '';
44
+ } catch (error) {
45
+ return '';
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Get all personalities from directory
51
+ */
52
+ function getPersonalities(personalitiesDir, currentPersonality) {
53
+ const personalities = [];
54
+
55
+ if (!fs.existsSync(personalitiesDir)) {
56
+ return personalities;
57
+ }
58
+
59
+ const files = fs.readdirSync(personalitiesDir);
60
+ for (const file of files) {
61
+ if (file.endsWith('.md')) {
62
+ const name = path.basename(file, '.md');
63
+ const filePath = path.join(personalitiesDir, file);
64
+ const description = getPersonalityDescription(filePath);
65
+
66
+ personalities.push({
67
+ name,
68
+ description,
69
+ current: name === currentPersonality
70
+ });
71
+ }
72
+ }
73
+
74
+ // Add special 'random' option
75
+ personalities.push({
76
+ name: 'random',
77
+ description: 'Picks randomly each time',
78
+ current: currentPersonality === 'random'
79
+ });
80
+
81
+ return personalities.sort((a, b) => {
82
+ // Keep 'random' at the end
83
+ if (a.name === 'random') return 1;
84
+ if (b.name === 'random') return -1;
85
+ return a.name.localeCompare(b.name);
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Main function
91
+ */
92
+ function main() {
93
+ const args = process.argv.slice(2);
94
+
95
+ // Parse arguments
96
+ const personalitiesDir = args[0] || path.join(os.homedir(), '.claude', 'personalities');
97
+ const currentPersonality = args[1] || 'normal';
98
+
99
+ const personalities = getPersonalities(personalitiesDir, currentPersonality);
100
+
101
+ // Display with boxen
102
+ const output = formatPersonalitiesList(personalities, {
103
+ columns: 2,
104
+ showUsage: true
105
+ });
106
+
107
+ console.log(output);
108
+ }
109
+
110
+ main();