humanbehavior-js 0.7.0 → 0.8.0-beta.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.
Files changed (39) hide show
  1. package/package.json +1 -1
  2. package/packages/browser/dist/cjs/index.js +6 -6
  3. package/packages/browser/dist/cjs/index.js.map +1 -1
  4. package/packages/browser/dist/esm/index.js +4 -4
  5. package/packages/browser/dist/esm/index.js.map +1 -1
  6. package/packages/browser/dist/index.min.js +1 -1
  7. package/packages/browser/dist/index.min.js.map +1 -1
  8. package/packages/core/dist/index.js +1 -1
  9. package/packages/core/dist/index.js.map +1 -1
  10. package/packages/core/dist/index.mjs +1 -1
  11. package/packages/core/dist/index.mjs.map +1 -1
  12. package/packages/core/dist/tracker.d.ts +12 -0
  13. package/packages/core/dist/tracker.d.ts.map +1 -1
  14. package/packages/react/dist/index.js +1 -1
  15. package/packages/react/dist/index.js.map +1 -1
  16. package/packages/react/dist/index.mjs +1 -1
  17. package/packages/react/dist/index.mjs.map +1 -1
  18. package/packages/wizard/dist/agent/claude-agent-installer.d.ts +17 -0
  19. package/packages/wizard/dist/agent/claude-agent-installer.d.ts.map +1 -0
  20. package/packages/wizard/dist/ai/ai-install-wizard.d.ts +2 -2
  21. package/packages/wizard/dist/ai/ai-install-wizard.d.ts.map +1 -1
  22. package/packages/wizard/dist/ai/manual-framework-wizard.d.ts +2 -2
  23. package/packages/wizard/dist/ai/manual-framework-wizard.d.ts.map +1 -1
  24. package/packages/wizard/dist/cli/ai-auto-install.d.ts +6 -0
  25. package/packages/wizard/dist/cli/ai-auto-install.d.ts.map +1 -1
  26. package/packages/wizard/dist/cli/ai-auto-install.js +641 -81
  27. package/packages/wizard/dist/cli/ai-auto-install.js.map +1 -1
  28. package/packages/wizard/dist/cli/auto-install.d.ts +4 -0
  29. package/packages/wizard/dist/cli/auto-install.d.ts.map +1 -1
  30. package/packages/wizard/dist/cli/auto-install.js +314 -56
  31. package/packages/wizard/dist/cli/auto-install.js.map +1 -1
  32. package/packages/wizard/dist/core/install-wizard.d.ts +51 -3
  33. package/packages/wizard/dist/core/install-wizard.d.ts.map +1 -1
  34. package/packages/wizard/dist/index.d.ts +2 -0
  35. package/packages/wizard/dist/index.d.ts.map +1 -1
  36. package/packages/wizard/dist/index.js +1 -1
  37. package/packages/wizard/dist/index.js.map +1 -1
  38. package/packages/wizard/dist/index.mjs +1 -1
  39. package/packages/wizard/dist/index.mjs.map +1 -1
@@ -9,9 +9,12 @@
9
9
  */
10
10
  interface CLIOptions {
11
11
  apiKey?: string;
12
+ apiKeyFile?: string;
13
+ reportJson?: string;
12
14
  projectPath?: string;
13
15
  yes?: boolean;
14
16
  dryRun?: boolean;
17
+ upgrade?: boolean;
15
18
  }
