faster-vibecraft 0.2.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.
- package/LICENSE +21 -0
- package/README.md +196 -0
- package/bin/cli.js +824 -0
- package/dist/assets/index-BdtVwnBw.js +4574 -0
- package/dist/assets/index-DCa-RF38.css +1 -0
- package/dist/index.html +497 -0
- package/dist/logo.svg +23 -0
- package/dist/multiclaude.png +0 -0
- package/dist/og-image.png +0 -0
- package/dist/server/server/GitStatusManager.js +232 -0
- package/dist/server/server/ProjectsManager.js +206 -0
- package/dist/server/server/index.js +2066 -0
- package/dist/server/shared/defaults.js +28 -0
- package/dist/server/shared/types.js +33 -0
- package/dist/version.json +6 -0
- package/hooks/install.sh +77 -0
- package/hooks/vibecraft-hook.sh +384 -0
- package/package.json +61 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Vibeshop CLI - 3D visualization for Claude Code
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx vibecraft # Start the server
|
|
8
|
+
* npx vibecraft --help # Show help
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Check if cwd exists (common issue when running from deleted directory)
|
|
12
|
+
try {
|
|
13
|
+
process.cwd()
|
|
14
|
+
} catch (e) {
|
|
15
|
+
console.error('Error: Current directory no longer exists.')
|
|
16
|
+
console.error('This happens when the directory you ran the command from was deleted.')
|
|
17
|
+
console.error('\nFix: cd to a valid directory first:')
|
|
18
|
+
console.error(' cd ~')
|
|
19
|
+
console.error(' npx vibecraft setup')
|
|
20
|
+
process.exit(1)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
import { spawn, execSync } from 'child_process'
|
|
24
|
+
import { dirname, resolve, join, basename } from 'path'
|
|
25
|
+
import { fileURLToPath } from 'url'
|
|
26
|
+
import { existsSync, mkdirSync, readFileSync } from 'fs'
|
|
27
|
+
import { homedir } from 'os'
|
|
28
|
+
|
|
29
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
30
|
+
const ROOT = resolve(__dirname, '..')
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Health Checks
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
function checkJq() {
|
|
37
|
+
try {
|
|
38
|
+
execSync('which jq', { stdio: 'ignore' })
|
|
39
|
+
return true
|
|
40
|
+
} catch {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function checkTmux() {
|
|
46
|
+
try {
|
|
47
|
+
execSync('which tmux', { stdio: 'ignore' })
|
|
48
|
+
return true
|
|
49
|
+
} catch {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function checkHooksConfigured() {
|
|
55
|
+
const settingsPath = join(homedir(), '.claude', 'settings.json')
|
|
56
|
+
if (!existsSync(settingsPath)) {
|
|
57
|
+
return { configured: false, reason: 'no settings file' }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'))
|
|
62
|
+
const hooks = settings.hooks || {}
|
|
63
|
+
const hasPreToolUse = hooks.PreToolUse?.some(h =>
|
|
64
|
+
h.hooks?.some(hh => hh.command?.includes('vibecraft-hook'))
|
|
65
|
+
)
|
|
66
|
+
const hasPostToolUse = hooks.PostToolUse?.some(h =>
|
|
67
|
+
h.hooks?.some(hh => hh.command?.includes('vibecraft-hook'))
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if (hasPreToolUse && hasPostToolUse) {
|
|
71
|
+
return { configured: true }
|
|
72
|
+
}
|
|
73
|
+
return { configured: false, reason: 'hooks not found in settings' }
|
|
74
|
+
} catch (e) {
|
|
75
|
+
return { configured: false, reason: 'failed to parse settings' }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function printHealthCheck() {
|
|
80
|
+
const jqOk = checkJq()
|
|
81
|
+
const tmuxOk = checkTmux()
|
|
82
|
+
const hooksResult = checkHooksConfigured()
|
|
83
|
+
|
|
84
|
+
let warnings = []
|
|
85
|
+
|
|
86
|
+
if (!jqOk) {
|
|
87
|
+
warnings.push(` [!] jq not found - hooks won't work without it
|
|
88
|
+
Install: brew install jq (macOS) or apt install jq (Linux)`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!tmuxOk) {
|
|
92
|
+
warnings.push(` [!] tmux not found - session management won't work
|
|
93
|
+
Install: brew install tmux (macOS) or apt install tmux (Linux)`)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!hooksResult.configured) {
|
|
97
|
+
warnings.push(` [!] Hooks not configured - events won't be captured
|
|
98
|
+
Run: npx vibecraft setup
|
|
99
|
+
Then restart Claude Code`)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (warnings.length > 0) {
|
|
103
|
+
console.log('\n Warnings:')
|
|
104
|
+
warnings.forEach(w => console.log(w))
|
|
105
|
+
console.log()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Parse arguments
|
|
110
|
+
const args = process.argv.slice(2)
|
|
111
|
+
|
|
112
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
113
|
+
console.log(`
|
|
114
|
+
vibecraft - 3D visualization for Claude Code
|
|
115
|
+
|
|
116
|
+
Usage:
|
|
117
|
+
vibecraft [options]
|
|
118
|
+
vibecraft setup Configure Claude Code hooks automatically
|
|
119
|
+
vibecraft uninstall Remove vibecraft hooks (keeps your data)
|
|
120
|
+
vibecraft doctor Diagnose common issues
|
|
121
|
+
|
|
122
|
+
Options:
|
|
123
|
+
--port, -p <port> WebSocket server port (default: 4003)
|
|
124
|
+
--help, -h Show this help message
|
|
125
|
+
--version, -v Show version
|
|
126
|
+
--hook-path Print path to hook script (for manual setup)
|
|
127
|
+
|
|
128
|
+
Environment Variables:
|
|
129
|
+
VIBECRAFT_PORT WebSocket server port (default: 4003)
|
|
130
|
+
VIBECRAFT_DEBUG Enable debug logging (true/false)
|
|
131
|
+
|
|
132
|
+
Setup:
|
|
133
|
+
1. Run: vibecraft setup
|
|
134
|
+
2. Start server: vibecraft
|
|
135
|
+
3. Open frontend in browser
|
|
136
|
+
|
|
137
|
+
Website: https://vibecraft.sh
|
|
138
|
+
GitHub: https://github.com/nearcyan/vibecraft
|
|
139
|
+
`)
|
|
140
|
+
process.exit(0)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Hook path command
|
|
144
|
+
if (args.includes('--hook-path')) {
|
|
145
|
+
console.log(resolve(ROOT, 'hooks/vibecraft-hook.sh'))
|
|
146
|
+
process.exit(0)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Setup command
|
|
150
|
+
if (args[0] === 'setup') {
|
|
151
|
+
const { writeFileSync, copyFileSync, chmodSync } = await import('fs')
|
|
152
|
+
|
|
153
|
+
console.log('Setting up vibecraft hooks...\n')
|
|
154
|
+
|
|
155
|
+
// ==========================================================================
|
|
156
|
+
// Step 1: Find Claude Code settings
|
|
157
|
+
// ==========================================================================
|
|
158
|
+
|
|
159
|
+
// Possible locations for Claude settings (in order of preference)
|
|
160
|
+
const possibleSettingsPaths = [
|
|
161
|
+
join(homedir(), '.claude', 'settings.json'), // Standard location
|
|
162
|
+
join(homedir(), '.config', 'claude', 'settings.json'), // XDG config
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
let settingsPath = null
|
|
166
|
+
let settingsDir = null
|
|
167
|
+
|
|
168
|
+
// Find existing settings file
|
|
169
|
+
for (const path of possibleSettingsPaths) {
|
|
170
|
+
if (existsSync(path)) {
|
|
171
|
+
settingsPath = path
|
|
172
|
+
settingsDir = dirname(path)
|
|
173
|
+
break
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// If no settings file found, use default location
|
|
178
|
+
if (!settingsPath) {
|
|
179
|
+
settingsPath = possibleSettingsPaths[0]
|
|
180
|
+
settingsDir = dirname(settingsPath)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
console.log(`Claude settings: ${settingsPath}`)
|
|
184
|
+
|
|
185
|
+
// ==========================================================================
|
|
186
|
+
// Step 2: Install hook script to ~/.vibecraft/hooks/
|
|
187
|
+
// ==========================================================================
|
|
188
|
+
|
|
189
|
+
const vibecraftHooksDir = join(homedir(), '.vibecraft', 'hooks')
|
|
190
|
+
const installedHookPath = join(vibecraftHooksDir, 'vibecraft-hook.sh')
|
|
191
|
+
const sourceHookPath = resolve(ROOT, 'hooks/vibecraft-hook.sh')
|
|
192
|
+
|
|
193
|
+
// Ensure hooks directory exists
|
|
194
|
+
if (!existsSync(vibecraftHooksDir)) {
|
|
195
|
+
mkdirSync(vibecraftHooksDir, { recursive: true })
|
|
196
|
+
console.log(`Created ${vibecraftHooksDir}`)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Copy hook script
|
|
200
|
+
if (!existsSync(sourceHookPath)) {
|
|
201
|
+
console.error(`ERROR: Hook script not found at ${sourceHookPath}`)
|
|
202
|
+
console.error('This is a bug - please report it.')
|
|
203
|
+
process.exit(1)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
copyFileSync(sourceHookPath, installedHookPath)
|
|
208
|
+
chmodSync(installedHookPath, 0o755) // Make executable
|
|
209
|
+
console.log(`Installed hook: ${installedHookPath}`)
|
|
210
|
+
} catch (e) {
|
|
211
|
+
console.error(`ERROR: Failed to install hook script: ${e.message}`)
|
|
212
|
+
process.exit(1)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ==========================================================================
|
|
216
|
+
// Step 3: Ensure data directory exists
|
|
217
|
+
// ==========================================================================
|
|
218
|
+
|
|
219
|
+
const dataDir = join(homedir(), '.vibecraft', 'data')
|
|
220
|
+
if (!existsSync(dataDir)) {
|
|
221
|
+
mkdirSync(dataDir, { recursive: true })
|
|
222
|
+
console.log(`Created ${dataDir}`)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ==========================================================================
|
|
226
|
+
// Step 4: Configure Claude Code settings
|
|
227
|
+
// ==========================================================================
|
|
228
|
+
|
|
229
|
+
// Ensure settings directory exists
|
|
230
|
+
if (!existsSync(settingsDir)) {
|
|
231
|
+
mkdirSync(settingsDir, { recursive: true })
|
|
232
|
+
console.log(`Created ${settingsDir}`)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Load or create settings
|
|
236
|
+
let settings = {}
|
|
237
|
+
if (existsSync(settingsPath)) {
|
|
238
|
+
try {
|
|
239
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'))
|
|
240
|
+
// Backup existing settings
|
|
241
|
+
const backupPath = `${settingsPath}.backup-${Date.now()}`
|
|
242
|
+
writeFileSync(backupPath, JSON.stringify(settings, null, 2))
|
|
243
|
+
console.log(`Backed up settings: ${backupPath}`)
|
|
244
|
+
} catch (e) {
|
|
245
|
+
console.error(`ERROR: Failed to parse ${settingsPath}: ${e.message}`)
|
|
246
|
+
console.error('Please fix the JSON syntax and try again.')
|
|
247
|
+
process.exit(1)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Hook configurations - use installed path (stable location)
|
|
252
|
+
const toolHookEntry = {
|
|
253
|
+
matcher: '*',
|
|
254
|
+
hooks: [{ type: 'command', command: installedHookPath, timeout: 5 }]
|
|
255
|
+
}
|
|
256
|
+
const genericHookEntry = {
|
|
257
|
+
hooks: [{ type: 'command', command: installedHookPath, timeout: 5 }]
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Initialize hooks object
|
|
261
|
+
settings.hooks = settings.hooks || {}
|
|
262
|
+
|
|
263
|
+
// Helper to add/update hooks (removes old vibecraft hooks first)
|
|
264
|
+
const addHook = (eventType, entry) => {
|
|
265
|
+
settings.hooks[eventType] = settings.hooks[eventType] || []
|
|
266
|
+
// Remove any existing vibecraft hooks (from any location)
|
|
267
|
+
settings.hooks[eventType] = settings.hooks[eventType].filter(h =>
|
|
268
|
+
!h.hooks?.some(hh => hh.command?.includes('vibecraft-hook'))
|
|
269
|
+
)
|
|
270
|
+
// Add new hook
|
|
271
|
+
settings.hooks[eventType].push(entry)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Configure ALL hooks
|
|
275
|
+
addHook('PreToolUse', toolHookEntry)
|
|
276
|
+
addHook('PostToolUse', toolHookEntry)
|
|
277
|
+
addHook('Stop', genericHookEntry)
|
|
278
|
+
addHook('SubagentStop', genericHookEntry)
|
|
279
|
+
addHook('SessionStart', genericHookEntry)
|
|
280
|
+
addHook('SessionEnd', genericHookEntry)
|
|
281
|
+
addHook('UserPromptSubmit', genericHookEntry)
|
|
282
|
+
addHook('Notification', genericHookEntry)
|
|
283
|
+
|
|
284
|
+
// Write settings
|
|
285
|
+
try {
|
|
286
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2))
|
|
287
|
+
console.log(`Updated settings: ${settingsPath}`)
|
|
288
|
+
} catch (e) {
|
|
289
|
+
console.error(`ERROR: Failed to write settings: ${e.message}`)
|
|
290
|
+
process.exit(1)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ==========================================================================
|
|
294
|
+
// Step 5: Verify and report
|
|
295
|
+
// ==========================================================================
|
|
296
|
+
|
|
297
|
+
console.log('\n' + '='.repeat(50))
|
|
298
|
+
console.log('Setup complete!')
|
|
299
|
+
console.log('='.repeat(50))
|
|
300
|
+
|
|
301
|
+
console.log('\nHooks configured:')
|
|
302
|
+
console.log(' - PreToolUse')
|
|
303
|
+
console.log(' - PostToolUse')
|
|
304
|
+
console.log(' - Stop')
|
|
305
|
+
console.log(' - SubagentStop')
|
|
306
|
+
console.log(' - SessionStart')
|
|
307
|
+
console.log(' - SessionEnd')
|
|
308
|
+
console.log(' - UserPromptSubmit')
|
|
309
|
+
console.log(' - Notification')
|
|
310
|
+
|
|
311
|
+
// Check dependencies
|
|
312
|
+
let hasWarnings = false
|
|
313
|
+
|
|
314
|
+
if (!checkJq()) {
|
|
315
|
+
hasWarnings = true
|
|
316
|
+
console.log('\n[!] Warning: jq not found')
|
|
317
|
+
console.log(' Install: brew install jq (macOS) or apt install jq (Linux)')
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (!checkTmux()) {
|
|
321
|
+
hasWarnings = true
|
|
322
|
+
console.log('\n[!] Warning: tmux not found')
|
|
323
|
+
console.log(' Install: brew install tmux (macOS) or apt install tmux (Linux)')
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!hasWarnings) {
|
|
327
|
+
console.log('\nAll dependencies found!')
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Check if server is already running (likely an update)
|
|
331
|
+
let serverRunning = false
|
|
332
|
+
try {
|
|
333
|
+
const res = execSync('curl -s http://localhost:4003/health', { timeout: 2000 })
|
|
334
|
+
if (res.toString().includes('"ok":true')) {
|
|
335
|
+
serverRunning = true
|
|
336
|
+
}
|
|
337
|
+
} catch {}
|
|
338
|
+
|
|
339
|
+
if (serverRunning) {
|
|
340
|
+
// Update scenario
|
|
341
|
+
console.log('\nTo complete the update:')
|
|
342
|
+
console.log(' 1. Restart vibecraft server (Ctrl+C, then run: npx vibecraft)')
|
|
343
|
+
console.log(' 2. Restart Claude Code (for hook changes to take effect)')
|
|
344
|
+
console.log(' 3. Refresh browser\n')
|
|
345
|
+
} else {
|
|
346
|
+
// Fresh install scenario
|
|
347
|
+
console.log('\nNext steps:')
|
|
348
|
+
console.log(' 1. Restart Claude Code (required for hooks to take effect)')
|
|
349
|
+
console.log(' 2. Run: npx vibecraft')
|
|
350
|
+
console.log(' 3. Open http://localhost:4003 in your browser\n')
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
process.exit(0)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Uninstall command
|
|
357
|
+
if (args[0] === 'uninstall') {
|
|
358
|
+
const { writeFileSync, rmSync } = await import('fs')
|
|
359
|
+
|
|
360
|
+
console.log('Uninstalling vibecraft hooks...\n')
|
|
361
|
+
|
|
362
|
+
// ==========================================================================
|
|
363
|
+
// Step 1: Find Claude Code settings
|
|
364
|
+
// ==========================================================================
|
|
365
|
+
|
|
366
|
+
const possibleSettingsPaths = [
|
|
367
|
+
join(homedir(), '.claude', 'settings.json'),
|
|
368
|
+
join(homedir(), '.config', 'claude', 'settings.json'),
|
|
369
|
+
]
|
|
370
|
+
|
|
371
|
+
let settingsPath = null
|
|
372
|
+
for (const path of possibleSettingsPaths) {
|
|
373
|
+
if (existsSync(path)) {
|
|
374
|
+
settingsPath = path
|
|
375
|
+
break
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (!settingsPath) {
|
|
380
|
+
console.log('No Claude settings file found - nothing to uninstall.')
|
|
381
|
+
process.exit(0)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
console.log(`Claude settings: ${settingsPath}`)
|
|
385
|
+
|
|
386
|
+
// ==========================================================================
|
|
387
|
+
// Step 2: Remove vibecraft hooks from settings
|
|
388
|
+
// ==========================================================================
|
|
389
|
+
|
|
390
|
+
let settings
|
|
391
|
+
try {
|
|
392
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'))
|
|
393
|
+
} catch (e) {
|
|
394
|
+
console.error(`ERROR: Failed to parse ${settingsPath}: ${e.message}`)
|
|
395
|
+
process.exit(1)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (!settings.hooks) {
|
|
399
|
+
console.log('No hooks configured - nothing to uninstall.')
|
|
400
|
+
process.exit(0)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Backup before modifying
|
|
404
|
+
const backupPath = `${settingsPath}.backup-${Date.now()}`
|
|
405
|
+
writeFileSync(backupPath, JSON.stringify(settings, null, 2))
|
|
406
|
+
console.log(`Backed up settings: ${backupPath}`)
|
|
407
|
+
|
|
408
|
+
// Remove vibecraft hooks from each event type
|
|
409
|
+
const hookTypes = [
|
|
410
|
+
'PreToolUse', 'PostToolUse', 'Stop', 'SubagentStop',
|
|
411
|
+
'SessionStart', 'SessionEnd', 'UserPromptSubmit', 'Notification'
|
|
412
|
+
]
|
|
413
|
+
|
|
414
|
+
let removedCount = 0
|
|
415
|
+
for (const hookType of hookTypes) {
|
|
416
|
+
if (!settings.hooks[hookType]) continue
|
|
417
|
+
|
|
418
|
+
const before = settings.hooks[hookType].length
|
|
419
|
+
settings.hooks[hookType] = settings.hooks[hookType].filter(h =>
|
|
420
|
+
!h.hooks?.some(hh => hh.command?.includes('vibecraft-hook'))
|
|
421
|
+
)
|
|
422
|
+
const after = settings.hooks[hookType].length
|
|
423
|
+
|
|
424
|
+
if (before !== after) {
|
|
425
|
+
removedCount += (before - after)
|
|
426
|
+
console.log(` Removed vibecraft hook from ${hookType}`)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Clean up empty arrays
|
|
430
|
+
if (settings.hooks[hookType].length === 0) {
|
|
431
|
+
delete settings.hooks[hookType]
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Clean up empty hooks object
|
|
436
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
437
|
+
delete settings.hooks
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (removedCount === 0) {
|
|
441
|
+
console.log('No vibecraft hooks found - nothing to remove.')
|
|
442
|
+
} else {
|
|
443
|
+
// Write updated settings
|
|
444
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2))
|
|
445
|
+
console.log(`\nRemoved ${removedCount} hook(s) from settings.`)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ==========================================================================
|
|
449
|
+
// Step 3: Remove hook script (but keep data)
|
|
450
|
+
// ==========================================================================
|
|
451
|
+
|
|
452
|
+
const hookScript = join(homedir(), '.vibecraft', 'hooks', 'vibecraft-hook.sh')
|
|
453
|
+
if (existsSync(hookScript)) {
|
|
454
|
+
rmSync(hookScript)
|
|
455
|
+
console.log(`Removed: ${hookScript}`)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Remove hooks directory if empty
|
|
459
|
+
const hooksDir = join(homedir(), '.vibecraft', 'hooks')
|
|
460
|
+
if (existsSync(hooksDir)) {
|
|
461
|
+
try {
|
|
462
|
+
const { readdirSync } = await import('fs')
|
|
463
|
+
if (readdirSync(hooksDir).length === 0) {
|
|
464
|
+
rmSync(hooksDir, { recursive: true })
|
|
465
|
+
console.log(`Removed empty directory: ${hooksDir}`)
|
|
466
|
+
}
|
|
467
|
+
} catch {}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// ==========================================================================
|
|
471
|
+
// Done
|
|
472
|
+
// ==========================================================================
|
|
473
|
+
|
|
474
|
+
console.log('\n' + '='.repeat(50))
|
|
475
|
+
console.log('Uninstall complete!')
|
|
476
|
+
console.log('='.repeat(50))
|
|
477
|
+
|
|
478
|
+
console.log('\nVibecraft hooks have been removed.')
|
|
479
|
+
console.log('Your data is preserved in ~/.vibecraft/data/')
|
|
480
|
+
console.log('\nTo remove all data:')
|
|
481
|
+
console.log(' rm -rf ~/.vibecraft')
|
|
482
|
+
console.log('\nRestart Claude Code for changes to take effect.\n')
|
|
483
|
+
|
|
484
|
+
process.exit(0)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Doctor command - diagnose common issues
|
|
488
|
+
if (args[0] === 'doctor') {
|
|
489
|
+
console.log('='.repeat(50))
|
|
490
|
+
console.log('Vibecraft Doctor - Diagnosing your setup...')
|
|
491
|
+
console.log('='.repeat(50))
|
|
492
|
+
console.log()
|
|
493
|
+
|
|
494
|
+
let issues = []
|
|
495
|
+
let warnings = []
|
|
496
|
+
|
|
497
|
+
// -------------------------------------------------------------------------
|
|
498
|
+
// 1. Check dependencies
|
|
499
|
+
// -------------------------------------------------------------------------
|
|
500
|
+
console.log('[1/6] Checking dependencies...')
|
|
501
|
+
|
|
502
|
+
// Node version
|
|
503
|
+
const nodeVersion = process.version
|
|
504
|
+
const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0])
|
|
505
|
+
if (nodeMajor >= 18) {
|
|
506
|
+
console.log(` ✓ Node.js ${nodeVersion}`)
|
|
507
|
+
} else {
|
|
508
|
+
console.log(` ✗ Node.js ${nodeVersion} (need 18+)`)
|
|
509
|
+
issues.push('Node.js 18+ required')
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// jq
|
|
513
|
+
if (checkJq()) {
|
|
514
|
+
try {
|
|
515
|
+
const jqVersion = execSync('jq --version 2>&1', { encoding: 'utf-8' }).trim()
|
|
516
|
+
console.log(` ✓ jq (${jqVersion})`)
|
|
517
|
+
} catch {
|
|
518
|
+
console.log(' ✓ jq')
|
|
519
|
+
}
|
|
520
|
+
} else {
|
|
521
|
+
console.log(' ✗ jq not found')
|
|
522
|
+
issues.push('jq not installed - hooks will not work')
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// tmux
|
|
526
|
+
if (checkTmux()) {
|
|
527
|
+
try {
|
|
528
|
+
const tmuxVersion = execSync('tmux -V 2>&1', { encoding: 'utf-8' }).trim()
|
|
529
|
+
console.log(` ✓ tmux (${tmuxVersion})`)
|
|
530
|
+
} catch {
|
|
531
|
+
console.log(' ✓ tmux')
|
|
532
|
+
}
|
|
533
|
+
} else {
|
|
534
|
+
console.log(' ⚠ tmux not found (optional - needed for browser prompts)')
|
|
535
|
+
warnings.push('tmux not installed - browser prompt feature won\'t work')
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// curl
|
|
539
|
+
try {
|
|
540
|
+
execSync('which curl', { stdio: 'ignore' })
|
|
541
|
+
console.log(' ✓ curl')
|
|
542
|
+
} catch {
|
|
543
|
+
console.log(' ✗ curl not found')
|
|
544
|
+
issues.push('curl not installed - hooks cannot send events to server')
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// -------------------------------------------------------------------------
|
|
548
|
+
// 2. Check hook script
|
|
549
|
+
// -------------------------------------------------------------------------
|
|
550
|
+
console.log('\n[2/6] Checking hook script...')
|
|
551
|
+
|
|
552
|
+
const hookScript = join(homedir(), '.vibecraft', 'hooks', 'vibecraft-hook.sh')
|
|
553
|
+
if (existsSync(hookScript)) {
|
|
554
|
+
console.log(` ✓ Hook script exists: ${hookScript}`)
|
|
555
|
+
|
|
556
|
+
// Check if executable
|
|
557
|
+
try {
|
|
558
|
+
const { accessSync, constants } = await import('fs')
|
|
559
|
+
accessSync(hookScript, constants.X_OK)
|
|
560
|
+
console.log(' ✓ Hook script is executable')
|
|
561
|
+
} catch {
|
|
562
|
+
console.log(' ✗ Hook script is not executable')
|
|
563
|
+
issues.push(`Hook script not executable. Run: chmod +x ${hookScript}`)
|
|
564
|
+
}
|
|
565
|
+
} else {
|
|
566
|
+
console.log(` ✗ Hook script not found: ${hookScript}`)
|
|
567
|
+
issues.push('Hook script not installed. Run: npx vibecraft setup')
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// -------------------------------------------------------------------------
|
|
571
|
+
// 3. Check Claude settings
|
|
572
|
+
// -------------------------------------------------------------------------
|
|
573
|
+
console.log('\n[3/6] Checking Claude Code settings...')
|
|
574
|
+
|
|
575
|
+
const settingsPaths = [
|
|
576
|
+
join(homedir(), '.claude', 'settings.json'),
|
|
577
|
+
join(homedir(), '.config', 'claude', 'settings.json'),
|
|
578
|
+
]
|
|
579
|
+
|
|
580
|
+
let settingsPath = null
|
|
581
|
+
for (const p of settingsPaths) {
|
|
582
|
+
if (existsSync(p)) {
|
|
583
|
+
settingsPath = p
|
|
584
|
+
break
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (!settingsPath) {
|
|
589
|
+
console.log(' ✗ No Claude settings file found')
|
|
590
|
+
issues.push('Claude settings not found. Run: npx vibecraft setup')
|
|
591
|
+
} else {
|
|
592
|
+
console.log(` ✓ Settings file: ${settingsPath}`)
|
|
593
|
+
|
|
594
|
+
try {
|
|
595
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'))
|
|
596
|
+
const hooks = settings.hooks || {}
|
|
597
|
+
|
|
598
|
+
const hookTypes = ['PreToolUse', 'PostToolUse', 'Stop', 'SubagentStop',
|
|
599
|
+
'SessionStart', 'SessionEnd', 'UserPromptSubmit', 'Notification']
|
|
600
|
+
|
|
601
|
+
let configuredHooks = []
|
|
602
|
+
let missingHooks = []
|
|
603
|
+
|
|
604
|
+
for (const hookType of hookTypes) {
|
|
605
|
+
const hasVibecraft = hooks[hookType]?.some(h =>
|
|
606
|
+
h.hooks?.some(hh => hh.command?.includes('vibecraft-hook'))
|
|
607
|
+
)
|
|
608
|
+
if (hasVibecraft) {
|
|
609
|
+
configuredHooks.push(hookType)
|
|
610
|
+
} else {
|
|
611
|
+
missingHooks.push(hookType)
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (configuredHooks.length === hookTypes.length) {
|
|
616
|
+
console.log(` ✓ All ${hookTypes.length} hooks configured`)
|
|
617
|
+
} else if (configuredHooks.length > 0) {
|
|
618
|
+
console.log(` ⚠ ${configuredHooks.length}/${hookTypes.length} hooks configured`)
|
|
619
|
+
console.log(` Missing: ${missingHooks.join(', ')}`)
|
|
620
|
+
warnings.push(`Some hooks not configured: ${missingHooks.join(', ')}`)
|
|
621
|
+
} else {
|
|
622
|
+
console.log(' ✗ No vibecraft hooks configured')
|
|
623
|
+
issues.push('Hooks not configured. Run: npx vibecraft setup')
|
|
624
|
+
}
|
|
625
|
+
} catch (e) {
|
|
626
|
+
console.log(` ✗ Failed to parse settings: ${e.message}`)
|
|
627
|
+
issues.push('Claude settings file has invalid JSON')
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// -------------------------------------------------------------------------
|
|
632
|
+
// 4. Check data directory
|
|
633
|
+
// -------------------------------------------------------------------------
|
|
634
|
+
console.log('\n[4/6] Checking data directory...')
|
|
635
|
+
|
|
636
|
+
const dataDir = join(homedir(), '.vibecraft', 'data')
|
|
637
|
+
if (existsSync(dataDir)) {
|
|
638
|
+
console.log(` ✓ Data directory exists: ${dataDir}`)
|
|
639
|
+
|
|
640
|
+
// Check events file
|
|
641
|
+
const eventsFile = join(dataDir, 'events.jsonl')
|
|
642
|
+
if (existsSync(eventsFile)) {
|
|
643
|
+
const { statSync } = await import('fs')
|
|
644
|
+
const stats = statSync(eventsFile)
|
|
645
|
+
const sizeMB = (stats.size / 1024 / 1024).toFixed(2)
|
|
646
|
+
const modifiedAgo = Math.round((Date.now() - stats.mtimeMs) / 1000)
|
|
647
|
+
|
|
648
|
+
let timeAgo
|
|
649
|
+
if (modifiedAgo < 60) timeAgo = `${modifiedAgo}s ago`
|
|
650
|
+
else if (modifiedAgo < 3600) timeAgo = `${Math.round(modifiedAgo/60)}m ago`
|
|
651
|
+
else if (modifiedAgo < 86400) timeAgo = `${Math.round(modifiedAgo/3600)}h ago`
|
|
652
|
+
else timeAgo = `${Math.round(modifiedAgo/86400)}d ago`
|
|
653
|
+
|
|
654
|
+
console.log(` ✓ Events file: ${sizeMB} MB, last modified ${timeAgo}`)
|
|
655
|
+
|
|
656
|
+
if (modifiedAgo > 86400) {
|
|
657
|
+
warnings.push('No events in 24+ hours - hooks may not be firing')
|
|
658
|
+
}
|
|
659
|
+
} else {
|
|
660
|
+
console.log(' ⚠ No events file yet (will be created when hooks fire)')
|
|
661
|
+
}
|
|
662
|
+
} else {
|
|
663
|
+
console.log(` ✗ Data directory not found: ${dataDir}`)
|
|
664
|
+
issues.push('Data directory not created. Run: npx vibecraft setup')
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// -------------------------------------------------------------------------
|
|
668
|
+
// 5. Check server status
|
|
669
|
+
// -------------------------------------------------------------------------
|
|
670
|
+
console.log('\n[5/6] Checking server status...')
|
|
671
|
+
|
|
672
|
+
try {
|
|
673
|
+
const healthRes = execSync('curl -s http://localhost:4003/health', {
|
|
674
|
+
timeout: 3000,
|
|
675
|
+
encoding: 'utf-8'
|
|
676
|
+
})
|
|
677
|
+
const health = JSON.parse(healthRes)
|
|
678
|
+
if (health.ok) {
|
|
679
|
+
console.log(` ✓ Server running on port 4003`)
|
|
680
|
+
console.log(` Version: ${health.version || 'unknown'}`)
|
|
681
|
+
console.log(` Clients: ${health.clients || 0}`)
|
|
682
|
+
console.log(` Events: ${health.events || 0}`)
|
|
683
|
+
}
|
|
684
|
+
} catch {
|
|
685
|
+
console.log(' ⚠ Server not running on port 4003')
|
|
686
|
+
warnings.push('Server not running. Start with: npx vibecraft')
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// -------------------------------------------------------------------------
|
|
690
|
+
// 6. Check tmux sessions
|
|
691
|
+
// -------------------------------------------------------------------------
|
|
692
|
+
console.log('\n[6/6] Checking tmux sessions...')
|
|
693
|
+
|
|
694
|
+
if (checkTmux()) {
|
|
695
|
+
try {
|
|
696
|
+
const sessions = execSync('tmux list-sessions 2>/dev/null', {
|
|
697
|
+
encoding: 'utf-8',
|
|
698
|
+
timeout: 2000
|
|
699
|
+
}).trim()
|
|
700
|
+
|
|
701
|
+
if (sessions) {
|
|
702
|
+
const sessionList = sessions.split('\n')
|
|
703
|
+
console.log(` ✓ ${sessionList.length} tmux session(s) found:`)
|
|
704
|
+
sessionList.forEach(s => console.log(` - ${s.split(':')[0]}`))
|
|
705
|
+
|
|
706
|
+
const hasClaude = sessionList.some(s => s.startsWith('claude:'))
|
|
707
|
+
if (!hasClaude) {
|
|
708
|
+
console.log(' ⚠ No "claude" session (browser prompts target this by default)')
|
|
709
|
+
}
|
|
710
|
+
} else {
|
|
711
|
+
console.log(' ⚠ No tmux sessions running')
|
|
712
|
+
}
|
|
713
|
+
} catch {
|
|
714
|
+
console.log(' ⚠ No tmux sessions running')
|
|
715
|
+
}
|
|
716
|
+
} else {
|
|
717
|
+
console.log(' - Skipped (tmux not installed)')
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// -------------------------------------------------------------------------
|
|
721
|
+
// Summary
|
|
722
|
+
// -------------------------------------------------------------------------
|
|
723
|
+
console.log('\n' + '='.repeat(50))
|
|
724
|
+
|
|
725
|
+
if (issues.length === 0 && warnings.length === 0) {
|
|
726
|
+
console.log('✓ All checks passed! Vibecraft should be working.')
|
|
727
|
+
} else {
|
|
728
|
+
if (issues.length > 0) {
|
|
729
|
+
console.log(`✗ ${issues.length} issue(s) found:\n`)
|
|
730
|
+
issues.forEach((issue, i) => console.log(` ${i + 1}. ${issue}`))
|
|
731
|
+
}
|
|
732
|
+
if (warnings.length > 0) {
|
|
733
|
+
console.log(`\n⚠ ${warnings.length} warning(s):\n`)
|
|
734
|
+
warnings.forEach((warning, i) => console.log(` ${i + 1}. ${warning}`))
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
console.log('\n' + '='.repeat(50))
|
|
739
|
+
console.log()
|
|
740
|
+
|
|
741
|
+
process.exit(issues.length > 0 ? 1 : 0)
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
745
|
+
const pkg = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8'))
|
|
746
|
+
console.log(`vibecraft v${pkg.version}`)
|
|
747
|
+
process.exit(0)
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Parse port from args
|
|
751
|
+
let port = process.env.VIBECRAFT_PORT || '4003'
|
|
752
|
+
const portIdx = args.findIndex(a => a === '--port' || a === '-p')
|
|
753
|
+
if (portIdx !== -1 && args[portIdx + 1]) {
|
|
754
|
+
port = args[portIdx + 1]
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Ensure data directory exists
|
|
758
|
+
const dataDir = resolve(ROOT, 'data')
|
|
759
|
+
if (!existsSync(dataDir)) {
|
|
760
|
+
mkdirSync(dataDir, { recursive: true })
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Banner
|
|
764
|
+
console.log(`
|
|
765
|
+
╭─────────────────────────────────────╮
|
|
766
|
+
│ │
|
|
767
|
+
│ vibecraft │
|
|
768
|
+
│ 3D visualization for Claude Code │
|
|
769
|
+
│ │
|
|
770
|
+
╰─────────────────────────────────────╯
|
|
771
|
+
`)
|
|
772
|
+
|
|
773
|
+
// Run health checks
|
|
774
|
+
printHealthCheck()
|
|
775
|
+
|
|
776
|
+
console.log(`Starting server on port ${port}...`)
|
|
777
|
+
console.log(`Open http://localhost:${port} in your browser`)
|
|
778
|
+
console.log()
|
|
779
|
+
|
|
780
|
+
// Check for compiled JS (npm package) or fall back to tsx (dev)
|
|
781
|
+
const compiledPath = resolve(ROOT, 'dist/server/server/index.js')
|
|
782
|
+
const sourcePath = resolve(ROOT, 'server/index.ts')
|
|
783
|
+
|
|
784
|
+
let server
|
|
785
|
+
if (existsSync(compiledPath)) {
|
|
786
|
+
// Use compiled JS (production/npm install)
|
|
787
|
+
server = spawn('node', [compiledPath], {
|
|
788
|
+
cwd: ROOT,
|
|
789
|
+
env: {
|
|
790
|
+
...process.env,
|
|
791
|
+
VIBECRAFT_PORT: port,
|
|
792
|
+
},
|
|
793
|
+
stdio: 'inherit',
|
|
794
|
+
})
|
|
795
|
+
} else {
|
|
796
|
+
// Fall back to tsx (development)
|
|
797
|
+
console.log('(dev mode - using tsx)')
|
|
798
|
+
server = spawn('npx', ['tsx', sourcePath], {
|
|
799
|
+
cwd: ROOT,
|
|
800
|
+
env: {
|
|
801
|
+
...process.env,
|
|
802
|
+
VIBECRAFT_PORT: port,
|
|
803
|
+
},
|
|
804
|
+
stdio: 'inherit',
|
|
805
|
+
})
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
server.on('error', (err) => {
|
|
809
|
+
console.error('Failed to start server:', err.message)
|
|
810
|
+
process.exit(1)
|
|
811
|
+
})
|
|
812
|
+
|
|
813
|
+
server.on('close', (code) => {
|
|
814
|
+
process.exit(code || 0)
|
|
815
|
+
})
|
|
816
|
+
|
|
817
|
+
// Handle signals
|
|
818
|
+
process.on('SIGINT', () => {
|
|
819
|
+
server.kill('SIGINT')
|
|
820
|
+
})
|
|
821
|
+
|
|
822
|
+
process.on('SIGTERM', () => {
|
|
823
|
+
server.kill('SIGTERM')
|
|
824
|
+
})
|