@amrhas82/agentic-kit 1.9.0 → 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: 13, skills: 8, description: 'Most users, general dev' },
87
- { id: 'pro', name: 'Pro', agents: 13, skills: 14, description: 'Advanced users, full 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 (13 agents, 8 skills) - For most users
202
- ${colors.cyan}pro${colors.reset} - Full setup (13 agents, 22 skills) - For advanced users
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,417 +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
- const variantInfo = this.variants.find(v => v.id === variant);
684
- console.log(`${colors.bright}Installation Summary:${colors.reset}`);
685
- console.log(`${colors.cyan}Variant:${colors.reset} ${variantInfo.name} (${variantInfo.agents} agents, ${variantInfo.skills} skills)`);
686
- console.log(`${colors.cyan}Tools:${colors.reset} ${this.selections.tools.join(', ')}`);
687
- console.log('');
688
-
689
- for (const toolId of this.selections.tools) {
690
- const tool = this.tools.find(t => t.id === toolId);
691
- const isCustom = this.selections.paths[toolId] !== tool.path;
692
- console.log(`${colors.bright}${tool.name}${colors.reset}`);
693
- console.log(` Path: ${this.selections.paths[toolId]}${isCustom ? ' (custom)' : ''}`);
694
- }
695
- console.log('');
696
-
697
- // Run installation
698
- try {
699
- await this.install();
700
- } catch (error) {
701
- console.error(`${colors.red}✗ Installation failed: ${error.message}${colors.reset}\n`);
702
- process.exit(1);
703
- }
704
- }
705
-
706
291
  /**
707
292
  * Handle fatal errors with detailed error messages and actionable advice
708
293
  * Categorizes errors and provides specific guidance for each type
@@ -715,26 +300,15 @@ ${colors.bright}For more information, visit:${colors.reset}
715
300
  // Categorize the error and provide appropriate guidance
716
301
  const errorInfo = this.categorizeError(error);
717
302
 
718
- console.log(`${colors.red}${colors.bright}✗ Installation Failed${colors.reset}`);
719
- console.log(`${colors.red}${'─'.repeat(60)}${colors.reset}\n`);
720
-
721
- console.log(`${colors.bright}Error Type:${colors.reset} ${errorInfo.type}`);
722
- console.log(`${colors.bright}Message:${colors.reset} ${error.message}\n`);
303
+ console.log(`${colors.red}Error: ${error.message}${colors.reset}`);
723
304
 
724
- if (errorInfo.advice.length > 0) {
725
- console.log(`${colors.yellow}${colors.bright}Suggested Actions:${colors.reset}`);
726
- errorInfo.advice.forEach((advice, index) => {
727
- console.log(` ${index + 1}. ${advice}`);
305
+ if (errorInfo.advice.length > 0 && errorInfo.advice.length <= 3) {
306
+ errorInfo.advice.forEach(advice => {
307
+ console.log(` - ${advice}`);
728
308
  });
729
309
  console.log('');
730
310
  }
731
311
 
732
- // Display additional technical details if available
733
- if (errorInfo.technicalDetails) {
734
- console.log(`${colors.cyan}Technical Details:${colors.reset}`);
735
- console.log(` ${errorInfo.technicalDetails}\n`);
736
- }
737
-
738
312
  // Exit with error code
739
313
  process.exit(1);
740
314
  }
@@ -754,9 +328,8 @@ ${colors.bright}For more information, visit:${colors.reset}
754
328
  return {
755
329
  type: 'Permission Error',
756
330
  advice: [
757
- 'Try running the installer with elevated permissions: sudo node installer/cli.js',
758
- 'Or choose a different installation directory where you have write access',
759
- 'Check directory permissions: ls -la on the parent directory'
331
+ 'Try: sudo node installer/cli.js',
332
+ 'Or choose a different installation directory'
760
333
  ],
761
334
  technicalDetails: `Error code: ${code || 'EACCES'}`
762
335
  };
@@ -767,10 +340,8 @@ ${colors.bright}For more information, visit:${colors.reset}
767
340
  return {
768
341
  type: 'Disk Space Error',
769
342
  advice: [
770
- 'Free up disk space by removing unnecessary files',
771
- 'Check available space: df -h',
772
- 'Consider installing to a different location with more space',
773
- 'The installer requires at least 50MB of free space'
343
+ 'Free up disk space',
344
+ 'Check: df -h'
774
345
  ],
775
346
  technicalDetails: 'Installation requires approximately 10MB per tool'
776
347
  };
@@ -781,10 +352,8 @@ ${colors.bright}For more information, visit:${colors.reset}
781
352
  return {
782
353
  type: 'Network Error',
783
354
  advice: [
784
- 'Check your internet connection',
785
- 'Verify proxy settings if behind a corporate firewall',
786
- 'Try again in a few moments',
787
- 'Use offline installation mode if available'
355
+ 'Check internet connection',
356
+ 'Try again later'
788
357
  ],
789
358
  technicalDetails: `Network error code: ${code || 'UNKNOWN'}`
790
359
  };
@@ -795,10 +364,8 @@ ${colors.bright}For more information, visit:${colors.reset}
795
364
  return {
796
365
  type: 'Missing Package Error',
797
366
  advice: [
798
- 'Ensure agentic-kit is properly installed: npm install -g @amrhas82/agentic-kit',
799
- 'Verify the packages directory exists and contains required files',
800
- 'Try reinstalling agentic-kit: npm uninstall -g @amrhas82/agentic-kit && npm install -g @amrhas82/agentic-kit',
801
- 'Check that you are running the installer from the correct directory'
367
+ 'Run: npm install -g @amrhas82/agentic-kit',
368
+ 'Check: packages directory exists'
802
369
  ],
803
370
  technicalDetails: `Missing file or package validation failed`
804
371
  };
@@ -809,10 +376,8 @@ ${colors.bright}For more information, visit:${colors.reset}
809
376
  return {
810
377
  type: 'Path Validation Error',
811
378
  advice: [
812
- 'Ensure the path is absolute (starts with / or ~)',
813
- 'Verify the parent directory exists',
814
- 'Check that you have write permissions for the specified path',
815
- 'Use default paths if custom paths are causing issues'
379
+ 'Path must be absolute (starts with / or ~)',
380
+ 'Check parent directory exists'
816
381
  ],
817
382
  technicalDetails: 'Paths must be absolute and writable'
818
383
  };
@@ -823,10 +388,8 @@ ${colors.bright}For more information, visit:${colors.reset}
823
388
  return {
824
389
  type: 'Invalid Input Error',
825
390
  advice: [
826
- 'Review your selections and ensure all required options are provided',
827
- 'At least one tool must be selected',
828
- 'Paths must be absolute (start with / or ~)',
829
- 'Restart the installer and try again'
391
+ 'Check your selections',
392
+ 'Restart installer and try again'
830
393
  ],
831
394
  technicalDetails: error.message
832
395
  };
@@ -837,11 +400,9 @@ ${colors.bright}For more information, visit:${colors.reset}
837
400
  return {
838
401
  type: 'Installation Error',
839
402
  advice: [
840
- 'Check available disk space: df -h',
841
- 'Verify write permissions on the target directory',
842
- 'Ensure no other process is using the installation directory',
843
- 'Try installing to a different location',
844
- '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'
845
406
  ],
846
407
  technicalDetails: 'Installation process encountered an error during file operations'
847
408
  };
@@ -851,10 +412,8 @@ ${colors.bright}For more information, visit:${colors.reset}
851
412
  return {
852
413
  type: 'Unknown Error',
853
414
  advice: [
854
- 'Try running the installer again',
855
- 'Check system logs for more details',
856
- 'Report this issue at: https://github.com/amrhas82/agentic-kit/issues',
857
- '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'
858
417
  ],
859
418
  technicalDetails: error.stack ? error.stack.split('\n')[1] : 'No additional details'
860
419
  };
@@ -869,50 +428,6 @@ ${colors.bright}For more information, visit:${colors.reset}
869
428
  * @param {number} totalTools - Total number of tools
870
429
  * @returns {Promise<boolean>} True to continue, false to cancel
871
430
  */
