agentgui 1.0.274 → 1.0.275

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 (69) hide show
  1. package/CLAUDE.md +280 -280
  2. package/IPFS_DOWNLOADER.md +277 -277
  3. package/TASK_2C_COMPLETION.md +334 -334
  4. package/bin/gmgui.cjs +54 -54
  5. package/build-portable.js +3 -42
  6. package/database.js +1422 -1406
  7. package/lib/claude-runner.js +1130 -1130
  8. package/lib/ipfs-downloader.js +459 -459
  9. package/lib/speech.js +152 -152
  10. package/package.json +1 -1
  11. package/readme.md +76 -76
  12. package/server.js +3787 -3794
  13. package/setup-npm-token.sh +68 -68
  14. package/static/app.js +773 -773
  15. package/static/event-rendering-showcase.html +708 -708
  16. package/static/index.html +3178 -3180
  17. package/static/js/agent-auth.js +298 -298
  18. package/static/js/audio-recorder-processor.js +18 -18
  19. package/static/js/client.js +2656 -2656
  20. package/static/js/conversations.js +583 -583
  21. package/static/js/dialogs.js +267 -267
  22. package/static/js/event-consolidator.js +101 -101
  23. package/static/js/event-filter.js +311 -311
  24. package/static/js/event-processor.js +452 -452
  25. package/static/js/features.js +413 -413
  26. package/static/js/kalman-filter.js +67 -67
  27. package/static/js/progress-dialog.js +130 -130
  28. package/static/js/script-runner.js +219 -219
  29. package/static/js/streaming-renderer.js +2123 -2120
  30. package/static/js/syntax-highlighter.js +269 -269
  31. package/static/js/tts-websocket-handler.js +152 -152
  32. package/static/js/ui-components.js +431 -431
  33. package/static/js/voice.js +849 -849
  34. package/static/js/websocket-manager.js +596 -596
  35. package/static/templates/INDEX.html +465 -465
  36. package/static/templates/README.md +190 -190
  37. package/static/templates/agent-capabilities.html +56 -56
  38. package/static/templates/agent-metadata-panel.html +44 -44
  39. package/static/templates/agent-status-badge.html +30 -30
  40. package/static/templates/code-annotation-panel.html +155 -155
  41. package/static/templates/code-suggestion-panel.html +184 -184
  42. package/static/templates/command-header.html +77 -77
  43. package/static/templates/command-output-scrollable.html +118 -118
  44. package/static/templates/elapsed-time.html +54 -54
  45. package/static/templates/error-alert.html +106 -106
  46. package/static/templates/error-history-timeline.html +160 -160
  47. package/static/templates/error-recovery-options.html +109 -109
  48. package/static/templates/error-stack-trace.html +95 -95
  49. package/static/templates/error-summary.html +80 -80
  50. package/static/templates/event-counter.html +48 -48
  51. package/static/templates/execution-actions.html +97 -97
  52. package/static/templates/execution-progress-bar.html +80 -80
  53. package/static/templates/execution-stepper.html +120 -120
  54. package/static/templates/file-breadcrumb.html +118 -118
  55. package/static/templates/file-diff-viewer.html +121 -121
  56. package/static/templates/file-metadata.html +133 -133
  57. package/static/templates/file-read-panel.html +66 -66
  58. package/static/templates/file-write-panel.html +120 -120
  59. package/static/templates/git-branch-remote.html +107 -107
  60. package/static/templates/git-diff-list.html +101 -101
  61. package/static/templates/git-log-visualization.html +153 -153
  62. package/static/templates/git-status-panel.html +115 -115
  63. package/static/templates/quality-metrics-display.html +170 -170
  64. package/static/templates/terminal-output-panel.html +87 -87
  65. package/static/templates/test-results-display.html +144 -144
  66. package/static/theme.js +72 -72
  67. package/test-download-progress.js +223 -223
  68. package/test-websocket-broadcast.js +147 -147
  69. package/tests/ipfs-downloader.test.js +370 -370
