@amrhas82/agentic-kit 1.9.1 → 1.10.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/installer/cli.js CHANGED
@@ -34,14 +34,10 @@ class InteractiveInstaller {
34
34
  });
35
35
 
36
36
  this.selections = {
37
- variant: null,
38
37
  tools: [],
39
38
  paths: {}
40
39
  };
41
40
 
42
- // Command-line arguments
43
- this.cliArgs = this.parseCommandLineArgs();
44
-
45
41
  // Initialize PackageManager for accessing package information
46
42
  const PackageManager = require('./package-manager');
47
43
  this.packageManager = new PackageManager();
@@ -51,240 +47,52 @@ class InteractiveInstaller {
51
47
  id: 'claude',
52
48
  name: 'Claude Code',
53
49
  path: '~/.claude',
54
- description: 'AI-powered development assistant',
55
- useCase: 'General software development with conversational AI',
56
- targetUsers: 'All developers'
50
+ description: 'AI-powered development assistant'
57
51
  },
58
52
  {
59
53
  id: 'opencode',
60
54
  name: 'Opencode',
61
55
  path: '~/.config/opencode',
62
- description: 'CLI-optimized AI codegen tool',
63
- useCase: 'Terminal-based development and automation',
64
- targetUsers: 'CLI power users, DevOps teams'
56
+ description: 'CLI-optimized AI codegen tool'
65
57
  },
66
58
  {
67
59
  id: 'ampcode',
68
60
  name: 'Ampcode',
69
61
  path: '~/.config/amp',
70
- description: 'Amplified AI development accelerator',
71
- useCase: 'Velocity-focused workflows and rapid prototyping',
72
- targetUsers: 'Teams seeking development acceleration'
62
+ description: 'Amplified AI development accelerator'
73
63
  },
74
64
  {
75
65
  id: 'droid',
76
66
  name: 'Droid',
77
67
  path: '~/.factory',
78
- description: 'Android-focused AI development companion',
79
- useCase: 'Mobile app development with Android Studio',
80
- targetUsers: 'Android developers, mobile teams'
68
+ description: 'Android-focused AI development companion'
81
69
  }
82
70
  ];
83
-
84
- this.variants = [
85
- { id: 'lite', name: 'Lite', agents: 3, skills: 0, description: 'Minimal setup, CI/CD' },
86
- { id: 'standard', name: 'Standard', agents: 14, skills: 8, description: 'Most users, general dev' },
87
- { id: 'pro', name: 'Pro', agents: 14, skills: 20, description: 'Full installation, all features' }
88
- ];
89
71
  }
90
72
 
