claude-comms 1.0.1 → 1.0.3

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.
@@ -0,0 +1,436 @@
1
+ /**
2
+ * CLI Runner - Main orchestrator for @claude-code/setup-installer
3
+ *
4
+ * Coordinates the complete installation flow:
5
+ * - CLI argument parsing
6
+ * - User interaction prompts
7
+ * - Progress indicators
8
+ * - Mock GitHub operations (for early development)
9
+ * - File installation coordination
10
+ * - Error handling and recovery
11
+ */
12
+
13
+ const ora = require('ora');
14
+ const chalk = require('chalk');
15
+ const path = require('path');
16
+ const fs = require('fs').promises;
17
+
18
+ const { parseArguments, displayHelp } = require('./parser');
19
+ const { runInstallationPrompts, setupInterruptHandler } = require('./prompts');
20
+
21
+ /**
22
+ * Main CLI entry point
23
+ * @param {string[]} argv - Command line arguments
24
+ * @returns {Promise<void>}
25
+ */
26
+ async function run(argv = process.argv) {
27
+ setupInterruptHandler();
28
+
29
+ try {
30
+ // Parse command line arguments
31
+ const parseResult = parseArguments(argv);
32
+
33
+ if (!parseResult.success) {
34
+ console.error(chalk.red(`Error: ${parseResult.error}`));
35
+ displayHelp(parseResult.command);
36
+ process.exit(1);
37
+ }
38
+
39
+ const { options } = parseResult;
40
+
41
+ // Show help if requested
42
+ if (options.help) {
43
+ displayHelp(parseResult.command);
44
+ return;
45
+ }
46
+
47
+ // Show version if requested
48
+ if (options.version) {
49
+ console.log('1.0.0');
50
+ return;
51
+ }
52
+
53
+ // Run installation flow
54
+ await runInstallationFlow(options);
55
+
56
+ } catch (error) {
57
+ handleUnexpectedError(error);
58
+ process.exit(1);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Main installation flow orchestrator
64
+ * @param {Object} options - Parsed CLI options
65
+ */
66
+ async function runInstallationFlow(options) {
67
+ console.log(chalk.cyan.bold('šŸš€ Claude Code Setup Installer v1.0.0'));
68
+ console.log(chalk.gray('Installing multi-agent orchestration setup\n'));
69
+
70
+ if (options.dryRun) {
71
+ console.log(chalk.yellow('šŸ” DRY RUN MODE - No files will be modified\n'));
72
+ }
73
+
74
+ // Step 1: User interaction and confirmation
75
+ const promptResult = await runInstallationPrompts(options);
76
+
77
+ if (!promptResult.confirmed) {
78
+ console.log(chalk.yellow('\nāŒ Installation cancelled'));
79
+ return;
80
+ }
81
+
82
+ // Step 2: Pre-installation checks
83
+ await runPreInstallationChecks(promptResult);
84
+
85
+ // Step 3: Fetch files (mocked for now)
86
+ const fetchedFiles = await fetchInstallationFiles(promptResult, options);
87
+
88
+ // Step 4: Install files
89
+ await installFiles(fetchedFiles, promptResult, options);
90
+
91
+ // Step 5: Post-installation tasks
92
+ await runPostInstallationTasks(promptResult, options);
93
+
94
+ // Step 6: Success message
95
+ displaySuccessMessage(promptResult);
96
+ }
97
+
98
+ /**
99
+ * Run pre-installation validation checks
100
+ * @param {Object} promptResult - User prompt responses
101
+ */
102
+ async function runPreInstallationChecks(promptResult) {
103
+ const spinner = ora('Running pre-installation checks...').start();
104
+
105
+ try {
106
+ // Check target directory
107
+ await validateTargetDirectory(promptResult.targetDir);
108
+ spinner.text = 'Validating target directory...';
109
+
110
+ // Check Python dependencies if requested
111
+ if (promptResult.pythonCheck) {
112
+ await checkPythonDependencies();
113
+ spinner.text = 'Checking Python dependencies...';
114
+ }
115
+
116
+ // Check disk space
117
+ await checkDiskSpace(promptResult.targetDir);
118
+ spinner.text = 'Checking available disk space...';
119
+
120
+ spinner.succeed('Pre-installation checks completed');
121
+ } catch (error) {
122
+ spinner.fail(`Pre-installation check failed: ${error.message}`);
123
+ throw error;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Fetch installation files (MOCKED for early development)
129
+ * @param {Object} promptResult - User prompt responses
130
+ * @param {Object} options - CLI options
131
+ * @returns {Promise<Array>} Fetched files data
132
+ */
133
+ async function fetchInstallationFiles(promptResult, options) {
134
+ const spinner = ora('Fetching files from GitHub...').start();
135
+
136
+ try {
137
+ // MOCK: Simulate fetching from GitHub
138
+ spinner.text = 'Connecting to GitHub API...';
139
+ await mockDelay(1000);
140
+
141
+ spinner.text = 'Fetching .claude directory structure...';
142
+ await mockDelay(1500);
143
+
144
+ spinner.text = 'Downloading hooks and agents...';
145
+ await mockDelay(2000);
146
+
147
+ spinner.text = 'Downloading configuration files...';
148
+ await mockDelay(1000);
149
+
150
+ // MOCK: Return simulated file structure
151
+ const mockFiles = generateMockFileStructure(options.tag);
152
+
153
+ spinner.succeed(`Fetched ${mockFiles.length} files from ${options.tag}`);
154
+
155
+ if (options.verbose) {
156
+ console.log(chalk.gray('Files to install:'));
157
+ mockFiles.forEach(file => {
158
+ console.log(chalk.gray(` • ${file.path}`));
159
+ });
160
+ }
161
+
162
+ return mockFiles;
163
+ } catch (error) {
164
+ spinner.fail(`Failed to fetch files: ${error.message}`);
165
+ throw error;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Install fetched files to target directory
171
+ * @param {Array} files - Files to install
172
+ * @param {Object} promptResult - User prompt responses
173
+ * @param {Object} options - CLI options
174
+ */
175
+ async function installFiles(files, promptResult, options) {
176
+ const spinner = ora('Installing files...').start();
177
+
178
+ try {
179
+ let installed = 0;
180
+ let skipped = 0;
181
+ let backed_up = 0;
182
+
183
+ for (const file of files) {
184
+ const targetPath = path.join(promptResult.targetDir, file.path);
185
+
186
+ spinner.text = `Installing ${file.path}...`;
187
+
188
+ if (options.dryRun) {
189
+ console.log(chalk.gray(`[DRY RUN] Would install: ${file.path}`));
190
+ continue;
191
+ }
192
+
193
+ // Handle existing files based on strategy
194
+ const exists = await fileExists(targetPath);
195
+ if (exists) {
196
+ switch (promptResult.overwriteStrategy) {
197
+ case 'skip':
198
+ skipped++;
199
+ continue;
200
+ case 'backup':
201
+ await createBackup(targetPath);
202
+ backed_up++;
203
+ break;
204
+ case 'overwrite':
205
+ // Will overwrite below
206
+ break;
207
+ }
208
+ }
209
+
210
+ // Create directory if needed
211
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
212
+
213
+ // Write file content (MOCKED)
214
+ await fs.writeFile(targetPath, file.content, { mode: file.mode });
215
+ installed++;
216
+
217
+ // Show progress for verbose mode
218
+ if (options.verbose) {
219
+ console.log(chalk.green(` āœ“ ${file.path}`));
220
+ }
221
+ }
222
+
223
+ const message = `Installed ${installed} files` +
224
+ (skipped > 0 ? `, skipped ${skipped}` : '') +
225
+ (backed_up > 0 ? `, backed up ${backed_up}` : '');
226
+
227
+ spinner.succeed(message);
228
+ } catch (error) {
229
+ spinner.fail(`Installation failed: ${error.message}`);
230
+ throw error;
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Run post-installation tasks
236
+ * @param {Object} promptResult - User prompt responses
237
+ * @param {Object} options - CLI options
238
+ */
239
+ async function runPostInstallationTasks(promptResult, _options) {
240
+ const spinner = ora('Completing setup...').start();
241
+
242
+ try {
243
+ // Generate settings.local.json
244
+ await generateSettingsFile(promptResult);
245
+ spinner.text = 'Creating settings.local.json...';
246
+
247
+ // Set executable permissions on hooks
248
+ await setExecutablePermissions(promptResult.targetDir);
249
+ spinner.text = 'Setting executable permissions...';
250
+
251
+ // Validate installation
252
+ await validateInstallation(promptResult.targetDir);
253
+ spinner.text = 'Validating installation...';
254
+
255
+ spinner.succeed('Setup completed successfully');
256
+ } catch (error) {
257
+ spinner.fail(`Post-installation failed: ${error.message}`);
258
+ throw error;
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Display success message and next steps
264
+ * @param {Object} promptResult - User prompt responses
265
+ */
266
+ function displaySuccessMessage(_promptResult) {
267
+ console.log(chalk.green.bold('\nāœ… Installation Complete!'));
268
+ console.log(chalk.gray('\nClaude multi-agent orchestration setup has been installed.'));
269
+
270
+ console.log(chalk.blue('\nšŸ“‹ Next Steps:'));
271
+ console.log(chalk.gray('1. Review .claude/settings.local.json for configuration'));
272
+ console.log(chalk.gray('2. Ensure Python and uv are installed for hooks functionality'));
273
+ console.log(chalk.gray('3. Start the observability server (optional)'));
274
+ console.log(chalk.gray('4. Open your project in Claude Code'));
275
+
276
+ console.log(chalk.blue('\nšŸ”— Resources:'));
277
+ console.log(chalk.gray('• Documentation: /docs/project/guides/installation-guide.md'));
278
+ console.log(chalk.gray('• Troubleshooting: /docs/project/guides/troubleshooting.md'));
279
+ console.log(chalk.gray('• GitHub: https://github.com/alexsavage/claude-code-hooks-multi-agent-observability'));
280
+
281
+ console.log(chalk.cyan('\nHappy coding with Claude! šŸ¤–\n'));
282
+ }
283
+
284
+ // ============================================================================
285
+ // MOCK IMPLEMENTATIONS (Replace with real implementations in future WPs)
286
+ // ============================================================================
287
+
288
+ /**
289
+ * Generate mock file structure for testing
290
+ * @param {string} tag - Git tag/branch
291
+ * @returns {Array} Mock file structure
292
+ */
293
+ function generateMockFileStructure(_tag) {
294
+ return [
295
+ {
296
+ path: '.claude/settings.json',
297
+ content: JSON.stringify({
298
+ version: '1.0.0',
299
+ observability_server: 'http://localhost:4000',
300
+ hooks_enabled: true
301
+ }, null, 2),
302
+ mode: 0o644
303
+ },
304
+ {
305
+ path: '.claude/hooks/comms/send_message.py',
306
+ content: '#!/usr/bin/env python3\n# Mock hook for team communication\nprint("Mock hook: send_message")\n',
307
+ mode: 0o755
308
+ },
309
+ {
310
+ path: '.claude/hooks/comms/get_unread_messages.py',
311
+ content: '#!/usr/bin/env python3\n# Mock hook for message retrieval\nprint("Mock hook: get_unread_messages")\n',
312
+ mode: 0o755
313
+ },
314
+ {
315
+ path: '.claude/agents/engineer.md',
316
+ content: '# Engineer Agent\n\nImplementation specialist for code development.\n',
317
+ mode: 0o644
318
+ },
319
+ {
320
+ path: '.claude/agents/architect.md',
321
+ content: '# Architect Agent\n\nSystem design and architecture specialist.\n',
322
+ mode: 0o644
323
+ },
324
+ {
325
+ path: 'CLAUDE.md',
326
+ content: '# Claude Code Setup\n\nThis project has been configured for Claude multi-agent orchestration.\n',
327
+ mode: 0o644
328
+ }
329
+ ];
330
+ }
331
+
332
+ // ============================================================================
333
+ // UTILITY FUNCTIONS
334
+ // ============================================================================
335
+
336
+ async function mockDelay(ms) {
337
+ return new Promise(resolve => setTimeout(resolve, ms));
338
+ }
339
+
340
+ async function validateTargetDirectory(targetDir) {
341
+ try {
342
+ await fs.access(targetDir);
343
+ } catch (error) {
344
+ await fs.mkdir(targetDir, { recursive: true });
345
+ }
346
+ }
347
+
348
+ async function checkPythonDependencies() {
349
+ // MOCK: In real implementation, check for python and uv
350
+ await mockDelay(500);
351
+ // For now, assume they exist
352
+ }
353
+
354
+ async function checkDiskSpace(_targetDir) {
355
+ // MOCK: In real implementation, check available disk space
356
+ await mockDelay(300);
357
+ // Assume sufficient space
358
+ }
359
+
360
+ async function fileExists(filePath) {
361
+ try {
362
+ await fs.access(filePath);
363
+ return true;
364
+ } catch (error) {
365
+ return false;
366
+ }
367
+ }
368
+
369
+ async function createBackup(filePath) {
370
+ const backupPath = `${filePath}.backup-${Date.now()}`;
371
+ await fs.copyFile(filePath, backupPath);
372
+ }
373
+
374
+ async function generateSettingsFile(promptResult) {
375
+ const settingsPath = path.join(promptResult.targetDir, '.claude/settings.local.json');
376
+ const settings = {
377
+ observability_server: promptResult.configUrl,
378
+ installation_date: new Date().toISOString(),
379
+ installation_mode: 'npx-installer'
380
+ };
381
+
382
+ await fs.mkdir(path.dirname(settingsPath), { recursive: true });
383
+ await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
384
+ }
385
+
386
+ async function setExecutablePermissions(targetDir) {
387
+ const hookFiles = [
388
+ '.claude/hooks/comms/send_message.py',
389
+ '.claude/hooks/comms/get_unread_messages.py'
390
+ ];
391
+
392
+ for (const hookFile of hookFiles) {
393
+ const hookPath = path.join(targetDir, hookFile);
394
+ try {
395
+ await fs.chmod(hookPath, 0o755);
396
+ } catch (error) {
397
+ // Hook file may not exist, continue
398
+ }
399
+ }
400
+ }
401
+
402
+ async function validateInstallation(targetDir) {
403
+ const requiredFiles = [
404
+ '.claude/settings.json',
405
+ 'CLAUDE.md'
406
+ ];
407
+
408
+ for (const file of requiredFiles) {
409
+ const filePath = path.join(targetDir, file);
410
+ await fs.access(filePath);
411
+ }
412
+ }
413
+
414
+ function handleUnexpectedError(error) {
415
+ console.error(chalk.red('\nšŸ’„ Unexpected error occurred:'));
416
+ console.error(chalk.red(error.message));
417
+
418
+ if (process.env.NODE_ENV === 'development') {
419
+ console.error(chalk.gray('\nStack trace:'));
420
+ console.error(chalk.gray(error.stack));
421
+ }
422
+
423
+ console.error(chalk.yellow('\nPlease report this issue at:'));
424
+ console.error(chalk.blue('https://github.com/alexsavage/claude-code-hooks-multi-agent-observability/issues'));
425
+ }
426
+
427
+ module.exports = {
428
+ run,
429
+ runInstallationFlow,
430
+ runPreInstallationChecks,
431
+ fetchInstallationFiles,
432
+ installFiles,
433
+ runPostInstallationTasks,
434
+ displaySuccessMessage,
435
+ generateMockFileStructure
436
+ };