claude-comms 1.0.1 → 1.0.2

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,267 @@
1
+ /**
2
+ * Claude Code Setup Installer - Main Module
3
+ *
4
+ * This module orchestrates the complete installation flow:
5
+ * 1. Validates target directory and dependencies
6
+ * 2. Fetches latest setup files from GitHub
7
+ * 3. Installs .claude directory and CLAUDE.md
8
+ * 4. Configures local settings
9
+ * 5. Provides post-installation guidance
10
+ */
11
+
12
+ const path = require('path');
13
+ const fs = require('fs-extra');
14
+ const ora = require('ora');
15
+
16
+ // Import core modules (these will be implemented in WP02-06)
17
+ const logger = require('./utils/logger');
18
+ const { InstallationError } = require('./utils/errors');
19
+ const fetcher = require('./fetcher/github');
20
+ const fileWriter = require('./installer/file-writer');
21
+ const dependencyChecker = require('./installer/dependency-check');
22
+ const prompts = require('./cli/prompts');
23
+
24
+ /**
25
+ * Main installation function
26
+ * @param {Object} options - Installation options
27
+ * @param {string} options.targetDir - Target directory for installation
28
+ * @param {string} options.gitRef - Git reference (branch/tag/commit) to install
29
+ * @param {boolean} options.force - Force overwrite existing files
30
+ * @param {boolean} options.checkPython - Whether to check Python/uv dependencies
31
+ * @param {boolean} options.dev - Development mode with verbose logging
32
+ * @param {boolean} options.verify - Verification mode (no changes)
33
+ * @param {boolean} options.dryRun - Dry run mode (show what would be done)
34
+ */
35
+ async function install(options = {}) {
36
+ const {
37
+ targetDir = process.cwd(),
38
+ gitRef = 'main',
39
+ force = false,
40
+ checkPython = true,
41
+ dev = false,
42
+ verify = false,
43
+ dryRun = false
44
+ } = options;
45
+
46
+ // Configure logger for development mode
47
+ logger.configure({ verbose: dev, verify, dryRun });
48
+
49
+ try {
50
+ logger.info('Starting Claude Code installation...');
51
+
52
+ // Phase 1: Pre-installation validation
53
+ await validateEnvironment(targetDir, checkPython);
54
+
55
+ // Phase 2: Handle existing files
56
+ await handleExistingFiles(targetDir, force, verify, dryRun);
57
+
58
+ // Phase 3: Fetch setup files from GitHub
59
+ const setupFiles = await fetchSetupFiles(gitRef);
60
+
61
+ // Phase 4: Install files to target directory
62
+ await installFiles(setupFiles, targetDir, verify, dryRun);
63
+
64
+ // Phase 5: Post-installation configuration
65
+ await configureLocalSettings(targetDir, verify, dryRun);
66
+
67
+ // Phase 6: Verify installation
68
+ await verifyInstallation(targetDir);
69
+
70
+ logger.success('Installation completed successfully!');
71
+
72
+ } catch (error) {
73
+ logger.error('Installation failed:', error.message);
74
+ throw error;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Validate installation environment
80
+ */
81
+ async function validateEnvironment(targetDir, checkPython) {
82
+ const spinner = ora('Validating environment...').start();
83
+
84
+ try {
85
+ // Check target directory exists and is writable
86
+ await fs.ensureDir(targetDir);
87
+ await fs.access(targetDir, fs.constants.W_OK);
88
+
89
+ // Check dependencies if requested
90
+ if (checkPython) {
91
+ await dependencyChecker.validatePythonEnvironment();
92
+ }
93
+
94
+ spinner.succeed('Environment validation complete');
95
+
96
+ } catch (error) {
97
+ spinner.fail('Environment validation failed');
98
+ throw new InstallationError(`Environment validation failed: ${error.message}`, 'ENV_VALIDATION_FAILED');
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Handle existing .claude directory and CLAUDE.md
104
+ */
105
+ async function handleExistingFiles(targetDir, force, verify, dryRun) {
106
+ const claudeDir = path.join(targetDir, '.claude');
107
+ const claudeMd = path.join(targetDir, 'CLAUDE.md');
108
+
109
+ const existingFiles = [];
110
+ if (await fs.pathExists(claudeDir)) existingFiles.push('.claude/');
111
+ if (await fs.pathExists(claudeMd)) existingFiles.push('CLAUDE.md');
112
+
113
+ if (existingFiles.length === 0) {
114
+ logger.info('No existing files found - proceeding with fresh installation');
115
+ return;
116
+ }
117
+
118
+ if (verify || dryRun) {
119
+ logger.info(`Found existing files: ${existingFiles.join(', ')}`);
120
+ return;
121
+ }
122
+
123
+ if (force) {
124
+ logger.info(`Force mode enabled - will overwrite: ${existingFiles.join(', ')}`);
125
+ return;
126
+ }
127
+
128
+ // Interactive prompt for handling existing files
129
+ const action = await prompts.handleExistingFiles(existingFiles);
130
+
131
+ switch (action) {
132
+ case 'backup':
133
+ await fileWriter.backupExistingFiles(targetDir, existingFiles);
134
+ break;
135
+ case 'skip':
136
+ logger.info('Skipping existing files');
137
+ break;
138
+ case 'cancel':
139
+ throw new InstallationError('Installation cancelled by user', 'USER_CANCELLED');
140
+ default:
141
+ // Overwrite - no action needed
142
+ break;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Fetch setup files from GitHub repository
148
+ */
149
+ async function fetchSetupFiles(gitRef) {
150
+ const spinner = ora(`Fetching setup files from GitHub (${gitRef})...`).start();
151
+
152
+ try {
153
+ const files = await fetcher.fetchClaudeSetup(gitRef);
154
+
155
+ spinner.succeed(`Fetched ${Object.keys(files).length} files from GitHub`);
156
+ logger.debug('Fetched files:', Object.keys(files));
157
+
158
+ return files;
159
+
160
+ } catch (error) {
161
+ spinner.fail('Failed to fetch setup files');
162
+ throw new InstallationError(`GitHub fetch failed: ${error.message}`, 'GITHUB_FETCH_FAILED');
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Install files to target directory
168
+ */
169
+ async function installFiles(setupFiles, targetDir, verify, dryRun) {
170
+ const spinner = ora('Installing files...').start();
171
+
172
+ try {
173
+ const stats = await fileWriter.writeSetupFiles(setupFiles, targetDir, { verify, dryRun });
174
+
175
+ if (verify || dryRun) {
176
+ spinner.succeed(`Would install ${stats.fileCount} files (${stats.totalSize} bytes)`);
177
+ } else {
178
+ spinner.succeed(`Installed ${stats.fileCount} files (${stats.totalSize} bytes)`);
179
+ }
180
+
181
+ } catch (error) {
182
+ spinner.fail('File installation failed');
183
+ throw new InstallationError(`File installation failed: ${error.message}`, 'FILE_INSTALL_FAILED');
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Configure local settings file
189
+ */
190
+ async function configureLocalSettings(targetDir, verify, dryRun) {
191
+ const spinner = ora('Configuring local settings...').start();
192
+
193
+ try {
194
+ const settingsPath = path.join(targetDir, '.claude', 'settings.local.json');
195
+
196
+ if (verify || dryRun) {
197
+ spinner.succeed('Would create settings.local.json');
198
+ return;
199
+ }
200
+
201
+ // Create basic local settings template
202
+ const defaultSettings = {
203
+ 'observability': {
204
+ 'server_url': 'http://localhost:3001',
205
+ 'enabled': true
206
+ },
207
+ 'hooks': {
208
+ 'enabled': true,
209
+ 'python_path': 'python3'
210
+ },
211
+ 'agents': {
212
+ 'max_concurrent': 10,
213
+ 'timeout': 300
214
+ }
215
+ };
216
+
217
+ await fs.writeJson(settingsPath, defaultSettings, { spaces: 2 });
218
+
219
+ spinner.succeed('Local settings configured');
220
+
221
+ } catch (error) {
222
+ spinner.fail('Settings configuration failed');
223
+ throw new InstallationError(`Settings configuration failed: ${error.message}`, 'SETTINGS_CONFIG_FAILED');
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Verify installation was successful
229
+ */
230
+ async function verifyInstallation(targetDir) {
231
+ const spinner = ora('Verifying installation...').start();
232
+
233
+ try {
234
+ const claudeDir = path.join(targetDir, '.claude');
235
+ const claudeMd = path.join(targetDir, 'CLAUDE.md');
236
+ const settingsPath = path.join(claudeDir, 'settings.local.json');
237
+
238
+ // Check required files exist
239
+ const checks = [
240
+ { path: claudeDir, name: '.claude directory' },
241
+ { path: claudeMd, name: 'CLAUDE.md' },
242
+ { path: settingsPath, name: 'settings.local.json' }
243
+ ];
244
+
245
+ for (const check of checks) {
246
+ if (!(await fs.pathExists(check.path))) {
247
+ throw new Error(`Missing ${check.name}`);
248
+ }
249
+ }
250
+
251
+ spinner.succeed('Installation verification complete');
252
+
253
+ } catch (error) {
254
+ spinner.fail('Installation verification failed');
255
+ throw new InstallationError(`Installation verification failed: ${error.message}`, 'VERIFICATION_FAILED');
256
+ }
257
+ }
258
+
259
+ module.exports = {
260
+ install,
261
+ validateEnvironment,
262
+ handleExistingFiles,
263
+ fetchSetupFiles,
264
+ installFiles,
265
+ configureLocalSettings,
266
+ verifyInstallation
267
+ };
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Mock Implementations for Team Integration - WP06
3
+ *
4
+ * Provides mock implementations that match the interface contracts
5
+ * for integration with WP03 (GitHub Fetcher) and WP04 (File Writer).
6
+ *
7
+ * These mocks allow the orchestrator to be tested independently
8
+ * while providing clear integration points for the real implementations.
9
+ */
10
+
11
+ /**
12
+ * Mock Fetcher implementation matching FetcherAPI interface
13
+ * Will be replaced by IsabelMatrix's GitHub fetcher (WP03)
14
+ */
15
+ class MockFetcher {
16
+ constructor(options = {}) {
17
+ this.mockData = options.mockData || this._getDefaultMockData();
18
+ this.simulateDelay = options.simulateDelay !== false;
19
+ this.shouldFail = options.shouldFail || false;
20
+ }
21
+
22
+ async fetchDirectory(path, _options = {}) {
23
+ if (this.simulateDelay) {
24
+ await this._delay(500);
25
+ }
26
+
27
+ if (this.shouldFail) {
28
+ throw new Error(`Mock fetcher: Failed to fetch directory ${path}`);
29
+ }
30
+
31
+ return this.mockData.directories[path] || {
32
+ path,
33
+ type: 'dir',
34
+ children: []
35
+ };
36
+ }
37
+
38
+ async fetchFile(path, _options = {}) {
39
+ if (this.simulateDelay) {
40
+ await this._delay(200);
41
+ }
42
+
43
+ if (this.shouldFail) {
44
+ throw new Error(`Mock fetcher: Failed to fetch file ${path}`);
45
+ }
46
+
47
+ return this.mockData.files[path] || {
48
+ path,
49
+ content: `# Mock content for ${path}`,
50
+ encoding: 'utf-8',
51
+ sha: 'mock-sha-' + Date.now()
52
+ };
53
+ }
54
+
55
+ async validateConnection() {
56
+ if (this.simulateDelay) {
57
+ await this._delay(100);
58
+ }
59
+ return !this.shouldFail;
60
+ }
61
+
62
+ _delay(ms) {
63
+ return new Promise(resolve => setTimeout(resolve, ms));
64
+ }
65
+
66
+ _getDefaultMockData() {
67
+ return {
68
+ directories: {
69
+ '.claude': {
70
+ path: '.claude',
71
+ type: 'dir',
72
+ children: [
73
+ {
74
+ path: 'settings.json',
75
+ type: 'file',
76
+ content: JSON.stringify({
77
+ version: '1.0.0',
78
+ observability_server: 'http://localhost:4000',
79
+ hooks_enabled: true
80
+ }, null, 2),
81
+ encoding: 'utf-8'
82
+ },
83
+ {
84
+ path: 'agents',
85
+ type: 'dir',
86
+ children: [
87
+ {
88
+ path: 'engineer.md',
89
+ type: 'file',
90
+ content: '# Engineer Agent\n\nImplementation specialist.',
91
+ encoding: 'utf-8'
92
+ },
93
+ {
94
+ path: 'architect.md',
95
+ type: 'file',
96
+ content: '# Architect Agent\n\nSystem design specialist.',
97
+ encoding: 'utf-8'
98
+ }
99
+ ]
100
+ },
101
+ {
102
+ path: 'hooks',
103
+ type: 'dir',
104
+ children: [
105
+ {
106
+ path: 'comms',
107
+ type: 'dir',
108
+ children: [
109
+ {
110
+ path: 'send_message.py',
111
+ type: 'file',
112
+ content: '#!/usr/bin/env python3\n# Team communication hook\nprint("Send message hook")',
113
+ encoding: 'utf-8',
114
+ permissions: 0o755
115
+ },
116
+ {
117
+ path: 'get_unread_messages.py',
118
+ type: 'file',
119
+ content: '#!/usr/bin/env python3\n# Message retrieval hook\nprint("Get messages hook")',
120
+ encoding: 'utf-8',
121
+ permissions: 0o755
122
+ }
123
+ ]
124
+ }
125
+ ]
126
+ }
127
+ ]
128
+ }
129
+ },
130
+ files: {
131
+ 'CLAUDE.md': {
132
+ path: 'CLAUDE.md',
133
+ content: '# Claude Code Multi-Agent Setup\n\nThis project is configured for Claude multi-agent orchestration.\n\n## Features\n- Multi-agent team coordination\n- Observability and monitoring\n- Hook-based extensibility\n\n## Getting Started\nReview .claude/settings.local.json for configuration.',
134
+ encoding: 'utf-8',
135
+ sha: 'mock-claude-md-sha'
136
+ }
137
+ }
138
+ };
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Mock Writer implementation matching WriterAPI interface
144
+ * Will be replaced by JackTensor's file writer (WP04)
145
+ */
146
+ class MockWriter {
147
+ constructor(options = {}) {
148
+ this.simulateDelay = options.simulateDelay !== false;
149
+ this.shouldFail = options.shouldFail || false;
150
+ this.dryRun = options.dryRun || false;
151
+ this.writtenFiles = new Set();
152
+ this.backups = new Map();
153
+ }
154
+
155
+ async writeFile(filePath, content, options = {}) {
156
+ if (this.simulateDelay) {
157
+ await this._delay(100);
158
+ }
159
+
160
+ if (this.shouldFail) {
161
+ throw new Error(`Mock writer: Failed to write file ${filePath}`);
162
+ }
163
+
164
+ if (options.dryRun || this.dryRun) {
165
+ console.log(`[DRY RUN] Would write file: ${filePath}`);
166
+ return;
167
+ }
168
+
169
+ this.writtenFiles.add(filePath);
170
+ console.log(`[MOCK] Wrote file: ${filePath} (${content.length} bytes)`);
171
+ }
172
+
173
+ async writeDirectory(files, options = {}) {
174
+ if (this.simulateDelay) {
175
+ await this._delay(300);
176
+ }
177
+
178
+ if (this.shouldFail) {
179
+ throw new Error('Mock writer: Failed to write directory');
180
+ }
181
+
182
+ const result = {
183
+ written: [],
184
+ skipped: [],
185
+ backed_up: new Map(),
186
+ errors: []
187
+ };
188
+
189
+ for (const file of files) {
190
+ const targetPath = options.targetDir
191
+ ? require('path').join(options.targetDir, file.path)
192
+ : file.path;
193
+
194
+ if (options.dryRun || this.dryRun) {
195
+ console.log(`[DRY RUN] Would write: ${targetPath}`);
196
+ continue;
197
+ }
198
+
199
+ // Simulate backup if file exists and backup is enabled
200
+ if (options.backup && this.writtenFiles.has(targetPath)) {
201
+ const backupPath = `${targetPath}.backup-${Date.now()}`;
202
+ result.backed_up.set(targetPath, backupPath);
203
+ this.backups.set(targetPath, backupPath);
204
+ }
205
+
206
+ this.writtenFiles.add(targetPath);
207
+ result.written.push(targetPath);
208
+
209
+ console.log(`[MOCK] Wrote: ${targetPath} (${file.content?.length || 0} bytes)`);
210
+ }
211
+
212
+ return result;
213
+ }
214
+
215
+ async backup(filePath) {
216
+ if (this.simulateDelay) {
217
+ await this._delay(50);
218
+ }
219
+
220
+ const backupPath = `${filePath}.backup-${Date.now()}`;
221
+ this.backups.set(filePath, backupPath);
222
+
223
+ console.log(`[MOCK] Created backup: ${filePath} -> ${backupPath}`);
224
+ return backupPath;
225
+ }
226
+
227
+ async rollback(backupPath, originalPath) {
228
+ if (this.simulateDelay) {
229
+ await this._delay(100);
230
+ }
231
+
232
+ console.log(`[MOCK] Rolled back: ${backupPath} -> ${originalPath}`);
233
+ this.writtenFiles.delete(originalPath);
234
+ }
235
+
236
+ async removeFile(filePath) {
237
+ if (this.simulateDelay) {
238
+ await this._delay(50);
239
+ }
240
+
241
+ console.log(`[MOCK] Removed file: ${filePath}`);
242
+ this.writtenFiles.delete(filePath);
243
+ }
244
+
245
+ _delay(ms) {
246
+ return new Promise(resolve => setTimeout(resolve, ms));
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Mock Logger implementation
252
+ */
253
+ class MockLogger {
254
+ constructor(options = {}) {
255
+ this.verbose = options.verbose || false;
256
+ this.prefix = options.prefix || '[MOCK]';
257
+ }
258
+
259
+ info(message, ...args) {
260
+ console.log(`${this.prefix} [INFO] ${message}`, ...args);
261
+ }
262
+
263
+ warn(message, ...args) {
264
+ console.warn(`${this.prefix} [WARN] ${message}`, ...args);
265
+ }
266
+
267
+ error(message, ...args) {
268
+ console.error(`${this.prefix} [ERROR] ${message}`, ...args);
269
+ }
270
+
271
+ debug(message, ...args) {
272
+ if (this.verbose) {
273
+ console.log(`${this.prefix} [DEBUG] ${message}`, ...args);
274
+ }
275
+ }
276
+
277
+ success(message, ...args) {
278
+ console.log(`${this.prefix} [SUCCESS] ${message}`, ...args);
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Mock Validator implementation
284
+ */
285
+ class MockValidator {
286
+ constructor(options = {}) {
287
+ this.shouldFail = options.shouldFail || false;
288
+ this.issues = options.issues || [];
289
+ }
290
+
291
+ async validateTargetDirectory(_targetDir) {
292
+ if (this.shouldFail) {
293
+ return {
294
+ code: 'E301',
295
+ message: 'Target directory is not writable',
296
+ severity: 'error'
297
+ };
298
+ }
299
+ return null;
300
+ }
301
+
302
+ async validatePythonEnvironment() {
303
+ if (this.shouldFail) {
304
+ return {
305
+ code: 'E302',
306
+ message: 'Python or uv not found',
307
+ severity: 'error'
308
+ };
309
+ }
310
+ return null;
311
+ }
312
+
313
+ async checkExistingFiles(_targetDir) {
314
+ // Return any pre-configured issues
315
+ return this.issues.find(issue => issue.type === 'existing_files') || null;
316
+ }
317
+
318
+ async validateNetworkAccess() {
319
+ if (this.shouldFail) {
320
+ return {
321
+ code: 'E101',
322
+ message: 'Cannot connect to GitHub',
323
+ severity: 'error'
324
+ };
325
+ }
326
+ return null;
327
+ }
328
+
329
+ async pathExists(_path) {
330
+ // Mock - assume paths exist unless configured otherwise
331
+ return true;
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Factory function to create a complete set of mock dependencies
337
+ */
338
+ function createMockDependencies(options = {}) {
339
+ return {
340
+ fetcher: new MockFetcher(options.fetcher),
341
+ writer: new MockWriter(options.writer),
342
+ logger: new MockLogger(options.logger),
343
+ validator: new MockValidator(options.validator)
344
+ };
345
+ }
346
+
347
+ /**
348
+ * Helper to create mock dependencies that simulate specific scenarios
349
+ */
350
+ function createScenarioMocks(scenario) {
351
+ switch (scenario) {
352
+ case 'success':
353
+ return createMockDependencies();
354
+
355
+ case 'network_failure':
356
+ return createMockDependencies({
357
+ fetcher: { shouldFail: true },
358
+ validator: { shouldFail: true }
359
+ });
360
+
361
+ case 'write_failure':
362
+ return createMockDependencies({
363
+ writer: { shouldFail: true }
364
+ });
365
+
366
+ case 'validation_failure':
367
+ return createMockDependencies({
368
+ validator: { shouldFail: true }
369
+ });
370
+
371
+ case 'dry_run':
372
+ return createMockDependencies({
373
+ writer: { dryRun: true }
374
+ });
375
+
376
+ default:
377
+ return createMockDependencies();
378
+ }
379
+ }
380
+
381
+ module.exports = {
382
+ MockFetcher,
383
+ MockWriter,
384
+ MockLogger,
385
+ MockValidator,
386
+ createMockDependencies,
387
+ createScenarioMocks
388
+ };