872
- async offerRecoveryOptions(failedTool, currentIndex, totalTools) {
873
- const remainingTools = totalTools - currentIndex;
874
-
875
- console.log(`${colors.yellow}┌─────────────────────────────────────────────────────────────┐${colors.reset}`);
876
- console.log(`${colors.yellow}│ Installation Failure - Recovery Options │${colors.reset}`);
877
- console.log(`${colors.yellow}└─────────────────────────────────────────────────────────────┘${colors.reset}\n`);
878
-
879
- console.log(`${colors.bright}Status:${colors.reset} ${failedTool} installation failed`);
880
- console.log(`${colors.bright}Remaining:${colors.reset} ${remainingTools} tool${remainingTools > 1 ? 's' : ''} to install\n`);
881
-
882
- // In silent mode, auto-continue with remaining tools
883
- if (this.cliArgs.silent) {
884
- console.log(`${colors.yellow}Silent mode: auto-continuing with remaining tools${colors.reset}\n`);
885
- console.log(`${colors.yellow}Note:${colors.reset} The failed installation has been rolled back automatically.`);
886
- console.log(`${colors.yellow}No partial files remain for ${failedTool}.${colors.reset}\n`);
887
- return true;
888
- }
889
-
890
- console.log(`${colors.cyan}Options:${colors.reset}`);
891
- console.log(` ${colors.green}C${colors.reset} - Continue with remaining tools (recommended)`);
892
- console.log(` ${colors.red}Q${colors.reset} - Quit installation (successful installations will be kept)\n`);
893
-
894
- console.log(`${colors.yellow}Note:${colors.reset} The failed installation has been rolled back automatically.`);
895
- console.log(`${colors.yellow}No partial files remain for ${failedTool}.${colors.reset}\n`);
896
-
897
- const answer = await this.askQuestion(
898
- `${colors.bright}Choose an option (C/Q):${colors.reset} `,
899
- 'C'
900
- );
901
-
902
- const choice = answer.toUpperCase();
903
-
904
- if (choice === 'C' || choice === '') {
905
- console.log(`${colors.green}Continuing with remaining tools...${colors.reset}\n`);
906
- return true;
907
- } else if (choice === 'Q') {
908
- return false;
909
- } else {
910
- // Invalid choice, ask again
911
- console.log(`${colors.red}Invalid choice. Please enter C or Q.${colors.reset}\n`);
912
- return this.offerRecoveryOptions(failedTool, currentIndex, totalTools);
913
- }
914
- }
915
-
916
431
  /**
917
432
  * Prompt user to resume interrupted installation
918
433
  * Shows summary of previous installation progress
@@ -920,213 +435,50 @@ ${colors.bright}For more information, visit:${colors.reset}
920
435
  * @param {InstallationEngine} installationEngine - Installation engine instance
921
436
  * @returns {Promise<boolean>} - True if user wants to resume, false otherwise
922
437
  */
923
- async promptResume(installationEngine) {
924
- const summary = await installationEngine.getResumeSummary();
925
-
926
- if (!summary) {
927
- return false;
928
- }
929
-
930
- console.clear();
931
- console.log(`\n${colors.yellow}${colors.bright}Previous Installation Detected${colors.reset}`);
932
- console.log(`${colors.yellow}${'─'.repeat(60)}${colors.reset}\n`);
933
-
934
- // Format timestamp
935
- const startedAt = new Date(summary.startedAt);
936
- const lastUpdated = new Date(summary.lastUpdated);
937
- const timeDiff = Math.floor((lastUpdated - startedAt) / 1000 / 60);
938
-
939
- console.log(`${colors.cyan}Session ID:${colors.reset} ${summary.sessionId}`);
940
- console.log(`${colors.cyan}Started:${colors.reset} ${startedAt.toLocaleString()}`);
941
- console.log(`${colors.cyan}Last Updated:${colors.reset} ${lastUpdated.toLocaleString()} (${timeDiff} min ago)`);
942
- console.log(`${colors.cyan}Variant:${colors.reset} ${summary.variant}`);
943
- console.log('');
944
-
945
- // Show progress
946
- console.log(`${colors.bright}Progress:${colors.reset}`);
947
- console.log(` Tools: ${summary.completedTools}/${summary.totalTools} completed`);
948
-
949
- if (summary.completedToolsList.length > 0) {
950
- console.log(`\n${colors.green}Completed:${colors.reset}`);
951
- summary.completedToolsList.forEach(toolId => {
952
- console.log(` ${colors.green}✓${colors.reset} ${toolId}`);
953
- });
954
- }
955
-
956
- if (summary.failedToolsList.length > 0) {
957
- console.log(`\n${colors.red}Failed:${colors.reset}`);
958
- summary.failedToolsList.forEach(toolId => {
959
- console.log(` ${colors.red}✗${colors.reset} ${toolId}`);
960
- });
961
- }
962
-
963
- if (summary.currentTool) {
964
- console.log(`\n${colors.yellow}Current Tool:${colors.reset} ${summary.currentTool}`);
965
- const progress = summary.currentToolProgress;
966
- if (progress.totalFiles > 0) {
967
- console.log(` Progress: ${progress.filesCompleted}/${progress.totalFiles} files (${progress.percentComplete}%)`);
968
- }
969
- }
970
-
971
- console.log('');
972
- console.log(`${colors.bright}Would you like to resume this installation?${colors.reset}`);
973
- console.log(` ${colors.green}Y${colors.reset} - Resume from where it left off`);
974
- console.log(` ${colors.yellow}N${colors.reset} - Start fresh (previous state will be cleared)\n`);
975
-
976
- const answer = await this.askQuestion(
977
- `${colors.bright}Resume installation? (Y/n):${colors.reset} `,
978
- 'Y'
979
- );
980
-
981
- return answer.toLowerCase() !== 'n';
982
- }
983
-
984
438
  /**
985
439
  * Resume interrupted installation
986
440
  * Uses saved state to continue from where it left off
987
441
  *
988
442
  * @param {InstallationEngine} installationEngine - Installation engine instance with loaded state
989
443
  */
990
- async resumeInstallation(installationEngine) {
991
- const state = installationEngine.getStateManager().getState();
992
-
993
- if (!state) {
994
- console.log(`${colors.red}Error: Could not load installation state${colors.reset}`);
995
- return;
996
- }
997
-
998
- 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
+ }
999
457
 