16
19
  declare class AutoInstallCLI {
17
20
  private options;
@@ -20,6 +23,7 @@ declare class AutoInstallCLI {
20
23
  private getApiKey;
21
24
  private confirmInstallation;
22
25
  private displayResults;
26
+ private writeReport;
23
27
  }
24
28
  export { AutoInstallCLI };
25
29
  //# sourceMappingURL=auto-install.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auto-install.d.ts","sourceRoot":"","sources":["../../src/cli/auto-install.ts"],"names":[],"mappings":";AAEA;;;;;;;GAOG;AAOH,UAAU,UAAU;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,cAAM,cAAc;IAClB,OAAO,CAAC,OAAO,CAAa;gBAEhB,OAAO,EAAE,UAAU;IAIzB,GAAG;YAyCK,SAAS;YAsBT,mBAAmB;IAQjC,OAAO,CAAC,cAAc;CA4BvB;AAsED,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"auto-install.d.ts","sourceRoot":"","sources":["../../src/cli/auto-install.ts"],"names":[],"mappings":";AAEA;;;;;;;GAOG;AAOH,UAAU,UAAU;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,cAAM,cAAc;IAClB,OAAO,CAAC,OAAO,CAAa;gBAEhB,OAAO,EAAE,UAAU;IAIzB,GAAG;YA6CK,SAAS;YA8BT,mBAAmB;IAQjC,OAAO,CAAC,cAAc;IA+CtB,OAAO,CAAC,WAAW;CAoBpB;AAkFD,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -10,12 +10,18 @@ import * as clack from '@clack/prompts';
10
10
  * This wizard automatically detects the user's framework and modifies their codebase
11
11
  * to integrate the SDK with minimal user intervention.
12
12
  */
13
+ const MIN_SUPPORTED_SDK_VERSION = '0.7.0';
14
+ const TESTED_SDK_VERSION = '0.7.0';
13
15
  class AutoInstallationWizard {
14
- constructor(apiKey, projectRoot = process.cwd()) {
16
+ constructor(apiKey, projectRoot = process.cwd(), options = {}) {
15
17
  this.framework = null;
16
18
  this.manualNotes = [];
17
19
  this.apiKey = apiKey;
18
- this.projectRoot = projectRoot;
20
+ this.projectRoot = path.resolve(projectRoot);
21
+ this.options = options;
22
+ }
23
+ setAgentPlan(agentPlan) {
24
+ this.options.agentPlan = agentPlan;
19
25
  }
20
26
  /**
21
27
  * Simple version comparison utility
@@ -46,11 +52,13 @@ class AutoInstallationWizard {
46
52
  try {
47
53
  // Step 1: Detect framework
48
54
  this.framework = await this.detectFramework();
49
- // Step 2: Install package
50
- await this.installPackage();
55
+ // Step 2: Plan and optionally install package
56
+ const installPlan = await this.installPackage();
51
57
  // Step 3: Generate and apply code modifications
52
58
  const modifications = await this.generateModifications();
53
- await this.applyModifications(modifications);
59
+ if (!this.options.dryRun) {
60
+ await this.applyModifications(modifications);
61
+ }
54
62
  // Step 4: Generate next steps
55
63
  const nextSteps = this.generateNextSteps();
56
64
  return {
@@ -58,7 +66,10 @@ class AutoInstallationWizard {
58
66
  framework: this.framework,
59
67
  modifications,
60
68
  errors: [],
61
- nextSteps
69
+ nextSteps,
70
+ dryRun: this.options.dryRun,
71
+ installPlan,
72
+ agentPlan: this.options.agentPlan
62
73
  };
63
74
  }
64
75
  catch (error) {
@@ -67,7 +78,9 @@ class AutoInstallationWizard {
67
78
  framework: this.framework || { name: 'unknown', type: 'vanilla' },
68
79
  modifications: [],
69
80
  errors: [error instanceof Error ? error.message : 'Unknown error'],
70
- nextSteps: []
81
+ nextSteps: [],
82
+ dryRun: this.options.dryRun,
83
+ agentPlan: this.options.agentPlan
71
84
  };
72
85
  }
73
86
  }
@@ -243,38 +256,212 @@ class AutoInstallationWizard {
243
256
  else if (dependencies.rollup) {
244
257
  framework.bundler = 'rollup';
245
258
  }
246
- // Detect package manager
247
- if (fs.existsSync(path.join(this.projectRoot, 'yarn.lock'))) {
248
- framework.packageManager = 'yarn';
249
- }
250
- else if (fs.existsSync(path.join(this.projectRoot, 'pnpm-lock.yaml'))) {
251
- framework.packageManager = 'pnpm';
252
- }
253
- else {
254
- framework.packageManager = 'npm';
255
- }
259
+ framework.packageManager = this.detectPackageManager(this.projectRoot);
256
260
  return framework;
257
261
  }
258
262
  /**
259
263
  * Install the SDK package with latest version range
260
264
  */
261
265
  async installPackage() {
262
- // Build base command with latest version range
263
- let command = this.framework?.packageManager === 'yarn'
264
- ? 'yarn add humanbehavior-js@latest'
265
- : this.framework?.packageManager === 'pnpm'
266
- ? 'pnpm add humanbehavior-js@latest'
267
- : 'npm install humanbehavior-js@latest';
268
- // Add legacy peer deps flag for npm to handle dependency conflicts
269
- if (this.framework?.packageManager !== 'yarn' && this.framework?.packageManager !== 'pnpm') {
270
- command += ' --legacy-peer-deps';
266
+ const plan = this.planPackageInstall();
267
+ if (!plan.shouldInstall || this.options.skipInstall || this.options.dryRun) {
268
+ return plan;
271
269
  }
272
270
  try {
273
- execSync(command, { cwd: this.projectRoot, stdio: 'inherit' });
271
+ execSync(plan.command, { cwd: plan.cwd, stdio: 'inherit' });
274
272
  }
275
273
  catch (error) {
276
274
  throw new Error(`Failed to install humanbehavior-js: ${error}`);
277
275
  }
276
+ return plan;
277
+ }
278
+ planPackageInstall() {
279
+ const packageManager = this.framework?.packageManager || this.detectPackageManager(this.projectRoot);
280
+ const workspaceRoot = this.findWorkspaceRoot(this.projectRoot);
281
+ const packageJson = this.readPackageJson(this.projectRoot);
282
+ const packageName = packageJson?.name;
283
+ const installedVersion = this.getInstalledSDKVersion(packageJson);
284
+ const alreadyInstalled = installedVersion !== undefined;
285
+ const versionStatus = this.getSDKVersionStatus(installedVersion);
286
+ const isWorkspace = workspaceRoot !== this.projectRoot;
287
+ const targetRef = packageName || path.relative(workspaceRoot, this.projectRoot);
288
+ const sdkPackage = 'humanbehavior-js@latest';
289
+ const shouldInstall = !alreadyInstalled ||
290
+ (versionStatus === 'stale' && this.options.upgrade === true);
291
+ const command = this.buildInstallCommand(packageManager, sdkPackage, {
292
+ isWorkspace,
293
+ targetRef
294
+ });
295
+ return {
296
+ packageName: 'humanbehavior-js',
297
+ packageManager,
298
+ command,
299
+ cwd: isWorkspace ? workspaceRoot : this.projectRoot,
300
+ targetPackagePath: this.projectRoot,
301
+ workspaceRoot,
302
+ isWorkspace,
303
+ alreadyInstalled,
304
+ installedVersion,
305
+ minimumSupportedVersion: MIN_SUPPORTED_SDK_VERSION,
306
+ testedVersion: TESTED_SDK_VERSION,
307
+ versionStatus,
308
+ shouldInstall,
309
+ reason: this.getInstallPlanReason({
310
+ alreadyInstalled,
311
+ installedVersion,
312
+ versionStatus,
313
+ shouldInstall,
314
+ isWorkspace,
315
+ targetRef
316
+ })
317
+ };
318
+ }
319
+ getInstallPlanReason(options) {
320
+ if (options.alreadyInstalled) {
321
+ switch (options.versionStatus) {
322
+ case 'compatible':
323
+ return `humanbehavior-js is already compatible (${options.installedVersion})`;
324
+ case 'stale':
325
+ return options.shouldInstall
326
+ ? `humanbehavior-js ${options.installedVersion} is below ${MIN_SUPPORTED_SDK_VERSION}; upgrade requested`
327
+ : `humanbehavior-js ${options.installedVersion} is below ${MIN_SUPPORTED_SDK_VERSION}; rerun with --upgrade to update`;
328
+ case 'newer':
329
+ return `humanbehavior-js ${options.installedVersion} is newer than tested ${TESTED_SDK_VERSION}; keeping existing version`;
330
+ case 'unknown':
331
+ return `humanbehavior-js is already declared with a non-semver range (${options.installedVersion}); keeping existing version`;
332
+ }
333
+ }
334
+ return options.isWorkspace
335
+ ? `Install scoped to workspace package ${options.targetRef}`
336
+ : 'Install in selected project package';
337
+ }
338
+ buildInstallCommand(packageManager, sdkPackage, options) {
339
+ if (options.isWorkspace) {
340
+ switch (packageManager) {
341
+ case 'pnpm':
342
+ return `pnpm --filter ${this.shellQuote(options.targetRef)} add ${sdkPackage}`;
343
+ case 'yarn':
344
+ return `yarn workspace ${this.shellQuote(options.targetRef)} add ${sdkPackage}`;
345
+ case 'bun':
346
+ return `bun add ${sdkPackage} --cwd ${this.shellQuote(options.targetRef)}`;
347
+ case 'npm':
348
+ default:
349
+ return `npm install ${sdkPackage} --workspace ${this.shellQuote(options.targetRef)} --legacy-peer-deps`;
350
+ }
351
+ }
352
+ switch (packageManager) {
353
+ case 'pnpm':
354
+ return `pnpm add ${sdkPackage}`;
355
+ case 'yarn':
356
+ return `yarn add ${sdkPackage}`;
357
+ case 'bun':
358
+ return `bun add ${sdkPackage}`;
359
+ case 'npm':
360
+ default:
361
+ return `npm install ${sdkPackage} --legacy-peer-deps`;
362
+ }
363
+ }
364
+ detectPackageManager(startDir) {
365
+ const root = this.findWorkspaceRoot(startDir);
366
+ const lockfileChecks = [
367
+ ['pnpm-lock.yaml', 'pnpm'],
368
+ ['yarn.lock', 'yarn'],
369
+ ['bun.lockb', 'bun'],
370
+ ['bun.lock', 'bun'],
371
+ ['package-lock.json', 'npm'],
372
+ ['npm-shrinkwrap.json', 'npm']
373
+ ];
374
+ for (const dir of [startDir, root]) {
375
+ for (const [lockfile, packageManager] of lockfileChecks) {
376
+ if (fs.existsSync(path.join(dir, lockfile))) {
377
+ return packageManager;
378
+ }
379
+ }
380
+ }
381
+ const packageJson = this.readPackageJson(root) || this.readPackageJson(startDir);
382
+ const packageManager = packageJson?.packageManager;
383
+ if (typeof packageManager === 'string') {
384
+ if (packageManager.startsWith('pnpm@'))
385
+ return 'pnpm';
386
+ if (packageManager.startsWith('yarn@'))
387
+ return 'yarn';
388
+ if (packageManager.startsWith('bun@'))
389
+ return 'bun';
390
+ if (packageManager.startsWith('npm@'))
391
+ return 'npm';
392
+ }
393
+ return 'npm';
394
+ }
395
+ findWorkspaceRoot(startDir) {
396
+ let current = path.resolve(startDir);
397
+ let bestRoot = current;
398
+ while (true) {
399
+ const packageJson = this.readPackageJson(current);
400
+ const hasWorkspacePackageJson = !!packageJson &&
401
+ (Array.isArray(packageJson.workspaces) ||
402
+ (packageJson.workspaces && Array.isArray(packageJson.workspaces.packages)));
403
+ const hasPnpmWorkspace = fs.existsSync(path.join(current, 'pnpm-workspace.yaml'));
404
+ if (hasWorkspacePackageJson || hasPnpmWorkspace) {
405
+ bestRoot = current;
406
+ }
407
+ const parent = path.dirname(current);
408
+ if (parent === current)
409
+ break;
410
+ current = parent;
411
+ }
412
+ return bestRoot;
413
+ }
414
+ readPackageJson(dir) {
415
+ const packageJsonPath = path.join(dir, 'package.json');
416
+ if (!fs.existsSync(packageJsonPath))
417
+ return null;
418
+ try {
419
+ return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
420
+ }
421
+ catch {
422
+ return null;
423
+ }
424
+ }
425
+ getInstalledSDKVersion(packageJson) {
426
+ if (!packageJson)
427
+ return undefined;
428
+ const dependencyFields = [
429
+ 'dependencies',
430
+ 'devDependencies',
431
+ 'peerDependencies',
432
+ 'optionalDependencies'
433
+ ];
434
+ for (const field of dependencyFields) {
435
+ const version = packageJson[field]?.['humanbehavior-js'];
436
+ if (typeof version === 'string') {
437
+ return version;
438
+ }
439
+ }
440
+ return undefined;
441
+ }
442
+ getSDKVersionStatus(installedVersion) {
443
+ if (!installedVersion)
444
+ return 'not-installed';
445
+ const normalized = this.extractSemver(installedVersion);
446
+ if (!normalized)
447
+ return 'unknown';
448
+ if (this.compareVersions(normalized, MIN_SUPPORTED_SDK_VERSION) < 0) {
449
+ return 'stale';
450
+ }
451
+ if (this.compareVersions(normalized, TESTED_SDK_VERSION) > 0) {
452
+ return 'newer';
453
+ }
454
+ return 'compatible';
455
+ }
456
+ extractSemver(versionRange) {
457
+ const match = versionRange.match(/\d+\.\d+\.\d+/);
458
+ return match ? match[0] : null;
459
+ }
460
+ shellQuote(value) {
461
+ if (/^[A-Za-z0-9_@./:-]+$/.test(value)) {
462
+ return value;
463
+ }
464
+ return `'${value.replace(/'/g, "'\\''")}'`;
278
465
  }
279
466
  /**
280
467
  * Generate code modifications based on framework
@@ -601,20 +788,14 @@ export function useHumanBehavior() {
601
788
  return { tracker: null }
602
789
  }
603
790
  `;
604
- try {
605
- if (!fs.existsSync(composableDir)) {
606
- fs.mkdirSync(composableDir, { recursive: true });
607
- }
608
- if (!fs.existsSync(composablePath)) {
609
- modifications.push({
610
- filePath: composablePath,
611
- action: 'create',
612
- content: composableContent,
613
- description: 'Created Vue composable useHumanBehavior'
614
- });
615
- }
791
+ if (!fs.existsSync(composablePath)) {
792
+ modifications.push({
793
+ filePath: composablePath,
794
+ action: 'create',
795
+ content: composableContent,
796
+ description: 'Created Vue composable useHumanBehavior'
797
+ });
616
798
  }
617
- catch { }
618
799
  if (mainFile) {
619
800
  const content = fs.readFileSync(mainFile, 'utf8');
620
801
  const modifiedContent = this.injectVuePlugin(content);
@@ -667,9 +848,6 @@ export class HumanBehavior {
667
848
  }
668
849
  }
669
850
  `;
670
- if (!fs.existsSync(serviceDir)) {
671
- fs.mkdirSync(serviceDir, { recursive: true });
672
- }
673
851
  if (!fs.existsSync(servicePath)) {
674
852
  modifications.push({
675
853
  filePath: servicePath,
@@ -681,11 +859,6 @@ export class HumanBehavior {
681
859
  // Handle Angular environment files (proper Angular way)
682
860
  const envFile = path.join(this.projectRoot, 'src', 'environments', 'environment.ts');
683
861
  const envProdFile = path.join(this.projectRoot, 'src', 'environments', 'environment.prod.ts');
684
- // Create environments directory if it doesn't exist
685
- const envDir = path.dirname(envFile);
686
- if (!fs.existsSync(envDir)) {
687
- fs.mkdirSync(envDir, { recursive: true });
688
- }
689
862
  // Create or update development environment
690
863
  if (fs.existsSync(envFile)) {
691
864
  const content = fs.readFileSync(envFile, 'utf8');
@@ -868,8 +1041,15 @@ export const onClientEntry = () => {
868
1041
  * Apply modifications to the codebase
869
1042
  */
870
1043
  async applyModifications(modifications) {
871
- for (const modification of modifications) {
872
- try {
1044
+ const snapshots = modifications.map((modification) => ({
1045
+ filePath: modification.filePath,
1046
+ existed: fs.existsSync(modification.filePath),
1047
+ content: fs.existsSync(modification.filePath)
1048
+ ? fs.readFileSync(modification.filePath, 'utf8')
1049
+ : null
1050
+ }));
1051
+ try {
1052
+ for (const modification of modifications) {
873
1053
  const dir = path.dirname(modification.filePath);
874
1054
  if (!fs.existsSync(dir)) {
875
1055
  fs.mkdirSync(dir, { recursive: true });
@@ -886,8 +1066,24 @@ export const onClientEntry = () => {
886
1066
  break;
887
1067
  }
888
1068
  }
889
- catch (error) {
890
- throw new Error(`Failed to apply modification to ${modification.filePath}: ${error}`);
1069
+ }
1070
+ catch (error) {
1071
+ this.rollbackModifications(snapshots);
1072
+ throw new Error(`Failed to apply modifications; rolled back file changes: ${error}`);
1073
+ }
1074
+ }
1075
+ rollbackModifications(snapshots) {
1076
+ for (const snapshot of snapshots.reverse()) {
1077
+ try {
1078
+ if (snapshot.existed && snapshot.content !== null) {
1079
+ fs.writeFileSync(snapshot.filePath, snapshot.content);
1080
+ }
1081
+ else if (!snapshot.existed && fs.existsSync(snapshot.filePath)) {
1082
+ fs.rmSync(snapshot.filePath, { force: true });
1083
+ }
1084
+ }
1085
+ catch {
1086
+ // Best-effort rollback. The original apply error is more actionable.
891
1087
  }
892
1088
  }
893
1089
  }
@@ -895,6 +1091,13 @@ export const onClientEntry = () => {
895
1091
  * Generate next steps for the user
896
1092
  */
897
1093
  generateNextSteps() {
1094
+ if (this.options.dryRun) {
1095
+ return [
1096
+ 'Dry run complete. No packages were installed and no files were changed.',
1097
+ 'Review the planned install command and file modifications.',
1098
+ 'Run again without --dry-run to apply these changes.'
1099
+ ];
1100
+ }
898
1101
  const steps = [
899
1102
  '✅ SDK installed and configured automatically!',
900
1103
  '🚀 Your app is now tracking user behavior',
@@ -1463,11 +1666,15 @@ class AutoInstallCLI {
1463
1666
  // Run installation
1464
1667
  const spinner = clack.spinner();
1465
1668
  spinner.start('🔍 Detecting framework and applying integration...');
1466
- const wizard = new AutoInstallationWizard(apiKey, projectPath);
1669
+ const wizard = new AutoInstallationWizard(apiKey, projectPath, {
1670
+ dryRun: this.options.dryRun,
1671
+ upgrade: this.options.upgrade
1672
+ });
1467
1673
  const result = await wizard.install();
1468
- spinner.stop('Installation complete!');
1674
+ spinner.stop(this.options.dryRun ? 'Dry run complete!' : 'Installation complete!');
1469
1675
  // Display results
1470
1676
  this.displayResults(result);
1677
+ this.writeReport(result);
1471
1678
  }
1472
1679
  catch (error) {
1473
1680
  clack.cancel(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -1478,6 +1685,12 @@ class AutoInstallCLI {
1478
1685
  if (this.options.apiKey) {
1479
1686
  return this.options.apiKey;
1480
1687
  }
1688
+ if (this.options.apiKeyFile) {
1689
+ return fs.readFileSync(this.options.apiKeyFile, 'utf8').trim();
1690
+ }
1691
+ if (process.env.HB_API_KEY) {
1692
+ return process.env.HB_API_KEY;
1693
+ }
1481
1694
  const apiKey = await clack.text({
1482
1695
  message: 'Enter your HumanBehavior API key:',
1483
1696
  placeholder: 'hb_...',
@@ -1500,13 +1713,27 @@ class AutoInstallCLI {
1500
1713
  }
1501
1714
  displayResults(result) {
1502
1715
  if (result.success) {
1503
- clack.outro('🎉 Installation completed successfully!');
1716
+ clack.outro(result.dryRun
1717
+ ? 'Dry run completed successfully. No packages were installed and no files were changed.'
1718
+ : '🎉 Installation completed successfully!');
1504
1719
  // Display framework info
1505
1720
  clack.note(`Framework detected: ${result.framework.name} (${result.framework.type})`, 'Framework Info');
1506
1721
  // Display modifications
1722
+ if (result.installPlan) {
1723
+ clack.note([
1724
+ `Command: ${result.installPlan.command}`,
1725
+ `Run from: ${result.installPlan.cwd}`,
1726
+ `Target package: ${result.installPlan.targetPackagePath}`,
1727
+ `Workspace: ${result.installPlan.isWorkspace ? 'yes' : 'no'}`,
1728
+ `Version status: ${result.installPlan.versionStatus}`,
1729
+ result.installPlan.alreadyInstalled
1730
+ ? `Already installed: ${result.installPlan.installedVersion}`
1731
+ : `Reason: ${result.installPlan.reason}`
1732
+ ].join('\n'), result.dryRun ? 'Planned Package Install' : 'Package Install');
1733
+ }
1507
1734
  if (result.modifications && result.modifications.length > 0) {
1508
1735
  const modifications = result.modifications.map((mod) => `${mod.action}: ${mod.filePath} - ${mod.description}`);
1509
- clack.note(modifications.join('\n'), 'Files Modified');
1736
+ clack.note(modifications.join('\n'), result.dryRun ? 'Planned File Changes' : 'Files Modified');
1510
1737
  }
1511
1738
  // Display next steps
1512
1739
  if (result.nextSteps && result.nextSteps.length > 0) {
@@ -1520,6 +1747,25 @@ class AutoInstallCLI {
1520
1747
  }
1521
1748
  }
1522
1749
  }
1750
+ writeReport(result) {
1751
+ if (!this.options.reportJson)
1752
+ return;
1753
+ const report = {
1754
+ schemaVersion: 1,
1755
+ status: result.success ? (result.dryRun ? 'dry-run' : 'completed') : 'failed',
1756
+ framework: result.framework,
1757
+ dryRun: result.dryRun === true,
1758
+ installPlan: result.installPlan,
1759
+ modifications: (result.modifications || []).map((mod) => ({
1760
+ filePath: mod.filePath,
1761
+ action: mod.action,
1762
+ description: mod.description
1763
+ })),
1764
+ errors: result.errors || [],
1765
+ nextSteps: result.nextSteps || []
1766
+ };
1767
+ fs.writeFileSync(this.options.reportJson, JSON.stringify(report, null, 2));
1768
+ }
1523
1769
  }
1524
1770
  function parseArgs() {
1525
1771
  const args = process.argv.slice(2);
@@ -1539,10 +1785,19 @@ function parseArgs() {
1539
1785
  case '--dry-run':
1540
1786
  options.dryRun = true;
1541
1787
  break;
1788
+ case '--upgrade':
1789
+ options.upgrade = true;
1790
+ break;
1542
1791
  case '--project':
1543
1792
  case '-p':
1544
1793
  options.projectPath = args[++i];
1545
1794
  break;
1795
+ case '--api-key-file':
1796
+ options.apiKeyFile = args[++i];
1797
+ break;
1798
+ case '--report-json':
1799
+ options.reportJson = args[++i];
1800
+ break;
1546
1801
  default:
1547
1802
  if (!options.apiKey && !arg.startsWith('-')) {
1548
1803
  options.apiKey = arg;
@@ -1564,6 +1819,9 @@ Options:
1564
1819
  -h, --help Show this help message
1565
1820
  -y, --yes Skip all prompts and use defaults
1566
1821
  --dry-run Show what would be changed without making changes
1822
+ --upgrade Upgrade stale existing SDK versions
1823
+ --api-key-file <path> Read API key from a local file
1824
+ --report-json <path> Write a structured install report without file contents
1567
1825
  -p, --project <path> Specify project directory
1568
1826
 
1569
1827
  Examples: