walkietalkiebot 0.3.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 (44) hide show
  1. package/.claude-plugin/plugin.json +8 -0
  2. package/.mcp.json +8 -0
  3. package/README.md +169 -0
  4. package/bin/wtb-server.js +336 -0
  5. package/bin/wtb.js +43 -0
  6. package/dist/Talkie_logo.png +0 -0
  7. package/dist/assets/index-UPnYoRh1.js +81 -0
  8. package/dist/assets/index-VbGv60d-.css +1 -0
  9. package/dist/index.html +14 -0
  10. package/mcp-server/dist/index.js +401 -0
  11. package/package.json +86 -0
  12. package/server/api.js +629 -0
  13. package/server/db/index.js +67 -0
  14. package/server/db/repositories/activities.js +85 -0
  15. package/server/db/repositories/activities.test.js +106 -0
  16. package/server/db/repositories/conversations.js +93 -0
  17. package/server/db/repositories/conversations.test.js +137 -0
  18. package/server/db/repositories/jobs.js +128 -0
  19. package/server/db/repositories/messages.js +98 -0
  20. package/server/db/repositories/messages.test.js +152 -0
  21. package/server/db/repositories/plans.js +57 -0
  22. package/server/db/repositories/plans.test.js +98 -0
  23. package/server/db/repositories/search.js +34 -0
  24. package/server/db/repositories/search.test.js +61 -0
  25. package/server/db/repositories/telegram.js +30 -0
  26. package/server/db/schema.js +165 -0
  27. package/server/index.js +137 -0
  28. package/server/jobs/api.js +108 -0
  29. package/server/jobs/manager.js +231 -0
  30. package/server/jobs/runner.js +246 -0
  31. package/server/notifications/dispatcher.js +40 -0
  32. package/server/notifications/macos.js +24 -0
  33. package/server/notifications/types.js +0 -0
  34. package/server/ssl.js +61 -0
  35. package/server/state.js +30 -0
  36. package/server/telegram/commands.js +160 -0
  37. package/server/telegram/handlers.js +299 -0
  38. package/server/telegram/index.js +46 -0
  39. package/server/test/helpers.js +14 -0
  40. package/skills/export-tape/SKILL.md +26 -0
  41. package/skills/launch-voice/SKILL.md +25 -0
  42. package/skills/manage-plans/SKILL.md +32 -0
  43. package/skills/save-conversation/SKILL.md +24 -0
  44. package/skills/search-tapes/SKILL.md +21 -0
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "wtb",
3
+ "description": "Walkie Talkie Bot — voice-first cassette tape interface for Claude Code. 30 MCP tools for conversations, plans, search, voice, and more. Data tools work offline via direct SQLite.",
4
+ "version": "0.3.0",
5
+ "author": { "name": "Ben Gillin" },
6
+ "homepage": "https://walkietalkie.bot",
7
+ "repository": "https://github.com/bengillin/walkietalkiebot"
8
+ }
package/.mcp.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "wtb": {
4
+ "command": "node",
5
+ "args": ["${CLAUDE_PLUGIN_ROOT}/mcp-server/dist/index.js"]
6
+ }
7
+ }
8
+ }
package/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # Walkie Talkie Bot
2
+
3
+ [![npm version](https://img.shields.io/npm/v/walkietalkiebot.svg)](https://www.npmjs.com/package/walkietalkiebot) [![npm downloads](https://img.shields.io/npm/dm/walkietalkiebot.svg)](https://www.npmjs.com/package/walkietalkiebot)
4
+
5
+ A walkie talkie for Claude. Your voice is the interface.
6
+
7
+ > **Website**: [walkietalkie.bot](https://walkietalkie.bot) -- **Docs**: [walkietalkie.bot/docs](https://walkietalkie.bot/docs/)
8
+
9
+ ## Install
10
+
11
+ Two ways to use Walkie Talkie Bot. Pick one (or both).
12
+
13
+ ### Plugin (recommended)
14
+
15
+ Add Walkie Talkie Bot as a Claude Code plugin for 30 MCP tools + 5 skills, no server required. Data tools talk directly to SQLite.
16
+
17
+ Add to `~/.claude/settings.json`:
18
+
19
+ ```json
20
+ {
21
+ "mcpServers": {
22
+ "wtb": {
23
+ "command": "npx",
24
+ "args": ["wtb-mcp"]
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ ### Full Install
31
+
32
+ The full server gives you the web UI, voice interface, Telegram bot, and everything.
33
+
34
+ ```bash
35
+ npx walkietalkiebot
36
+ ```
37
+
38
+ This starts the HTTPS server and opens `https://localhost:5173` in your browser.
39
+
40
+ > Requires Chrome or Edge for voice (Web Speech API needs secure context).
41
+
42
+ ## What You Get
43
+
44
+ ### Plugin
45
+
46
+ - **15 data tools** -- conversations, messages, plans, search, liner notes, export. Always work via direct SQLite, no server needed.
47
+ - **15 server tools** -- voice, IPC, sessions, jobs, image analysis, browser control. Require the Walkie Talkie Bot server to be running.
48
+ - **5 skills** -- save-conversation, search-tapes, manage-plans, launch-voice, export-tape.
49
+
50
+ ### Full Install
51
+
52
+ Everything above, plus:
53
+
54
+ - Web UI with 6 retro themes and cassette tape metaphor
55
+ - Push-to-talk, wake word ("hey talkie"), and continuous listening
56
+ - Streaming text-to-speech with selectable system voices
57
+ - Animated robot avatar with 6 states
58
+ - Real-time activity feed for Claude Code tool calls
59
+ - Automatic plan detection and management
60
+ - Per-conversation liner notes
61
+ - Full-text search across all conversations (Cmd+K)
62
+ - Background job execution with SSE streaming
63
+ - Telegram bot integration
64
+ - Image drag-and-drop with Claude vision analysis
65
+ - Markdown and JSON export
66
+
67
+ ## Themes
68
+
69
+ Six fully themed visual experiences. Every UI element reskins -- headers, chat bubbles, conversation cards, viewport borders, modals, and the robot avatar.
70
+
71
+ | Theme | Internal Name | Vibe |
72
+ |-------|--------------|------|
73
+ | **TalkBoy** | `mccallister` | Silver cassette recorder with chunky buttons and red accents |
74
+ | **Bubble** | `imessage` | Minimal and polished, inspired by modern Apple interfaces |
75
+ | **Dial-Up** | `aol` | Beveled gray panels and buddy list energy from the 90s internet |
76
+ | **Finder** | `classic-mac` | The elegant gray desktop of classic Mac OS (System 7/8/9) |
77
+ | **Guestbook** | `geocities` | Neon text on dark backgrounds, like a 90s homepage under construction |
78
+ | **1984** | `apple-1984` | Rainbow Apple warmth from the original Macintosh era |
79
+
80
+ Switch themes via the header button or settings drawer.
81
+
82
+ ## MCP Tools
83
+
84
+ ### Data Tools (offline)
85
+
86
+ Work directly against SQLite at `~/.wtb/wtb.db`. No server needed.
87
+
88
+ | Tool | Description |
89
+ |------|-------------|
90
+ | `list_conversations` | List all saved conversations with pagination |
91
+ | `get_conversation` | Get full conversation with messages, images, and activities |
92
+ | `create_conversation` | Create a new conversation (cassette tape) |
93
+ | `rename_conversation` | Rename an existing conversation |
94
+ | `delete_conversation` | Delete a conversation permanently |
95
+ | `search_conversations` | Full-text search (FTS5) across all conversations |
96
+ | `add_message` | Add a message to a conversation |
97
+ | `list_plans` | List all plans |
98
+ | `get_plan` | Get plan by ID with full content |
99
+ | `create_plan` | Create a plan (draft/approved/in_progress/completed/archived) |
100
+ | `update_plan` | Update plan title, content, or status |
101
+ | `delete_plan` | Delete a plan |
102
+ | `get_liner_notes` | Get per-conversation markdown notes |
103
+ | `set_liner_notes` | Set or clear liner notes |
104
+ | `export_conversation` | Export as markdown or JSON |
105
+
106
+ ### Server Tools (requires server)
107
+
108
+ Require the Walkie Talkie Bot server to be running (`npx walkietalkiebot`).
109
+
110
+ | Tool | Description |
111
+ |------|-------------|
112
+ | `launch_wtb` | Start server and open browser |
113
+ | `get_wtb_status` | Check running status and avatar state |
114
+ | `get_transcript` | Get latest voice transcript |
115
+ | `get_conversation_history` | Get current tape's conversation messages |
116
+ | `get_claude_session` | Get connected session ID |
117
+ | `set_claude_session` | Connect to a Claude Code session |
118
+ | `disconnect_claude_session` | Disconnect session |
119
+ | `get_pending_message` | Poll for user messages (IPC mode) |
120
+ | `respond_to_wtb` | Send response to user (IPC mode) |
121
+ | `update_wtb_state` | Set avatar state and transcript |
122
+ | `analyze_image` | Analyze image via Claude vision |
123
+ | `open_url` | Open URL in default browser |
124
+ | `create_wtb_job` | Create background async job |
125
+ | `get_wtb_job` | Get job status and result |
126
+ | `list_wtb_jobs` | List jobs by status |
127
+
128
+ ### Skills
129
+
130
+ | Skill | Description |
131
+ |-------|-------------|
132
+ | `save-conversation` | Save the current conversation as a cassette tape |
133
+ | `search-tapes` | Search across all saved tapes |
134
+ | `manage-plans` | Create, update, and track plans |
135
+ | `launch-voice` | Start the voice interface |
136
+ | `export-tape` | Export a conversation to markdown or JSON |
137
+
138
+ ## Configuration
139
+
140
+ Set `WTB_PORT` to change the default port (5173).
141
+
142
+ **Server management:**
143
+
144
+ ```bash
145
+ wtb-server start [-f] # Start (background or foreground)
146
+ wtb-server stop # Stop
147
+ wtb-server status # Show status
148
+ wtb-server logs [-f] # View logs
149
+ wtb-server install # Install as macOS launchd daemon
150
+ ```
151
+
152
+ **Telegram bot:** Create a bot via [@BotFather](https://t.me/BotFather), then set `TELEGRAM_BOT_TOKEN` or save to `~/.wtb/telegram.token`. The bot starts automatically with the server.
153
+
154
+ ## Development
155
+
156
+ ```bash
157
+ npm install # Install dependencies
158
+ npm run dev # Vite dev server (frontend only)
159
+ npm run build # TypeScript check + Vite build + server bundle + MCP server
160
+ npm run test # Run all tests (140 tests across 10 files)
161
+ ```
162
+
163
+ The frontend is decomposed into custom hooks (`src/hooks/`) for voice I/O, Claude chat, keyboard control, FAB dragging, image analysis, and server sync. The server has full test coverage for all database repositories and HTTP API endpoints.
164
+
165
+ Full architecture, API reference, and integration guides at [walkietalkie.bot/docs](https://walkietalkie.bot/docs/).
166
+
167
+ ## License
168
+
169
+ MIT
@@ -0,0 +1,336 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn, execSync } from 'child_process'
4
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync, createReadStream } from 'fs'
5
+ import { join, dirname } from 'path'
6
+ import { homedir } from 'os'
7
+ import { fileURLToPath } from 'url'
8
+ import { createInterface } from 'readline'
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url))
11
+ const PLIST_NAME = 'com.wtb.server'
12
+ const PLIST_PATH = join(homedir(), 'Library', 'LaunchAgents', `${PLIST_NAME}.plist`)
13
+ const LOG_DIR = join(homedir(), '.wtb', 'logs')
14
+ const PID_FILE = join(homedir(), '.wtb', 'server.pid')
15
+ const PORT = parseInt(process.env.WTB_PORT || '5173', 10)
16
+
17
+ function ensureDirs() {
18
+ const wtbDir = join(homedir(), '.wtb')
19
+ if (!existsSync(wtbDir)) {
20
+ mkdirSync(wtbDir, { recursive: true })
21
+ }
22
+ if (!existsSync(LOG_DIR)) {
23
+ mkdirSync(LOG_DIR, { recursive: true })
24
+ }
25
+ const launchAgentsDir = dirname(PLIST_PATH)
26
+ if (!existsSync(launchAgentsDir)) {
27
+ mkdirSync(launchAgentsDir, { recursive: true })
28
+ }
29
+ }
30
+
31
+ function generatePlist() {
32
+ const serverScript = join(__dirname, '..', 'server', 'index.js')
33
+
34
+ return `<?xml version="1.0" encoding="UTF-8"?>
35
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
36
+ <plist version="1.0">
37
+ <dict>
38
+ <key>Label</key>
39
+ <string>${PLIST_NAME}</string>
40
+ <key>ProgramArguments</key>
41
+ <array>
42
+ <string>/usr/bin/env</string>
43
+ <string>node</string>
44
+ <string>${serverScript}</string>
45
+ </array>
46
+ <key>EnvironmentVariables</key>
47
+ <dict>
48
+ <key>PORT</key>
49
+ <string>${PORT}</string>
50
+ <key>PATH</key>
51
+ <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
52
+ </dict>
53
+ <key>WorkingDirectory</key>
54
+ <string>${join(__dirname, '..')}</string>
55
+ <key>RunAtLoad</key>
56
+ <true/>
57
+ <key>KeepAlive</key>
58
+ <true/>
59
+ <key>StandardOutPath</key>
60
+ <string>${join(LOG_DIR, 'stdout.log')}</string>
61
+ <key>StandardErrorPath</key>
62
+ <string>${join(LOG_DIR, 'stderr.log')}</string>
63
+ </dict>
64
+ </plist>`
65
+ }
66
+
67
+ function isLaunchctlLoaded() {
68
+ try {
69
+ const result = execSync(`launchctl list ${PLIST_NAME} 2>/dev/null`, { encoding: 'utf-8' })
70
+ return result.includes(PLIST_NAME)
71
+ } catch {
72
+ return false
73
+ }
74
+ }
75
+
76
+ function isServerRunning() {
77
+ try {
78
+ execSync(`curl -s -k https://localhost:${PORT}/api/status`, { encoding: 'utf-8' })
79
+ return true
80
+ } catch {
81
+ return false
82
+ }
83
+ }
84
+
85
+ function getPid() {
86
+ if (existsSync(PID_FILE)) {
87
+ return parseInt(readFileSync(PID_FILE, 'utf-8').trim(), 10)
88
+ }
89
+ return null
90
+ }
91
+
92
+ async function commands() {
93
+ const cmd = process.argv[2]
94
+ const args = process.argv.slice(3)
95
+
96
+ switch (cmd) {
97
+ case 'start':
98
+ await startServer(args.includes('--foreground') || args.includes('-f'))
99
+ break
100
+
101
+ case 'stop':
102
+ await stopServer()
103
+ break
104
+
105
+ case 'restart':
106
+ await stopServer()
107
+ await startServer(false)
108
+ break
109
+
110
+ case 'status':
111
+ await showStatus()
112
+ break
113
+
114
+ case 'logs':
115
+ await showLogs(args.includes('--follow') || args.includes('-f'))
116
+ break
117
+
118
+ case 'install':
119
+ await installDaemon()
120
+ break
121
+
122
+ case 'uninstall':
123
+ await uninstallDaemon()
124
+ break
125
+
126
+ default:
127
+ showHelp()
128
+ }
129
+ }
130
+
131
+ async function startServer(foreground = false) {
132
+ ensureDirs()
133
+
134
+ if (isServerRunning()) {
135
+ console.log('Server is already running.')
136
+ return
137
+ }
138
+
139
+ if (foreground) {
140
+ console.log(`Starting Walkie Talkie Bot server in foreground on port ${PORT}...`)
141
+ const { startServer } = await import('../server/index.js')
142
+ await startServer(PORT)
143
+ } else {
144
+ // Check if launchd plist is installed
145
+ if (existsSync(PLIST_PATH)) {
146
+ console.log('Starting via launchd...')
147
+ execSync(`launchctl load ${PLIST_PATH}`)
148
+ } else {
149
+ // Start as background process
150
+ console.log('Starting as background process...')
151
+ const serverScript = join(__dirname, '..', 'server', 'index.js')
152
+ const child = spawn('node', [serverScript], {
153
+ detached: true,
154
+ stdio: ['ignore', 'pipe', 'pipe'],
155
+ env: { ...process.env, PORT: String(PORT) },
156
+ cwd: join(__dirname, '..'),
157
+ })
158
+
159
+ // Save PID
160
+ writeFileSync(PID_FILE, String(child.pid))
161
+
162
+ // Pipe logs
163
+ const stdout = createWriteStream(join(LOG_DIR, 'stdout.log'), { flags: 'a' })
164
+ const stderr = createWriteStream(join(LOG_DIR, 'stderr.log'), { flags: 'a' })
165
+ child.stdout?.pipe(stdout)
166
+ child.stderr?.pipe(stderr)
167
+
168
+ child.unref()
169
+ }
170
+
171
+ // Wait for server to be ready
172
+ console.log('Waiting for server to start...')
173
+ for (let i = 0; i < 30; i++) {
174
+ await new Promise(r => setTimeout(r, 500))
175
+ if (isServerRunning()) {
176
+ console.log(`Walkie Talkie Bot server running at https://localhost:${PORT}`)
177
+ return
178
+ }
179
+ }
180
+
181
+ console.error('Server failed to start. Check logs with: wtb-server logs')
182
+ }
183
+ }
184
+
185
+ async function stopServer() {
186
+ if (isLaunchctlLoaded()) {
187
+ console.log('Stopping via launchd...')
188
+ try {
189
+ execSync(`launchctl unload ${PLIST_PATH}`)
190
+ } catch {
191
+ // Ignore errors
192
+ }
193
+ }
194
+
195
+ const pid = getPid()
196
+ if (pid) {
197
+ console.log(`Stopping process ${pid}...`)
198
+ try {
199
+ process.kill(pid, 'SIGTERM')
200
+ unlinkSync(PID_FILE)
201
+ } catch {
202
+ // Process may already be dead
203
+ }
204
+ }
205
+
206
+ // Wait for server to stop
207
+ for (let i = 0; i < 10; i++) {
208
+ await new Promise(r => setTimeout(r, 500))
209
+ if (!isServerRunning()) {
210
+ console.log('Server stopped.')
211
+ return
212
+ }
213
+ }
214
+
215
+ if (isServerRunning()) {
216
+ console.log('Warning: Server may still be running.')
217
+ } else {
218
+ console.log('Server stopped.')
219
+ }
220
+ }
221
+
222
+ async function showStatus() {
223
+ const running = isServerRunning()
224
+ const launchd = isLaunchctlLoaded()
225
+
226
+ console.log('Walkie Talkie Bot Server Status')
227
+ console.log('================================')
228
+ console.log(`Server: ${running ? '✅ Running' : '❌ Stopped'}`)
229
+ console.log(`Port: ${PORT}`)
230
+ console.log(`launchd: ${launchd ? '✅ Loaded' : '⚪ Not loaded'}`)
231
+ console.log(`Plist: ${existsSync(PLIST_PATH) ? '✅ Installed' : '⚪ Not installed'}`)
232
+
233
+ if (running) {
234
+ try {
235
+ const response = await fetch(`https://localhost:${PORT}/api/status`, {
236
+ // @ts-expect-error
237
+ agent: new (await import('https')).Agent({ rejectUnauthorized: false })
238
+ })
239
+ const data = await response.json()
240
+ console.log(`Database: ${data.dbStatus === 'connected' ? '✅ Connected' : '⚠️ Unavailable'}`)
241
+ console.log(`Avatar: ${data.avatarState}`)
242
+ } catch {
243
+ // Ignore
244
+ }
245
+ }
246
+ }
247
+
248
+ async function showLogs(follow = false) {
249
+ const logFile = join(LOG_DIR, 'stdout.log')
250
+ const errFile = join(LOG_DIR, 'stderr.log')
251
+
252
+ if (!existsSync(logFile) && !existsSync(errFile)) {
253
+ console.log('No logs found.')
254
+ return
255
+ }
256
+
257
+ if (follow) {
258
+ console.log('Following logs (Ctrl+C to stop)...\n')
259
+ const tail = spawn('tail', ['-f', logFile, errFile])
260
+ tail.stdout.pipe(process.stdout)
261
+ tail.stderr.pipe(process.stderr)
262
+
263
+ process.on('SIGINT', () => {
264
+ tail.kill()
265
+ process.exit(0)
266
+ })
267
+
268
+ await new Promise(() => {}) // Wait forever
269
+ } else {
270
+ // Show last 50 lines
271
+ if (existsSync(logFile)) {
272
+ console.log('=== stdout.log ===')
273
+ const stdout = readFileSync(logFile, 'utf-8').split('\n').slice(-50).join('\n')
274
+ console.log(stdout)
275
+ }
276
+
277
+ if (existsSync(errFile)) {
278
+ console.log('\n=== stderr.log ===')
279
+ const stderr = readFileSync(errFile, 'utf-8').split('\n').slice(-50).join('\n')
280
+ console.log(stderr)
281
+ }
282
+ }
283
+ }
284
+
285
+ async function installDaemon() {
286
+ ensureDirs()
287
+
288
+ const plist = generatePlist()
289
+ writeFileSync(PLIST_PATH, plist)
290
+ console.log(`Installed plist to ${PLIST_PATH}`)
291
+
292
+ // Load the daemon
293
+ execSync(`launchctl load ${PLIST_PATH}`)
294
+ console.log('Daemon loaded and started.')
295
+ console.log(`Server will start automatically on login.`)
296
+ console.log(`\nTo uninstall: wtb-server uninstall`)
297
+ }
298
+
299
+ async function uninstallDaemon() {
300
+ if (isLaunchctlLoaded()) {
301
+ execSync(`launchctl unload ${PLIST_PATH}`)
302
+ console.log('Daemon unloaded.')
303
+ }
304
+
305
+ if (existsSync(PLIST_PATH)) {
306
+ unlinkSync(PLIST_PATH)
307
+ console.log(`Removed ${PLIST_PATH}`)
308
+ }
309
+
310
+ console.log('Daemon uninstalled.')
311
+ }
312
+
313
+ function showHelp() {
314
+ console.log(`
315
+ Walkie Talkie Bot Server CLI
316
+
317
+ Usage: wtb-server <command> [options]
318
+
319
+ Commands:
320
+ start [-f] Start the server (use -f for foreground)
321
+ stop Stop the server
322
+ restart Restart the server
323
+ status Show server status
324
+ logs [-f] Show logs (use -f to follow)
325
+ install Install as launchd daemon (auto-start on login)
326
+ uninstall Remove launchd daemon
327
+
328
+ Environment:
329
+ WTB_PORT Server port (default: 5173)
330
+ `)
331
+ }
332
+
333
+ // Import createWriteStream for logging
334
+ import { createWriteStream } from 'fs'
335
+
336
+ commands().catch(console.error)
package/bin/wtb.js ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { startServer } from '../server/index.js'
4
+ import open from 'open'
5
+
6
+ const PORT = parseInt(process.env.WTB_PORT || '5173', 10)
7
+ const URL = `https://localhost:${PORT}`
8
+
9
+ async function main() {
10
+ console.log('Starting Walkie Talkie Bot...')
11
+
12
+ try {
13
+ await startServer(PORT)
14
+
15
+ // Open browser after server starts (prefer Chrome for Web Speech API support)
16
+ console.log(`Opening ${URL}...`)
17
+ console.log('(Requires Chrome/Edge - Firefox does not support Web Speech API)')
18
+
19
+ // Try to open in Chrome, fall back to default browser
20
+ try {
21
+ await open(URL, { app: { name: 'google chrome' } })
22
+ } catch {
23
+ await open(URL)
24
+ }
25
+
26
+ console.log('\nWalkie Talkie Bot is running. Press Ctrl+C to stop.')
27
+ } catch (err) {
28
+ if (err instanceof Error && err.message.includes('already in use')) {
29
+ console.log(`Walkie Talkie Bot appears to already be running on port ${PORT}`)
30
+ console.log(`Opening ${URL}...`)
31
+ try {
32
+ await open(URL, { app: { name: 'google chrome' } })
33
+ } catch {
34
+ await open(URL)
35
+ }
36
+ } else {
37
+ console.error('Failed to start Walkie Talkie Bot:', err)
38
+ process.exit(1)
39
+ }
40
+ }
41
+ }
42
+
43
+ main()
Binary file