91
- /**
92
- * Parse command-line arguments
93
- * Supports various flags for non-interactive operation
94
- *
95
- * @returns {Object} - Parsed arguments object
96
- */
97
- parseCommandLineArgs() {
98
- const args = process.argv.slice(2);
99
- const parsed = {
100
- help: false,
101
- uninstall: null,
102
- upgrade: null,
103
- upgradeVariant: null,
104
- variant: null,
105
- tools: [],
106
- paths: {},
107
- silent: false,
108
- nonInteractive: false,
109
- config: null,
110
- noTelemetry: false
111
- };
112
-
113
- for (let i = 0; i < args.length; i++) {
114
- const arg = args[i];
115
-
116
- if (arg === '--help' || arg === '-h') {
117
- parsed.help = true;
118
- } else if (arg === '--uninstall') {
119
- parsed.uninstall = args[++i];
120
- } else if (arg === '--upgrade') {
121
- parsed.upgrade = args[++i];
122
- parsed.upgradeVariant = args[++i];
123
- } else if (arg === '--variant') {
124
- parsed.variant = args[++i];
125
- } else if (arg === '--tools') {
126
- parsed.tools = args[++i].split(',').map(t => t.trim());
127
- } else if (arg === '--path') {
128
- const pathArg = args[++i];
129
- const [tool, pathValue] = pathArg.split('=');
130
- parsed.paths[tool] = pathValue;
131
- } else if (arg === '--silent' || arg === '--non-interactive' || arg === '--yes' || arg === '-y') {
132
- parsed.silent = true;
133
- parsed.nonInteractive = true;
134
- } else if (arg === '--config') {
135
- parsed.config = args[++i];
136
- } else if (arg === '--no-telemetry') {
137
- parsed.noTelemetry = true;
138
- }
139
- }
73
+ async askInstallOrUninstall() {
74
+ console.log('\nWhat would you like to do?\n');
75
+ console.log(' 1. Install tools');
76
+ console.log(' 2. Uninstall tools\n');
140
77
 
141
- return parsed;
78
+ const choice = await this.askQuestion('Enter choice (1 or 2): ', '1');
79
+ return choice === '2' ? 'uninstall' : 'install';
142
80
  }
143
81
 
144
- /**
145
- * Display help information
146
- * Shows usage, options, and examples
147
- */
148
- showHelp() {
149
- console.log(`
150
- ${colors.bright}${colors.cyan}Agentic Kit Installer${colors.reset}
151
-
152
- ${colors.bright}USAGE:${colors.reset}
153
- node installer/cli.js [OPTIONS]
154
-
155
- ${colors.bright}OPTIONS:${colors.reset}
156
- ${colors.green}--help, -h${colors.reset}
157
- Display this help message
158
-
159
- ${colors.green}--uninstall <tool>${colors.reset}
160
- Uninstall a specific tool
161
- Example: node installer/cli.js --uninstall claude
162
-
163
- ${colors.green}--upgrade <tool> <variant>${colors.reset}
164
- Upgrade or downgrade a tool to a different variant
165
- Example: node installer/cli.js --upgrade claude pro
166
- Example: node installer/cli.js --upgrade claude lite
167
-
168
- ${colors.green}--variant <lite|standard|pro>${colors.reset}
169
- Select package variant (non-interactive mode)
170
- Example: node installer/cli.js --variant standard --tools claude
171
-
172
- ${colors.green}--tools <tool1,tool2,...>${colors.reset}
173
- Select tools to install (comma-separated, non-interactive mode)
174
- Example: node installer/cli.js --variant standard --tools claude,opencode
175
-
176
- ${colors.green}--path <tool>=<path>${colors.reset}
177
- Specify custom installation path for a tool
178
- Example: node installer/cli.js --variant standard --tools claude --path claude=/custom/path
179
-
180
- ${colors.green}--silent, --non-interactive, --yes, -y${colors.reset}
181
- Run in silent mode (no prompts, auto-confirm all)
182
- Example: node installer/cli.js --variant standard --tools claude --silent
183
-
184
- ${colors.green}--config <file>${colors.reset}
185
- Load configuration from JSON file
186
- Example: node installer/cli.js --config install-config.json
187
-
188
- ${colors.green}--no-telemetry${colors.reset}
189
- Disable anonymous usage statistics collection
190
- Example: node installer/cli.js --no-telemetry
191
- See docs/PRIVACY.md for details
192
-
193
- ${colors.bright}TOOLS:${colors.reset}
194
- ${colors.cyan}claude${colors.reset} - Claude Code (AI-powered development assistant)
195
- ${colors.cyan}opencode${colors.reset} - Opencode (CLI-optimized AI codegen tool)
196
- ${colors.cyan}ampcode${colors.reset} - Ampcode (Amplified AI development accelerator)
197
- ${colors.cyan}droid${colors.reset} - Droid (Android-focused AI development companion)
198
-
199
- ${colors.bright}VARIANTS:${colors.reset}
200
- ${colors.cyan}lite${colors.reset} - Minimal setup (3 agents, 0 skills) - For CI/CD
201
- ${colors.cyan}standard${colors.reset} - Standard setup (14 agents, 8 skills) - For most users
202
- ${colors.cyan}pro${colors.reset} - Full setup (14 agents, 20 commands) - Default for everyone
203
-
204
- ${colors.bright}EXAMPLES:${colors.reset}
205
- # Interactive installation (default)
206
- node installer/cli.js
207
-
208
- # Install Claude with Standard variant (non-interactive)
209
- node installer/cli.js --variant standard --tools claude
210
-
211
- # Install multiple tools with custom paths
212
- node installer/cli.js --variant pro --tools claude,opencode --path claude=~/.claude-custom
213
-
214
- # Uninstall a tool
215
- node installer/cli.js --uninstall claude
216
-
217
- # Silent installation for CI/CD
218
- node installer/cli.js --variant lite --tools claude --silent
219
-
220
- ${colors.bright}For more information, visit:${colors.reset}
221
- https://github.com/amrhas82/agentic-kit
222
- `);
223
- }
224
82
 
225
83
  async run() {
226
84
  try {
227
- // Handle --help flag
228
- if (this.cliArgs.help) {
229
- this.showHelp();
230
- process.exit(0);
231
- }
232
-
233
- // Handle --uninstall flag
234
- if (this.cliArgs.uninstall) {
235
- await this.runUninstall(this.cliArgs.uninstall);
236
- process.exit(0);
237
- }
238
-
239
- // Handle --upgrade flag
240
- if (this.cliArgs.upgrade) {
241
- await this.runUpgrade(this.cliArgs.upgrade, this.cliArgs.upgradeVariant);
242
- process.exit(0);
243
- }
244
-
245
- // Handle --config flag
246
- if (this.cliArgs.config) {
247
- await this.loadConfig(this.cliArgs.config);
248
- }
249
-
250
- // Handle non-interactive mode
251
- if (this.cliArgs.variant && this.cliArgs.tools.length > 0) {
252
- await this.runNonInteractive();
253
- process.exit(0);
254
- }
255
-
256
- // Interactive mode
257
85
  this.showWelcome();
258
86
 
259
- // Hardcode to Pro variant - everyone gets all features
260
- this.selections.variant = 'pro';
261
-
262
- // Check for interrupted installation
263
- const InstallationEngine = require('./installation-engine');
264
- const PathManager = require('./path-manager');
265
- const pathManager = new PathManager();
266
- const installationEngine = new InstallationEngine(pathManager, this.packageManager);
267
-
268
- const hasInterrupted = await installationEngine.hasInterruptedInstallation();
87
+ const action = await this.askInstallOrUninstall();
269
88
 
270
- if (hasInterrupted) {
271
- const shouldResume = await this.promptResume(installationEngine);
272
-
273
- if (shouldResume) {
274
- await this.resumeInstallation(installationEngine);
275
- return; // Exit after resume
276
- } else {
277
- // Clear state and start fresh
278
- await installationEngine.getStateManager().clearState();
279
- console.log(`${colors.yellow}Starting fresh installation...${colors.reset}\n`);
280
- }
89
+ if (action === 'uninstall') {
90
+ await this.runUninstall();
91
+ } else {
92
+ await this.selectTools();
93
+ this.setDefaultPaths();
94
+ await this.install();
281
95
  }
282
-
283
- // Simplified installation flow
284
- await this.selectTools();
285
- await this.setDefaultPaths();
286
- await this.showSummary();
287
- await this.install();
288
96
  } catch (error) {
289
97
  await this.handleFatalError(error);
290
98
  } finally {
@@ -292,416 +100,194 @@ ${colors.bright}For more information, visit:${colors.reset}
292
100
  }
293
101
  }
294
102
 
295
- /**
296
- * Run uninstall command for a specific tool
297
- * Detects installation, prompts for confirmation, and executes uninstall
298
- *
299
- * @param {string} toolId - Tool to uninstall (claude, opencode, ampcode, droid)
300
- */
301
- async runUninstall(toolId) {
302
- console.log(`\n${colors.bright}${colors.cyan}Agentic Kit Uninstaller${colors.reset}\n`);
303
-
304
- // Validate tool ID
305
- const tool = this.tools.find(t => t.id === toolId);
306
- if (!tool) {
307
- console.log(`${colors.red}Error: Unknown tool '${toolId}'${colors.reset}`);
308
- console.log(`${colors.yellow}Available tools: ${this.tools.map(t => t.id).join(', ')}${colors.reset}\n`);
309
- process.exit(1);
310
- }
103
+ async runUninstall() {
104
+ console.log('\nDetecting installed tools...\n');
311
105
 
312
- // Detect installation at default path
313
106
  const os = require('os');
314
- const defaultPath = tool.path.startsWith('~')
315
- ? path.join(os.homedir(), tool.path.slice(1))
316
- : path.resolve(tool.path);
317
-
318
- // Check if manifest exists at default path
319
- const manifestPath = path.join(defaultPath, 'manifest.json');
320
- let targetPath = defaultPath;
321
-
322
- if (!fs.existsSync(manifestPath)) {
323
- console.log(`${colors.yellow}No installation found at default path: ${defaultPath}${colors.reset}\n`);
324
-
325
- // If not in silent mode, ask for custom path
326
- if (!this.cliArgs.silent) {
327
- const customPath = await this.askQuestion(
328
- `${colors.cyan}Enter installation path (or press Enter to cancel):${colors.reset} `,
329
- ''
330
- );
331
-
332
- if (!customPath) {
333
- console.log(`${colors.yellow}Uninstall cancelled${colors.reset}\n`);
334
- return;
335
- }
336
107
 
337
- targetPath = customPath.startsWith('~')
338
- ? path.join(os.homedir(), customPath.slice(1))
339
- : path.resolve(customPath);
340
-
341
- const customManifestPath = path.join(targetPath, 'manifest.json');
342
- if (!fs.existsSync(customManifestPath)) {
343
- console.log(`${colors.red}Error: No installation found at ${targetPath}${colors.reset}\n`);
344
- process.exit(1);
345
- }
346
- } else {
347
- console.log(`${colors.red}Error: No installation found at default path${colors.reset}\n`);
348
- process.exit(1);
108
+ // Detect installed tools by checking if paths exist
109
+ const installedTools = [];
110
+ for (const tool of this.tools) {
111
+ const targetPath = tool.path.startsWith('~')
112
+ ? path.join(os.homedir(), tool.path.slice(1))
113
+ : path.resolve(tool.path);
114
+
115
+ if (fs.existsSync(targetPath)) {
116
+ // Check for backups
117
+ const backups = this.findBackups(targetPath);
118
+ installedTools.push({
119
+ ...tool,
120
+ expandedPath: targetPath,
121
+ hasBackup: backups.length > 0,
122
+ latestBackup: backups.sort().reverse()[0]
123
+ });
349
124
  }
350
125
  }
351
126
 
352
- console.log(`${colors.bright}Tool:${colors.reset} ${tool.name}`);
353
- console.log(`${colors.bright}Path:${colors.reset} ${targetPath}\n`);
354
-
355
- // Initialize InstallationEngine
356
- const PathManager = require('./path-manager');
357
- const InstallationEngine = require('./installation-engine');
358
- const pathManager = new PathManager();
359
- const installationEngine = new InstallationEngine(pathManager, this.packageManager);
360
-
361
- try {
362
- // Read manifest to get file count
363
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
364
- const fileCount = manifest.installedFiles
365
- ? Object.values(manifest.installedFiles).reduce((sum, arr) => sum + arr.length, 0)
366
- : 0;
367
-
368
- // Confirmation prompt (unless in silent mode)
369
- if (!this.cliArgs.silent) {
370
- console.log(`${colors.yellow}This will remove ${fileCount} file(s) and create a backup.${colors.reset}\n`);
371
-
372
- const confirm = await this.askQuestion(
373
- `${colors.bright}Proceed with uninstallation? (y/N):${colors.reset} `,
374
- 'n'
375
- );
376
-
377
- if (confirm.toLowerCase() !== 'y') {
378
- console.log(`${colors.yellow}Uninstall cancelled${colors.reset}\n`);
379
- return;
380
- }
381
- }
127
+ if (installedTools.length === 0) {
128
+ console.log('No tools are currently installed.\n');
129
+ return;
130
+ }
382
131
 
383
- console.log(`\n${colors.bright}Uninstalling ${tool.name}...${colors.reset}\n`);
384
-
385
- // Uninstall with progress callback
386
- const result = await installationEngine.uninstall(
387
- toolId,
388
- targetPath,
389
- null, // confirmCallback (already confirmed above)
390
- (progress) => {
391
- // Display progress
392
- const percentage = Math.round((progress.filesRemoved / progress.totalFiles) * 100);
393
- process.stdout.write(`\r${colors.cyan}Progress:${colors.reset} ${percentage}% (${progress.filesRemoved}/${progress.totalFiles} files removed)`);
394
- }
395
- );
132
+ // Show installed tools
133
+ console.log('Installed tools:\n');
134
+ installedTools.forEach((tool, index) => {
135
+ const backupStatus = tool.hasBackup ? '(has backup)' : '(no backup)';
136
+ console.log(` ${index + 1}. ${tool.name} - ${tool.path} ${backupStatus}`);
137
+ });
396
138
 
397
- // Clear progress line
398
- process.stdout.write('\r' + ' '.repeat(80) + '\r');
139
+ // Select tools to uninstall
140
+ console.log('\nSelect tools to uninstall:');
141
+ const selectedIndices = await this.selectToolsCheckbox(installedTools);
399
142
 
400
- // Display results
401
- console.log(`${colors.green}✓ ${tool.name} uninstalled successfully${colors.reset}`);
402
- console.log(`${colors.cyan}Files removed:${colors.reset} ${result.filesRemoved}`);
403
- console.log(`${colors.cyan}Directories removed:${colors.reset} ${result.directoriesRemoved}`);
404
- console.log(`${colors.cyan}Backup created:${colors.reset} ${result.backupPath}\n`);
143
+ if (selectedIndices.length === 0) {
144
+ console.log('No tools selected. Cancelled.\n');
145
+ return;
146
+ }
405
147
 
406
- if (result.warnings.length > 0) {
407
- console.log(`${colors.yellow}Warnings:${colors.reset}`);
408
- result.warnings.forEach(warning => {
409
- console.log(` ${colors.yellow}⚠${colors.reset} ${warning.message}`);
410
- });
411
- console.log('');
412
- }
148
+ // Uninstall each selected tool
149
+ for (const index of selectedIndices) {
150
+ const tool = installedTools[index];
151
+ console.log(`\nUninstalling ${tool.name}...`);
413
152
 
414
- if (result.errors.length > 0) {
415
- console.log(`${colors.red}Errors:${colors.reset}`);
416
- result.errors.forEach(error => {
417
- console.log(` ${colors.red}✗${colors.reset} ${error.message}`);
418
- });
419
- console.log('');
153
+ try {
154
+ if (tool.hasBackup) {
155
+ // Backup current, then restore from previous backup
156
+ const currentBackup = `${tool.expandedPath}.uninstall-backup.${new Date().toISOString().replace(/[:.]/g, '-')}`;
157
+ fs.renameSync(tool.expandedPath, currentBackup);
158
+ console.log(`Current installation backed up: ${tool.path}.uninstall-backup.${new Date().toISOString().replace(/[:.]/g, '-')}`);
159
+
160
+ // Restore from latest backup
161
+ fs.renameSync(tool.latestBackup, tool.expandedPath);
162
+ console.log(`✓ Restored previous installation from: ${path.basename(tool.latestBackup)}`);
163
+ } else {
164
+ // No backup, just delete
165
+ const rimraf = (dir) => {
166
+ if (fs.existsSync(dir)) {
167
+ fs.readdirSync(dir).forEach((file) => {
168
+ const curPath = path.join(dir, file);
169
+ if (fs.lstatSync(curPath).isDirectory()) {
170
+ rimraf(curPath);
171
+ } else {
172
+ fs.unlinkSync(curPath);
173
+ }
174
+ });
175
+ fs.rmdirSync(dir);
176
+ }
177
+ };
178
+ rimraf(tool.expandedPath);
179
+ console.log(`✓ Deleted installation from: ${tool.path}`);
180
+ }
181
+ } catch (error) {
182
+ console.log(`✗ Failed to uninstall ${tool.name}: ${error.message}`);
420
183
  }
421
-
422
- } catch (error) {
423
- console.error(`\n${colors.red}✗ Uninstall failed: ${error.message}${colors.reset}\n`);
424
- process.exit(1);
425
184
  }
185
+
186
+ console.log('\nDone!');
426
187
  }
427
188
 
428
- /**
429
- * Run upgrade/downgrade command for a specific tool
430
- * Changes the variant of an installed tool
431
- *
432
- * @param {string} toolId - Tool to upgrade (claude, opencode, ampcode, droid)
433
- * @param {string} newVariant - Target variant (lite, standard, pro)
434
- */
435
- async runUpgrade(toolId, newVariant) {
436
- console.log(`\n${colors.bright}${colors.cyan}Agentic Kit Variant Upgrade${colors.reset}\n`);
437
-
438
- // Validate tool ID
439
- const tool = this.tools.find(t => t.id === toolId);
440
- if (!tool) {
441
- console.log(`${colors.red}Error: Unknown tool '${toolId}'${colors.reset}`);
442
- console.log(`${colors.yellow}Available tools: ${this.tools.map(t => t.id).join(', ')}${colors.reset}\n`);
443
- process.exit(1);
444
- }
189
+ findBackups(targetPath) {
190
+ const dir = path.dirname(targetPath);
191
+ const base = path.basename(targetPath);
192
+ const pattern = `${base}.backup.`;
445
193
 
446
- // Validate variant
447
- const validVariants = ['lite', 'standard', 'pro'];
448
- if (!validVariants.includes(newVariant)) {
449
- console.log(`${colors.red}Error: Invalid variant '${newVariant}'${colors.reset}`);
450
- console.log(`${colors.yellow}Valid variants: ${validVariants.join(', ')}${colors.reset}\n`);
451
- process.exit(1);
194
+ try {
195
+ return fs.readdirSync(dir)
196
+ .filter(f => f.startsWith(pattern))
197
+ .map(f => path.join(dir, f));
198
+ } catch (error) {
199
+ return [];
452
200
  }
201
+ }
453
202
 
454
- // Detect installation at default path
455
- const os = require('os');
456
- const defaultPath = tool.path.startsWith('~')
457
- ? path.join(os.homedir(), tool.path.slice(1))
458
- : path.resolve(tool.path);
459
-
460
- // Check if manifest exists at default path
461
- const manifestPath = path.join(defaultPath, 'manifest.json');
462
- let targetPath = defaultPath;
463
-
464
- if (!fs.existsSync(manifestPath)) {
465
- console.log(`${colors.yellow}No installation found at default path: ${defaultPath}${colors.reset}\n`);
466
-
467
- // If not in silent mode, ask for custom path
468
- if (!this.cliArgs.silent) {
469
- const customPath = await this.askQuestion(
470
- `${colors.cyan}Enter installation path (or press Enter to cancel):${colors.reset} `,
471
- ''
472
- );
473
-
474
- if (!customPath) {
475
- console.log(`${colors.yellow}Upgrade cancelled${colors.reset}\n`);
476
- return;
477
- }
478
-
479
- targetPath = customPath.startsWith('~')
480
- ? path.join(os.homedir(), customPath.slice(1))
481
- : path.resolve(customPath);
203
+ async selectToolsCheckbox(tools) {
204
+ const selected = new Set();
205
+ let currentIndex = 0;
482
206
 
483
- const customManifestPath = path.join(targetPath, 'manifest.json');
484
- if (!fs.existsSync(customManifestPath)) {
485
- console.log(`${colors.red}Error: No installation found at ${targetPath}${colors.reset}\n`);
486
- process.exit(1);
487
- }
488
- } else {
489
- console.log(`${colors.red}Error: No installation found at default path${colors.reset}\n`);
490
- process.exit(1);
207
+ const renderList = () => {
208
+ // Move cursor up and clear each line individually
209
+ for (let i = 0; i <= tools.length; i++) {
210
+ process.stdout.write('\x1b[1A'); // Move up one line
211
+ process.stdout.write('\r'); // Carriage return to start of line
212
+ process.stdout.write('\x1b[2K'); // Clear entire line
491
213
  }
492
- }
493
214
 
494
- // Read current variant
495
- const manifest = JSON.parse(fs.readFileSync(path.join(targetPath, 'manifest.json'), 'utf8'));
496
- const currentVariant = manifest.variant;
215
+ tools.forEach((tool, index) => {
216
+ const isSelected = selected.has(index);
217
+ const isCurrent = index === currentIndex;
218
+ const checkbox = isSelected ? '[x]' : '[ ]';
219
+ const pointer = isCurrent ? '»' : ' ';
220
+ const paddedName = tool.name.padEnd(20);
497
221
 
498
- console.log(`${colors.bright}Tool:${colors.reset} ${tool.name}`);
499
- console.log(`${colors.bright}Current variant:${colors.reset} ${currentVariant}`);
500
- console.log(`${colors.bright}Target variant:${colors.reset} ${newVariant}`);
501
- console.log(`${colors.bright}Path:${colors.reset} ${targetPath}\n`);
222
+ console.log(`${pointer} ${checkbox} ${paddedName} - ${tool.description || ''}`);
223
+ });
224
+ console.log(''); // Empty line at bottom
225
+ };
502
226
 
503
- // Check if same variant
504
- if (currentVariant === newVariant) {
505
- console.log(`${colors.yellow}Already using variant: ${newVariant}${colors.reset}\n`);
506
- return;
507
- }
227
+ // Initial render
228
+ tools.forEach((tool, index) => {
229
+ const isSelected = selected.has(index);
230
+ const isCurrent = index === currentIndex;
231
+ const checkbox = isSelected ? '[x]' : '[ ]';
232
+ const pointer = isCurrent ? '»' : ' ';
233
+ const paddedName = tool.name.padEnd(20);
508
234
 
509
- // Initialize InstallationEngine
510
- const PathManager = require('./path-manager');
511
- const InstallationEngine = require('./installation-engine');
512
- const pathManager = new PathManager();
513
- const installationEngine = new InstallationEngine(pathManager, this.packageManager);
235
+ console.log(`${pointer} ${checkbox} ${paddedName} - ${tool.description || ''}`);
236
+ });
237
+ console.log('');
514
238
 
515
- try {
516
- // Confirmation callback (unless in silent mode)
517
- const confirmCallback = this.cliArgs.silent ? null : (data) => {
518
- return new Promise(async (resolve) => {
519
- const action = data.filesAdded > data.filesRemoved ? 'Upgrade' : 'Downgrade';
520
- console.log(`${colors.yellow}${action} Summary:${colors.reset}`);
521
- console.log(` ${colors.green}+${data.filesAdded} file(s) to add${colors.reset}`);
522
- console.log(` ${colors.red}-${data.filesRemoved} file(s) to remove${colors.reset}\n`);
523
-
524
- const confirm = await this.askQuestion(
525
- `${colors.bright}Proceed with ${action.toLowerCase()}? (y/N):${colors.reset} `,
526
- 'n'
527
- );
528
-
529
- resolve(confirm.toLowerCase() === 'y');
530
- });
531
- };
239
+ return new Promise((resolve) => {
240
+ const stdin = process.stdin;
241
+ stdin.setRawMode(true);
242
+ stdin.resume();
243
+ stdin.setEncoding('utf8');
532
244
 
533
- // Progress callback
534
- let lastStage = '';
535
- const progressCallback = (progress) => {
536
- if (progress.stage !== lastStage) {
537
- if (lastStage) {
538
- process.stdout.write('\n');
245
+ const onKeypress = (key) => {
246
+ if (key === '\u0003' || key === '\u001b') { // Ctrl+C or ESC
247
+ stdin.setRawMode(false);
248
+ stdin.pause();
249
+ process.exit(0);
250
+ } else if (key === '\r' || key === '\n') { // Enter
251
+ stdin.setRawMode(false);
252
+ stdin.removeListener('data', onKeypress);
253
+ resolve(Array.from(selected));
254
+ } else if (key === ' ') { // Space - toggle
255
+ if (selected.has(currentIndex)) {
256
+ selected.delete(currentIndex);
257
+ } else {
258
+ selected.add(currentIndex);
539
259
  }
540
- lastStage = progress.stage;
541
-
542
- const stageMessages = {
543
- reading_manifest: 'Reading current installation...',
544
- comparing_variants: 'Comparing variants...',
545
- creating_backup: 'Creating backup...',
546
- removing_files: `Removing ${progress.count || 0} file(s)...`,
547
- adding_files: `Adding ${progress.count || 0} file(s)...`,
548
- updating_manifest: 'Updating manifest...',
549
- verifying: 'Verifying installation...',
550
- complete: 'Complete!'
551
- };
552
-
553
- const message = stageMessages[progress.stage] || progress.stage;
554
- process.stdout.write(`${colors.cyan}${message}${colors.reset}`);
260
+ renderList();
261
+ } else if (key === '\u001b[A') { // Up arrow
262
+ currentIndex = currentIndex > 0 ? currentIndex - 1 : tools.length - 1;
263
+ renderList();
264
+ } else if (key === '\u001b[B') { // Down arrow
265
+ currentIndex = currentIndex < tools.length - 1 ? currentIndex + 1 : 0;
266
+ renderList();
555
267
  }
556
268
  };
557
269
 
558
- console.log(`${colors.bright}Upgrading ${tool.name}...${colors.reset}\n`);
559
-
560
- // Execute upgrade
561
- const result = await installationEngine.upgradeVariant(
562
- toolId,
563
- newVariant,
564
- targetPath,
565
- confirmCallback,
566
- progressCallback
567
- );
568
-
569
- // Clear progress line
570
- process.stdout.write('\n\n');
571
-
572
- // Display results
573
- if (!result.success) {
574
- console.log(`${colors.red}✗ Upgrade failed: ${result.error}${colors.reset}\n`);
575
- process.exit(1);
576
- }
577
-
578
- const action = result.filesAdded > result.filesRemoved ? 'Upgrade' : 'Downgrade';
579
- console.log(`${colors.green}✓ ${tool.name} ${action.toLowerCase()}d successfully${colors.reset}`);
580
- console.log(`${colors.cyan}From variant:${colors.reset} ${result.fromVariant}`);
581
- console.log(`${colors.cyan}To variant:${colors.reset} ${result.toVariant}`);
582
- console.log(`${colors.cyan}Files added:${colors.reset} ${result.filesAdded}`);
583
- console.log(`${colors.cyan}Files removed:${colors.reset} ${result.filesRemoved}`);
584
- console.log(`${colors.cyan}Backup created:${colors.reset} ${result.backupPath}\n`);
585
-
586
- if (result.verification && !result.verification.valid) {
587
- console.log(`${colors.yellow}Warnings:${colors.reset}`);
588
- result.verification.issues.forEach(issue => {
589
- console.log(` ${colors.yellow}⚠${colors.reset} ${issue.message}`);
590
- });
591
- console.log('');
592
- }
593
-
594
- } catch (error) {
595
- console.error(`\n${colors.red}✗ Upgrade failed: ${error.message}${colors.reset}\n`);
596
- process.exit(1);
597
- }
270
+ stdin.on('data', onKeypress);
271
+ });
598
272
  }
599
273
 
274
+ /**
275
+ * Run upgrade/downgrade command for a specific tool
276
+ * Changes the variant of an installed tool
277
+ *
278
+ * @param {string} toolId - Tool to upgrade (claude, opencode, ampcode, droid)
279
+ * @param {string} newVariant - Target variant (lite, standard, pro)
280
+ */
600
281
  /**
601
282
  * Load configuration from JSON file
602
283
  * Used with --config flag
603
284
  *
604
285
  * @param {string} configPath - Path to configuration file
605
286
  */
606
- async loadConfig(configPath) {
607
- try {
608
- const configContent = await fs.promises.readFile(configPath, 'utf8');
609
- const config = JSON.parse(configContent);
610
-
611
- // Validate and apply configuration
612
- if (config.variant) {
613
- this.cliArgs.variant = config.variant;
614
- }
615
-
616
- if (config.tools && Array.isArray(config.tools)) {
617
- this.cliArgs.tools = config.tools;
618
- }
619
-
620
- if (config.paths && typeof config.paths === 'object') {
621
- this.cliArgs.paths = { ...this.cliArgs.paths, ...config.paths };
622
- }
623
-
624
- if (config.silent !== undefined) {
625
- this.cliArgs.silent = config.silent;
626
- this.cliArgs.nonInteractive = config.silent;
627
- }
628
-
629
- console.log(`${colors.green}✓ Configuration loaded from ${configPath}${colors.reset}\n`);
630
-
631
- } catch (error) {
632
- console.error(`${colors.red}Error loading configuration: ${error.message}${colors.reset}\n`);
633
- process.exit(1);
634
- }
635
- }
636
-
637
287
  /**
638
288
  * Run non-interactive installation
639
289
  * Uses command-line arguments instead of prompts
640
290
  */
641
- async runNonInteractive() {
642
- console.log(`\n${colors.bright}${colors.cyan}Agentic Kit Installer (Non-Interactive Mode)${colors.reset}\n`);
643
-
644
- // Validate variant
645
- const variant = this.cliArgs.variant;
646
- if (!['lite', 'standard', 'pro'].includes(variant)) {
647
- console.log(`${colors.red}Error: Invalid variant '${variant}'${colors.reset}`);
648
- console.log(`${colors.yellow}Valid variants: lite, standard, pro${colors.reset}\n`);
649
- process.exit(1);
650
- }
651
-
652
- // Validate tools
653
- const validTools = this.tools.map(t => t.id);
654
- const invalidTools = this.cliArgs.tools.filter(t => !validTools.includes(t));
655
- if (invalidTools.length > 0) {
656
- console.log(`${colors.red}Error: Invalid tool(s): ${invalidTools.join(', ')}${colors.reset}`);
657
- console.log(`${colors.yellow}Valid tools: ${validTools.join(', ')}${colors.reset}\n`);
658
- process.exit(1);
659
- }
660
-
661
- if (this.cliArgs.tools.length === 0) {
662
- console.log(`${colors.red}Error: At least one tool must be specified${colors.reset}\n`);
663
- process.exit(1);
664
- }
665
-
666
- // Set selections
667
- this.selections.variant = variant;
668
- this.selections.tools = this.cliArgs.tools;
669
-
670
- // Set paths (use custom paths or defaults)
671
- const os = require('os');
672
- for (const toolId of this.selections.tools) {
673
- const tool = this.tools.find(t => t.id === toolId);
674
-
675
- if (this.cliArgs.paths[toolId]) {
676
- this.selections.paths[toolId] = this.cliArgs.paths[toolId];
677
- } else {
678
- this.selections.paths[toolId] = tool.path;
679
- }
680
- }
681
-
682
- // Display summary
683
- console.log(`${colors.bright}Installation Summary:${colors.reset}`);
684
- console.log(`${colors.cyan}Installing:${colors.reset} ${this.selections.tools.join(', ')}`);
685
- console.log(`${colors.cyan}Each tool includes:${colors.reset} 14 agents + 20 commands`);
686
- console.log('');
687
-
688
- for (const toolId of this.selections.tools) {
689
- const tool = this.tools.find(t => t.id === toolId);
690
- const isCustom = this.selections.paths[toolId] !== tool.path;
691
- console.log(`${colors.bright}${tool.name}${colors.reset}`);
692
- console.log(` Path: ${this.selections.paths[toolId]}${isCustom ? ' (custom)' : ''}`);
693
- }
694
- console.log('');
695
-
696
- // Run installation
697
- try {
698
- await this.install();
699
- } catch (error) {
700
- console.error(`${colors.red}✗ Installation failed: ${error.message}${colors.reset}\n`);
701
- process.exit(1);
702
- }
703
- }
704
-
705
291
  /**
706
292
  * Handle fatal errors with detailed error messages and actionable advice
707
293
  * Categorizes errors and provides specific guidance for each type
@@ -714,26 +300,15 @@ ${colors.bright}For more information, visit:${colors.reset}
714
300
  // Categorize the error and provide appropriate guidance
715
301
  const errorInfo = this.categorizeError(error);
716
302
 
717
- console.log(`${colors.red}${colors.bright}✗ Installation Failed${colors.reset}`);
718
- console.log(`${colors.red}${'─'.repeat(60)}${colors.reset}\n`);
719
-
720
- console.log(`${colors.bright}Error Type:${colors.reset} ${errorInfo.type}`);
721
- console.log(`${colors.bright}Message:${colors.reset} ${error.message}\n`);
303
+ console.log(`${colors.red}Error: ${error.message}${colors.reset}`);
722
304
 
723
- if (errorInfo.advice.length > 0) {
724
- console.log(`${colors.yellow}${colors.bright}Suggested Actions:${colors.reset}`);
725
- errorInfo.advice.forEach((advice, index) => {
726
- console.log(` ${index + 1}. ${advice}`);
305
+ if (errorInfo.advice.length > 0 && errorInfo.advice.length <= 3) {
306
+ errorInfo.advice.forEach(advice => {
307
+ console.log(` - ${advice}`);
727
308
  });
728
309
  console.log('');
729
310
  }
730
311
 
731
- // Display additional technical details if available
732
- if (errorInfo.technicalDetails) {
733
- console.log(`${colors.cyan}Technical Details:${colors.reset}`);
734
- console.log(` ${errorInfo.technicalDetails}\n`);
735
- }
736
-
737
312
  // Exit with error code
738
313
  process.exit(1);
739
314
  }
@@ -753,9 +328,8 @@ ${colors.bright}For more information, visit:${colors.reset}
753
328
  return {
754
329
  type: 'Permission Error',
755
330
  advice: [
756
- 'Try running the installer with elevated permissions: sudo node installer/cli.js',
757
- 'Or choose a different installation directory where you have write access',
758
- 'Check directory permissions: ls -la on the parent directory'
331
+ 'Try: sudo node installer/cli.js',
332
+ 'Or choose a different installation directory'
759
333
  ],
760
334
  technicalDetails: `Error code: ${code || 'EACCES'}`
761
335
  };
@@ -766,10 +340,8 @@ ${colors.bright}For more information, visit:${colors.reset}
766
340
  return {
767
341
  type: 'Disk Space Error',
768
342
  advice: [
769
- 'Free up disk space by removing unnecessary files',
770
- 'Check available space: df -h',
771
- 'Consider installing to a different location with more space',
772
- 'The installer requires at least 50MB of free space'
343
+ 'Free up disk space',
344
+ 'Check: df -h'
773
345
  ],
774
346
  technicalDetails: 'Installation requires approximately 10MB per tool'
775
347
  };
@@ -780,10 +352,8 @@ ${colors.bright}For more information, visit:${colors.reset}
780
352
  return {
781
353
  type: 'Network Error',
782
354
  advice: [
783
- 'Check your internet connection',
784
- 'Verify proxy settings if behind a corporate firewall',
785
- 'Try again in a few moments',
786
- 'Use offline installation mode if available'
355
+ 'Check internet connection',
356
+ 'Try again later'
787
357
  ],
788
358
  technicalDetails: `Network error code: ${code || 'UNKNOWN'}`
789
359
  };
@@ -794,10 +364,8 @@ ${colors.bright}For more information, visit:${colors.reset}
794
364
  return {
795
365
  type: 'Missing Package Error',
796
366
  advice: [
797
- 'Ensure agentic-kit is properly installed: npm install -g @amrhas82/agentic-kit',
798
- 'Verify the packages directory exists and contains required files',
799
- 'Try reinstalling agentic-kit: npm uninstall -g @amrhas82/agentic-kit && npm install -g @amrhas82/agentic-kit',
800
- 'Check that you are running the installer from the correct directory'
367
+ 'Run: npm install -g @amrhas82/agentic-kit',
368
+ 'Check: packages directory exists'
801
369
  ],
802
370
  technicalDetails: `Missing file or package validation failed`
803
371
  };
@@ -808,10 +376,8 @@ ${colors.bright}For more information, visit:${colors.reset}
808
376
  return {
809
377
  type: 'Path Validation Error',
810
378
  advice: [
811
- 'Ensure the path is absolute (starts with / or ~)',
812
- 'Verify the parent directory exists',
813
- 'Check that you have write permissions for the specified path',
814
- 'Use default paths if custom paths are causing issues'
379
+ 'Path must be absolute (starts with / or ~)',
380
+ 'Check parent directory exists'
815
381
  ],
816
382
  technicalDetails: 'Paths must be absolute and writable'
817
383
  };
@@ -822,10 +388,8 @@ ${colors.bright}For more information, visit:${colors.reset}
822
388
  return {
823
389
  type: 'Invalid Input Error',
824
390
  advice: [
825
- 'Review your selections and ensure all required options are provided',
826
- 'At least one tool must be selected',
827
- 'Paths must be absolute (start with / or ~)',
828
- 'Restart the installer and try again'
391
+ 'Check your selections',
392
+ 'Restart installer and try again'
829
393
  ],
830
394
  technicalDetails: error.message
831
395
  };
@@ -836,11 +400,9 @@ ${colors.bright}For more information, visit:${colors.reset}
836
400
  return {
837
401
  type: 'Installation Error',
838
402
  advice: [
839
- 'Check available disk space: df -h',
840
- 'Verify write permissions on the target directory',
841
- 'Ensure no other process is using the installation directory',
842
- 'Try installing to a different location',
843
- 'Review the installation log at ~/.agentic-kit-install.log for details'
403
+ 'Check disk space: df -h',
404
+ 'Verify write permissions',
405
+ 'Try different location'
844
406
  ],
845
407
  technicalDetails: 'Installation process encountered an error during file operations'
846
408
  };
@@ -850,10 +412,8 @@ ${colors.bright}For more information, visit:${colors.reset}
850
412
  return {
851
413
  type: 'Unknown Error',
852
414
  advice: [
853
- 'Try running the installer again',
854
- 'Check system logs for more details',
855
- 'Report this issue at: https://github.com/amrhas82/agentic-kit/issues',
856
- 'Include the error message and your system information (OS, Node version)'
415
+ 'Try running installer again',
416
+ 'Report at: https://github.com/amrhas82/agentic-kit/issues'
857
417
  ],
858
418
  technicalDetails: error.stack ? error.stack.split('\n')[1] : 'No additional details'
859
419
  };
@@ -868,50 +428,6 @@ ${colors.bright}For more information, visit:${colors.reset}
868
428
  * @param {number} totalTools - Total number of tools
869
429
  * @returns {Promise<boolean>} True to continue, false to cancel
870
430
  */
871
- async offerRecoveryOptions(failedTool, currentIndex, totalTools) {
872
- const remainingTools = totalTools - currentIndex;
873
-
874
- console.log(`${colors.yellow}┌─────────────────────────────────────────────────────────────┐${colors.reset}`);
875
- console.log(`${colors.yellow}│ Installation Failure - Recovery Options │${colors.reset}`);
876
- console.log(`${colors.yellow}└─────────────────────────────────────────────────────────────┘${colors.reset}\n`);
877
-
878
- console.log(`${colors.bright}Status:${colors.reset} ${failedTool} installation failed`);
879
- console.log(`${colors.bright}Remaining:${colors.reset} ${remainingTools} tool${remainingTools > 1 ? 's' : ''} to install\n`);
880
-
881
- // In silent mode, auto-continue with remaining tools
882
- if (this.cliArgs.silent) {
883
- console.log(`${colors.yellow}Silent mode: auto-continuing with remaining tools${colors.reset}\n`);
884
- console.log(`${colors.yellow}Note:${colors.reset} The failed installation has been rolled back automatically.`);
885
- console.log(`${colors.yellow}No partial files remain for ${failedTool}.${colors.reset}\n`);
886
- return true;
887
- }
888
-
889
- console.log(`${colors.cyan}Options:${colors.reset}`);
890
- console.log(` ${colors.green}C${colors.reset} - Continue with remaining tools (recommended)`);
891
- console.log(` ${colors.red}Q${colors.reset} - Quit installation (successful installations will be kept)\n`);
892
-
893
- console.log(`${colors.yellow}Note:${colors.reset} The failed installation has been rolled back automatically.`);
894
- console.log(`${colors.yellow}No partial files remain for ${failedTool}.${colors.reset}\n`);
895
-
896
- const answer = await this.askQuestion(
897
- `${colors.bright}Choose an option (C/Q):${colors.reset} `,
898
- 'C'
899
- );
900
-
901
- const choice = answer.toUpperCase();
902
-
903
- if (choice === 'C' || choice === '') {
904
- console.log(`${colors.green}Continuing with remaining tools...${colors.reset}\n`);
905
- return true;
906
- } else if (choice === 'Q') {
907
- return false;
908
- } else {
909
- // Invalid choice, ask again
910
- console.log(`${colors.red}Invalid choice. Please enter C or Q.${colors.reset}\n`);
911
- return this.offerRecoveryOptions(failedTool, currentIndex, totalTools);
912
- }
913
- }
914
-
915
431
  /**
916
432
  * Prompt user to resume interrupted installation
917
433
  * Shows summary of previous installation progress
@@ -919,213 +435,50 @@ ${colors.bright}For more information, visit:${colors.reset}
919
435
  * @param {InstallationEngine} installationEngine - Installation engine instance
920
436
  * @returns {Promise<boolean>} - True if user wants to resume, false otherwise
921
437
  */
922
- async promptResume(installationEngine) {
923
- const summary = await installationEngine.getResumeSummary();
924
-
925
- if (!summary) {
926
- return false;
927
- }
928
-
929
- console.clear();
930
- console.log(`\n${colors.yellow}${colors.bright}Previous Installation Detected${colors.reset}`);
931
- console.log(`${colors.yellow}${'─'.repeat(60)}${colors.reset}\n`);
932
-
933
- // Format timestamp
934
- const startedAt = new Date(summary.startedAt);
935
- const lastUpdated = new Date(summary.lastUpdated);
936
- const timeDiff = Math.floor((lastUpdated - startedAt) / 1000 / 60);
937
-
938
- console.log(`${colors.cyan}Session ID:${colors.reset} ${summary.sessionId}`);
939
- console.log(`${colors.cyan}Started:${colors.reset} ${startedAt.toLocaleString()}`);
940
- console.log(`${colors.cyan}Last Updated:${colors.reset} ${lastUpdated.toLocaleString()} (${timeDiff} min ago)`);
941
- console.log(`${colors.cyan}Variant:${colors.reset} ${summary.variant}`);
942
- console.log('');
943
-
944
- // Show progress
945
- console.log(`${colors.bright}Progress:${colors.reset}`);
946
- console.log(` Tools: ${summary.completedTools}/${summary.totalTools} completed`);
947
-
948
- if (summary.completedToolsList.length > 0) {
949
- console.log(`\n${colors.green}Completed:${colors.reset}`);
950
- summary.completedToolsList.forEach(toolId => {
951
- console.log(` ${colors.green}✓${colors.reset} ${toolId}`);
952
- });
953
- }
954
-
955
- if (summary.failedToolsList.length > 0) {
956
- console.log(`\n${colors.red}Failed:${colors.reset}`);
957
- summary.failedToolsList.forEach(toolId => {
958
- console.log(` ${colors.red}✗${colors.reset} ${toolId}`);
959
- });
960
- }
961
-
962
- if (summary.currentTool) {
963
- console.log(`\n${colors.yellow}Current Tool:${colors.reset} ${summary.currentTool}`);
964
- const progress = summary.currentToolProgress;
965
- if (progress.totalFiles > 0) {
966
- console.log(` Progress: ${progress.filesCompleted}/${progress.totalFiles} files (${progress.percentComplete}%)`);
967
- }
968
- }
969
-
970
- console.log('');
971
- console.log(`${colors.bright}Would you like to resume this installation?${colors.reset}`);
972
- console.log(` ${colors.green}Y${colors.reset} - Resume from where it left off`);
973
- console.log(` ${colors.yellow}N${colors.reset} - Start fresh (previous state will be cleared)\n`);
974
-
975
- const answer = await this.askQuestion(
976
- `${colors.bright}Resume installation? (Y/n):${colors.reset} `,
977
- 'Y'
978
- );
979
-
980
- return answer.toLowerCase() !== 'n';
981
- }
982
-
983
438
  /**
984
439
  * Resume interrupted installation
985
440
  * Uses saved state to continue from where it left off
986
441
  *
987
442
  * @param {InstallationEngine} installationEngine - Installation engine instance with loaded state
988
443
  */
989
- async resumeInstallation(installationEngine) {
990
- const state = installationEngine.getStateManager().getState();
991
-
992
- if (!state) {
993
- console.log(`${colors.red}Error: Could not load installation state${colors.reset}`);
994
- return;
995
- }
996
-
997
- console.log(`\n${colors.bright}${colors.green}Resuming installation...${colors.reset}\n`);
444
+ showWelcome() {
445
+ console.clear();
446
+ console.log(`
447
+ ${colors.bright}${colors.cyan} █████╗ ██████╗ ███████╗███╗ ██╗████████╗██╗ ██████╗ ██╗ ██╗██╗████████╗${colors.reset}
448
+ ${colors.bright}${colors.cyan}██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝██║██╔════╝ ██║ ██╔╝██║╚══██╔══╝${colors.reset}
449
+ ${colors.bright}${colors.cyan}███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ██║██║ █████╔╝ ██║ ██║${colors.reset}
450
+ ${colors.bright}${colors.cyan}██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ██║██║ ██╔═██╗ ██║ ██║${colors.reset}
451
+ ${colors.bright}${colors.cyan}██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ██║╚██████╗ ██║ ██╗██║ ██║${colors.reset}
452
+ ${colors.bright}${colors.cyan}╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝${colors.reset}
453
+
454
+ ${colors.bright}v1.10.0 | 14 agents + 20 commands per tool${colors.reset}
455
+ `);
456
+ }
998
457
 
999
- // Restore selections from state
1000
- this.selections.variant = state.variant;
1001
- this.selections.tools = state.tools;
1002
- this.selections.paths = state.paths;
1003
-
1004
- // Use installMultipleTools with resume flag
1005
- try {
1006
- const results = await installationEngine.installMultipleTools(
1007
- state.variant,
1008
- state.tools,
1009
- state.paths,
1010
- (progress) => {
1011
- // Progress callback (same as normal installation)
1012
- this.drawProgressBar(
1013
- progress.filesCompleted,
1014
- progress.totalFiles,
1015
- progress.percentage,
1016
- progress.currentFile,
1017
- this.formatBytes(progress.bytesTransferred),
1018
- this.formatBytes(progress.totalBytes),
1019
- this.formatBytes(progress.bytesTransferred / ((Date.now() - Date.now()) / 1000 || 1)) + '/s',
1020
- '0:00',
1021
- '0:00'
1022
- );
1023
- },
1024
- true // resume = true
1025
- );
1026
-
1027
- // Display results
1028
- console.log(`\n${colors.bright}Installation Complete${colors.reset}\n`);
1029
-
1030
- if (results.successful.length > 0) {
1031
- console.log(`${colors.green}Successfully installed:${colors.reset}`);
1032
- results.successful.forEach(toolId => {
1033
- console.log(` ${colors.green}✓${colors.reset} ${toolId}`);
1034
- });
1035
- }
1036
-
1037
- if (results.skipped.length > 0) {
1038
- console.log(`\n${colors.yellow}Skipped (already completed):${colors.reset}`);
1039
- results.skipped.forEach(toolId => {
1040
- console.log(` ${colors.yellow}○${colors.reset} ${toolId}`);
1041
- });
1042
- }
1043
-
1044
- if (results.failed.length > 0) {
1045
- console.log(`\n${colors.red}Failed:${colors.reset}`);
1046
- results.failed.forEach(failure => {
1047
- console.log(` ${colors.red}✗${colors.reset} ${failure.toolId}: ${failure.error}`);
1048
- });
1049
- }
1050
-
1051
- } catch (error) {
1052
- console.error(`${colors.red}Resume failed: ${error.message}${colors.reset}`);
1053
- throw error;
1054
- }
1055
- }
1056
-
1057
- showWelcome() {
1058
- console.clear();
1059
- console.log(`
1060
- ${colors.bright}${colors.cyan}┌─────────────────────────────────────────────────────────────┐${colors.reset}
1061
- ${colors.bright}${colors.cyan}│ Agentic Kit Installer v1.2.0 │${colors.reset}
1062
- ${colors.bright}${colors.cyan}│ 14 Agents + 20 Commands Per Tool │${colors.reset}
1063
- ${colors.bright}${colors.cyan}└─────────────────────────────────────────────────────────────┘${colors.reset}
1064
-
1065
- ${colors.bright}Available tools:${colors.reset}
1066
- • ${colors.cyan}claude${colors.reset} - Claude Code (AI development assistant)
1067
- • ${colors.cyan}opencode${colors.reset} - OpenCode (CLI-optimized AI tool)
1068
- • ${colors.cyan}ampcode${colors.reset} - Ampcode (Development accelerator)
1069
- • ${colors.cyan}droid${colors.reset} - Droid (Android-focused assistant)
1070
-
1071
- ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
1072
- `);
1073
-
1074
- return new Promise(resolve => {
1075
- this.rl.question('', resolve);
1076
- });
1077
- }
1078
-
1079
- async selectVariant() {
1080
- console.log(`\n${colors.bright}Step 1/4 — Choose Package Variant${colors.reset}\n`);
1081
-
1082
- console.log('┌─────────────┬─────────┬─────────┬─────────────────────────────┐');
1083
- console.log('│ Variant │ Agents │ Skills │ Description │');
1084
- console.log('├─────────────┼─────────┼─────────┼─────────────────────────────┤');
1085
-
1086
- this.variants.forEach((variant, index) => {
1087
- const selected = index === 1 ? '●' : '○';
1088
- const color = index === 1 ? colors.blue : colors.reset;
1089
- console.log(`│ ${selected} ${color}${variant.name.padEnd(10)}${colors.reset} │ ${variant.agents.toString().padEnd(7)} │ ${variant.skills.toString().padEnd(7)} │ ${variant.description.padEnd(27)} │`);
1090
- });
1091
-
1092
- console.log('└─────────────┴─────────┴─────────┴─────────────────────────────┘');
1093
- console.log('\nUse arrow keys to navigate, Enter to select');
1094
-
1095
- // Default to Standard
1096
- this.selections.variant = 'standard';
1097
-
1098
- return new Promise(resolve => {
1099
- this.rl.question('\nPress Enter to continue (default: Standard)', (answer) => {
1100
- if (answer && ['lite', 'standard', 'pro'].includes(answer.toLowerCase())) {
1101
- this.selections.variant = answer.toLowerCase();
1102
- }
1103
- resolve();
1104
- });
1105
- });
1106
- }
1107
-
1108
- async selectTools() {
1109
- console.log(`\n${colors.bright}Select tools to install${colors.reset}\n`);
1110
- console.log(`${colors.cyan}(↑↓ navigate, space=toggle, a=all, enter=confirm)${colors.reset}\n`);
458
+ async selectTools() {
459
+ console.log(`\n${colors.bright}Select tools to install${colors.reset}\n`);
460
+ console.log(`${colors.cyan}(↑↓ navigate, space=toggle, a=all, enter=confirm)${colors.reset}\n`);
1111
461
 
1112
462
  // Interactive checkbox selection
1113
463
  const selected = new Set(['claude']); // Default to claude
1114
464
  let currentIndex = 0;
1115
465
 
1116
466
  const renderList = () => {
1117
- // Clear previous list
1118
- process.stdout.write('\x1b[' + (this.tools.length + 1) + 'A'); // Move up
1119
- process.stdout.write('\x1b[0J'); // Clear from cursor down
467
+ // Move cursor up and clear each line individually
468
+ for (let i = 0; i <= this.tools.length; i++) {
469
+ process.stdout.write('\x1b[1A'); // Move up one line
470
+ process.stdout.write('\r'); // Carriage return to start of line
471
+ process.stdout.write('\x1b[2K'); // Clear entire line
472
+ }
1120
473
 
1121
474
  this.tools.forEach((tool, index) => {
1122
475
  const isSelected = selected.has(tool.id);
1123
476
  const isCurrent = index === currentIndex;
1124
- const checkbox = isSelected ? '' : '';
477
+ const checkbox = isSelected ? '[x]' : '[ ]';
1125
478
  const pointer = isCurrent ? '»' : ' ';
1126
- const color = isCurrent ? colors.cyan : colors.reset;
479
+ const paddedName = tool.name.padEnd(20);
1127
480
 
1128
- console.log(`${pointer} ${color}${checkbox} ${tool.name.padEnd(20)}${colors.reset} - ${tool.description}`);
481
+ console.log(`${pointer} ${checkbox} ${paddedName} - ${tool.description}`);
1129
482
  });
1130
483
  console.log(''); // Empty line at bottom
1131
484
  };
@@ -1134,11 +487,11 @@ ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
1134
487
  this.tools.forEach((tool, index) => {
1135
488
  const isSelected = selected.has(tool.id);
1136
489
  const isCurrent = index === currentIndex;
1137
- const checkbox = isSelected ? '' : '';
490
+ const checkbox = isSelected ? '[x]' : '[ ]';
1138
491
  const pointer = isCurrent ? '»' : ' ';
1139
- const color = isCurrent ? colors.cyan : colors.reset;
492
+ const paddedName = tool.name.padEnd(20);
1140
493
 
1141
- console.log(`${pointer} ${color}${checkbox} ${tool.name.padEnd(20)}${colors.reset} - ${tool.description}`);
494
+ console.log(`${pointer} ${checkbox} ${paddedName} - ${tool.description}`);
1142
495
  });
1143
496
  console.log('');
1144
497
 
@@ -1160,12 +513,7 @@ ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
1160
513
  this.selections.tools = Array.from(selected);
1161
514
 
1162
515
  // Show selection summary
1163
- console.log(`${colors.green}Installing ${this.selections.tools.length} tool(s):${colors.reset}`);
1164
- this.selections.tools.forEach(id => {
1165
- const tool = this.tools.find(t => t.id === id);
1166
- console.log(` ${colors.green}✓${colors.reset} ${tool.name} ${colors.cyan}(14 agents + 20 commands)${colors.reset}`);
1167
- });
1168
- console.log('');
516
+ console.log(`${colors.green}Installing ${this.selections.tools.length} tool(s)${colors.reset}\n`);
1169
517
 
1170
518
  resolve();
1171
519
  } else if (key === ' ') { // Space - toggle
@@ -1200,270 +548,6 @@ ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
1200
548
  }
1201
549
  }
1202
550
 
1203
- async configurePaths() {
1204
- console.log(`\n${colors.bright}Step 3/4 — Path Configuration${colors.reset}\n`);
1205
-
1206
- console.log('Default installation paths for selected tools:\n');
1207
-
1208
- for (const toolId of this.selections.tools) {
1209
- const tool = this.tools.find(t => t.id === toolId);
1210
- console.log(`${colors.bright}${tool.name}${colors.reset}`);
1211
- console.log(`${colors.cyan}Default path:${colors.reset} ${tool.path}`);
1212
-
1213
- const customPath = await this.askQuestion(
1214
- `Enter path (or press Enter for default): `,
1215
- tool.path
1216
- );
1217
-
1218
- // Detect custom path
1219
- if (customPath !== tool.path) {
1220
- // Show custom path confirmation dialog
1221
- const confirmed = await this.showCustomPathConfirmation(tool, customPath);
1222
-
1223
- if (confirmed) {
1224
- this.selections.paths[toolId] = customPath;
1225
- console.log(`${colors.green}✓ Using custom path: ${customPath}${colors.reset}\n`);
1226
- } else {
1227
- this.selections.paths[toolId] = tool.path;
1228
- console.log(`${colors.blue}Using default path: ${tool.path}${colors.reset}\n`);
1229
- }
1230
- } else {
1231
- this.selections.paths[toolId] = tool.path;
1232
- console.log(`${colors.green}✓ Using default path${colors.reset}\n`);
1233
- }
1234
- }
1235
- }
1236
-
1237
- async showCustomPathConfirmation(tool, customPath) {
1238
- console.log(`\n${colors.yellow}┌─────────────────────────────────────────────────────────────┐${colors.reset}`);
1239
- console.log(`${colors.yellow}│ Custom Path Confirmation │${colors.reset}`);
1240
- console.log(`${colors.yellow}└─────────────────────────────────────────────────────────────┘${colors.reset}\n`);
1241
-
1242
- console.log(`${colors.bright}Tool:${colors.reset} ${tool.name}`);
1243
- console.log(`${colors.bright}Proposed custom path:${colors.reset} ${customPath}`);
1244
- console.log(`${colors.bright}Default path:${colors.reset} ${tool.path}\n`);
1245
-
1246
- // Validate the custom path
1247
- const validation = this.validatePath(customPath);
1248
-
1249
- // Display validation results
1250
- console.log(`${colors.cyan}Validation Results:${colors.reset}`);
1251
-
1252
- if (validation.valid) {
1253
- console.log(`${colors.green}✓ Path is valid${colors.reset}`);
1254
- if (validation.parentExists) {
1255
- console.log(`${colors.green}✓ Parent directory exists${colors.reset}`);
1256
- }
1257
- if (validation.hasPermission) {
1258
- console.log(`${colors.green}✓ Write permission available${colors.reset}`);
1259
- }
1260
- if (validation.hasDiskSpace) {
1261
- console.log(`${colors.green}✓ Sufficient disk space${colors.reset}`);
1262
- }
1263
- } else {
1264
- // Show validation warnings/errors
1265
- validation.issues.forEach(issue => {
1266
- const icon = issue.severity === 'error' ? '✗' : '⚠';
1267
- const color = issue.severity === 'error' ? colors.red : colors.yellow;
1268
- console.log(`${color}${icon} ${issue.message}${colors.reset}`);
1269
- });
1270
- }
1271
-
1272
- console.log('');
1273
-
1274
- // Require explicit confirmation
1275
- if (!validation.valid && validation.issues.some(i => i.severity === 'error')) {
1276
- console.log(`${colors.red}Cannot use this path due to validation errors${colors.reset}`);
1277
- console.log(`${colors.yellow}Press Enter to use default path instead${colors.reset}`);
1278
- await this.askQuestion('');
1279
- return false;
1280
- }
1281
-
1282
- // Ask for confirmation
1283
- const answer = await this.askQuestion(
1284
- `${colors.bright}Confirm custom path? (y/N):${colors.reset} `,
1285
- 'n'
1286
- );
1287
-
1288
- return answer.toLowerCase() === 'y';
1289
- }
1290
-
1291
- validatePath(customPath) {
1292
- const result = {
1293
- valid: true,
1294
- issues: [],
1295
- parentExists: false,
1296
- hasPermission: false,
1297
- hasDiskSpace: false
1298
- };
1299
-
1300
- // Expand tilde to home directory
1301
- const expandedPath = customPath.startsWith('~')
1302
- ? path.join(require('os').homedir(), customPath.slice(1))
1303
- : path.resolve(customPath);
1304
-
1305
- try {
1306
- // Check if path is absolute or can be resolved
1307
- if (!path.isAbsolute(expandedPath)) {
1308
- result.issues.push({
1309
- severity: 'error',
1310
- message: 'Path must be absolute'
1311
- });
1312
- result.valid = false;
1313
- }
1314
-
1315
- // Check parent directory exists
1316
- const parentDir = path.dirname(expandedPath);
1317
- if (fs.existsSync(parentDir)) {
1318
- result.parentExists = true;
1319
-
1320
- // Check write permission on parent directory
1321
- try {
1322
- fs.accessSync(parentDir, fs.constants.W_OK);
1323
- result.hasPermission = true;
1324
- } catch (err) {
1325
- result.issues.push({
1326
- severity: 'error',
1327
- message: 'No write permission for parent directory'
1328
- });
1329
- result.valid = false;
1330
- }
1331
-
1332
- // Check disk space (require at least 50MB free)
1333
- try {
1334
- const stats = fs.statfsSync ? fs.statfsSync(parentDir) : null;
1335
- if (stats) {
1336
- const availableSpace = stats.bavail * stats.bsize;
1337
- const requiredSpace = 50 * 1024 * 1024; // 50MB
1338
-
1339
- if (availableSpace >= requiredSpace) {
1340
- result.hasDiskSpace = true;
1341
- } else {
1342
- result.issues.push({
1343
- severity: 'warning',
1344
- message: `Low disk space (${Math.round(availableSpace / 1024 / 1024)}MB available, 50MB recommended)`
1345
- });
1346
- }
1347
- } else {
1348
- // Cannot check disk space on this platform
1349
- result.hasDiskSpace = true; // Assume OK
1350
- }
1351
- } catch (err) {
1352
- // Disk space check failed, but don't block installation
1353
- result.hasDiskSpace = true; // Assume OK
1354
- }
1355
- } else {
1356
- result.issues.push({
1357
- severity: 'warning',
1358
- message: 'Parent directory does not exist (will be created)'
1359
- });
1360
- // Still allow installation if parent can be created
1361
- result.parentExists = false;
1362
- result.hasPermission = true; // Assume OK if we can check grandparent
1363
- result.hasDiskSpace = true; // Assume OK
1364
- }
1365
-
1366
- // Check if path already exists
1367
- if (fs.existsSync(expandedPath)) {
1368
- result.issues.push({
1369
- severity: 'warning',
1370
- message: 'Path already exists (files may be overwritten)'
1371
- });
1372
- }
1373
-
1374
- } catch (err) {
1375
- result.issues.push({
1376
- severity: 'error',
1377
- message: `Path validation error: ${err.message}`
1378
- });
1379
- result.valid = false;
1380
- }
1381
-
1382
- return result;
1383
- }
1384
-
1385
- async showSummary() {
1386
- console.log(`\n${colors.bright}Step 4/4 — Installation Summary${colors.reset}\n`);
1387
-
1388
- console.log(`Installing: ${this.selections.tools.map(id =>
1389
- this.tools.find(t => t.id === id).name
1390
- ).join(', ')}`);
1391
- console.log(`Each tool includes: 14 agents + 20 commands\n`);
1392
-
1393
- console.log('Installation Details:');
1394
- console.log('┌─────────────┬──────────────────┬──────────┬─────────────┐');
1395
- console.log('│ Tool │ Path │ Files │ Size │');
1396
- console.log('├─────────────┼──────────────────┼──────────┼─────────────┤');
1397
-
1398
- // Collect actual file counts and sizes for each tool
1399
- let totalFiles = 0;
1400
- let totalBytes = 0;
1401
- const toolData = [];
1402
-
1403
- for (const toolId of this.selections.tools) {
1404
- const tool = this.tools.find(t => t.id === toolId);
1405
- const installPath = this.selections.paths[toolId];
1406
- const isCustom = installPath !== tool.path;
1407
-
1408
- try {
1409
- // Get actual package contents and size from PackageManager
1410
- const contents = await this.packageManager.getPackageContents(toolId, this.selections.variant);
1411
- const sizeInfo = await this.packageManager.getPackageSize(toolId, this.selections.variant);
1412
-
1413
- toolData.push({
1414
- tool,
1415
- path: installPath,
1416
- isCustom,
1417
- fileCount: contents.totalFiles,
1418
- size: sizeInfo.formattedSize
1419
- });
1420
-
1421
- totalFiles += contents.totalFiles;
1422
- totalBytes += sizeInfo.size;
1423
- } catch (error) {
1424
- // If package data not available, show placeholder
1425
- toolData.push({
1426
- tool,
1427
- path: installPath,
1428
- isCustom,
1429
- fileCount: 'N/A',
1430
- size: 'N/A'
1431
- });
1432
- }
1433
- }
1434
-
1435
- // Display tool rows with actual data
1436
- for (const data of toolData) {
1437
- const pathDisplay = data.isCustom ? `${data.path} *` : data.path;
1438
-
1439
- // Format file count and size for display
1440
- const fileCountStr = typeof data.fileCount === 'number' ? `${data.fileCount}` : data.fileCount;
1441
- const sizeStr = data.size;
1442
-
1443
- console.log(`│ ${data.tool.name.padEnd(11)} │ ${pathDisplay.padEnd(16)} │ ${fileCountStr.padEnd(8)} │ ${sizeStr.padEnd(11)} │`);
1444
- }
1445
-
1446
- console.log('└─────────────┴──────────────────┴──────────┴─────────────┘');
1447
-
1448
- // Show custom path footnote if applicable
1449
- if (toolData.some(d => d.isCustom)) {
1450
- console.log('\n* Custom path specified');
1451
- }
1452
-
1453
- // Show totals if we have data
1454
- if (totalFiles > 0) {
1455
- // Format total size
1456
- const totalSizeFormatted = this.formatBytes(totalBytes);
1457
- console.log(`\n${colors.cyan}Total:${colors.reset} ${totalFiles} files, ${totalSizeFormatted}`);
1458
- }
1459
-
1460
- console.log('\nPress Enter to install or Esc to cancel');
1461
-
1462
- return new Promise(resolve => {
1463
- this.rl.question('', resolve);
1464
- });
1465
- }
1466
-
1467
551
  /**
1468
552
  * Format bytes to human-readable size (helper method for summary display)
1469
553
  * @param {number} bytes - Size in bytes
@@ -1493,492 +577,133 @@ ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
1493
577
  *
1494
578
  * @returns {Promise<object>} Object with success flag, errors array, and warnings array
1495
579
  */
1496
- async performPreInstallationChecks() {
1497
- const errors = [];
1498
- const warnings = [];
1499
- const os = require('os');
1500
-
1501
- // Check Node.js version (require 14+)
1502
- const nodeVersion = process.version;
1503
- const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
1504
- if (majorVersion < 14) {
1505
- errors.push(`Node.js version ${nodeVersion} is too old. Please upgrade to Node.js 14 or higher.`);
1506
- }
1507
-
1508
- // Validate each selected tool's package
1509
- for (const toolId of this.selections.tools) {
1510
- try {
1511
- const validation = await this.packageManager.validatePackage(toolId, this.selections.variant);
1512
- if (!validation.valid) {
1513
- errors.push(`Package validation failed for ${toolId}: ${validation.error}`);
1514
- }
1515
- } catch (error) {
1516
- errors.push(`Cannot validate package for ${toolId}: ${error.message}`);
1517
- }
1518
- }
1519
-
1520
- // Validate all installation paths
1521
- for (const toolId of this.selections.tools) {
1522
- const targetPath = this.selections.paths[toolId];
1523
- const expandedPath = targetPath.startsWith('~')
1524
- ? path.join(os.homedir(), targetPath.slice(1))
1525
- : path.resolve(targetPath);
1526
-
1527
- // Check parent directory write permissions
1528
- const parentDir = path.dirname(expandedPath);
1529
- try {
1530
- if (fs.existsSync(parentDir)) {
1531
- fs.accessSync(parentDir, fs.constants.W_OK);
1532
- } else {
1533
- // Check if we can create parent directory
1534
- const grandParentDir = path.dirname(parentDir);
1535
- if (fs.existsSync(grandParentDir)) {
1536
- fs.accessSync(grandParentDir, fs.constants.W_OK);
1537
- } else {
1538
- errors.push(`Cannot create installation path for ${toolId}: parent directories do not exist`);
1539
- }
1540
- }
1541
- } catch (error) {
1542
- errors.push(`No write permission for ${toolId} installation path: ${targetPath}`);
1543
- }
1544
-
1545
- // Check if path already exists and has content
1546
- if (fs.existsSync(expandedPath)) {
1547
- try {
1548
- const files = fs.readdirSync(expandedPath);
1549
- if (files.length > 0) {
1550
- warnings.push(`${toolId} installation path already exists and contains ${files.length} file(s). Existing installation will be backed up.`);
1551
- }
1552
- } catch (error) {
1553
- warnings.push(`Cannot read existing installation directory for ${toolId}`);
1554
- }
1555
- }
1556
- }
1557
-
1558
- // Check available disk space
1559
- try {
1560
- // Calculate total required space for all selected tools
1561
- let totalRequiredSpace = 0;
1562
- for (const toolId of this.selections.tools) {
1563
- try {
1564
- const sizeInfo = await this.packageManager.getPackageSize(toolId, this.selections.variant);
1565
- totalRequiredSpace += sizeInfo.size;
1566
- } catch (error) {
1567
- // Skip if we can't determine size
1568
- }
1569
- }
1570
-
1571
- // Check disk space on home directory
1572
- const homeDir = os.homedir();
1573
- if (fs.statfsSync) {
1574
- const stats = fs.statfsSync(homeDir);
1575
- const availableSpace = stats.bavail * stats.bsize;
1576
- const requiredSpace = totalRequiredSpace * 1.5; // 50% buffer for safety
1577
-
1578
- if (availableSpace < requiredSpace) {
1579
- const availableMB = Math.round(availableSpace / 1024 / 1024);
1580
- const requiredMB = Math.round(requiredSpace / 1024 / 1024);
1581
- errors.push(`Insufficient disk space: ${availableMB}MB available, ${requiredMB}MB required`);
1582
- } else if (availableSpace < totalRequiredSpace * 2) {
1583
- const availableMB = Math.round(availableSpace / 1024 / 1024);
1584
- warnings.push(`Low disk space: ${availableMB}MB available. Consider freeing up space.`);
1585
- }
1586
- }
1587
- } catch (error) {
1588
- // Disk space check not available on this platform
1589
- warnings.push('Could not check disk space (platform limitation)');
1590
- }
1591
-
1592
- // Check for conflicting installations
1593
- for (const toolId of this.selections.tools) {
1594
- const targetPath = this.selections.paths[toolId];
1595
- const expandedPath = targetPath.startsWith('~')
1596
- ? path.join(os.homedir(), targetPath.slice(1))
1597
- : path.resolve(targetPath);
1598
-
1599
- const manifestPath = path.join(expandedPath, 'manifest.json');
1600
- if (fs.existsSync(manifestPath)) {
1601
- try {
1602
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
1603
- if (manifest.tool !== toolId) {
1604
- warnings.push(`Path ${targetPath} contains a different tool (${manifest.tool}). This may cause conflicts.`);
1605
- }
1606
- } catch (error) {
1607
- warnings.push(`Cannot read existing manifest at ${targetPath}`);
1608
- }
1609
- }
1610
- }
1611
-
1612
- return {
1613
- success: errors.length === 0,
1614
- errors,
1615
- warnings
1616
- };
1617
- }
1618
-
1619
580
  async install() {
1620
- console.log(`\n${colors.bright}Installing ${this.selections.variant} package...${colors.reset}\n`);
1621
-
1622
- // Perform pre-installation checks
1623
- console.log(`${colors.cyan}Performing pre-installation checks...${colors.reset}`);
1624
- const preCheckResult = await this.performPreInstallationChecks();
1625
-
1626
- if (!preCheckResult.success) {
1627
- console.log(`\n${colors.red}Pre-installation checks failed:${colors.reset}`);
1628
- preCheckResult.errors.forEach(error => {
1629
- console.log(` ${colors.red}✗${colors.reset} ${error}`);
1630
- });
1631
-
1632
- if (preCheckResult.warnings.length > 0) {
1633
- console.log(`\n${colors.yellow}Warnings:${colors.reset}`);
1634
- preCheckResult.warnings.forEach(warning => {
1635
- console.log(` ${colors.yellow}⚠${colors.reset} ${warning}`);
1636
- });
1637
- }
1638
-
1639
- throw new Error('Pre-installation checks failed. Please resolve the issues above and try again.');
1640
- }
1641
-
1642
- if (preCheckResult.warnings.length > 0) {
1643
- console.log(`${colors.yellow}Warnings detected:${colors.reset}`);
1644
- preCheckResult.warnings.forEach(warning => {
1645
- console.log(` ${colors.yellow}⚠${colors.reset} ${warning}`);
1646
- });
1647
-
1648
- // In silent mode, auto-proceed with warnings
1649
- if (!this.cliArgs.silent) {
1650
- const proceed = await this.askQuestion(
1651
- `\n${colors.bright}Continue despite warnings? (Y/n):${colors.reset} `,
1652
- 'Y'
1653
- );
1654
-
1655
- if (proceed.toLowerCase() === 'n') {
1656
- throw new Error('Installation cancelled by user due to warnings');
1657
- }
1658
- } else {
1659
- console.log(`${colors.yellow}Silent mode: auto-proceeding despite warnings${colors.reset}`);
1660
- }
1661
- }
1662
-
1663
- console.log(`${colors.green}✓ Pre-installation checks passed${colors.reset}\n`);
1664
-
1665
581
  // Initialize InstallationEngine
1666
582
  const PathManager = require('./path-manager');
1667
583
  const InstallationEngine = require('./installation-engine');
584
+ const os = require('os');
1668
585
 
1669
586
  const pathManager = new PathManager();
1670
587
  const installationEngine = new InstallationEngine(pathManager, this.packageManager);
1671
588
 
1672
- // Initialize state management for resume capability
1673
- installationEngine.getStateManager().initializeState(
1674
- this.selections.variant,
1675
- this.selections.tools,
1676
- this.selections.paths
1677
- );
1678
- await installationEngine.getStateManager().saveState({ stage: 'initializing' });
1679
-
1680
- // Track overall progress
1681
- let overallFilesCompleted = 0;
1682
- let overallTotalFiles = 0;
1683
- let successfulInstalls = [];
1684
- let failedInstalls = [];
1685
- let verificationResults = [];
1686
-
1687
- // Calculate total files across all tools (only for valid tools)
1688
- const toolFileCount = {};
1689
- for (const toolId of this.selections.tools) {
1690
- try {
1691
- const contents = await this.packageManager.getPackageContents(toolId, this.selections.variant);
1692
- toolFileCount[toolId] = contents.totalFiles;
1693
- overallTotalFiles += contents.totalFiles;
1694
- } catch (error) {
1695
- // Skip tools that don't have valid packages
1696
- toolFileCount[toolId] = 0;
1697
- }
1698
- }
1699
-
1700
- const startTime = Date.now();
1701
-
1702
589
  // Install each selected tool
1703
590
  for (let i = 0; i < this.selections.tools.length; i++) {
1704
591
  const toolId = this.selections.tools[i];
1705
592
  const tool = this.tools.find(t => t.id === toolId);
1706
593
  const targetPath = this.selections.paths[toolId];
1707
594
 
1708
- console.log(`\n${colors.bright}[${i + 1}/${this.selections.tools.length}] Installing ${tool.name}...${colors.reset}`);
1709
- console.log(`${colors.cyan}Target:${colors.reset} ${targetPath}\n`);
1710
-
1711
- // Track files completed for this tool
1712
- let toolFilesCompleted = 0;
1713
- let toolStartTime = Date.now();
595
+ console.log(`\n${colors.bright}${colors.cyan}Installing ${tool.name}...${colors.reset}`);
1714
596
 
1715
597
  try {
1716
- // Install with progress callback
598
+ const expandedPath = targetPath.startsWith('~')
599
+ ? path.join(os.homedir(), targetPath.slice(1))
600
+ : path.resolve(targetPath);
601
+
602
+ // Start spinner
603
+ const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
604
+ let spinnerIndex = 0;
605
+ const spinner = setInterval(() => {
606
+ process.stdout.write(`\r ${colors.cyan}${spinnerFrames[spinnerIndex]}${colors.reset} Installing...`);
607
+ spinnerIndex = (spinnerIndex + 1) % spinnerFrames.length;
608
+ }, 80);
609
+
610
+ // Install
1717
611
  await installationEngine.installPackage(
1718
612
  toolId,
1719
- this.selections.variant,
613
+ 'pro', // Always pro variant
1720
614
  targetPath,
1721
- (progress) => {
1722
- // Update tool files completed (only count new files)
1723
- if (progress.filesCompleted > toolFilesCompleted) {
1724
- const newFiles = progress.filesCompleted - toolFilesCompleted;
1725
- overallFilesCompleted += newFiles;
1726
- toolFilesCompleted = progress.filesCompleted;
1727
- }
1728
-
1729
- // Calculate elapsed time and speed
1730
- const elapsedMs = Date.now() - toolStartTime;
1731
- const elapsedSec = Math.floor(elapsedMs / 1000);
1732
- const elapsedMin = Math.floor(elapsedSec / 60);
1733
- const elapsedSecRemainder = elapsedSec % 60;
1734
- const elapsedFormatted = `${elapsedMin}:${elapsedSecRemainder.toString().padStart(2, '0')}`;
1735
-
1736
- // Calculate transfer speed (bytes per second)
1737
- const speed = elapsedMs > 0 ? progress.bytesTransferred / (elapsedMs / 1000) : 0;
1738
- const speedFormatted = this.formatBytes(speed);
1739
-
1740
- // Calculate ETA
1741
- const remainingBytes = progress.totalBytes - progress.bytesTransferred;
1742
- const etaSec = speed > 0 ? Math.floor(remainingBytes / speed) : 0;
1743
- const etaMin = Math.floor(etaSec / 60);
1744
- const etaSecRemainder = etaSec % 60;
1745
- const etaFormatted = `${etaMin}:${etaSecRemainder.toString().padStart(2, '0')}`;
1746
-
1747
- // Draw progress bar for current tool
1748
- this.drawProgressBar(
1749
- progress.filesCompleted,
1750
- progress.totalFiles,
1751
- progress.percentage,
1752
- progress.currentFile,
1753
- this.formatBytes(progress.bytesTransferred),
1754
- this.formatBytes(progress.totalBytes),
1755
- speedFormatted,
1756
- elapsedFormatted,
1757
- etaFormatted
1758
- );
1759
-
1760
- // Draw overall progress
1761
- // Cap overall percentage at 100% to handle file count mismatches
1762
- const overallPercentage = overallTotalFiles > 0
1763
- ? Math.min(100, Math.round((overallFilesCompleted / overallTotalFiles) * 100))
1764
- : 0;
1765
-
1766
- this.drawOverallProgress(
1767
- overallFilesCompleted,
1768
- overallTotalFiles,
1769
- overallPercentage,
1770
- i + 1,
1771
- this.selections.tools.length
1772
- );
1773
- }
615
+ null // No progress callback
1774
616
  );
1775
617
 
1776
- // Clear progress lines after completion
1777
- process.stdout.write('\x1b[2K\r'); // Clear current line
1778
- process.stdout.write('\x1b[1A\x1b[2K\r'); // Clear previous line
1779
-
1780
- console.log(`${colors.green}✓ ${tool.name} installed successfully${colors.reset}`);
1781
- console.log(` ${colors.cyan}Location:${colors.reset} ${targetPath}`);
1782
- console.log(` ${colors.cyan}Files:${colors.reset} ${toolFilesCompleted} files`);
618
+ // Stop spinner
619
+ clearInterval(spinner);
620
+ process.stdout.write('\r\x1b[2K'); // Clear spinner line
1783
621
 
1784
- // Verify installation
1785
- console.log(` ${colors.cyan}Verifying installation...${colors.reset}`);
1786
- const verification = await installationEngine.verifyInstallation(toolId, targetPath);
622
+ // Get backup info from installation engine
623
+ const lastBackup = installationEngine.backupLog.length > 0
624
+ ? installationEngine.backupLog[installationEngine.backupLog.length - 1]
625
+ : null;
1787
626
 
1788
- if (verification.valid) {
1789
- console.log(` ${colors.green}✓ Verification passed${colors.reset}`);
1790
- } else {
1791
- console.log(` ${colors.yellow}⚠ Verification completed with issues${colors.reset}`);
627
+ // Count actual installed components by checking directories
628
+ const countItems = (dir) => {
629
+ try {
630
+ if (fs.existsSync(dir)) {
631
+ return fs.readdirSync(dir).filter(f => !f.startsWith('.')).length;
632
+ }
633
+ } catch (e) {}
634
+ return 0;
635
+ };
636
+
637
+ // Count only .md files in a directory
638
+ const countMdFiles = (dir) => {
639
+ try {
640
+ if (fs.existsSync(dir)) {
641
+ return fs.readdirSync(dir).filter(f => f.endsWith('.md')).length;
642
+ }
643
+ } catch (e) {}
644
+ return 0;
645
+ };
646
+
647
+ // Check for agents (agents, agent, or droids)
648
+ let agentsCount = countItems(path.join(expandedPath, 'agents')) ||
649
+ countItems(path.join(expandedPath, 'agent')) ||
650
+ countItems(path.join(expandedPath, 'droids'));
651
+ let agentDir = 'agents';
652
+ if (fs.existsSync(path.join(expandedPath, 'agents'))) {
653
+ agentDir = 'agents';
654
+ } else if (fs.existsSync(path.join(expandedPath, 'agent'))) {
655
+ agentDir = 'agent';
656
+ } else if (fs.existsSync(path.join(expandedPath, 'droids'))) {
657
+ agentDir = 'droids';
1792
658
  }
1793
659
 
1794
- successfulInstalls.push({
1795
- toolId,
1796
- name: tool.name,
1797
- path: targetPath,
1798
- fileCount: toolFilesCompleted
1799
- });
1800
-
1801
- verificationResults.push(verification);
660
+ // Check for skills (count directories)
661
+ const skillsCount = countItems(path.join(expandedPath, 'skills'));
1802
662
 
1803
- // Mark tool as completed in state
1804
- await installationEngine.getStateManager().completeCurrentTool();
663
+ // Check for commands (count only .md files, both singular and plural)
664
+ const commandsCount = countMdFiles(path.join(expandedPath, 'commands')) ||
665
+ countMdFiles(path.join(expandedPath, 'command'));
666
+ const cmdDir = fs.existsSync(path.join(expandedPath, 'commands')) ? 'commands' : 'command';
1805
667
 
1806
- } catch (error) {
1807
- // Clear progress lines on error
1808
- process.stdout.write('\x1b[2K\r');
1809
- process.stdout.write('\x1b[1A\x1b[2K\r');
1810
-
1811
- // Categorize the error for better user guidance
1812
- const errorInfo = this.categorizeError(error);
1813
-
1814
- console.error(`${colors.red}✗ Failed to install ${tool.name}${colors.reset}`);
1815
- console.error(` ${colors.red}Error Type:${colors.reset} ${errorInfo.type}`);
1816
- console.error(` ${colors.red}Message:${colors.reset} ${error.message}\n`);
1817
-
1818
- // Display actionable advice
1819
- if (errorInfo.advice.length > 0) {
1820
- console.error(` ${colors.yellow}Suggested Actions:${colors.reset}`);
1821
- errorInfo.advice.slice(0, 3).forEach((advice, index) => {
1822
- console.error(` ${index + 1}. ${advice}`);
1823
- });
1824
- console.error('');
668
+ // Show backup info if it was created
669
+ if (lastBackup && lastBackup.original === expandedPath) {
670
+ const backupShortPath = lastBackup.backup.replace(os.homedir(), '~');
671
+ console.log(` ${colors.yellow}Backup:${colors.reset} ${backupShortPath}`);
1825
672
  }
1826
673
 
1827
- failedInstalls.push({
1828
- toolId,
1829
- name: tool.name,
1830
- path: targetPath,
1831
- error: error.message,
1832
- errorType: errorInfo.type
1833
- });
1834
-
1835
- // Mark tool as failed in state
1836
- await installationEngine.getStateManager().failCurrentTool(error);
1837
-
1838
- // Offer recovery options if there are more tools to install
1839
- if (i < this.selections.tools.length - 1) {
1840
- const shouldContinue = await this.offerRecoveryOptions(tool.name, i + 1, this.selections.tools.length);
1841
-
1842
- if (!shouldContinue) {
1843
- console.log(`\n${colors.yellow}Installation cancelled by user${colors.reset}`);
1844
- break; // Stop installing remaining tools
1845
- }
674
+ // Show components with color
675
+ if (agentsCount > 0) {
676
+ console.log(` ${colors.green}✓${colors.reset} ${agentsCount} agents → ${targetPath}/${agentDir}`);
677
+ }
678
+ if (skillsCount > 0) {
679
+ console.log(` ${colors.green}✓${colors.reset} ${skillsCount} skills → ${targetPath}/skills`);
680
+ }
681
+ if (commandsCount > 0) {
682
+ console.log(` ${colors.green}✓${colors.reset} ${commandsCount} commands ${targetPath}/${cmdDir}`);
1846
683
  }
1847
- }
1848
- }
1849
-
1850
- // Calculate total elapsed time
1851
- const totalElapsedMs = Date.now() - startTime;
1852
- const totalElapsedSec = Math.floor(totalElapsedMs / 1000);
1853
- const totalElapsedMin = Math.floor(totalElapsedSec / 60);
1854
- const totalElapsedSecRemainder = totalElapsedSec % 60;
1855
- const totalElapsedFormatted = `${totalElapsedMin}:${totalElapsedSecRemainder.toString().padStart(2, '0')}`;
1856
-
1857
- // Display final summary
1858
- console.log(`\n${colors.bright}Installation Complete${colors.reset}`);
1859
- console.log(`${colors.cyan}Total time:${colors.reset} ${totalElapsedFormatted}`);
1860
- console.log(`${colors.cyan}Total files:${colors.reset} ${overallFilesCompleted} files\n`);
1861
-
1862
- if (successfulInstalls.length > 0) {
1863
- console.log(`${colors.green}Successfully installed:${colors.reset}`);
1864
- for (const install of successfulInstalls) {
1865
- console.log(` ${colors.green}✓${colors.reset} ${install.name} (${install.fileCount} files)`);
1866
- }
1867
- }
1868
-
1869
- if (failedInstalls.length > 0) {
1870
- console.log(`\n${colors.red}Failed installations:${colors.reset}`);
1871
- for (const install of failedInstalls) {
1872
- console.log(` ${colors.red}✗${colors.reset} ${install.name} (${install.errorType}): ${install.error}`);
1873
- }
1874
- console.log(`\n${colors.yellow}Note: Failed installations have been automatically rolled back${colors.reset}`);
1875
- console.log(`${colors.yellow}No partial installations remain on your system${colors.reset}`);
1876
-
1877
- // Offer retry options for failed installations
1878
- if (failedInstalls.length > 0 && successfulInstalls.length > 0) {
1879
- console.log(`\n${colors.cyan}You can retry failed installations by running the installer again${colors.reset}`);
1880
- }
1881
- }
1882
684
 
1883
- // Display detailed verification reports for successful installations
1884
- if (successfulInstalls.length > 0 && verificationResults.length > 0) {
1885
- console.log(`\n${colors.bright}${'='.repeat(60)}${colors.reset}`);
1886
- for (let i = 0; i < successfulInstalls.length; i++) {
1887
- const install = successfulInstalls[i];
1888
- const verification = verificationResults[i];
1889
- this.displayVerificationReport(verification, install.name);
685
+ } catch (error) {
686
+ // On ANY error, show it and exit immediately
687
+ throw error; // Will be caught by run() and handled by handleFatalError()
1890
688
  }
1891
- console.log(`${colors.bright}${'='.repeat(60)}${colors.reset}\n`);
1892
- }
1893
-
1894
- // Generate and save installation report
1895
- if (successfulInstalls.length > 0 || failedInstalls.length > 0) {
1896
- await this.generateInstallationReport(
1897
- successfulInstalls,
1898
- failedInstalls,
1899
- verificationResults,
1900
- totalElapsedMs
1901
- );
1902
- }
1903
-
1904
- // Clear installation state if all tools completed successfully
1905
- if (failedInstalls.length === 0) {
1906
- await installationEngine.getStateManager().clearState();
1907
- console.log(`${colors.green}✓ Installation state cleared${colors.reset}`);
1908
- } else {
1909
- console.log(`\n${colors.yellow}Installation state preserved for resume capability${colors.reset}`);
1910
- console.log(`${colors.yellow}Run the installer again to retry failed tools${colors.reset}`);
1911
689
  }
1912
690
 
1913
- // Collect telemetry data (if user has consented)
1914
- await this.collectInstallationTelemetry(
1915
- failedInstalls.length === 0,
1916
- this.selections.tools.length,
1917
- failedInstalls.length,
1918
- 0, // warnings are tracked in verification
1919
- totalElapsedMs
1920
- );
691
+ console.log(`\n${colors.bright}${colors.green}Done!${colors.reset}`);
1921
692
  }
1922
693
 
1923
694
  /**
1924
695
  * Draw progress bar for current tool installation
1925
696
  * Updates in place without scrolling using ANSI escape codes
1926
697
  */
1927
- drawProgressBar(filesCompleted, totalFiles, percentage, currentFile, bytesTransferred, totalBytes, speed, elapsed, eta) {
1928
- // Move cursor to beginning of line and clear it
1929
- process.stdout.write('\x1b[2K\r');
1930
-
1931
- // Calculate progress bar width (40 characters)
698
+ drawProgressBar(filesCompleted, totalFiles, percentage, currentFile) {
1932
699
  const barWidth = 40;
1933
700
  const filledWidth = Math.round((percentage / 100) * barWidth);
1934
- const emptyWidth = barWidth - filledWidth;
1935
-
1936
- // Build progress bar
1937
- const bar = '█'.repeat(filledWidth) + '░'.repeat(emptyWidth);
701
+ const bar = '#'.repeat(filledWidth) + '-'.repeat(barWidth - filledWidth);
1938
702
 
1939
- // Truncate current file name if too long
1940
- const maxFileNameLength = 50;
1941
- let displayFileName = currentFile;
1942
- if (displayFileName.length > maxFileNameLength) {
1943
- displayFileName = '...' + displayFileName.slice(-(maxFileNameLength - 3));
1944
- }
1945
-
1946
- // Display progress bar
1947
- process.stdout.write(
1948
- `[${colors.cyan}${bar}${colors.reset}] ${percentage}% (${filesCompleted}/${totalFiles} files)\n`
1949
- );
1950
-
1951
- // Display current file and stats
1952
- process.stdout.write(
1953
- `${colors.yellow}Copying:${colors.reset} ${displayFileName} | ` +
1954
- `${bytesTransferred}/${totalBytes} | ` +
1955
- `${speed}/s | ` +
1956
- `Elapsed: ${elapsed} | ` +
1957
- `ETA: ${eta}`
1958
- );
1959
-
1960
- // Move cursor up one line to overwrite on next update
1961
- process.stdout.write('\x1b[1A');
703
+ // Single line, update in place
704
+ process.stdout.write(`\r[${bar}] ${percentage}% (${filesCompleted}/${totalFiles} files)`);
1962
705
  }
1963
706
 
1964
- /**
1965
- * Draw overall progress across all tools
1966
- */
1967
- drawOverallProgress(filesCompleted, totalFiles, percentage, currentTool, totalTools) {
1968
- // This is called after the tool progress, so cursor is already up one line
1969
- // Move down to write overall progress below the tool progress
1970
- process.stdout.write('\x1b[2B'); // Move down 2 lines
1971
- process.stdout.write('\x1b[2K\r'); // Clear line
1972
-
1973
- // Display overall progress
1974
- process.stdout.write(
1975
- `${colors.bright}Overall:${colors.reset} Tool ${currentTool}/${totalTools} | ` +
1976
- `${filesCompleted}/${totalFiles} files (${percentage}%)`
1977
- );
1978
-
1979
- // Move cursor back up
1980
- process.stdout.write('\x1b[1A'); // Move up 1 line to be ready for next tool progress update
1981
- }
1982
707
 
1983
708
  askQuestion(prompt, defaultValue = '') {
1984
709
  return new Promise(resolve => {
@@ -1995,79 +720,6 @@ ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
1995
720
  * @param {object} verification - Verification result from InstallationEngine
1996
721
  * @param {string} toolName - Display name of the tool
1997
722
  */
1998
- displayVerificationReport(verification, toolName) {
1999
- console.log(`\n${colors.bright}Verification Report: ${toolName}${colors.reset}`);
2000
- console.log('─'.repeat(60));
2001
-
2002
- if (verification.valid) {
2003
- console.log(`${colors.green}✓ Installation verified successfully${colors.reset}`);
2004
- } else {
2005
- console.log(`${colors.red}✗ Verification failed${colors.reset}`);
2006
- }
2007
-
2008
- // Display manifest location
2009
- const manifestPath = path.join(verification.targetPath, 'manifest.json');
2010
- console.log(`\n${colors.cyan}Manifest:${colors.reset} ${manifestPath}`);
2011
-
2012
- // Display variant and version info
2013
- if (verification.variant) {
2014
- console.log(`${colors.cyan}Variant:${colors.reset} ${verification.variant}`);
2015
- }
2016
- if (verification.version) {
2017
- console.log(`${colors.cyan}Version:${colors.reset} ${verification.version}`);
2018
- }
2019
-
2020
- // Display component counts
2021
- console.log(`\n${colors.cyan}Components:${colors.reset}`);
2022
- const components = verification.components;
2023
- const agentCount = components.agents.found;
2024
- const skillCount = components.skills.found;
2025
- const resourceCount = components.resources.found;
2026
- const hookCount = components.hooks.found;
2027
-
2028
- const componentSummary = [];
2029
- if (agentCount > 0) componentSummary.push(`${agentCount} agent${agentCount !== 1 ? 's' : ''}`);
2030
- if (skillCount > 0) componentSummary.push(`${skillCount} skill${skillCount !== 1 ? 's' : ''}`);
2031
- if (resourceCount > 0) componentSummary.push(`${resourceCount} resource${resourceCount !== 1 ? 's' : ''}`);
2032
- if (hookCount > 0) componentSummary.push(`${hookCount} hook${hookCount !== 1 ? 's' : ''}`);
2033
-
2034
- console.log(` ${componentSummary.join(', ')}`);
2035
-
2036
- // Display issues if any
2037
- if (verification.issues.length > 0) {
2038
- console.log(`\n${colors.red}Issues:${colors.reset}`);
2039
- for (const issue of verification.issues) {
2040
- console.log(` ${colors.red}✗${colors.reset} ${issue.message}`);
2041
- }
2042
- }
2043
-
2044
- // Display warnings if any
2045
- if (verification.warnings.length > 0) {
2046
- console.log(`\n${colors.yellow}Warnings:${colors.reset}`);
2047
- for (const warning of verification.warnings) {
2048
- console.log(` ${colors.yellow}⚠${colors.reset} ${warning.message}`);
2049
- }
2050
- }
2051
-
2052
- // Display next steps for successful installations
2053
- if (verification.valid) {
2054
- console.log(`\n${colors.bright}Next Steps:${colors.reset}`);
2055
- console.log(` To use ${toolName}, navigate to: ${verification.targetPath}`);
2056
-
2057
- // Provide tool-specific usage hints
2058
- const toolId = verification.toolId;
2059
- if (toolId === 'claude') {
2060
- console.log(` ${colors.cyan}Quick start:${colors.reset} Run 'claude' to start using Claude Code`);
2061
- } else if (toolId === 'opencode') {
2062
- console.log(` ${colors.cyan}Quick start:${colors.reset} Run 'opencode' to start using Opencode`);
2063
- } else if (toolId === 'ampcode') {
2064
- console.log(` ${colors.cyan}Quick start:${colors.reset} Run 'ampcode' to start using Ampcode`);
2065
- } else if (toolId === 'droid') {
2066
- console.log(` ${colors.cyan}Quick start:${colors.reset} Run 'droid' to start using Droid`);
2067
- }
2068
- }
2069
- }
2070
-
2071
723
  /**
2072
724
  * Generate and save installation report to ~/.agentic-kit-install.log
2073
725
  * Creates a detailed log of the installation session
@@ -2077,153 +729,12 @@ ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
2077
729
  * @param {array} verificationResults - Array of verification result objects
2078
730
  * @param {number} totalElapsedMs - Total elapsed time in milliseconds
2079
731
  */
2080
- async generateInstallationReport(successfulInstalls, failedInstalls, verificationResults, totalElapsedMs) {
2081
- const ReportTemplate = require('./report-template');
2082
- const reportTemplate = new ReportTemplate();
2083
-
2084
- // Prepare installation data for the report template
2085
- const startTime = Date.now() - totalElapsedMs;
2086
- const endTime = Date.now();
2087
-
2088
- // Build tools array with complete information
2089
- const tools = [];
2090
- const allErrors = [];
2091
- const allWarnings = [];
2092
-
2093
- // Process successful installations
2094
- for (let i = 0; i < successfulInstalls.length; i++) {
2095
- const install = successfulInstalls[i];
2096
- const verification = verificationResults[i];
2097
-
2098
- // Get package size information
2099
- let sizeBytes = 0;
2100
- try {
2101
- const sizeInfo = await this.packageManager.getPackageSize(install.toolId, this.selections.variant);
2102
- sizeBytes = sizeInfo.bytes || 0;
2103
- } catch (error) {
2104
- // If we can't get size, estimate based on file count (rough estimate: 15KB per file)
2105
- sizeBytes = install.fileCount * 15 * 1024;
2106
- }
2107
-
2108
- // Get manifest path
2109
- const manifestPath = path.join(install.path, 'manifest.json');
2110
-
2111
- tools.push({
2112
- toolId: install.toolId,
2113
- path: install.path,
2114
- filesInstalled: install.fileCount,
2115
- sizeBytes: sizeBytes,
2116
- components: verification && verification.components ? {
2117
- agents: verification.components.agents.found,
2118
- skills: verification.components.skills.found,
2119
- resources: verification.components.resources.found,
2120
- hooks: verification.components.hooks.found
2121
- } : {},
2122
- verified: verification ? verification.valid : false,
2123
- verificationStatus: verification && verification.valid ? 'All components verified successfully' : 'Verification completed with issues',
2124
- manifestPath: manifestPath
2125
- });
2126
-
2127
- // Collect warnings from verification
2128
- if (verification && verification.warnings && verification.warnings.length > 0) {
2129
- verification.warnings.forEach(warning => {
2130
- allWarnings.push(`[${install.toolId}] ${warning.message}`);
2131
- });
2132
- }
2133
-
2134
- // Collect issues from verification as errors
2135
- if (verification && verification.issues && verification.issues.length > 0) {
2136
- verification.issues.forEach(issue => {
2137
- allErrors.push(`[${install.toolId}] ${issue.message}`);
2138
- });
2139
- }
2140
- }
2141
-
2142
- // Process failed installations
2143
- for (const install of failedInstalls) {
2144
- allErrors.push(`[${install.toolId}] Installation failed: ${install.error}`);
2145
- }
2146
-
2147
- // Build installation data object
2148
- const installationData = {
2149
- variant: this.selections.variant,
2150
- tools: tools,
2151
- startTime: startTime,
2152
- endTime: endTime,
2153
- success: failedInstalls.length === 0,
2154
- errors: allErrors,
2155
- warnings: allWarnings
2156
- };
2157
-
2158
- // Generate and save report using ReportTemplate
2159
- try {
2160
- const reportPath = await reportTemplate.createAndSaveReport(installationData);
2161
- console.log(`\n${colors.cyan}Installation report saved to:${colors.reset} ${reportPath}`);
2162
- } catch (error) {
2163
- console.warn(`${colors.yellow}Warning: Could not save installation report: ${error.message}${colors.reset}`);
2164
- }
2165
- }
2166
-
2167
732
  /**
2168
733
  * Prompt user for telemetry consent
2169
734
  * Only prompts if consent hasn't been set before and --no-telemetry flag not present
2170
735
  *
2171
736
  * @returns {Promise<void>}
2172
737
  */
2173
- async promptTelemetryConsent() {
2174
- // Skip if --no-telemetry flag is present
2175
- if (this.cliArgs.noTelemetry) {
2176
- return;
2177
- }
2178
-
2179
- // Skip if in silent mode
2180
- if (this.cliArgs.silent) {
2181
- return;
2182
- }
2183
-
2184
- const Telemetry = require('./telemetry');
2185
- const telemetry = new Telemetry();
2186
-
2187
- // Check if user has already made a decision
2188
- const hasConsent = await telemetry.hasConsent();
2189
- const hasOptedOut = await telemetry.hasOptedOut();
2190
-
2191
- // Only prompt if user hasn't decided yet
2192
- if (!hasConsent && !hasOptedOut) {
2193
- console.log(`\n${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}`);
2194
- console.log(`${colors.bright}Help Improve Agentic Kit${colors.reset}\n`);
2195
- console.log('Would you like to share anonymous usage statistics to help improve agentic-kit?');
2196
- console.log('\nData collected:');
2197
- console.log(' • Package variant selected (lite/standard/pro)');
2198
- console.log(' • Number of tools installed');
2199
- console.log(' • Installation time and success status');
2200
- console.log(' • Operating system type');
2201
- console.log(' • Node.js version');
2202
- console.log('\nData NOT collected:');
2203
- console.log(' • File paths or directory locations');
2204
- console.log(' • Personal information');
2205
- console.log(' • Specific tool names');
2206
- console.log(' • Any identifying information');
2207
- console.log('\nYou can change this setting later or opt-out anytime.');
2208
- console.log(`For details, see: ${colors.cyan}docs/PRIVACY.md${colors.reset}`);
2209
- console.log(`${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}\n`);
2210
-
2211
- const answer = await this.askQuestion(
2212
- `${colors.bright}Share anonymous usage data? (y/N):${colors.reset} `,
2213
- 'N'
2214
- );
2215
-
2216
- const consent = answer.toLowerCase() === 'y';
2217
- await telemetry.setConsent(consent);
2218
-
2219
- if (consent) {
2220
- console.log(`${colors.green}✓ Thank you! Anonymous usage statistics enabled${colors.reset}\n`);
2221
- } else {
2222
- console.log(`${colors.yellow}Usage statistics disabled${colors.reset}\n`);
2223
- }
2224
- }
2225
- }
2226
-
2227
738
  /**
2228
739
  * Collect and send telemetry data for installation
2229
740
  *
@@ -2234,28 +745,6 @@ ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
2234
745
  * @param {number} installationTime - Installation time in milliseconds
2235
746
  * @returns {Promise<void>}
2236
747
  */
2237
- async collectInstallationTelemetry(success, toolCount, errorCount, warningCount, installationTime) {
2238
- // Skip if --no-telemetry flag is present
2239
- if (this.cliArgs.noTelemetry) {
2240
- return;
2241
- }
2242
-
2243
- const Telemetry = require('./telemetry');
2244
- const telemetry = new Telemetry();
2245
-
2246
- try {
2247
- await telemetry.collectInstallationStats({
2248
- variant: this.selections.variant,
2249
- toolCount: toolCount,
2250
- installationTime: installationTime,
2251
- success: success,
2252
- errorCount: errorCount,
2253
- warningCount: warningCount
2254
- });
2255
- } catch (error) {
2256
- // Silently fail - don't interrupt user experience for telemetry issues
2257
- }
2258
- }
2259
748
  }
2260
749
 
2261
750
  // Run installer if called directly