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
@@ -2,6 +2,7 @@
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import { execSync } from 'child_process';
5
+ import { query } from '@anthropic-ai/claude-agent-sdk';
5
6
  import * as clack from '@clack/prompts';
6
7
 
7
8
  /**
@@ -10,12 +11,18 @@ import * as clack from '@clack/prompts';
10
11
  * This wizard automatically detects the user's framework and modifies their codebase
11
12
  * to integrate the SDK with minimal user intervention.
12
13
  */
14
+ const MIN_SUPPORTED_SDK_VERSION = '0.7.0';
15
+ const TESTED_SDK_VERSION = '0.7.0';
13
16
  class AutoInstallationWizard {
14
- constructor(apiKey, projectRoot = process.cwd()) {
17
+ constructor(apiKey, projectRoot = process.cwd(), options = {}) {
15
18
  this.framework = null;
16
19
  this.manualNotes = [];
17
20
  this.apiKey = apiKey;
18
- this.projectRoot = projectRoot;
21
+ this.projectRoot = path.resolve(projectRoot);
22
+ this.options = options;
23
+ }
24
+ setAgentPlan(agentPlan) {
25
+ this.options.agentPlan = agentPlan;
19
26
  }
20
27
  /**
21
28
  * Simple version comparison utility
@@ -46,11 +53,13 @@ class AutoInstallationWizard {
46
53
  try {
47
54
  // Step 1: Detect framework
48
55
  this.framework = await this.detectFramework();
49
- // Step 2: Install package
50
- await this.installPackage();
56
+ // Step 2: Plan and optionally install package
57
+ const installPlan = await this.installPackage();
51
58
  // Step 3: Generate and apply code modifications
52
59
  const modifications = await this.generateModifications();
53
- await this.applyModifications(modifications);
60
+ if (!this.options.dryRun) {
61
+ await this.applyModifications(modifications);
62
+ }
54
63
  // Step 4: Generate next steps
55
64
  const nextSteps = this.generateNextSteps();
56
65
  return {
@@ -58,7 +67,10 @@ class AutoInstallationWizard {
58
67
  framework: this.framework,
59
68
  modifications,
60
69
  errors: [],
61
- nextSteps
70
+ nextSteps,
71
+ dryRun: this.options.dryRun,
72
+ installPlan,
73
+ agentPlan: this.options.agentPlan
62
74
  };
63
75
  }
64
76
  catch (error) {
@@ -67,7 +79,9 @@ class AutoInstallationWizard {
67
79
  framework: this.framework || { name: 'unknown', type: 'vanilla' },
68
80
  modifications: [],
69
81
  errors: [error instanceof Error ? error.message : 'Unknown error'],
70
- nextSteps: []
82
+ nextSteps: [],
83
+ dryRun: this.options.dryRun,
84
+ agentPlan: this.options.agentPlan
71
85
  };
72
86
  }
73
87
  }
@@ -243,38 +257,212 @@ class AutoInstallationWizard {
243
257
  else if (dependencies.rollup) {
244
258
  framework.bundler = 'rollup';
245
259
  }
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
- }
260
+ framework.packageManager = this.detectPackageManager(this.projectRoot);
256
261
  return framework;
257
262
  }
258
263
  /**
259
264
  * Install the SDK package with latest version range
260
265
  */
261
266
  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';
267
+ const plan = this.planPackageInstall();
268
+ if (!plan.shouldInstall || this.options.skipInstall || this.options.dryRun) {
269
+ return plan;
271
270
  }
272
271
  try {
273
- execSync(command, { cwd: this.projectRoot, stdio: 'inherit' });
272
+ execSync(plan.command, { cwd: plan.cwd, stdio: 'inherit' });
274
273
  }
275
274
  catch (error) {
276
275
  throw new Error(`Failed to install humanbehavior-js: ${error}`);
277
276
  }
277
+ return plan;
278
+ }
279
+ planPackageInstall() {
280
+ const packageManager = this.framework?.packageManager || this.detectPackageManager(this.projectRoot);
281
+ const workspaceRoot = this.findWorkspaceRoot(this.projectRoot);
282
+ const packageJson = this.readPackageJson(this.projectRoot);
283
+ const packageName = packageJson?.name;
284
+ const installedVersion = this.getInstalledSDKVersion(packageJson);
285
+ const alreadyInstalled = installedVersion !== undefined;
286
+ const versionStatus = this.getSDKVersionStatus(installedVersion);
287
+ const isWorkspace = workspaceRoot !== this.projectRoot;
288
+ const targetRef = packageName || path.relative(workspaceRoot, this.projectRoot);
289
+ const sdkPackage = 'humanbehavior-js@latest';
290
+ const shouldInstall = !alreadyInstalled ||
291
+ (versionStatus === 'stale' && this.options.upgrade === true);
292
+ const command = this.buildInstallCommand(packageManager, sdkPackage, {
293
+ isWorkspace,
294
+ targetRef
295
+ });
296
+ return {
297
+ packageName: 'humanbehavior-js',
298
+ packageManager,
299
+ command,
300
+ cwd: isWorkspace ? workspaceRoot : this.projectRoot,
301
+ targetPackagePath: this.projectRoot,
302
+ workspaceRoot,
303
+ isWorkspace,
304
+ alreadyInstalled,
305
+ installedVersion,
306
+ minimumSupportedVersion: MIN_SUPPORTED_SDK_VERSION,
307
+ testedVersion: TESTED_SDK_VERSION,
308
+ versionStatus,
309
+ shouldInstall,
310
+ reason: this.getInstallPlanReason({
311
+ alreadyInstalled,
312
+ installedVersion,
313
+ versionStatus,
314
+ shouldInstall,
315
+ isWorkspace,
316
+ targetRef
317
+ })
318
+ };
319
+ }
320
+ getInstallPlanReason(options) {
321
+ if (options.alreadyInstalled) {
322
+ switch (options.versionStatus) {
323
+ case 'compatible':
324
+ return `humanbehavior-js is already compatible (${options.installedVersion})`;
325
+ case 'stale':
326
+ return options.shouldInstall
327
+ ? `humanbehavior-js ${options.installedVersion} is below ${MIN_SUPPORTED_SDK_VERSION}; upgrade requested`
328
+ : `humanbehavior-js ${options.installedVersion} is below ${MIN_SUPPORTED_SDK_VERSION}; rerun with --upgrade to update`;
329
+ case 'newer':
330
+ return `humanbehavior-js ${options.installedVersion} is newer than tested ${TESTED_SDK_VERSION}; keeping existing version`;
331
+ case 'unknown':
332
+ return `humanbehavior-js is already declared with a non-semver range (${options.installedVersion}); keeping existing version`;
333
+ }
334
+ }
335
+ return options.isWorkspace
336
+ ? `Install scoped to workspace package ${options.targetRef}`
337
+ : 'Install in selected project package';
338
+ }
339
+ buildInstallCommand(packageManager, sdkPackage, options) {
340
+ if (options.isWorkspace) {
341
+ switch (packageManager) {
342
+ case 'pnpm':
343
+ return `pnpm --filter ${this.shellQuote(options.targetRef)} add ${sdkPackage}`;
344
+ case 'yarn':
345
+ return `yarn workspace ${this.shellQuote(options.targetRef)} add ${sdkPackage}`;
346
+ case 'bun':
347
+ return `bun add ${sdkPackage} --cwd ${this.shellQuote(options.targetRef)}`;
348
+ case 'npm':
349
+ default:
350
+ return `npm install ${sdkPackage} --workspace ${this.shellQuote(options.targetRef)} --legacy-peer-deps`;
351
+ }
352
+ }
353
+ switch (packageManager) {
354
+ case 'pnpm':
355
+ return `pnpm add ${sdkPackage}`;
356
+ case 'yarn':
357
+ return `yarn add ${sdkPackage}`;
358
+ case 'bun':
359
+ return `bun add ${sdkPackage}`;
360
+ case 'npm':
361
+ default:
362
+ return `npm install ${sdkPackage} --legacy-peer-deps`;
363
+ }
364
+ }
365
+ detectPackageManager(startDir) {
366
+ const root = this.findWorkspaceRoot(startDir);
367
+ const lockfileChecks = [
368
+ ['pnpm-lock.yaml', 'pnpm'],
369
+ ['yarn.lock', 'yarn'],
370
+ ['bun.lockb', 'bun'],
371
+ ['bun.lock', 'bun'],
372
+ ['package-lock.json', 'npm'],
373
+ ['npm-shrinkwrap.json', 'npm']
374
+ ];
375
+ for (const dir of [startDir, root]) {
376
+ for (const [lockfile, packageManager] of lockfileChecks) {
377
+ if (fs.existsSync(path.join(dir, lockfile))) {
378
+ return packageManager;
379
+ }
380
+ }
381
+ }
382
+ const packageJson = this.readPackageJson(root) || this.readPackageJson(startDir);
383
+ const packageManager = packageJson?.packageManager;
384
+ if (typeof packageManager === 'string') {
385
+ if (packageManager.startsWith('pnpm@'))
386
+ return 'pnpm';
387
+ if (packageManager.startsWith('yarn@'))
388
+ return 'yarn';
389
+ if (packageManager.startsWith('bun@'))
390
+ return 'bun';
391
+ if (packageManager.startsWith('npm@'))
392
+ return 'npm';
393
+ }
394
+ return 'npm';
395
+ }
396
+ findWorkspaceRoot(startDir) {
397
+ let current = path.resolve(startDir);
398
+ let bestRoot = current;
399
+ while (true) {
400
+ const packageJson = this.readPackageJson(current);
401
+ const hasWorkspacePackageJson = !!packageJson &&
402
+ (Array.isArray(packageJson.workspaces) ||
403
+ (packageJson.workspaces && Array.isArray(packageJson.workspaces.packages)));
404
+ const hasPnpmWorkspace = fs.existsSync(path.join(current, 'pnpm-workspace.yaml'));
405
+ if (hasWorkspacePackageJson || hasPnpmWorkspace) {
406
+ bestRoot = current;
407
+ }
408
+ const parent = path.dirname(current);
409
+ if (parent === current)
410
+ break;
411
+ current = parent;
412
+ }
413
+ return bestRoot;
414
+ }
415
+ readPackageJson(dir) {
416
+ const packageJsonPath = path.join(dir, 'package.json');
417
+ if (!fs.existsSync(packageJsonPath))
418
+ return null;
419
+ try {
420
+ return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
421
+ }
422
+ catch {
423
+ return null;
424
+ }
425
+ }
426
+ getInstalledSDKVersion(packageJson) {
427
+ if (!packageJson)
428
+ return undefined;
429
+ const dependencyFields = [
430
+ 'dependencies',
431
+ 'devDependencies',
432
+ 'peerDependencies',
433
+ 'optionalDependencies'
434
+ ];
435
+ for (const field of dependencyFields) {
436
+ const version = packageJson[field]?.['humanbehavior-js'];
437
+ if (typeof version === 'string') {
438
+ return version;
439
+ }
440
+ }
441
+ return undefined;
442
+ }
443
+ getSDKVersionStatus(installedVersion) {
444
+ if (!installedVersion)
445
+ return 'not-installed';
446
+ const normalized = this.extractSemver(installedVersion);
447
+ if (!normalized)
448
+ return 'unknown';
449
+ if (this.compareVersions(normalized, MIN_SUPPORTED_SDK_VERSION) < 0) {
450
+ return 'stale';
451
+ }
452
+ if (this.compareVersions(normalized, TESTED_SDK_VERSION) > 0) {
453
+ return 'newer';
454
+ }
455
+ return 'compatible';
456
+ }
457
+ extractSemver(versionRange) {
458
+ const match = versionRange.match(/\d+\.\d+\.\d+/);
459
+ return match ? match[0] : null;
460
+ }
461
+ shellQuote(value) {
462
+ if (/^[A-Za-z0-9_@./:-]+$/.test(value)) {
463
+ return value;
464
+ }
465
+ return `'${value.replace(/'/g, "'\\''")}'`;
278
466
  }
279
467
  /**
280
468
  * Generate code modifications based on framework
@@ -601,20 +789,14 @@ export function useHumanBehavior() {
601
789
  return { tracker: null }
602
790
  }
603
791
  `;
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
- }
792
+ if (!fs.existsSync(composablePath)) {
793
+ modifications.push({
794
+ filePath: composablePath,
795
+ action: 'create',
796
+ content: composableContent,
797
+ description: 'Created Vue composable useHumanBehavior'
798
+ });
616
799
  }
617
- catch { }
618
800
  if (mainFile) {
619
801
  const content = fs.readFileSync(mainFile, 'utf8');
620
802
  const modifiedContent = this.injectVuePlugin(content);
@@ -667,9 +849,6 @@ export class HumanBehavior {
667
849
  }
668
850
  }
669
851
  `;
670
- if (!fs.existsSync(serviceDir)) {
671
- fs.mkdirSync(serviceDir, { recursive: true });
672
- }
673
852
  if (!fs.existsSync(servicePath)) {
674
853
  modifications.push({
675
854
  filePath: servicePath,
@@ -681,11 +860,6 @@ export class HumanBehavior {
681
860
  // Handle Angular environment files (proper Angular way)
682
861
  const envFile = path.join(this.projectRoot, 'src', 'environments', 'environment.ts');
683
862
  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
863
  // Create or update development environment
690
864
  if (fs.existsSync(envFile)) {
691
865
  const content = fs.readFileSync(envFile, 'utf8');
@@ -868,8 +1042,15 @@ export const onClientEntry = () => {
868
1042
  * Apply modifications to the codebase
869
1043
  */
870
1044
  async applyModifications(modifications) {
871
- for (const modification of modifications) {
872
- try {
1045
+ const snapshots = modifications.map((modification) => ({
1046
+ filePath: modification.filePath,
1047
+ existed: fs.existsSync(modification.filePath),
1048
+ content: fs.existsSync(modification.filePath)
1049
+ ? fs.readFileSync(modification.filePath, 'utf8')
1050
+ : null
1051
+ }));
1052
+ try {
1053
+ for (const modification of modifications) {
873
1054
  const dir = path.dirname(modification.filePath);
874
1055
  if (!fs.existsSync(dir)) {
875
1056
  fs.mkdirSync(dir, { recursive: true });
@@ -886,8 +1067,24 @@ export const onClientEntry = () => {
886
1067
  break;
887
1068
  }
888
1069
  }
889
- catch (error) {
890
- throw new Error(`Failed to apply modification to ${modification.filePath}: ${error}`);
1070
+ }
1071
+ catch (error) {
1072
+ this.rollbackModifications(snapshots);
1073
+ throw new Error(`Failed to apply modifications; rolled back file changes: ${error}`);
1074
+ }
1075
+ }
1076
+ rollbackModifications(snapshots) {
1077
+ for (const snapshot of snapshots.reverse()) {
1078
+ try {
1079
+ if (snapshot.existed && snapshot.content !== null) {
1080
+ fs.writeFileSync(snapshot.filePath, snapshot.content);
1081
+ }
1082
+ else if (!snapshot.existed && fs.existsSync(snapshot.filePath)) {
1083
+ fs.rmSync(snapshot.filePath, { force: true });
1084
+ }
1085
+ }
1086
+ catch {
1087
+ // Best-effort rollback. The original apply error is more actionable.
891
1088
  }
892
1089
  }
893
1090
  }
@@ -895,6 +1092,13 @@ export const onClientEntry = () => {
895
1092
  * Generate next steps for the user
896
1093
  */
897
1094
  generateNextSteps() {
1095
+ if (this.options.dryRun) {
1096
+ return [
1097
+ 'Dry run complete. No packages were installed and no files were changed.',
1098
+ 'Review the planned install command and file modifications.',
1099
+ 'Run again without --dry-run to apply these changes.'
1100
+ ];
1101
+ }
898
1102
  const steps = [
899
1103
  '✅ SDK installed and configured automatically!',
900
1104
  '🚀 Your app is now tracking user behavior',
@@ -1638,8 +1842,8 @@ class RemoteAIService {
1638
1842
  * Useful when auto-detection fails or users want more control.
1639
1843
  */
1640
1844
  class ManualFrameworkInstallationWizard extends AutoInstallationWizard {
1641
- constructor(apiKey, projectRoot = process.cwd(), framework) {
1642
- super(apiKey, projectRoot);
1845
+ constructor(apiKey, projectRoot = process.cwd(), framework, options = {}) {
1846
+ super(apiKey, projectRoot, options);
1643
1847
  this.selectedFramework = framework.toLowerCase();
1644
1848
  this.framework = this.createFrameworkInfo(this.selectedFramework);
1645
1849
  }
@@ -1669,10 +1873,12 @@ class ManualFrameworkInstallationWizard extends AutoInstallationWizard {
1669
1873
  };
1670
1874
  }
1671
1875
  // Step 4: Install package
1672
- await this.installPackage();
1876
+ const installPlan = await this.installPackage();
1673
1877
  // Step 5: Generate and apply code modifications
1674
1878
  const modifications = await this.generateModifications();
1675
- await this.applyModifications(modifications);
1879
+ if (!this.options.dryRun) {
1880
+ await this.applyModifications(modifications);
1881
+ }
1676
1882
  // Step 6: Generate next steps
1677
1883
  const nextSteps = this.generateManualNextSteps();
1678
1884
  return {
@@ -1681,6 +1887,8 @@ class ManualFrameworkInstallationWizard extends AutoInstallationWizard {
1681
1887
  modifications,
1682
1888
  errors: [],
1683
1889
  nextSteps,
1890
+ dryRun: this.options.dryRun,
1891
+ installPlan,
1684
1892
  selectedFramework: this.selectedFramework,
1685
1893
  manualMode: true
1686
1894
  };
@@ -1692,6 +1900,7 @@ class ManualFrameworkInstallationWizard extends AutoInstallationWizard {
1692
1900
  modifications: [],
1693
1901
  errors: [error instanceof Error ? error.message : 'Unknown error'],
1694
1902
  nextSteps: [],
1903
+ dryRun: this.options.dryRun,
1695
1904
  selectedFramework: this.selectedFramework,
1696
1905
  manualMode: true
1697
1906
  };
@@ -1812,6 +2021,14 @@ class ManualFrameworkInstallationWizard extends AutoInstallationWizard {
1812
2021
  * Generate next steps with manual mode info
1813
2022
  */
1814
2023
  generateManualNextSteps() {
2024
+ if (this.options.dryRun) {
2025
+ return [
2026
+ 'Dry run complete. No packages were installed and no files were changed.',
2027
+ `Selected framework: ${this.framework?.name || 'unknown'}`,
2028
+ 'Review the planned install command and file modifications.',
2029
+ 'Run again without --dry-run to apply these changes.'
2030
+ ];
2031
+ }
1815
2032
  return [
1816
2033
  '✅ Manual framework installation completed!',
1817
2034
  `🎯 Selected framework: ${this.framework?.name || 'unknown'}`,
@@ -1840,6 +2057,232 @@ class ManualFrameworkInstallationWizard extends AutoInstallationWizard {
1840
2057
  }
1841
2058
  }
1842
2059
 
2060
+ const AGENT_PLAN_SCHEMA = {
2061
+ type: 'object',
2062
+ properties: {
2063
+ summary: { type: 'string' },
2064
+ targetPackagePath: { type: 'string' },
2065
+ framework: { type: 'string' },
2066
+ packageManager: { type: 'string' },
2067
+ recommendedChecks: {
2068
+ type: 'array',
2069
+ items: { type: 'string' }
2070
+ },
2071
+ risks: {
2072
+ type: 'array',
2073
+ items: { type: 'string' }
2074
+ },
2075
+ confidence: {
2076
+ type: 'number',
2077
+ minimum: 0,
2078
+ maximum: 1
2079
+ }
2080
+ },
2081
+ required: ['summary', 'recommendedChecks', 'risks', 'confidence'],
2082
+ additionalProperties: false
2083
+ };
2084
+ async function runClaudeAgentInstaller(options) {
2085
+ const prompt = `You are the HumanBehavior setup wizard agent.
2086
+
2087
+ Inspect this repository in read-only planning mode and produce a JSON setup plan.
2088
+ Do not edit files. Do not install packages. Do not include API keys or secrets.
2089
+
2090
+ Known deterministic install plan:
2091
+ - Target package path: ${options.installPlan.targetPackagePath}
2092
+ - Workspace root: ${options.installPlan.workspaceRoot}
2093
+ - Package manager: ${options.installPlan.packageManager}
2094
+ - Install command: ${options.installPlan.command}
2095
+ - Framework guess: ${options.framework}
2096
+ - Dry run: ${options.dryRun === true}
2097
+
2098
+ Return only the structured JSON matching the requested schema. Focus on:
2099
+ 1. Whether the target package looks correct.
2100
+ 2. Which files should be instrumented.
2101
+ 3. Which checks should run after deterministic mutation.
2102
+ 4. Risks or ambiguities the deterministic wizard should surface.`;
2103
+ let resultText = '';
2104
+ for await (const message of query({
2105
+ prompt,
2106
+ options: {
2107
+ cwd: options.projectRoot,
2108
+ permissionMode: 'plan',
2109
+ allowedTools: ['Read', 'Glob', 'Grep', 'LS'],
2110
+ disallowedTools: ['Edit', 'MultiEdit', 'Write', 'NotebookEdit', 'Bash'],
2111
+ maxTurns: 6,
2112
+ outputFormat: {
2113
+ type: 'json_schema',
2114
+ schema: AGENT_PLAN_SCHEMA
2115
+ },
2116
+ persistSession: false
2117
+ }
2118
+ })) {
2119
+ if (message.type === 'result' && message.subtype === 'success') {
2120
+ resultText = message.result || '';
2121
+ }
2122
+ }
2123
+ return validateAgentPlan(resultText);
2124
+ }
2125
+ async function runClaudeAgentFullFlow(options) {
2126
+ const snapshot = snapshotProject(options.projectRoot);
2127
+ try {
2128
+ const prompt = `You are the HumanBehavior setup wizard agent.
2129
+
2130
+ Run the full setup flow for this app:
2131
+ 1. Scan the codebase.
2132
+ 2. Install the SDK using exactly this command if install is needed: ${options.installPlan.command}
2133
+ 3. Instrument the client entrypoint using HumanBehavior SDK best practices.
2134
+ 4. Use environment-variable based API key access only. Never write a raw API key.
2135
+ 5. Run safe local checks if package scripts exist.
2136
+ 6. Return a concise JSON summary matching the requested schema.
2137
+
2138
+ Hard constraints:
2139
+ - Work only in this target package: ${options.installPlan.targetPackagePath}
2140
+ - Do not edit files outside the target package.
2141
+ - Do not run destructive commands.
2142
+ - Do not include secrets in output.
2143
+ - If unsure, stop and report risks instead of guessing.
2144
+
2145
+ Framework guess: ${options.framework}
2146
+ Package manager: ${options.installPlan.packageManager}
2147
+ Workspace root: ${options.installPlan.workspaceRoot}`;
2148
+ let resultText = '';
2149
+ for await (const message of query({
2150
+ prompt,
2151
+ options: {
2152
+ cwd: options.installPlan.targetPackagePath,
2153
+ permissionMode: 'acceptEdits',
2154
+ allowedTools: ['Read', 'Glob', 'Grep', 'LS', 'Edit', 'Bash'],
2155
+ disallowedTools: ['Write', 'NotebookEdit', 'MultiEdit'],
2156
+ maxTurns: 12,
2157
+ outputFormat: {
2158
+ type: 'json_schema',
2159
+ schema: AGENT_PLAN_SCHEMA
2160
+ },
2161
+ persistSession: false,
2162
+ canUseTool: async (toolName, input) => {
2163
+ if (toolName === 'Bash') {
2164
+ const command = String(input.command || '');
2165
+ return isAllowedBash(command, options.installPlan.command)
2166
+ ? { behavior: 'allow' }
2167
+ : { behavior: 'deny', message: `Command is outside setup wizard allowlist: ${command}` };
2168
+ }
2169
+ return { behavior: 'allow' };
2170
+ }
2171
+ }
2172
+ })) {
2173
+ if (message.type === 'result') {
2174
+ if (message.subtype === 'success') {
2175
+ resultText = message.result || '';
2176
+ }
2177
+ else {
2178
+ throw new Error(message.subtype);
2179
+ }
2180
+ }
2181
+ }
2182
+ const agentPlan = validateAgentPlan(resultText);
2183
+ return {
2184
+ success: true,
2185
+ agentPlan,
2186
+ result: resultText,
2187
+ rolledBack: false
2188
+ };
2189
+ }
2190
+ catch (error) {
2191
+ restoreProject(options.projectRoot, snapshot);
2192
+ return {
2193
+ success: false,
2194
+ error: error instanceof Error ? error.message : String(error),
2195
+ rolledBack: true
2196
+ };
2197
+ }
2198
+ }
2199
+ function validateAgentPlan(rawResult) {
2200
+ let parsed;
2201
+ try {
2202
+ parsed = JSON.parse(rawResult);
2203
+ }
2204
+ catch {
2205
+ throw new Error('Claude agent did not return valid JSON');
2206
+ }
2207
+ if (typeof parsed.summary !== 'string') {
2208
+ throw new Error('Claude agent plan missing summary');
2209
+ }
2210
+ if (!Array.isArray(parsed.recommendedChecks) || !parsed.recommendedChecks.every((item) => typeof item === 'string')) {
2211
+ throw new Error('Claude agent plan has invalid recommendedChecks');
2212
+ }
2213
+ if (!Array.isArray(parsed.risks) || !parsed.risks.every((item) => typeof item === 'string')) {
2214
+ throw new Error('Claude agent plan has invalid risks');
2215
+ }
2216
+ if (typeof parsed.confidence !== 'number' || parsed.confidence < 0 || parsed.confidence > 1) {
2217
+ throw new Error('Claude agent plan has invalid confidence');
2218
+ }
2219
+ return {
2220
+ summary: parsed.summary,
2221
+ targetPackagePath: typeof parsed.targetPackagePath === 'string' ? parsed.targetPackagePath : undefined,
2222
+ framework: typeof parsed.framework === 'string' ? parsed.framework : undefined,
2223
+ packageManager: typeof parsed.packageManager === 'string' ? parsed.packageManager : undefined,
2224
+ recommendedChecks: parsed.recommendedChecks,
2225
+ risks: parsed.risks,
2226
+ confidence: parsed.confidence,
2227
+ rawResult
2228
+ };
2229
+ }
2230
+ function isAllowedBash(command, installCommand) {
2231
+ if (!command)
2232
+ return false;
2233
+ const trimmed = command.trim();
2234
+ if (trimmed === installCommand)
2235
+ return true;
2236
+ if (/^(npm|pnpm|yarn|bun) run (build|test|lint|typecheck|check)(\s|$)/.test(trimmed))
2237
+ return true;
2238
+ if (/^(npm|pnpm|yarn|bun) (test|lint)(\s|$)/.test(trimmed))
2239
+ return true;
2240
+ return false;
2241
+ }
2242
+ function snapshotProject(root) {
2243
+ const snapshot = new Map();
2244
+ for (const filePath of listProjectFiles(root)) {
2245
+ snapshot.set(filePath, fs.readFileSync(filePath, 'utf8'));
2246
+ }
2247
+ return snapshot;
2248
+ }
2249
+ function restoreProject(root, snapshot) {
2250
+ const currentFiles = new Set(listProjectFiles(root));
2251
+ for (const filePath of currentFiles) {
2252
+ if (!snapshot.has(filePath)) {
2253
+ fs.rmSync(filePath, { force: true });
2254
+ }
2255
+ }
2256
+ for (const [filePath, content] of snapshot.entries()) {
2257
+ if (content !== null) {
2258
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
2259
+ fs.writeFileSync(filePath, content);
2260
+ }
2261
+ }
2262
+ }
2263
+ function listProjectFiles(root) {
2264
+ const out = [];
2265
+ const ignored = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.nuxt', '.svelte-kit']);
2266
+ const walk = (dir) => {
2267
+ if (!fs.existsSync(dir))
2268
+ return;
2269
+ for (const entry of fs.readdirSync(dir)) {
2270
+ if (ignored.has(entry))
2271
+ continue;
2272
+ const fullPath = path.join(dir, entry);
2273
+ const stat = fs.statSync(fullPath);
2274
+ if (stat.isDirectory()) {
2275
+ walk(fullPath);
2276
+ }
2277
+ else if (stat.isFile() && stat.size <= 1024 * 1024) {
2278
+ out.push(fullPath);
2279
+ }
2280
+ }
2281
+ };
2282
+ walk(root);
2283
+ return out;
2284
+ }
2285
+
1843
2286
  /**
1844
2287
  * AI-Enhanced HumanBehavior SDK Auto-Installation CLI
1845
2288
  *
@@ -1880,11 +2323,52 @@ class AIAutoInstallCLI {
1880
2323
  // Run installation
1881
2324
  const spinner = clack.spinner();
1882
2325
  spinner.start('🔍 Analyzing your project with AI...');
1883
- const wizard = new ManualFrameworkInstallationWizard(apiKey, projectPath, framework);
2326
+ const wizard = new ManualFrameworkInstallationWizard(apiKey, projectPath, framework, {
2327
+ dryRun: this.options.dryRun,
2328
+ upgrade: this.options.upgrade
2329
+ });
2330
+ if (this.options.agent) {
2331
+ const detected = await wizard.detectFramework();
2332
+ const installPlan = wizard.planPackageInstall();
2333
+ const agentResult = this.options.agentApply && !this.options.dryRun
2334
+ ? await runClaudeAgentFullFlow({
2335
+ projectRoot: projectPath,
2336
+ installPlan,
2337
+ framework: detected.name,
2338
+ dryRun: this.options.dryRun
2339
+ })
2340
+ : { success: true, agentPlan: await runClaudeAgentInstaller({
2341
+ projectRoot: projectPath,
2342
+ installPlan,
2343
+ framework: detected.name,
2344
+ dryRun: this.options.dryRun
2345
+ }), rolledBack: false };
2346
+ if (agentResult.success && agentResult.agentPlan) {
2347
+ wizard.setAgentPlan(agentResult.agentPlan);
2348
+ if (this.options.agentApply && !this.options.dryRun) {
2349
+ clack.outro('Claude agent completed setup successfully.');
2350
+ this.writeReport({
2351
+ success: true,
2352
+ framework: detected,
2353
+ modifications: [],
2354
+ errors: [],
2355
+ nextSteps: agentResult.agentPlan.recommendedChecks,
2356
+ dryRun: false,
2357
+ installPlan,
2358
+ agentPlan: agentResult.agentPlan
2359
+ }, framework);
2360
+ return;
2361
+ }
2362
+ }
2363
+ else {
2364
+ clack.note(`${agentResult.error || 'Unknown agent error'}\nRolled back: ${agentResult.rolledBack ? 'yes' : 'no'}\nContinuing with deterministic fallback.`, 'Claude Agent Fallback');
2365
+ }
2366
+ }
1884
2367
  const result = await wizard.install();
1885
- spinner.stop('Analysis complete!');
2368
+ spinner.stop(this.options.dryRun ? 'Dry run complete!' : 'Analysis complete!');
1886
2369
  // Display results
1887
2370
  this.displayResults(result, framework);
2371
+ this.writeReport(result, framework);
1888
2372
  }
1889
2373
  catch (error) {
1890
2374
  clack.cancel(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -1895,6 +2379,12 @@ class AIAutoInstallCLI {
1895
2379
  if (this.options.apiKey) {
1896
2380
  return this.options.apiKey;
1897
2381
  }
2382
+ if (this.options.apiKeyFile) {
2383
+ return fs.readFileSync(this.options.apiKeyFile, 'utf8').trim();
2384
+ }
2385
+ if (process.env.HB_API_KEY) {
2386
+ return process.env.HB_API_KEY;
2387
+ }
1898
2388
  const apiKey = await clack.text({
1899
2389
  message: 'Enter your HumanBehavior API key:',
1900
2390
  placeholder: 'hb_...',
@@ -1915,6 +2405,9 @@ class AIAutoInstallCLI {
1915
2405
  return confirmed;
1916
2406
  }
1917
2407
  async chooseFramework() {
2408
+ if (this.options.framework) {
2409
+ return this.options.framework;
2410
+ }
1918
2411
  const framework = await clack.select({
1919
2412
  message: 'Select your framework:',
1920
2413
  options: [
@@ -1934,13 +2427,39 @@ class AIAutoInstallCLI {
1934
2427
  }
1935
2428
  displayResults(result, framework) {
1936
2429
  if (result.success) {
1937
- clack.outro('🎉 Installation completed successfully!');
2430
+ clack.outro(result.dryRun
2431
+ ? 'Dry run completed successfully. No packages were installed and no files were changed.'
2432
+ : '🎉 Installation completed successfully!');
1938
2433
  // Display framework info
1939
2434
  clack.note(`Framework detected: ${result.framework.name} (${result.framework.type})`, 'Framework Info');
1940
2435
  // Display modifications
2436
+ if (result.installPlan) {
2437
+ clack.note([
2438
+ `Command: ${result.installPlan.command}`,
2439
+ `Run from: ${result.installPlan.cwd}`,
2440
+ `Target package: ${result.installPlan.targetPackagePath}`,
2441
+ `Workspace: ${result.installPlan.isWorkspace ? 'yes' : 'no'}`,
2442
+ `Version status: ${result.installPlan.versionStatus}`,
2443
+ result.installPlan.alreadyInstalled
2444
+ ? `Already installed: ${result.installPlan.installedVersion}`
2445
+ : `Reason: ${result.installPlan.reason}`
2446
+ ].join('\n'), result.dryRun ? 'Planned Package Install' : 'Package Install');
2447
+ }
1941
2448
  if (result.modifications && result.modifications.length > 0) {
1942
2449
  const modifications = result.modifications.map((mod) => `${mod.action}: ${mod.filePath} - ${mod.description}`);
1943
- clack.note(modifications.join('\n'), 'Files Modified');
2450
+ clack.note(modifications.join('\n'), result.dryRun ? 'Planned File Changes' : 'Files Modified');
2451
+ }
2452
+ if (result.agentPlan) {
2453
+ clack.note([
2454
+ result.agentPlan.summary,
2455
+ `Confidence: ${Math.round(result.agentPlan.confidence * 100)}%`,
2456
+ result.agentPlan.recommendedChecks.length
2457
+ ? `Checks: ${result.agentPlan.recommendedChecks.join(', ')}`
2458
+ : 'Checks: none',
2459
+ result.agentPlan.risks.length
2460
+ ? `Risks: ${result.agentPlan.risks.join(', ')}`
2461
+ : 'Risks: none'
2462
+ ].join('\n'), 'Claude Agent Plan');
1944
2463
  }
1945
2464
  // Display next steps
1946
2465
  if (result.nextSteps && result.nextSteps.length > 0) {
@@ -1961,6 +2480,37 @@ class AIAutoInstallCLI {
1961
2480
  }
1962
2481
  }
1963
2482
  }
2483
+ writeReport(result, selectedFramework) {
2484
+ if (!this.options.reportJson)
2485
+ return;
2486
+ const report = {
2487
+ schemaVersion: 1,
2488
+ status: result.success ? (result.dryRun ? 'dry-run' : 'completed') : 'failed',
2489
+ selectedFramework,
2490
+ framework: result.framework,
2491
+ dryRun: result.dryRun === true,
2492
+ installPlan: result.installPlan,
2493
+ agentPlan: result.agentPlan
2494
+ ? {
2495
+ summary: result.agentPlan.summary,
2496
+ targetPackagePath: result.agentPlan.targetPackagePath,
2497
+ framework: result.agentPlan.framework,
2498
+ packageManager: result.agentPlan.packageManager,
2499
+ recommendedChecks: result.agentPlan.recommendedChecks,
2500
+ risks: result.agentPlan.risks,
2501
+ confidence: result.agentPlan.confidence
2502
+ }
2503
+ : undefined,
2504
+ modifications: (result.modifications || []).map((mod) => ({
2505
+ filePath: mod.filePath,
2506
+ action: mod.action,
2507
+ description: mod.description
2508
+ })),
2509
+ errors: result.errors || [],
2510
+ nextSteps: result.nextSteps || []
2511
+ };
2512
+ fs.writeFileSync(this.options.reportJson, JSON.stringify(report, null, 2));
2513
+ }
1964
2514
  }
1965
2515
  function parseArgs() {
1966
2516
  const args = process.argv.slice(2);
@@ -1980,20 +2530,34 @@ function parseArgs() {
1980
2530
  case '--dry-run':
1981
2531
  options.dryRun = true;
1982
2532
  break;
2533
+ case '--upgrade':
2534
+ options.upgrade = true;
2535
+ break;
2536
+ case '--agent':
2537
+ options.agent = true;
2538
+ break;
2539
+ case '--agent-apply':
2540
+ options.agent = true;
2541
+ options.agentApply = true;
2542
+ break;
1983
2543
  case '--project':
1984
2544
  case '-p':
1985
2545
  options.projectPath = args[++i];
1986
2546
  break;
2547
+ case '--api-key-file':
2548
+ options.apiKeyFile = args[++i];
2549
+ break;
2550
+ case '--report-json':
2551
+ options.reportJson = args[++i];
2552
+ break;
1987
2553
  case '--framework':
1988
2554
  case '-f':
1989
2555
  options.framework = args[++i];
1990
2556
  break;
1991
2557
  case 'init':
1992
- if (!args[i + 1] || args[i + 1].startsWith('-')) {
1993
- showInitHelp();
1994
- process.exit(1);
2558
+ if (args[i + 1] && !args[i + 1].startsWith('-')) {
2559
+ options.apiKey = args[++i];
1995
2560
  }
1996
- options.apiKey = args[++i];
1997
2561
  break;
1998
2562
  default:
1999
2563
  if (!options.apiKey && !arg.startsWith('-')) {
@@ -2014,7 +2578,12 @@ Options:
2014
2578
  -h, --help Show this help message
2015
2579
  -y, --yes Skip all prompts and use defaults
2016
2580
  --dry-run Show what would be changed without making changes
2581
+ --upgrade Upgrade stale existing SDK versions
2582
+ --agent Use Claude Agent SDK to inspect and plan before deterministic changes
2583
+ --agent-apply Let Claude attempt guarded install/edit/check before deterministic fallback
2017
2584
 
2585
+ --api-key-file <path> Read API key from a local file
2586
+ --report-json <path> Write a structured install report without file contents
2018
2587
  -p, --project <path> Specify project directory
2019
2588
  -f, --framework <name> Specify framework manually
2020
2589
 
@@ -2025,24 +2594,15 @@ Examples:
2025
2594
  npx humanbehavior-js ai-auto-install --framework react --yes
2026
2595
  `);
2027
2596
  }
2028
- function showInitHelp() {
2029
- console.log(`
2030
- Usage: npx humanbehavior-js init <api-key>
2031
-
2032
- Arguments:
2033
- api-key Your HumanBehavior API key (required, starts with "hb_")
2034
-
2035
- Examples:
2036
- npx humanbehavior-js init hb_your_api_key_here
2037
- `);
2597
+ const isMain = import.meta.url === `file://${process.argv[1]}`;
2598
+ if (isMain) {
2599
+ const options = parseArgs();
2600
+ const cli = new AIAutoInstallCLI(options);
2601
+ cli.run().catch((error) => {
2602
+ clack.cancel(`Unexpected error: ${error.message}`);
2603
+ process.exit(1);
2604
+ });
2038
2605
  }
2039
- // Main execution
2040
- const options = parseArgs();
2041
- const cli = new AIAutoInstallCLI(options);
2042
- cli.run().catch((error) => {
2043
- clack.cancel(`Unexpected error: ${error.message}`);
2044
- process.exit(1);
2045
- });
2046
2606
 
2047
2607
  export { AIAutoInstallCLI };
2048
2608
  //# sourceMappingURL=ai-auto-install.js.map