1000
- // Restore selections from state
1001
- this.selections.variant = state.variant;
1002
- this.selections.tools = state.tools;
1003
- this.selections.paths = state.paths;
1004
-
1005
- // Use installMultipleTools with resume flag
1006
- try {
1007
- const results = await installationEngine.installMultipleTools(
1008
- state.variant,
1009
- state.tools,
1010
- state.paths,
1011
- (progress) => {
1012
- // Progress callback (same as normal installation)
1013
- this.drawProgressBar(
1014
- progress.filesCompleted,
1015
- progress.totalFiles,
1016
- progress.percentage,
1017
- progress.currentFile,
1018
- this.formatBytes(progress.bytesTransferred),
1019
- this.formatBytes(progress.totalBytes),
1020
- this.formatBytes(progress.bytesTransferred / ((Date.now() - Date.now()) / 1000 || 1)) + '/s',
1021
- '0:00',
1022
- '0:00'
1023
- );
1024
- },
1025
- true // resume = true
1026
- );
1027
-
1028
- // Display results
1029
- console.log(`\n${colors.bright}Installation Complete${colors.reset}\n`);
1030
-
1031
- if (results.successful.length > 0) {
1032
- console.log(`${colors.green}Successfully installed:${colors.reset}`);
1033
- results.successful.forEach(toolId => {
1034
- console.log(` ${colors.green}✓${colors.reset} ${toolId}`);
1035
- });
1036
- }
1037
-
1038
- if (results.skipped.length > 0) {
1039
- console.log(`\n${colors.yellow}Skipped (already completed):${colors.reset}`);
1040
- results.skipped.forEach(toolId => {
1041
- console.log(` ${colors.yellow}○${colors.reset} ${toolId}`);
1042
- });
1043
- }
1044
-
1045
- if (results.failed.length > 0) {
1046
- console.log(`\n${colors.red}Failed:${colors.reset}`);
1047
- results.failed.forEach(failure => {
1048
- console.log(` ${colors.red}✗${colors.reset} ${failure.toolId}: ${failure.error}`);
1049
- });
1050
- }
1051
-
1052
- } catch (error) {
1053
- console.error(`${colors.red}Resume failed: ${error.message}${colors.reset}`);
1054
- throw error;
1055
- }
1056
- }
1057
-
1058
- showWelcome() {
1059
- console.clear();
1060
- console.log(`
1061
- ${colors.bright}${colors.cyan}┌─────────────────────────────────────────────────────────────┐${colors.reset}
1062
- ${colors.bright}${colors.cyan}│ Agentic Kit Installer v1.2.0 │${colors.reset}
1063
- ${colors.bright}${colors.cyan}│ 14 Agents + 20 Commands Per Tool │${colors.reset}
1064
- ${colors.bright}${colors.cyan}└─────────────────────────────────────────────────────────────┘${colors.reset}
1065
-
1066
- ${colors.bright}Available tools:${colors.reset}
1067
- • ${colors.cyan}claude${colors.reset} - Claude Code (AI development assistant)
1068
- • ${colors.cyan}opencode${colors.reset} - OpenCode (CLI-optimized AI tool)
1069
- • ${colors.cyan}ampcode${colors.reset} - Ampcode (Development accelerator)
1070
- • ${colors.cyan}droid${colors.reset} - Droid (Android-focused assistant)
1071
-
1072
- ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
1073
- `);
1074
-
1075
- return new Promise(resolve => {
1076
- this.rl.question('', resolve);
1077
- });
1078
- }
1079
-
1080
- async selectVariant() {
1081
- console.log(`\n${colors.bright}Step 1/4 — Choose Package Variant${colors.reset}\n`);
1082
-
1083
- console.log('┌─────────────┬─────────┬─────────┬─────────────────────────────┐');
1084
- console.log('│ Variant │ Agents │ Skills │ Description │');
1085
- console.log('├─────────────┼─────────┼─────────┼─────────────────────────────┤');
1086
-
1087
- this.variants.forEach((variant, index) => {
1088
- const selected = index === 1 ? '●' : '○';
1089
- const color = index === 1 ? colors.blue : colors.reset;
1090
- console.log(`│ ${selected} ${color}${variant.name.padEnd(10)}${colors.reset} │ ${variant.agents.toString().padEnd(7)} │ ${variant.skills.toString().padEnd(7)} │ ${variant.description.padEnd(27)} │`);
1091
- });
1092
-
1093
- console.log('└─────────────┴─────────┴─────────┴─────────────────────────────┘');
1094
- console.log('\nUse arrow keys to navigate, Enter to select');
1095
-
1096
- // Default to Standard
1097
- this.selections.variant = 'standard';
1098
-
1099
- return new Promise(resolve => {
1100
- this.rl.question('\nPress Enter to continue (default: Standard)', (answer) => {
1101
- if (answer && ['lite', 'standard', 'pro'].includes(answer.toLowerCase())) {
1102
- this.selections.variant = answer.toLowerCase();
1103
- }
1104
- resolve();
1105
- });
1106
- });
1107
- }
1108
-
1109
- async selectTools() {
1110
- console.log(`\n${colors.bright}Select tools to install${colors.reset}\n`);
1111
- 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`);
1112
461
 
1113
462
  // Interactive checkbox selection
1114
463
  const selected = new Set(['claude']); // Default to claude
1115
464
  let currentIndex = 0;
1116
465
 
1117
466
  const renderList = () => {
1118
- // Clear previous list
1119
- process.stdout.write('\x1b[' + (this.tools.length + 1) + 'A'); // Move up
1120
- 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
+ }
1121
473
 
1122
474
  this.tools.forEach((tool, index) => {
1123
475
  const isSelected = selected.has(tool.id);
1124
476
  const isCurrent = index === currentIndex;
1125
- const checkbox = isSelected ? '' : '';
477
+ const checkbox = isSelected ? '[x]' : '[ ]';
1126
478
  const pointer = isCurrent ? '»' : ' ';
1127
- const color = isCurrent ? colors.cyan : colors.reset;
479
+ const paddedName = tool.name.padEnd(20);
1128
480
 
1129
- console.log(`${pointer} ${color}${checkbox} ${tool.name.padEnd(20)}${colors.reset} - ${tool.description}`);
481
+ console.log(`${pointer} ${checkbox} ${paddedName} - ${tool.description}`);
1130
482
  });
1131
483
  console.log(''); // Empty line at bottom
1132
484
  };
@@ -1135,11 +487,11 @@ ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
1135
487
  this.tools.forEach((tool, index) => {
1136
488
  const isSelected = selected.has(tool.id);
1137
489
  const isCurrent = index === currentIndex;
1138
- const checkbox = isSelected ? '' : '';
490
+ const checkbox = isSelected ? '[x]' : '[ ]';
1139
491
  const pointer = isCurrent ? '»' : ' ';
1140
- const color = isCurrent ? colors.cyan : colors.reset;
492
+ const paddedName = tool.name.padEnd(20);
1141
493
 
1142
- console.log(`${pointer} ${color}${checkbox} ${tool.name.padEnd(20)}${colors.reset} - ${tool.description}`);
494
+ console.log(`${pointer} ${checkbox} ${paddedName} - ${tool.description}`);
1143
495
  });
1144
496
  console.log('');
1145
497
 
@@ -1161,12 +513,7 @@ ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
1161
513
  this.selections.tools = Array.from(selected);
1162
514
 
1163
515
  // Show selection summary
1164
- console.log(`${colors.green}Installing ${this.selections.tools.length} tool(s):${colors.reset}`);
1165
- this.selections.tools.forEach(id => {
1166
- const tool = this.tools.find(t => t.id === id);
1167
- console.log(` ${colors.green}✓${colors.reset} ${tool.name} ${colors.cyan}(14 agents + 20 commands)${colors.reset}`);
1168
- });
1169
- console.log('');
516
+ console.log(`${colors.green}Installing ${this.selections.tools.length} tool(s)${colors.reset}\n`);
1170
517
 
1171
518
  resolve();
1172
519
  } else if (key === ' ') { // Space - toggle
@@ -1201,271 +548,6 @@ ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
1201
548
  }
1202
549
  }
1203
550
 
1204
- async configurePaths() {
1205
- console.log(`\n${colors.bright}Step 3/4 — Path Configuration${colors.reset}\n`);
1206
-
1207
- console.log('Default installation paths for selected tools:\n');
1208
-
1209
- for (const toolId of this.selections.tools) {
1210
- const tool = this.tools.find(t => t.id === toolId);
1211
- console.log(`${colors.bright}${tool.name}${colors.reset}`);
1212
- console.log(`${colors.cyan}Default path:${colors.reset} ${tool.path}`);
1213
-
1214
- const customPath = await this.askQuestion(
1215
- `Enter path (or press Enter for default): `,
1216
- tool.path
1217
- );
1218
-
1219
- // Detect custom path
1220
- if (customPath !== tool.path) {
1221
- // Show custom path confirmation dialog
1222
- const confirmed = await this.showCustomPathConfirmation(tool, customPath);
1223
-
1224
- if (confirmed) {
1225
- this.selections.paths[toolId] = customPath;
1226
- console.log(`${colors.green}✓ Using custom path: ${customPath}${colors.reset}\n`);
1227
- } else {
1228
- this.selections.paths[toolId] = tool.path;
1229
- console.log(`${colors.blue}Using default path: ${tool.path}${colors.reset}\n`);
1230
- }
1231
- } else {
1232
- this.selections.paths[toolId] = tool.path;
1233
- console.log(`${colors.green}✓ Using default path${colors.reset}\n`);
1234
- }
1235
- }
1236
- }
1237
-
1238
- async showCustomPathConfirmation(tool, customPath) {
1239
- console.log(`\n${colors.yellow}┌─────────────────────────────────────────────────────────────┐${colors.reset}`);
1240
- console.log(`${colors.yellow}│ Custom Path Confirmation │${colors.reset}`);
1241
- console.log(`${colors.yellow}└─────────────────────────────────────────────────────────────┘${colors.reset}\n`);
1242
-
1243
- console.log(`${colors.bright}Tool:${colors.reset} ${tool.name}`);
1244
- console.log(`${colors.bright}Proposed custom path:${colors.reset} ${customPath}`);
1245
- console.log(`${colors.bright}Default path:${colors.reset} ${tool.path}\n`);
1246
-
1247
- // Validate the custom path
1248
- const validation = this.validatePath(customPath);
1249
-
1250
- // Display validation results
1251
- console.log(`${colors.cyan}Validation Results:${colors.reset}`);
1252
-
1253
- if (validation.valid) {
1254
- console.log(`${colors.green}✓ Path is valid${colors.reset}`);
1255
- if (validation.parentExists) {
1256
- console.log(`${colors.green}✓ Parent directory exists${colors.reset}`);
1257
- }
1258
- if (validation.hasPermission) {
1259
- console.log(`${colors.green}✓ Write permission available${colors.reset}`);
1260
- }
1261
- if (validation.hasDiskSpace) {
1262
- console.log(`${colors.green}✓ Sufficient disk space${colors.reset}`);
1263
- }
1264
- } else {
1265
- // Show validation warnings/errors
1266
- validation.issues.forEach(issue => {
1267
- const icon = issue.severity === 'error' ? '✗' : '⚠';
1268
- const color = issue.severity === 'error' ? colors.red : colors.yellow;
1269
- console.log(`${color}${icon} ${issue.message}${colors.reset}`);
1270
- });
1271
- }
1272
-
1273
- console.log('');
1274
-
1275
- // Require explicit confirmation
1276
- if (!validation.valid && validation.issues.some(i => i.severity === 'error')) {
1277
- console.log(`${colors.red}Cannot use this path due to validation errors${colors.reset}`);
1278
- console.log(`${colors.yellow}Press Enter to use default path instead${colors.reset}`);
1279
- await this.askQuestion('');
1280
- return false;
1281
- }
1282
-
1283
- // Ask for confirmation
1284
- const answer = await this.askQuestion(
1285
- `${colors.bright}Confirm custom path? (y/N):${colors.reset} `,
1286
- 'n'
1287
- );
1288
-
1289
- return answer.toLowerCase() === 'y';
1290
- }
1291
-
1292
- validatePath(customPath) {
1293
- const result = {
1294
- valid: true,
1295
- issues: [],
1296
- parentExists: false,
1297
- hasPermission: false,
1298
- hasDiskSpace: false
1299
- };
1300
-
1301
- // Expand tilde to home directory
1302
- const expandedPath = customPath.startsWith('~')
1303
- ? path.join(require('os').homedir(), customPath.slice(1))
1304
- : path.resolve(customPath);
1305
-
1306
- try {
1307
- // Check if path is absolute or can be resolved
1308
- if (!path.isAbsolute(expandedPath)) {
1309
- result.issues.push({
1310
- severity: 'error',
1311
- message: 'Path must be absolute'
1312
- });
1313
- result.valid = false;
1314
- }
1315
-
1316
- // Check parent directory exists
1317
- const parentDir = path.dirname(expandedPath);
1318
- if (fs.existsSync(parentDir)) {
1319
- result.parentExists = true;
1320
-
1321
- // Check write permission on parent directory
1322
- try {
1323
- fs.accessSync(parentDir, fs.constants.W_OK);
1324
- result.hasPermission = true;
1325
- } catch (err) {
1326
- result.issues.push({
1327
- severity: 'error',
1328
- message: 'No write permission for parent directory'
1329
- });
1330
- result.valid = false;
1331
- }
1332
-
1333
- // Check disk space (require at least 50MB free)
1334
- try {
1335
- const stats = fs.statfsSync ? fs.statfsSync(parentDir) : null;
1336
- if (stats) {
1337
- const availableSpace = stats.bavail * stats.bsize;
1338
- const requiredSpace = 50 * 1024 * 1024; // 50MB
1339
-
1340
- if (availableSpace >= requiredSpace) {
1341
- result.hasDiskSpace = true;
1342
- } else {
1343
- result.issues.push({
1344
- severity: 'warning',
1345
- message: `Low disk space (${Math.round(availableSpace / 1024 / 1024)}MB available, 50MB recommended)`
1346
- });
1347
- }
1348
- } else {
1349
- // Cannot check disk space on this platform
1350
- result.hasDiskSpace = true; // Assume OK
1351
- }
1352
- } catch (err) {
1353
- // Disk space check failed, but don't block installation
1354
- result.hasDiskSpace = true; // Assume OK
1355
- }
1356
- } else {
1357
- result.issues.push({
1358
- severity: 'warning',
1359
- message: 'Parent directory does not exist (will be created)'
1360
- });
1361
- // Still allow installation if parent can be created
1362
- result.parentExists = false;
1363
- result.hasPermission = true; // Assume OK if we can check grandparent
1364
- result.hasDiskSpace = true; // Assume OK
1365
- }
1366
-
1367
- // Check if path already exists
1368
- if (fs.existsSync(expandedPath)) {
1369
- result.issues.push({
1370
- severity: 'warning',
1371
- message: 'Path already exists (files may be overwritten)'
1372
- });
1373
- }
1374
-
1375
- } catch (err) {
1376
- result.issues.push({
1377
- severity: 'error',
1378
- message: `Path validation error: ${err.message}`
1379
- });
1380
- result.valid = false;
1381
- }
1382
-
1383
- return result;
1384
- }
1385
-
1386
- async showSummary() {
1387
- console.log(`\n${colors.bright}Step 4/4 — Installation Summary${colors.reset}\n`);
1388
-
1389
- const variant = this.variants.find(v => v.id === this.selections.variant);
1390
- console.log(`Package: ${variant.name} (${variant.agents} agents, ${variant.skills} skills)`);
1391
- console.log(`Tools: ${this.selections.tools.map(id =>
1392
- this.tools.find(t => t.id === id).name
1393
- ).join(', ')}\n`);
1394
-
1395
- console.log('Installation Details:');
1396
- console.log('┌─────────────┬──────────────────┬──────────┬─────────────┐');
1397
- console.log('│ Tool │ Path │ Files │ Size │');
1398
- console.log('├─────────────┼──────────────────┼──────────┼─────────────┤');
1399
-
1400
- // Collect actual file counts and sizes for each tool
1401
- let totalFiles = 0;
1402
- let totalBytes = 0;
1403
- const toolData = [];
1404
-
1405
- for (const toolId of this.selections.tools) {
1406
- const tool = this.tools.find(t => t.id === toolId);
1407
- const installPath = this.selections.paths[toolId];
1408
- const isCustom = installPath !== tool.path;
1409
-
1410
- try {
1411
- // Get actual package contents and size from PackageManager
1412
- const contents = await this.packageManager.getPackageContents(toolId, this.selections.variant);
1413
- const sizeInfo = await this.packageManager.getPackageSize(toolId, this.selections.variant);
1414
-
1415
- toolData.push({
1416
- tool,
1417
- path: installPath,
1418
- isCustom,
1419
- fileCount: contents.totalFiles,
1420
- size: sizeInfo.formattedSize
1421
- });
1422
-
1423
- totalFiles += contents.totalFiles;
1424
- totalBytes += sizeInfo.size;
1425
- } catch (error) {
1426
- // If package data not available, show placeholder
1427
- toolData.push({
1428
- tool,
1429
- path: installPath,
1430
- isCustom,
1431
- fileCount: 'N/A',
1432
- size: 'N/A'
1433
- });
1434
- }
1435
- }
1436
-
1437
- // Display tool rows with actual data
1438
- for (const data of toolData) {
1439
- const pathDisplay = data.isCustom ? `${data.path} *` : data.path;
1440
-
1441
- // Format file count and size for display
1442
- const fileCountStr = typeof data.fileCount === 'number' ? `${data.fileCount}` : data.fileCount;
1443
- const sizeStr = data.size;
1444
-
1445
- console.log(`│ ${data.tool.name.padEnd(11)} │ ${pathDisplay.padEnd(16)} │ ${fileCountStr.padEnd(8)} │ ${sizeStr.padEnd(11)} │`);
1446
- }
1447
-
1448
- console.log('└─────────────┴──────────────────┴──────────┴─────────────┘');
1449
-
1450
- // Show custom path footnote if applicable
1451
- if (toolData.some(d => d.isCustom)) {
1452
- console.log('\n* Custom path specified');
1453
- }
1454
-
1455
- // Show totals if we have data
1456
- if (totalFiles > 0) {
1457
- // Format total size
1458
- const totalSizeFormatted = this.formatBytes(totalBytes);
1459
- console.log(`\n${colors.cyan}Total:${colors.reset} ${totalFiles} files, ${totalSizeFormatted}`);
1460
- }
1461
-
1462
- console.log('\nPress Enter to install or Esc to cancel');
1463
-
1464
- return new Promise(resolve => {
1465
- this.rl.question('', resolve);
1466
- });
1467
- }
1468
-
1469
551
  /**
1470
552
  * Format bytes to human-readable size (helper method for summary display)
1471
553
  * @param {number} bytes - Size in bytes
@@ -1495,492 +577,133 @@ ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
1495
577
  *
1496
578
  * @returns {Promise<object>} Object with success flag, errors array, and warnings array
1497
579
  */
1498
- async performPreInstallationChecks() {
1499
- const errors = [];
1500
- const warnings = [];
1501
- const os = require('os');
1502
-
1503
- // Check Node.js version (require 14+)
1504
- const nodeVersion = process.version;
1505
- const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
1506
- if (majorVersion < 14) {
1507
- errors.push(`Node.js version ${nodeVersion} is too old. Please upgrade to Node.js 14 or higher.`);
1508
- }
1509
-
1510
- // Validate each selected tool's package
1511
- for (const toolId of this.selections.tools) {
1512
- try {
1513
- const validation = await this.packageManager.validatePackage(toolId, this.selections.variant);
1514
- if (!validation.valid) {
1515
- errors.push(`Package validation failed for ${toolId}: ${validation.error}`);
1516
- }
1517
- } catch (error) {
1518
- errors.push(`Cannot validate package for ${toolId}: ${error.message}`);
1519
- }
1520
- }
1521
-
1522
- // Validate all installation paths
1523
- for (const toolId of this.selections.tools) {
1524
- const targetPath = this.selections.paths[toolId];
1525
- const expandedPath = targetPath.startsWith('~')
1526
- ? path.join(os.homedir(), targetPath.slice(1))
1527
- : path.resolve(targetPath);
1528
-
1529
- // Check parent directory write permissions
1530
- const parentDir = path.dirname(expandedPath);
1531
- try {
1532
- if (fs.existsSync(parentDir)) {
1533
- fs.accessSync(parentDir, fs.constants.W_OK);
1534
- } else {
1535
- // Check if we can create parent directory
1536
- const grandParentDir = path.dirname(parentDir);
1537
- if (fs.existsSync(grandParentDir)) {
1538
- fs.accessSync(grandParentDir, fs.constants.W_OK);
1539
- } else {
1540
- errors.push(`Cannot create installation path for ${toolId}: parent directories do not exist`);
1541
- }
1542
- }
1543
- } catch (error) {
1544
- errors.push(`No write permission for ${toolId} installation path: ${targetPath}`);
1545
- }
1546
-
1547
- // Check if path already exists and has content
1548
- if (fs.existsSync(expandedPath)) {
1549
- try {
1550
- const files = fs.readdirSync(expandedPath);
1551
- if (files.length > 0) {
1552
- warnings.push(`${toolId} installation path already exists and contains ${files.length} file(s). Existing installation will be backed up.`);
1553
- }
1554
- } catch (error) {
1555
- warnings.push(`Cannot read existing installation directory for ${toolId}`);
1556
- }
1557
- }
1558
- }
1559
-
1560
- // Check available disk space
1561
- try {
1562
- // Calculate total required space for all selected tools
1563
- let totalRequiredSpace = 0;
1564
- for (const toolId of this.selections.tools) {
1565
- try {
1566
- const sizeInfo = await this.packageManager.getPackageSize(toolId, this.selections.variant);
1567
- totalRequiredSpace += sizeInfo.size;
1568
- } catch (error) {
1569
- // Skip if we can't determine size
1570
- }
1571
- }
1572
-
1573
- // Check disk space on home directory
1574
- const homeDir = os.homedir();
1575
- if (fs.statfsSync) {
1576
- const stats = fs.statfsSync(homeDir);
1577
- const availableSpace = stats.bavail * stats.bsize;
1578
- const requiredSpace = totalRequiredSpace * 1.5; // 50% buffer for safety
1579
-
1580
- if (availableSpace < requiredSpace) {
1581
- const availableMB = Math.round(availableSpace / 1024 / 1024);
1582
- const requiredMB = Math.round(requiredSpace / 1024 / 1024);
1583
- errors.push(`Insufficient disk space: ${availableMB}MB available, ${requiredMB}MB required`);
1584
- } else if (availableSpace < totalRequiredSpace * 2) {
1585
- const availableMB = Math.round(availableSpace / 1024 / 1024);
1586
- warnings.push(`Low disk space: ${availableMB}MB available. Consider freeing up space.`);
1587
- }
1588
- }
1589
- } catch (error) {
1590
- // Disk space check not available on this platform
1591
- warnings.push('Could not check disk space (platform limitation)');
1592
- }
1593
-
1594
- // Check for conflicting installations
1595
- for (const toolId of this.selections.tools) {
1596
- const targetPath = this.selections.paths[toolId];
1597
- const expandedPath = targetPath.startsWith('~')
1598
- ? path.join(os.homedir(), targetPath.slice(1))
1599
- : path.resolve(targetPath);
1600
-
1601
- const manifestPath = path.join(expandedPath, 'manifest.json');
1602
- if (fs.existsSync(manifestPath)) {
1603
- try {
1604
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
1605
- if (manifest.tool !== toolId) {
1606
- warnings.push(`Path ${targetPath} contains a different tool (${manifest.tool}). This may cause conflicts.`);
1607
- }
1608
- } catch (error) {
1609
- warnings.push(`Cannot read existing manifest at ${targetPath}`);
1610
- }
1611
- }
1612
- }
1613
-
1614
- return {
1615
- success: errors.length === 0,
1616
- errors,
1617
- warnings
1618
- };
1619
- }
1620
-
1621
580
  async install() {
1622
- console.log(`\n${colors.bright}Installing ${this.selections.variant} package...${colors.reset}\n`);
1623
-
1624
- // Perform pre-installation checks
1625
- console.log(`${colors.cyan}Performing pre-installation checks...${colors.reset}`);
1626
- const preCheckResult = await this.performPreInstallationChecks();
1627
-
1628
- if (!preCheckResult.success) {
1629
- console.log(`\n${colors.red}Pre-installation checks failed:${colors.reset}`);
1630
- preCheckResult.errors.forEach(error => {
1631
- console.log(` ${colors.red}✗${colors.reset} ${error}`);
1632
- });
1633
-
1634
- if (preCheckResult.warnings.length > 0) {
1635
- console.log(`\n${colors.yellow}Warnings:${colors.reset}`);
1636
- preCheckResult.warnings.forEach(warning => {
1637
- console.log(` ${colors.yellow}⚠${colors.reset} ${warning}`);
1638
- });
1639
- }
1640
-
1641
- throw new Error('Pre-installation checks failed. Please resolve the issues above and try again.');
1642
- }
1643
-
1644
- if (preCheckResult.warnings.length > 0) {
1645
- console.log(`${colors.yellow}Warnings detected:${colors.reset}`);
1646
- preCheckResult.warnings.forEach(warning => {
1647
- console.log(` ${colors.yellow}⚠${colors.reset} ${warning}`);
1648
- });
1649
-
1650
- // In silent mode, auto-proceed with warnings
1651
- if (!this.cliArgs.silent) {
1652
- const proceed = await this.askQuestion(
1653
- `\n${colors.bright}Continue despite warnings? (Y/n):${colors.reset} `,
1654
- 'Y'
1655
- );
1656
-
1657
- if (proceed.toLowerCase() === 'n') {
1658
- throw new Error('Installation cancelled by user due to warnings');
1659
- }
1660
- } else {
1661
- console.log(`${colors.yellow}Silent mode: auto-proceeding despite warnings${colors.reset}`);
1662
- }
1663
- }
1664
-
1665
- console.log(`${colors.green}✓ Pre-installation checks passed${colors.reset}\n`);
1666
-
1667
581
  // Initialize InstallationEngine
1668
582
  const PathManager = require('./path-manager');
1669
583
  const InstallationEngine = require('./installation-engine');
584
+ const os = require('os');
1670
585
 
1671
586
  const pathManager = new PathManager();
1672
587
  const installationEngine = new InstallationEngine(pathManager, this.packageManager);
1673
588
 
1674
- // Initialize state management for resume capability
1675
- installationEngine.getStateManager().initializeState(
1676
- this.selections.variant,
1677
- this.selections.tools,
1678
- this.selections.paths
1679
- );
1680
- await installationEngine.getStateManager().saveState({ stage: 'initializing' });
1681
-
1682
- // Track overall progress
1683
- let overallFilesCompleted = 0;
1684
- let overallTotalFiles = 0;
1685
- let successfulInstalls = [];
1686
- let failedInstalls = [];
1687
- let verificationResults = [];
1688
-
1689
- // Calculate total files across all tools (only for valid tools)
1690
- const toolFileCount = {};
1691
- for (const toolId of this.selections.tools) {
1692
- try {
1693
- const contents = await this.packageManager.getPackageContents(toolId, this.selections.variant);
1694
- toolFileCount[toolId] = contents.totalFiles;
1695
- overallTotalFiles += contents.totalFiles;
1696
- } catch (error) {
1697
- // Skip tools that don't have valid packages
1698
- toolFileCount[toolId] = 0;
1699
- }
1700
- }
1701
-
1702
- const startTime = Date.now();
1703
-
1704
589
  // Install each selected tool
1705
590
  for (let i = 0; i < this.selections.tools.length; i++) {
1706
591
  const toolId = this.selections.tools[i];
1707
592
  const tool = this.tools.find(t => t.id === toolId);
1708
593
  const targetPath = this.selections.paths[toolId];
1709
594
 
1710
- console.log(`\n${colors.bright}[${i + 1}/${this.selections.tools.length}] Installing ${tool.name}...${colors.reset}`);
1711
- console.log(`${colors.cyan}Target:${colors.reset} ${targetPath}\n`);
1712
-
1713
- // Track files completed for this tool
1714
- let toolFilesCompleted = 0;
1715
- let toolStartTime = Date.now();
595
+ console.log(`\n${colors.bright}${colors.cyan}Installing ${tool.name}...${colors.reset}`);
1716
596
 
1717
597
  try {
1718
- // 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
1719
611
  await installationEngine.installPackage(
1720
612
  toolId,
1721
- this.selections.variant,
613
+ 'pro', // Always pro variant
1722
614
  targetPath,
1723
- (progress) => {
1724
- // Update tool files completed (only count new files)
1725
- if (progress.filesCompleted > toolFilesCompleted) {
1726
- const newFiles = progress.filesCompleted - toolFilesCompleted;
1727
- overallFilesCompleted += newFiles;
1728
- toolFilesCompleted = progress.filesCompleted;
1729
- }
1730
-
1731
- // Calculate elapsed time and speed
1732
- const elapsedMs = Date.now() - toolStartTime;
1733
- const elapsedSec = Math.floor(elapsedMs / 1000);
1734
- const elapsedMin = Math.floor(elapsedSec / 60);
1735
- const elapsedSecRemainder = elapsedSec % 60;
1736
- const elapsedFormatted = `${elapsedMin}:${elapsedSecRemainder.toString().padStart(2, '0')}`;
1737
-
1738
- // Calculate transfer speed (bytes per second)
1739
- const speed = elapsedMs > 0 ? progress.bytesTransferred / (elapsedMs / 1000) : 0;
1740
- const speedFormatted = this.formatBytes(speed);
1741
-
1742
- // Calculate ETA
1743
- const remainingBytes = progress.totalBytes - progress.bytesTransferred;
1744
- const etaSec = speed > 0 ? Math.floor(remainingBytes / speed) : 0;
1745
- const etaMin = Math.floor(etaSec / 60);
1746
- const etaSecRemainder = etaSec % 60;
1747
- const etaFormatted = `${etaMin}:${etaSecRemainder.toString().padStart(2, '0')}`;
1748
-
1749
- // Draw progress bar for current tool
1750
- this.drawProgressBar(
1751
- progress.filesCompleted,
1752
- progress.totalFiles,
1753
- progress.percentage,
1754
- progress.currentFile,
1755
- this.formatBytes(progress.bytesTransferred),
1756
- this.formatBytes(progress.totalBytes),
1757
- speedFormatted,
1758
- elapsedFormatted,
1759
- etaFormatted
1760
- );
1761
-
1762
- // Draw overall progress
1763
- // Cap overall percentage at 100% to handle file count mismatches
1764
- const overallPercentage = overallTotalFiles > 0
1765
- ? Math.min(100, Math.round((overallFilesCompleted / overallTotalFiles) * 100))
1766
- : 0;
1767
-
1768
- this.drawOverallProgress(
1769
- overallFilesCompleted,
1770
- overallTotalFiles,
1771
- overallPercentage,
1772
- i + 1,
1773
- this.selections.tools.length
1774
- );
1775
- }
615
+ null // No progress callback
1776
616
  );
1777
617
 
1778
- // Clear progress lines after completion
1779
- process.stdout.write('\x1b[2K\r'); // Clear current line
1780
- process.stdout.write('\x1b[1A\x1b[2K\r'); // Clear previous line
1781
-
1782
- console.log(`${colors.green}✓ ${tool.name} installed successfully${colors.reset}`);
1783
- console.log(` ${colors.cyan}Location:${colors.reset} ${targetPath}`);
1784
- 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
1785
621
 
1786
- // Verify installation
1787
- console.log(` ${colors.cyan}Verifying installation...${colors.reset}`);
1788
- 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;
1789
626
 
1790
- if (verification.valid) {
1791
- console.log(` ${colors.green}✓ Verification passed${colors.reset}`);
1792
- } else {
1793
- 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';
1794
658
  }
1795
659
 
1796
- successfulInstalls.push({
1797
- toolId,
1798
- name: tool.name,
1799
- path: targetPath,
1800
- fileCount: toolFilesCompleted
1801
- });
1802
-
1803
- verificationResults.push(verification);
660
+ // Check for skills (count directories)
661
+ const skillsCount = countItems(path.join(expandedPath, 'skills'));
1804
662
 
1805
- // Mark tool as completed in state
1806
- 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';
1807
667
 
1808
- } catch (error) {
1809
- // Clear progress lines on error
1810
- process.stdout.write('\x1b[2K\r');
1811
- process.stdout.write('\x1b[1A\x1b[2K\r');
1812
-
1813
- // Categorize the error for better user guidance
1814
- const errorInfo = this.categorizeError(error);
1815
-
1816
- console.error(`${colors.red}✗ Failed to install ${tool.name}${colors.reset}`);
1817
- console.error(` ${colors.red}Error Type:${colors.reset} ${errorInfo.type}`);
1818
- console.error(` ${colors.red}Message:${colors.reset} ${error.message}\n`);
1819
-
1820
- // Display actionable advice
1821
- if (errorInfo.advice.length > 0) {
1822
- console.error(` ${colors.yellow}Suggested Actions:${colors.reset}`);
1823
- errorInfo.advice.slice(0, 3).forEach((advice, index) => {
1824
- console.error(` ${index + 1}. ${advice}`);
1825
- });
1826
- 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}`);
1827
672
  }
1828
673
 
1829
- failedInstalls.push({
1830
- toolId,
1831
- name: tool.name,
1832
- path: targetPath,
1833
- error: error.message,
1834
- errorType: errorInfo.type
1835
- });
1836
-
1837
- // Mark tool as failed in state
1838
- await installationEngine.getStateManager().failCurrentTool(error);
1839
-
1840
- // Offer recovery options if there are more tools to install
1841
- if (i < this.selections.tools.length - 1) {
1842
- const shouldContinue = await this.offerRecoveryOptions(tool.name, i + 1, this.selections.tools.length);
1843
-
1844
- if (!shouldContinue) {
1845
- console.log(`\n${colors.yellow}Installation cancelled by user${colors.reset}`);
1846
- break; // Stop installing remaining tools
1847
- }
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}`);
1848
683
  }
1849
- }
1850
- }
1851
-
1852
- // Calculate total elapsed time
1853
- const totalElapsedMs = Date.now() - startTime;
1854
- const totalElapsedSec = Math.floor(totalElapsedMs / 1000);
1855
- const totalElapsedMin = Math.floor(totalElapsedSec / 60);
1856
- const totalElapsedSecRemainder = totalElapsedSec % 60;
1857
- const totalElapsedFormatted = `${totalElapsedMin}:${totalElapsedSecRemainder.toString().padStart(2, '0')}`;
1858
-
1859
- // Display final summary
1860
- console.log(`\n${colors.bright}Installation Complete${colors.reset}`);
1861
- console.log(`${colors.cyan}Total time:${colors.reset} ${totalElapsedFormatted}`);
1862
- console.log(`${colors.cyan}Total files:${colors.reset} ${overallFilesCompleted} files\n`);
1863
-
1864
- if (successfulInstalls.length > 0) {
1865
- console.log(`${colors.green}Successfully installed:${colors.reset}`);
1866
- for (const install of successfulInstalls) {
1867
- console.log(` ${colors.green}✓${colors.reset} ${install.name} (${install.fileCount} files)`);
1868
- }
1869
- }
1870
-
1871
- if (failedInstalls.length > 0) {
1872
- console.log(`\n${colors.red}Failed installations:${colors.reset}`);
1873
- for (const install of failedInstalls) {
1874
- console.log(` ${colors.red}✗${colors.reset} ${install.name} (${install.errorType}): ${install.error}`);
1875
- }
1876
- console.log(`\n${colors.yellow}Note: Failed installations have been automatically rolled back${colors.reset}`);
1877
- console.log(`${colors.yellow}No partial installations remain on your system${colors.reset}`);
1878
-
1879
- // Offer retry options for failed installations
1880
- if (failedInstalls.length > 0 && successfulInstalls.length > 0) {
1881
- console.log(`\n${colors.cyan}You can retry failed installations by running the installer again${colors.reset}`);
1882
- }
1883
- }
1884
684
 
1885
- // Display detailed verification reports for successful installations
1886
- if (successfulInstalls.length > 0 && verificationResults.length > 0) {
1887
- console.log(`\n${colors.bright}${'='.repeat(60)}${colors.reset}`);
1888
- for (let i = 0; i < successfulInstalls.length; i++) {
1889
- const install = successfulInstalls[i];
1890
- const verification = verificationResults[i];
1891
- 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()
1892
688
  }
1893
- console.log(`${colors.bright}${'='.repeat(60)}${colors.reset}\n`);
1894
- }
1895
-
1896
- // Generate and save installation report
1897
- if (successfulInstalls.length > 0 || failedInstalls.length > 0) {
1898
- await this.generateInstallationReport(
1899
- successfulInstalls,
1900
- failedInstalls,
1901
- verificationResults,
1902
- totalElapsedMs
1903
- );
1904
- }
1905
-
1906
- // Clear installation state if all tools completed successfully
1907
- if (failedInstalls.length === 0) {
1908
- await installationEngine.getStateManager().clearState();
1909
- console.log(`${colors.green}✓ Installation state cleared${colors.reset}`);
1910
- } else {
1911
- console.log(`\n${colors.yellow}Installation state preserved for resume capability${colors.reset}`);
1912
- console.log(`${colors.yellow}Run the installer again to retry failed tools${colors.reset}`);
1913
689
  }
1914
690
 
1915
- // Collect telemetry data (if user has consented)
1916
- await this.collectInstallationTelemetry(
1917
- failedInstalls.length === 0,
1918
- this.selections.tools.length,
1919
- failedInstalls.length,
1920
- 0, // warnings are tracked in verification
1921
- totalElapsedMs
1922
- );
691
+ console.log(`\n${colors.bright}${colors.green}Done!${colors.reset}`);
1923
692
  }
1924
693
 
1925
694
  /**
1926
695
  * Draw progress bar for current tool installation
1927
696
  * Updates in place without scrolling using ANSI escape codes
1928
697
  */
1929
- drawProgressBar(filesCompleted, totalFiles, percentage, currentFile, bytesTransferred, totalBytes, speed, elapsed, eta) {
1930
- // Move cursor to beginning of line and clear it
1931
- process.stdout.write('\x1b[2K\r');
1932
-
1933
- // Calculate progress bar width (40 characters)
698
+ drawProgressBar(filesCompleted, totalFiles, percentage, currentFile) {
1934
699
  const barWidth = 40;
1935
700
  const filledWidth = Math.round((percentage / 100) * barWidth);
1936
- const emptyWidth = barWidth - filledWidth;
1937
-
1938
- // Build progress bar
1939
- const bar = '█'.repeat(filledWidth) + '░'.repeat(emptyWidth);
701
+ const bar = '#'.repeat(filledWidth) + '-'.repeat(barWidth - filledWidth);
1940
702
 
1941
- // Truncate current file name if too long
1942
- const maxFileNameLength = 50;
1943
- let displayFileName = currentFile;
1944
- if (displayFileName.length > maxFileNameLength) {
1945
- displayFileName = '...' + displayFileName.slice(-(maxFileNameLength - 3));
1946
- }
1947
-
1948
- // Display progress bar
1949
- process.stdout.write(
1950
- `[${colors.cyan}${bar}${colors.reset}] ${percentage}% (${filesCompleted}/${totalFiles} files)\n`
1951
- );
1952
-
1953
- // Display current file and stats
1954
- process.stdout.write(
1955
- `${colors.yellow}Copying:${colors.reset} ${displayFileName} | ` +
1956
- `${bytesTransferred}/${totalBytes} | ` +
1957
- `${speed}/s | ` +
1958
- `Elapsed: ${elapsed} | ` +
1959
- `ETA: ${eta}`
1960
- );
1961
-
1962
- // Move cursor up one line to overwrite on next update
1963
- process.stdout.write('\x1b[1A');
703
+ // Single line, update in place
704
+ process.stdout.write(`\r[${bar}] ${percentage}% (${filesCompleted}/${totalFiles} files)`);
1964
705
  }
1965
706
 
1966
- /**
1967
- * Draw overall progress across all tools
1968
- */
1969
- drawOverallProgress(filesCompleted, totalFiles, percentage, currentTool, totalTools) {
1970
- // This is called after the tool progress, so cursor is already up one line
1971
- // Move down to write overall progress below the tool progress
1972
- process.stdout.write('\x1b[2B'); // Move down 2 lines
1973
- process.stdout.write('\x1b[2K\r'); // Clear line
1974
-
1975
- // Display overall progress
1976
- process.stdout.write(
1977
- `${colors.bright}Overall:${colors.reset} Tool ${currentTool}/${totalTools} | ` +
1978
- `${filesCompleted}/${totalFiles} files (${percentage}%)`
1979
- );
1980
-
1981
- // Move cursor back up
1982
- process.stdout.write('\x1b[1A'); // Move up 1 line to be ready for next tool progress update
1983
- }
1984
707
 
1985
708
  askQuestion(prompt, defaultValue = '') {
1986
709
  return new Promise(resolve => {
@@ -1997,79 +720,6 @@ ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
1997
720
  * @param {object} verification - Verification result from InstallationEngine
1998
721
  * @param {string} toolName - Display name of the tool
1999
722
  */
2000
- displayVerificationReport(verification, toolName) {
2001
- console.log(`\n${colors.bright}Verification Report: ${toolName}${colors.reset}`);
2002
- console.log('─'.repeat(60));
2003
-
2004
- if (verification.valid) {
2005
- console.log(`${colors.green}✓ Installation verified successfully${colors.reset}`);
2006
- } else {
2007
- console.log(`${colors.red}✗ Verification failed${colors.reset}`);
2008
- }
2009
-
2010
- // Display manifest location
2011
- const manifestPath = path.join(verification.targetPath, 'manifest.json');
2012
- console.log(`\n${colors.cyan}Manifest:${colors.reset} ${manifestPath}`);
2013
-
2014
- // Display variant and version info
2015
- if (verification.variant) {
2016
- console.log(`${colors.cyan}Variant:${colors.reset} ${verification.variant}`);
2017
- }
2018
- if (verification.version) {
2019
- console.log(`${colors.cyan}Version:${colors.reset} ${verification.version}`);
2020
- }
2021
-
2022
- // Display component counts
2023
- console.log(`\n${colors.cyan}Components:${colors.reset}`);
2024
- const components = verification.components;
2025
- const agentCount = components.agents.found;
2026
- const skillCount = components.skills.found;
2027
- const resourceCount = components.resources.found;
2028
- const hookCount = components.hooks.found;
2029
-
2030
- const componentSummary = [];
2031
- if (agentCount > 0) componentSummary.push(`${agentCount} agent${agentCount !== 1 ? 's' : ''}`);
2032
- if (skillCount > 0) componentSummary.push(`${skillCount} skill${skillCount !== 1 ? 's' : ''}`);
2033
- if (resourceCount > 0) componentSummary.push(`${resourceCount} resource${resourceCount !== 1 ? 's' : ''}`);
2034
- if (hookCount > 0) componentSummary.push(`${hookCount} hook${hookCount !== 1 ? 's' : ''}`);
2035
-
2036
- console.log(` ${componentSummary.join(', ')}`);
2037
-
2038
- // Display issues if any
2039
- if (verification.issues.length > 0) {
2040
- console.log(`\n${colors.red}Issues:${colors.reset}`);
2041
- for (const issue of verification.issues) {
2042
- console.log(` ${colors.red}✗${colors.reset} ${issue.message}`);
2043
- }
2044
- }
2045
-
2046
- // Display warnings if any
2047
- if (verification.warnings.length > 0) {
2048
- console.log(`\n${colors.yellow}Warnings:${colors.reset}`);
2049
- for (const warning of verification.warnings) {
2050
- console.log(` ${colors.yellow}⚠${colors.reset} ${warning.message}`);
2051
- }
2052
- }
2053
-
2054
- // Display next steps for successful installations
2055
- if (verification.valid) {
2056
- console.log(`\n${colors.bright}Next Steps:${colors.reset}`);
2057
- console.log(` To use ${toolName}, navigate to: ${verification.targetPath}`);
2058
-
2059
- // Provide tool-specific usage hints
2060
- const toolId = verification.toolId;
2061
- if (toolId === 'claude') {
2062
- console.log(` ${colors.cyan}Quick start:${colors.reset} Run 'claude' to start using Claude Code`);
2063
- } else if (toolId === 'opencode') {
2064
- console.log(` ${colors.cyan}Quick start:${colors.reset} Run 'opencode' to start using Opencode`);
2065
- } else if (toolId === 'ampcode') {
2066
- console.log(` ${colors.cyan}Quick start:${colors.reset} Run 'ampcode' to start using Ampcode`);
2067
- } else if (toolId === 'droid') {
2068
- console.log(` ${colors.cyan}Quick start:${colors.reset} Run 'droid' to start using Droid`);
2069
- }
2070
- }
2071
- }
2072
-
2073
723
  /**
2074
724
  * Generate and save installation report to ~/.agentic-kit-install.log
2075
725
  * Creates a detailed log of the installation session
@@ -2079,153 +729,12 @@ ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
2079
729
  * @param {array} verificationResults - Array of verification result objects
2080
730
  * @param {number} totalElapsedMs - Total elapsed time in milliseconds
2081
731
  */
2082
- async generateInstallationReport(successfulInstalls, failedInstalls, verificationResults, totalElapsedMs) {
2083
- const ReportTemplate = require('./report-template');
2084
- const reportTemplate = new ReportTemplate();
2085
-
2086
- // Prepare installation data for the report template
2087
- const startTime = Date.now() - totalElapsedMs;
2088
- const endTime = Date.now();
2089
-
2090
- // Build tools array with complete information
2091
- const tools = [];
2092
- const allErrors = [];
2093
- const allWarnings = [];
2094
-
2095
- // Process successful installations
2096
- for (let i = 0; i < successfulInstalls.length; i++) {
2097
- const install = successfulInstalls[i];
2098
- const verification = verificationResults[i];
2099
-
2100
- // Get package size information
2101
- let sizeBytes = 0;
2102
- try {
2103
- const sizeInfo = await this.packageManager.getPackageSize(install.toolId, this.selections.variant);
2104
- sizeBytes = sizeInfo.bytes || 0;
2105
- } catch (error) {
2106
- // If we can't get size, estimate based on file count (rough estimate: 15KB per file)
2107
- sizeBytes = install.fileCount * 15 * 1024;
2108
- }
2109
-
2110
- // Get manifest path
2111
- const manifestPath = path.join(install.path, 'manifest.json');
2112
-
2113
- tools.push({
2114
- toolId: install.toolId,
2115
- path: install.path,
2116
- filesInstalled: install.fileCount,
2117
- sizeBytes: sizeBytes,
2118
- components: verification && verification.components ? {
2119
- agents: verification.components.agents.found,
2120
- skills: verification.components.skills.found,
2121
- resources: verification.components.resources.found,
2122
- hooks: verification.components.hooks.found
2123
- } : {},
2124
- verified: verification ? verification.valid : false,
2125
- verificationStatus: verification && verification.valid ? 'All components verified successfully' : 'Verification completed with issues',
2126
- manifestPath: manifestPath
2127
- });
2128
-
2129
- // Collect warnings from verification
2130
- if (verification && verification.warnings && verification.warnings.length > 0) {
2131
- verification.warnings.forEach(warning => {
2132
- allWarnings.push(`[${install.toolId}] ${warning.message}`);
2133
- });
2134
- }
2135
-
2136
- // Collect issues from verification as errors
2137
- if (verification && verification.issues && verification.issues.length > 0) {
2138
- verification.issues.forEach(issue => {
2139
- allErrors.push(`[${install.toolId}] ${issue.message}`);
2140
- });
2141
- }
2142
- }
2143
-
2144
- // Process failed installations
2145
- for (const install of failedInstalls) {
2146
- allErrors.push(`[${install.toolId}] Installation failed: ${install.error}`);
2147
- }
2148
-
2149
- // Build installation data object
2150
- const installationData = {
2151
- variant: this.selections.variant,
2152
- tools: tools,
2153
- startTime: startTime,
2154
- endTime: endTime,
2155
- success: failedInstalls.length === 0,
2156
- errors: allErrors,
2157
- warnings: allWarnings
2158
- };
2159
-
2160
- // Generate and save report using ReportTemplate
2161
- try {
2162
- const reportPath = await reportTemplate.createAndSaveReport(installationData);
2163
- console.log(`\n${colors.cyan}Installation report saved to:${colors.reset} ${reportPath}`);
2164
- } catch (error) {
2165
- console.warn(`${colors.yellow}Warning: Could not save installation report: ${error.message}${colors.reset}`);
2166
- }
2167
- }
2168
-
2169
732
  /**
2170
733
  * Prompt user for telemetry consent
2171
734
  * Only prompts if consent hasn't been set before and --no-telemetry flag not present
2172
735
  *
2173
736
  * @returns {Promise<void>}
2174
737
  */
2175
- async promptTelemetryConsent() {
2176
- // Skip if --no-telemetry flag is present
2177
- if (this.cliArgs.noTelemetry) {
2178
- return;
2179
- }
2180
-
2181
- // Skip if in silent mode
2182
- if (this.cliArgs.silent) {
2183
- return;
2184
- }
2185
-
2186
- const Telemetry = require('./telemetry');
2187
- const telemetry = new Telemetry();
2188
-
2189
- // Check if user has already made a decision
2190
- const hasConsent = await telemetry.hasConsent();
2191
- const hasOptedOut = await telemetry.hasOptedOut();
2192
-
2193
- // Only prompt if user hasn't decided yet
2194
- if (!hasConsent && !hasOptedOut) {
2195
- console.log(`\n${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}`);
2196
- console.log(`${colors.bright}Help Improve Agentic Kit${colors.reset}\n`);
2197
- console.log('Would you like to share anonymous usage statistics to help improve agentic-kit?');
2198
- console.log('\nData collected:');
2199
- console.log(' • Package variant selected (lite/standard/pro)');
2200
- console.log(' • Number of tools installed');
2201
- console.log(' • Installation time and success status');
2202
- console.log(' • Operating system type');
2203
- console.log(' • Node.js version');
2204
- console.log('\nData NOT collected:');
2205
- console.log(' • File paths or directory locations');
2206
- console.log(' • Personal information');
2207
- console.log(' • Specific tool names');
2208
- console.log(' • Any identifying information');
2209
- console.log('\nYou can change this setting later or opt-out anytime.');
2210
- console.log(`For details, see: ${colors.cyan}docs/PRIVACY.md${colors.reset}`);
2211
- console.log(`${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}\n`);
2212
-
2213
- const answer = await this.askQuestion(
2214
- `${colors.bright}Share anonymous usage data? (y/N):${colors.reset} `,
2215
- 'N'
2216
- );
2217
-
2218
- const consent = answer.toLowerCase() === 'y';
2219
- await telemetry.setConsent(consent);
2220
-
2221
- if (consent) {
2222
- console.log(`${colors.green}✓ Thank you! Anonymous usage statistics enabled${colors.reset}\n`);
2223
- } else {
2224
- console.log(`${colors.yellow}Usage statistics disabled${colors.reset}\n`);
2225
- }
2226
- }
2227
- }
2228
-
2229
738
  /**
2230
739
  * Collect and send telemetry data for installation
2231
740
  *
@@ -2236,28 +745,6 @@ ${colors.yellow}Press Enter to begin or Ctrl+C to exit${colors.reset}
2236
745
  * @param {number} installationTime - Installation time in milliseconds
2237
746
  * @returns {Promise<void>}
2238
747
  */
2239
- async collectInstallationTelemetry(success, toolCount, errorCount, warningCount, installationTime) {
2240
- // Skip if --no-telemetry flag is present
2241
- if (this.cliArgs.noTelemetry) {
2242
- return;
2243
- }
2244
-
2245
- const Telemetry = require('./telemetry');
2246
- const telemetry = new Telemetry();
2247
-
2248
- try {
2249
- await telemetry.collectInstallationStats({
2250
- variant: this.selections.variant,
2251
- toolCount: toolCount,
2252
- installationTime: installationTime,
2253
- success: success,
2254
- errorCount: errorCount,
2255
- warningCount: warningCount
2256
- });
2257
- } catch (error) {
2258
- // Silently fail - don't interrupt user experience for telemetry issues
2259
- }
2260
- }
2261
748
  }
2262
749
 
2263
750
  // Run installer if called directly