package/lib/speech.js CHANGED
@@ -1,152 +1,152 @@
1
- import { createRequire } from 'module';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import os from 'os';
5
- import http from 'http';
6
- import { fileURLToPath } from 'url';
7
-
8
- const require = createRequire(import.meta.url);
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
- const ROOT = path.dirname(__dirname);
11
-
12
- const serverSTT = require('webtalk/server-stt');
13
- const serverTTS = require('webtalk/server-tts');
14
-
15
- const EXTRA_VOICE_DIRS = [path.join(ROOT, 'voices')];
16
-
17
- const POCKET_TTS_VOICES = [
18
- { id: 'default', name: 'Default', gender: 'female', accent: 'French' },
19
- { id: 'alba', name: 'Alba', gender: 'female', accent: 'French' },
20
- { id: 'marius', name: 'Marius', gender: 'male', accent: 'French' },
21
- { id: 'javert', name: 'Javert', gender: 'male', accent: 'French' },
22
- { id: 'jean', name: 'Jean', gender: 'male', accent: 'French' },
23
- { id: 'fantine', name: 'Fantine', gender: 'female', accent: 'French' },
24
- { id: 'cosette', name: 'Cosette', gender: 'female', accent: 'French' },
25
- { id: 'eponine', name: 'Eponine', gender: 'female', accent: 'French' },
26
- { id: 'azelma', name: 'Azelma', gender: 'female', accent: 'French' },
27
- ];
28
-
29
- const PREDEFINED_IDS = new Set(POCKET_TTS_VOICES.filter(v => v.id !== 'default').map(v => v.id));
30
- const POCKET_PORT = 8787;
31
-
32
- function safeGetVoices(extraDirs) {
33
- if (typeof serverTTS.getVoices === 'function') {
34
- return serverTTS.getVoices(extraDirs || []);
35
- }
36
- return [];
37
- }
38
-
39
- const needsPatch = !safeGetVoices(EXTRA_VOICE_DIRS).some(v => v.id === 'alba' && !v.isCustom);
40
-
41
- function synthesizeDirect(text, voiceId) {
42
- const voicePath = serverTTS.findVoiceFile(voiceId, EXTRA_VOICE_DIRS);
43
- const isPredefined = voiceId && PREDEFINED_IDS.has(voiceId);
44
- const boundary = '----PocketTTS' + Date.now();
45
- const parts = [];
46
- parts.push(`--${boundary}\r\nContent-Disposition: form-data; name="text"\r\n\r\n${text}\r\n`);
47
- if (voicePath) {
48
- const data = fs.readFileSync(voicePath);
49
- const name = path.basename(voicePath);
50
- parts.push(`--${boundary}\r\nContent-Disposition: form-data; name="voice_wav"; filename="${name}"\r\nContent-Type: audio/wav\r\n\r\n`);
51
- parts.push(data);
52
- parts.push('\r\n');
53
- } else if (isPredefined) {
54
- parts.push(`--${boundary}\r\nContent-Disposition: form-data; name="voice_url"\r\n\r\n${voiceId}\r\n`);
55
- }
56
- parts.push(`--${boundary}--\r\n`);
57
- const body = Buffer.concat(parts.map(p => Buffer.isBuffer(p) ? p : Buffer.from(p)));
58
- return new Promise((resolve, reject) => {
59
- const req = http.request({
60
- hostname: '127.0.0.1', port: POCKET_PORT, path: '/tts', method: 'POST',
61
- headers: { 'Content-Type': `multipart/form-data; boundary=${boundary}`, 'Content-Length': body.length },
62
- timeout: 60000,
63
- }, res => {
64
- if (res.statusCode !== 200) {
65
- let e = '';
66
- res.on('data', d => e += d);
67
- res.on('end', () => reject(new Error(`pocket-tts HTTP ${res.statusCode}: ${e}`)));
68
- return;
69
- }
70
- const chunks = [];
71
- res.on('data', d => chunks.push(d));
72
- res.on('end', () => resolve(Buffer.concat(chunks)));
73
- });
74
- req.on('error', reject);
75
- req.on('timeout', () => { req.destroy(); reject(new Error('pocket-tts timeout')); });
76
- req.write(body);
77
- req.end();
78
- });
79
- }
80
-
81
- function transcribe(audioBuffer) {
82
- return serverSTT.transcribe(audioBuffer);
83
- }
84
-
85
- function getSTT() {
86
- return serverSTT.getSTT();
87
- }
88
-
89
- function synthesize(text, voiceId) {
90
- if (needsPatch && voiceId && PREDEFINED_IDS.has(voiceId)) {
91
- return synthesizeDirect(text, voiceId);
92
- }
93
- return serverTTS.synthesize(text, voiceId, EXTRA_VOICE_DIRS);
94
- }
95
-
96
- function synthesizeStream(text, voiceId) {
97
- if (needsPatch && voiceId && PREDEFINED_IDS.has(voiceId)) {
98
- return (async function* () {
99
- yield await synthesizeDirect(text, voiceId);
100
- })();
101
- }
102
- return serverTTS.synthesizeStream(text, voiceId, EXTRA_VOICE_DIRS);
103
- }
104
-
105
- function getVoices() {
106
- const upstream = safeGetVoices(EXTRA_VOICE_DIRS);
107
- const custom = upstream.filter(v => v.isCustom);
108
- return [...POCKET_TTS_VOICES, ...custom];
109
- }
110
-
111
- function getStatus() {
112
- const sttStatus = serverSTT.getStatus();
113
- const ttsStatus = serverTTS.getStatus();
114
- return {
115
- sttReady: sttStatus.ready,
116
- ttsReady: ttsStatus.ready,
117
- sttLoading: sttStatus.loading,
118
- ttsLoading: false,
119
- sttError: sttStatus.error,
120
- ttsError: ttsStatus.ready ? null : (ttsStatus.lastError || 'pocket-tts not running'),
121
- pocketTts: ttsStatus,
122
- };
123
- }
124
-
125
- function preloadTTS() {
126
- if (typeof serverTTS.findVoiceFile !== 'function' || typeof serverTTS.start !== 'function') {
127
- console.log('[TTS] pocket-tts functions not available');
128
- return;
129
- }
130
- const defaultVoice = serverTTS.findVoiceFile('custom_cleetus', EXTRA_VOICE_DIRS) || '/config/voices/cleetus.wav';
131
- const voicePath = fs.existsSync(defaultVoice) ? defaultVoice : null;
132
- serverTTS.start(voicePath, {}).then(ok => {
133
- if (ok) console.log('[TTS] pocket-tts sidecar started');
134
- else console.log('[TTS] pocket-tts failed to start');
135
- }).catch(err => {
136
- console.error('[TTS] pocket-tts start error:', err.message);
137
- });
138
- }
139
-
140
- function ttsCacheKey(text, voiceId) {
141
- return typeof serverTTS.ttsCacheKey === 'function' ? serverTTS.ttsCacheKey(text, voiceId) : null;
142
- }
143
-
144
- function ttsCacheGet(key) {
145
- return typeof serverTTS.ttsCacheGet === 'function' ? serverTTS.ttsCacheGet(key) : null;
146
- }
147
-
148
- function splitSentences(text) {
149
- return typeof serverTTS.splitSentences === 'function' ? serverTTS.splitSentences(text) : [text];
150
- }
151
-
152
- export { transcribe, synthesize, synthesizeStream, getSTT, getStatus, getVoices, preloadTTS, ttsCacheKey, ttsCacheGet, splitSentences };
1
+ import { createRequire } from 'module';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import http from 'http';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const require = createRequire(import.meta.url);
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const ROOT = path.dirname(__dirname);
11
+
12
+ const serverSTT = require('webtalk/server-stt');
13
+ const serverTTS = require('webtalk/server-tts');
14
+
15
+ const EXTRA_VOICE_DIRS = [path.join(ROOT, 'voices')];
16
+
17
+ const POCKET_TTS_VOICES = [
18
+ { id: 'default', name: 'Default', gender: 'female', accent: 'French' },
19
+ { id: 'alba', name: 'Alba', gender: 'female', accent: 'French' },
20
+ { id: 'marius', name: 'Marius', gender: 'male', accent: 'French' },
21
+ { id: 'javert', name: 'Javert', gender: 'male', accent: 'French' },
22
+ { id: 'jean', name: 'Jean', gender: 'male', accent: 'French' },
23
+ { id: 'fantine', name: 'Fantine', gender: 'female', accent: 'French' },
24
+ { id: 'cosette', name: 'Cosette', gender: 'female', accent: 'French' },
25
+ { id: 'eponine', name: 'Eponine', gender: 'female', accent: 'French' },
26
+ { id: 'azelma', name: 'Azelma', gender: 'female', accent: 'French' },
27
+ ];
28
+
29
+ const PREDEFINED_IDS = new Set(POCKET_TTS_VOICES.filter(v => v.id !== 'default').map(v => v.id));
30
+ const POCKET_PORT = 8787;
31
+
32
+ function safeGetVoices(extraDirs) {
33
+ if (typeof serverTTS.getVoices === 'function') {
34
+ return serverTTS.getVoices(extraDirs || []);
35
+ }
36
+ return [];
37
+ }
38
+
39
+ const needsPatch = !safeGetVoices(EXTRA_VOICE_DIRS).some(v => v.id === 'alba' && !v.isCustom);
40
+
41
+ function synthesizeDirect(text, voiceId) {
42
+ const voicePath = serverTTS.findVoiceFile(voiceId, EXTRA_VOICE_DIRS);
43
+ const isPredefined = voiceId && PREDEFINED_IDS.has(voiceId);
44
+ const boundary = '----PocketTTS' + Date.now();
45
+ const parts = [];
46
+ parts.push(`--${boundary}\r\nContent-Disposition: form-data; name="text"\r\n\r\n${text}\r\n`);
47
+ if (voicePath) {
48
+ const data = fs.readFileSync(voicePath);
49
+ const name = path.basename(voicePath);
50
+ parts.push(`--${boundary}\r\nContent-Disposition: form-data; name="voice_wav"; filename="${name}"\r\nContent-Type: audio/wav\r\n\r\n`);
51
+ parts.push(data);
52
+ parts.push('\r\n');
53
+ } else if (isPredefined) {
54
+ parts.push(`--${boundary}\r\nContent-Disposition: form-data; name="voice_url"\r\n\r\n${voiceId}\r\n`);
55
+ }
56
+ parts.push(`--${boundary}--\r\n`);
57
+ const body = Buffer.concat(parts.map(p => Buffer.isBuffer(p) ? p : Buffer.from(p)));
58
+ return new Promise((resolve, reject) => {
59
+ const req = http.request({
60
+ hostname: '127.0.0.1', port: POCKET_PORT, path: '/tts', method: 'POST',
61
+ headers: { 'Content-Type': `multipart/form-data; boundary=${boundary}`, 'Content-Length': body.length },
62
+ timeout: 60000,
63
+ }, res => {
64
+ if (res.statusCode !== 200) {
65
+ let e = '';
66
+ res.on('data', d => e += d);
67
+ res.on('end', () => reject(new Error(`pocket-tts HTTP ${res.statusCode}: ${e}`)));
68
+ return;
69
+ }
70
+ const chunks = [];
71
+ res.on('data', d => chunks.push(d));
72
+ res.on('end', () => resolve(Buffer.concat(chunks)));
73
+ });
74
+ req.on('error', reject);
75
+ req.on('timeout', () => { req.destroy(); reject(new Error('pocket-tts timeout')); });
76
+ req.write(body);
77
+ req.end();
78
+ });
79
+ }
80
+
81
+ function transcribe(audioBuffer) {
82
+ return serverSTT.transcribe(audioBuffer);
83
+ }
84
+
85
+ function getSTT() {
86
+ return serverSTT.getSTT();
87
+ }
88
+
89
+ function synthesize(text, voiceId) {
90
+ if (needsPatch && voiceId && PREDEFINED_IDS.has(voiceId)) {
91
+ return synthesizeDirect(text, voiceId);
92
+ }
93
+ return serverTTS.synthesize(text, voiceId, EXTRA_VOICE_DIRS);
94
+ }
95
+
96
+ function synthesizeStream(text, voiceId) {
97
+ if (needsPatch && voiceId && PREDEFINED_IDS.has(voiceId)) {
98
+ return (async function* () {
99
+ yield await synthesizeDirect(text, voiceId);
100
+ })();
101
+ }
102
+ return serverTTS.synthesizeStream(text, voiceId, EXTRA_VOICE_DIRS);
103
+ }
104
+
105
+ function getVoices() {
106
+ const upstream = safeGetVoices(EXTRA_VOICE_DIRS);
107
+ const custom = upstream.filter(v => v.isCustom);
108
+ return [...POCKET_TTS_VOICES, ...custom];
109
+ }
110
+
111
+ function getStatus() {
112
+ const sttStatus = serverSTT.getStatus();
113
+ const ttsStatus = serverTTS.getStatus();
114
+ return {
115
+ sttReady: sttStatus.ready,
116
+ ttsReady: ttsStatus.ready,
117
+ sttLoading: sttStatus.loading,
118
+ ttsLoading: false,
119
+ sttError: sttStatus.error,
120
+ ttsError: ttsStatus.ready ? null : (ttsStatus.lastError || 'pocket-tts not running'),
121
+ pocketTts: ttsStatus,
122
+ };
123
+ }
124
+
125
+ function preloadTTS() {
126
+ if (typeof serverTTS.findVoiceFile !== 'function' || typeof serverTTS.start !== 'function') {
127
+ console.log('[TTS] pocket-tts functions not available');
128
+ return;
129
+ }
130
+ const defaultVoice = serverTTS.findVoiceFile('custom_cleetus', EXTRA_VOICE_DIRS) || '/config/voices/cleetus.wav';
131
+ const voicePath = fs.existsSync(defaultVoice) ? defaultVoice : null;
132
+ serverTTS.start(voicePath, {}).then(ok => {
133
+ if (ok) console.log('[TTS] pocket-tts sidecar started');
134
+ else console.log('[TTS] pocket-tts failed to start');
135
+ }).catch(err => {
136
+ console.error('[TTS] pocket-tts start error:', err.message);
137
+ });
138
+ }
139
+
140
+ function ttsCacheKey(text, voiceId) {
141
+ return typeof serverTTS.ttsCacheKey === 'function' ? serverTTS.ttsCacheKey(text, voiceId) : null;
142
+ }
143
+
144
+ function ttsCacheGet(key) {
145
+ return typeof serverTTS.ttsCacheGet === 'function' ? serverTTS.ttsCacheGet(key) : null;
146
+ }
147
+
148
+ function splitSentences(text) {
149
+ return typeof serverTTS.splitSentences === 'function' ? serverTTS.splitSentences(text) : [text];
150
+ }
151
+
152
+ export { transcribe, synthesize, synthesizeStream, getSTT, getStatus, getVoices, preloadTTS, ttsCacheKey, ttsCacheGet, splitSentences };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.274",
3
+ "version": "1.0.275",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/readme.md CHANGED
@@ -1,76 +1,76 @@
1
- # AgentGUI
2
-
3
- A multi-agent GUI for AI coding assistants. Connects to CLI-based agents (Claude Code, Gemini CLI, OpenCode, Goose, and others) and provides a web interface with real-time streaming output.
4
-
5
- ## Quick Start
6
-
7
- ```bash
8
- npx agentgui
9
- ```
10
-
11
- Or install and run manually:
12
-
13
- ```bash
14
- git clone https://github.com/AnEntrypoint/agentgui.git
15
- cd agentgui
16
- npm install
17
- npm run dev
18
- ```
19
-
20
- Open `http://localhost:3000` in your browser.
21
-
22
- ## What It Does
23
-
24
- - Auto-discovers AI coding agents installed on your system (Claude Code, Gemini CLI, OpenCode, Goose, Codex, Kiro, etc.)
25
- - Runs agents with streaming JSON output and displays results in real-time via WebSocket
26
- - Manages conversations with SQLite persistence
27
- - Supports concurrent agent sessions
28
- - Provides file browsing and upload for agent working directories
29
- - Includes speech-to-text and text-to-speech
30
-
31
- ## Architecture
32
-
33
- - `server.js` - HTTP server, REST API, WebSocket endpoint, static file serving
34
- - `database.js` - SQLite database (WAL mode) at `~/.gmgui/data.db`
35
- - `lib/claude-runner.js` - Agent runner framework, spawns CLI processes and parses streaming output
36
- - `lib/speech.js` - Speech processing via Hugging Face transformers
37
- - `static/` - Browser client with streaming renderer, WebSocket manager, and HTML templates
38
- - `bin/gmgui.cjs` - CLI entry point for `npx agentgui`
39
-
40
- ## Text-to-Speech on Windows
41
-
42
- On Windows, AgentGUI automatically sets up pocket-tts (text-to-speech) on your first TTS request. No manual setup required.
43
-
44
- ### What Happens
45
- 1. Server detects Python 3.9+ installation
46
- 2. Creates virtual environment at `~/.gmgui/pocket-venv`
47
- 3. Installs pocket-tts via pip
48
- 4. All subsequent TTS requests use cached installation
49
-
50
- ### Requirements
51
- - Python 3.9+ (check with `python --version`)
52
- - ~200 MB free disk space
53
- - Internet connection for first setup
54
-
55
- ### Troubleshooting
56
- - **Python not found**: Download from https://www.python.org and ensure "Add Python to PATH" is checked
57
- - **Setup fails**: Check that you have write access to your home directory (~/.gmgui/)
58
- - **Manual cleanup**: Delete `%USERPROFILE%\.gmgui\pocket-venv` and try again
59
-
60
- For manual setup or detailed troubleshooting, see the setup instructions in the code or check `/api/speech-status` endpoint for error details.
61
-
62
- ## Configuration
63
-
64
- | Variable | Default | Description |
65
- |----------|---------|-------------|
66
- | `PORT` | 3000 | Server port |
67
- | `BASE_URL` | /gm | URL prefix |
68
- | `HOT_RELOAD` | true | Watch mode for development |
69
-
70
- ## License
71
-
72
- MIT
73
-
74
- ## Repository
75
-
76
- https://github.com/AnEntrypoint/agentgui
1
+ # AgentGUI
2
+
3
+ A multi-agent GUI for AI coding assistants. Connects to CLI-based agents (Claude Code, Gemini CLI, OpenCode, Goose, and others) and provides a web interface with real-time streaming output.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npx agentgui
9
+ ```
10
+
11
+ Or install and run manually:
12
+
13
+ ```bash
14
+ git clone https://github.com/AnEntrypoint/agentgui.git
15
+ cd agentgui
16
+ npm install
17
+ npm run dev
18
+ ```
19
+
20
+ Open `http://localhost:3000` in your browser.
21
+
22
+ ## What It Does
23
+
24
+ - Auto-discovers AI coding agents installed on your system (Claude Code, Gemini CLI, OpenCode, Goose, Codex, Kiro, etc.)
25
+ - Runs agents with streaming JSON output and displays results in real-time via WebSocket
26
+ - Manages conversations with SQLite persistence
27
+ - Supports concurrent agent sessions
28
+ - Provides file browsing and upload for agent working directories
29
+ - Includes speech-to-text and text-to-speech
30
+
31
+ ## Architecture
32
+
33
+ - `server.js` - HTTP server, REST API, WebSocket endpoint, static file serving
34
+ - `database.js` - SQLite database (WAL mode) at `~/.gmgui/data.db`
35
+ - `lib/claude-runner.js` - Agent runner framework, spawns CLI processes and parses streaming output
36
+ - `lib/speech.js` - Speech processing via Hugging Face transformers
37
+ - `static/` - Browser client with streaming renderer, WebSocket manager, and HTML templates
38
+ - `bin/gmgui.cjs` - CLI entry point for `npx agentgui`
39
+
40
+ ## Text-to-Speech on Windows
41
+
42
+ On Windows, AgentGUI automatically sets up pocket-tts (text-to-speech) on your first TTS request. No manual setup required.
43
+
44
+ ### What Happens
45
+ 1. Server detects Python 3.9+ installation
46
+ 2. Creates virtual environment at `~/.gmgui/pocket-venv`
47
+ 3. Installs pocket-tts via pip
48
+ 4. All subsequent TTS requests use cached installation
49
+
50
+ ### Requirements
51
+ - Python 3.9+ (check with `python --version`)
52
+ - ~200 MB free disk space
53
+ - Internet connection for first setup
54
+
55
+ ### Troubleshooting
56
+ - **Python not found**: Download from https://www.python.org and ensure "Add Python to PATH" is checked
57
+ - **Setup fails**: Check that you have write access to your home directory (~/.gmgui/)
58
+ - **Manual cleanup**: Delete `%USERPROFILE%\.gmgui\pocket-venv` and try again
59
+
60
+ For manual setup or detailed troubleshooting, see the setup instructions in the code or check `/api/speech-status` endpoint for error details.
61
+
62
+ ## Configuration
63
+
64
+ | Variable | Default | Description |
65
+ |----------|---------|-------------|
66
+ | `PORT` | 3000 | Server port |
67
+ | `BASE_URL` | /gm | URL prefix |
68
+ | `HOT_RELOAD` | true | Watch mode for development |
69
+
70
+ ## License
71
+
72
+ MIT
73
+
74
+ ## Repository
75
+
76
+ https://github.com/AnEntrypoint/agentgui