@paulduvall/claude-dev-toolkit 0.0.1-alpha.10 → 0.0.1-alpha.12

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/README.md CHANGED
@@ -1,12 +1,5 @@
1
1
  # Claude Dev Toolkit
2
2
 
3
- [![npm version](https://badge.fury.io/js/%40paulduvall%2Fclaude-dev-toolkit.svg)](https://www.npmjs.com/package/@paulduvall/claude-dev-toolkit)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- ![Test Status](https://img.shields.io/badge/tests-100%25%20passing-brightgreen)
6
- ![Active Commands](https://img.shields.io/badge/active%20commands-13-blue)
7
- ![Experimental Commands](https://img.shields.io/badge/experimental%20commands-45-orange)
8
- ![Total Commands](https://img.shields.io/badge/total%20commands-58-brightgreen)
9
-
10
3
  **Transform Claude Code into a complete development platform** with 58 AI-powered custom commands that automate your entire software development workflow.
11
4
 
12
5
  ## šŸš€ Quick Installation
@@ -266,11 +266,24 @@ program
266
266
  const oidcCmd = new OidcCommand();
267
267
  try {
268
268
  const result = await oidcCmd.execute(options);
269
- if (!result.success && !result.dryRun) {
269
+ if (result.success) {
270
+ if (result.message) {
271
+ console.log(result.message);
272
+ }
273
+ if (options.verbose && result.duration) {
274
+ console.log(`āœ… Completed in ${result.duration}ms`);
275
+ }
276
+ } else {
277
+ console.error(`āŒ OIDC setup failed: ${result.error || 'Unknown error'}`);
278
+ if (result.enhancedError && result.enhancedError.suggestions) {
279
+ result.enhancedError.suggestions.forEach(suggestion => {
280
+ console.log(suggestion);
281
+ });
282
+ }
270
283
  process.exit(1);
271
284
  }
272
285
  } catch (error) {
273
- console.error(`OIDC setup failed: ${error.message}`);
286
+ console.error(`āŒ OIDC setup failed: ${error.message}`);
274
287
  process.exit(1);
275
288
  }
276
289
  });
@@ -292,19 +292,81 @@ Run 'claude-commands oidc --help' for complete setup requirements.`;
292
292
  const { dryRun = false } = options;
293
293
 
294
294
  if (dryRun) {
295
- return this.showDryRun(options);
295
+ await this.showDryRunWithDetection(options);
296
+ return {
297
+ message: 'āœ… Dry run completed successfully',
298
+ dryRun: true
299
+ };
296
300
  }
297
301
 
298
- // Minimal implementation for current phase
299
- this.showProgress('Initializing OIDC command...', options);
302
+ // Phase 2: Auto-detect configuration
303
+ this.showProgress('šŸš€ Initializing OIDC command...', options);
304
+
305
+ const configResult = await this.autoDetectConfiguration(options);
306
+ if (!configResult.success) {
307
+ console.log(`āŒ Configuration detection failed: ${configResult.error}`);
308
+ if (configResult.suggestions) {
309
+ configResult.suggestions.forEach(suggestion => console.log(suggestion));
310
+ }
311
+ return {
312
+ message: 'āŒ OIDC setup failed during configuration detection',
313
+ error: configResult.error
314
+ };
315
+ }
316
+
317
+ // Display detected configuration
318
+ console.log('āœ… Configuration detected successfully:');
319
+ console.log(` šŸ“‚ Repository: ${configResult.git.owner}/${configResult.git.repo}`);
320
+ console.log(` šŸŒ AWS Region: ${configResult.aws.region} (${configResult.aws.source})`);
321
+ console.log(` šŸŽ­ IAM Role: ${configResult.roleName}`);
322
+ console.log('');
323
+
324
+ // For now, show that detection is working but full implementation is still in development
325
+ console.log('šŸ“‹ OIDC Setup Status: Auto-detection implemented āœ…');
326
+ console.log('āš ļø AWS resource creation is in development (Phase 3)');
327
+ console.log('šŸ’” Use --dry-run to preview complete functionality');
300
328
 
301
329
  return {
302
- message: 'OIDC command executed successfully'
330
+ message: 'āœ… OIDC command executed successfully (Phase 2: Detection completed)',
331
+ configuration: configResult
303
332
  };
304
333
  }
305
334
 
306
335
  /**
307
- * Show dry run preview
336
+ * Show dry run preview with Phase 2 detection
337
+ */
338
+ async showDryRunWithDetection(options) {
339
+ console.log('šŸ” Dry Run - Preview of OIDC configuration actions:\n');
340
+
341
+ // Show what detection would find
342
+ console.log('šŸ“‹ Phase 2: Auto-Detection Preview:');
343
+ try {
344
+ const configResult = await this.autoDetectConfiguration(options);
345
+ if (configResult.success) {
346
+ console.log(` āœ… Repository: ${configResult.git.owner}/${configResult.git.repo}`);
347
+ console.log(` āœ… AWS Region: ${configResult.aws.region} (${configResult.aws.source})`);
348
+ console.log(` āœ… IAM Role: ${configResult.roleName}`);
349
+ console.log(` āœ… Policy Template: ${configResult.policyTemplate.name}`);
350
+ } else {
351
+ console.log(` āŒ Configuration detection would fail: ${configResult.error}`);
352
+ }
353
+ } catch (error) {
354
+ console.log(` āŒ Detection error: ${error.message}`);
355
+ }
356
+
357
+ console.log('\nšŸ“‹ Phase 3: AWS Resource Creation (Planned):');
358
+ console.log(' • Create AWS OIDC Identity Provider for GitHub');
359
+ console.log(' • Create IAM role with trust policy for GitHub Actions');
360
+ console.log(' • Attach permission policies to IAM role');
361
+ console.log(' • Set up GitHub repository variables (AWS_DEPLOYMENT_ROLE, AWS_REGION)');
362
+ console.log('\nšŸ’” This was a dry run - no changes were made');
363
+ console.log(' Run without --dry-run to execute OIDC setup (Phase 2 detection only)');
364
+
365
+ return { dryRun: true, message: 'Dry run completed' };
366
+ }
367
+
368
+ /**
369
+ * Show dry run preview (legacy method)
308
370
  */
309
371
  showDryRun(options) {
310
372
  console.log('šŸ” Dry Run - Preview of OIDC configuration actions:\n');
@@ -320,6 +382,330 @@ Run 'claude-commands oidc --help' for complete setup requirements.`;
320
382
  return { dryRun: true, message: 'Dry run completed' };
321
383
  }
322
384
 
385
+ /**
386
+ * REQ-DETECT-001: Git Repository Detection
387
+ * Auto-detect GitHub org/repo from git remote
388
+ */
389
+ async detectGitRepository(options = {}) {
390
+ try {
391
+ const { execSync } = require('child_process');
392
+
393
+ // Get git remotes
394
+ const remotesOutput = execSync('git remote -v', {
395
+ encoding: 'utf8',
396
+ cwd: options.repositoryPath || process.cwd()
397
+ });
398
+
399
+ const remotes = this.parseGitRemotes(remotesOutput);
400
+ const selectedRemote = this.selectPreferredRemote(remotes);
401
+
402
+ if (!selectedRemote) {
403
+ throw new Error('No GitHub remote found. Please add a GitHub remote origin.');
404
+ }
405
+
406
+ const repoInfo = this.parseGitRemote(selectedRemote.url);
407
+ return {
408
+ success: true,
409
+ owner: repoInfo.owner,
410
+ repo: repoInfo.repo,
411
+ remote: selectedRemote.name,
412
+ url: selectedRemote.url
413
+ };
414
+
415
+ } catch (error) {
416
+ return {
417
+ success: false,
418
+ error: error.message,
419
+ suggestions: [
420
+ 'šŸ”§ Ensure you are in a git repository',
421
+ 'šŸ”— Add a GitHub remote: git remote add origin <github-url>',
422
+ 'āœ… Verify remote: git remote -v'
423
+ ]
424
+ };
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Parse git remotes output into structured format
430
+ */
431
+ parseGitRemotes(remotesOutput) {
432
+ const remotes = [];
433
+ const lines = remotesOutput.split('\n').filter(line => line.trim());
434
+
435
+ lines.forEach(line => {
436
+ const match = line.match(/^(\w+)\s+(.+?)\s+\((fetch|push)\)$/);
437
+ if (match && match[3] === 'fetch') { // Only use fetch URLs
438
+ const [, name, url] = match;
439
+ if (url.includes('github.com')) {
440
+ remotes.push({ name, url });
441
+ }
442
+ }
443
+ });
444
+
445
+ return remotes;
446
+ }
447
+
448
+ /**
449
+ * Select preferred remote (prioritize 'origin')
450
+ */
451
+ selectPreferredRemote(remotes) {
452
+ if (remotes.length === 0) return null;
453
+
454
+ // Prefer 'origin' remote
455
+ const origin = remotes.find(remote => remote.name === 'origin');
456
+ if (origin) return origin;
457
+
458
+ // Fall back to first GitHub remote
459
+ return remotes[0];
460
+ }
461
+
462
+ /**
463
+ * Parse git remote URL to extract owner/repo
464
+ * Supports both SSH and HTTPS formats
465
+ */
466
+ parseGitRemote(url) {
467
+ // SSH format: git@github.com:owner/repo.git
468
+ const sshMatch = url.match(/^git@github\.com:([^\/]+)\/(.+?)(?:\.git)?$/);
469
+ if (sshMatch) {
470
+ return {
471
+ owner: sshMatch[1],
472
+ repo: sshMatch[2]
473
+ };
474
+ }
475
+
476
+ // HTTPS format: https://github.com/owner/repo.git
477
+ const httpsMatch = url.match(/^https:\/\/github\.com\/([^\/]+)\/(.+?)(?:\.git)?$/);
478
+ if (httpsMatch) {
479
+ return {
480
+ owner: httpsMatch[1],
481
+ repo: httpsMatch[2]
482
+ };
483
+ }
484
+
485
+ throw new Error(`Unsupported git remote URL format: ${url}`);
486
+ }
487
+
488
+ /**
489
+ * REQ-DETECT-002: AWS Configuration Detection
490
+ * Read AWS CLI config and environment variables
491
+ */
492
+ async detectAWSConfiguration(options = {}) {
493
+ try {
494
+ const fs = require('fs');
495
+ const path = require('path');
496
+ const os = require('os');
497
+
498
+ let region = null;
499
+ let profile = 'default';
500
+
501
+ // 1. Check environment variable first (highest priority)
502
+ region = this.getAWSRegionFromEnvironment();
503
+
504
+ // 2. If no env var, try to read from AWS config files
505
+ if (!region) {
506
+ const awsConfigResult = this.readAWSConfigFiles();
507
+ region = awsConfigResult.region;
508
+ profile = awsConfigResult.profile;
509
+ }
510
+
511
+ // 3. Default to us-east-1 if nothing found
512
+ if (!region) {
513
+ region = 'us-east-1';
514
+ }
515
+
516
+ // 4. Validate region
517
+ const regionValid = this.validateAWSRegion(region);
518
+
519
+ return {
520
+ success: true,
521
+ region: region,
522
+ profile: profile,
523
+ source: region === this.getAWSRegionFromEnvironment() ? 'environment' :
524
+ region === 'us-east-1' ? 'default' : 'config-file',
525
+ valid: regionValid
526
+ };
527
+
528
+ } catch (error) {
529
+ return {
530
+ success: false,
531
+ error: error.message,
532
+ region: 'us-east-1', // Fallback
533
+ suggestions: [
534
+ 'šŸ”§ Set AWS region: export AWS_DEFAULT_REGION=us-east-1',
535
+ 'āš™ļø Configure AWS CLI: aws configure',
536
+ 'āœ… Verify config: aws configure list'
537
+ ]
538
+ };
539
+ }
540
+ }
541
+
542
+ /**
543
+ * Get AWS region from environment variables
544
+ */
545
+ getAWSRegionFromEnvironment() {
546
+ return process.env.AWS_DEFAULT_REGION || process.env.AWS_REGION || null;
547
+ }
548
+
549
+ /**
550
+ * Read AWS CLI config files (~/.aws/config and ~/.aws/credentials)
551
+ */
552
+ readAWSConfigFiles() {
553
+ const fs = require('fs');
554
+ const path = require('path');
555
+ const os = require('os');
556
+
557
+ const awsDir = path.join(os.homedir(), '.aws');
558
+ const configPath = path.join(awsDir, 'config');
559
+ const credentialsPath = path.join(awsDir, 'credentials');
560
+
561
+ let region = null;
562
+ let profile = 'default';
563
+
564
+ // Try to read config file first
565
+ try {
566
+ if (fs.existsSync(configPath)) {
567
+ const configContent = fs.readFileSync(configPath, 'utf8');
568
+ const regionMatch = configContent.match(/^\s*region\s*=\s*(.+)$/m);
569
+ if (regionMatch) {
570
+ region = regionMatch[1].trim();
571
+ }
572
+ }
573
+ } catch (error) {
574
+ // Ignore config file read errors
575
+ }
576
+
577
+ return { region, profile };
578
+ }
579
+
580
+ /**
581
+ * Validate AWS region format and existence
582
+ */
583
+ validateAWSRegion(region) {
584
+ if (!region) return false;
585
+
586
+ // Basic format validation: region should be like us-east-1, eu-west-1, etc.
587
+ const regionPattern = /^[a-z]{2,3}-[a-z]+-\d+$/;
588
+ if (!regionPattern.test(region)) {
589
+ return false;
590
+ }
591
+
592
+ // List of valid AWS regions (simplified list for validation)
593
+ const validRegions = [
594
+ 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2',
595
+ 'eu-west-1', 'eu-west-2', 'eu-west-3', 'eu-central-1',
596
+ 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'ap-northeast-2',
597
+ 'sa-east-1', 'ca-central-1', 'ap-south-1'
598
+ ];
599
+
600
+ return validRegions.includes(region);
601
+ }
602
+
603
+ /**
604
+ * REQ-CLI-003: Zero Configuration Mode
605
+ * Combine git detection + AWS detection for zero-config experience
606
+ */
607
+ async autoDetectConfiguration(options = {}) {
608
+ try {
609
+ this.showProgress('šŸ” Auto-detecting project configuration...', options);
610
+
611
+ // Detect Git repository information
612
+ const gitResult = await this.detectGitRepository(options);
613
+ if (!gitResult.success) {
614
+ return {
615
+ success: false,
616
+ error: 'Git repository detection failed',
617
+ details: gitResult,
618
+ suggestions: gitResult.suggestions
619
+ };
620
+ }
621
+
622
+ // Detect AWS configuration
623
+ const awsResult = await this.detectAWSConfiguration(options);
624
+ if (!awsResult.success) {
625
+ return {
626
+ success: false,
627
+ error: 'AWS configuration detection failed',
628
+ details: awsResult,
629
+ suggestions: awsResult.suggestions
630
+ };
631
+ }
632
+
633
+ // Generate role name based on repository
634
+ const roleName = this.generateRoleName(gitResult.owner, gitResult.repo, options);
635
+
636
+ // Get default policy template
637
+ const policyTemplate = this.getDefaultPolicyTemplate();
638
+
639
+ return {
640
+ success: true,
641
+ git: gitResult,
642
+ aws: awsResult,
643
+ roleName: roleName,
644
+ policyTemplate: policyTemplate,
645
+ zeroConfig: true
646
+ };
647
+
648
+ } catch (error) {
649
+ return {
650
+ success: false,
651
+ error: error.message,
652
+ suggestions: [
653
+ 'šŸ”§ Ensure you are in a git repository with GitHub remote',
654
+ 'āš™ļø Configure AWS CLI or set AWS_DEFAULT_REGION',
655
+ 'āœ… Run with --dry-run to see what would be configured'
656
+ ]
657
+ };
658
+ }
659
+ }
660
+
661
+ /**
662
+ * Auto-generate role name based on repository
663
+ */
664
+ generateRoleName(owner, repo, options = {}) {
665
+ // Use provided role name if specified
666
+ if (options.roleName && options.roleName !== 'GitHubActionsRole') {
667
+ return options.roleName;
668
+ }
669
+
670
+ // Generate role name: GitHubActions-owner-repo
671
+ const safeName = `GitHubActions-${owner}-${repo}`.replace(/[^a-zA-Z0-9-]/g, '-');
672
+
673
+ // Ensure it meets IAM role name requirements (max 64 chars, alphanumeric + hyphens)
674
+ if (safeName.length > 64) {
675
+ return `GitHubActions-${owner}`.substring(0, 64);
676
+ }
677
+
678
+ return safeName;
679
+ }
680
+
681
+ /**
682
+ * Get default policy template for standard use cases
683
+ */
684
+ getDefaultPolicyTemplate() {
685
+ return {
686
+ name: 'standard',
687
+ description: 'Standard permissions for common GitHub Actions workflows',
688
+ policies: [
689
+ {
690
+ name: 'GitHubActionsBasePolicy',
691
+ document: {
692
+ Version: '2012-10-17',
693
+ Statement: [
694
+ {
695
+ Effect: 'Allow',
696
+ Action: [
697
+ 'sts:GetCallerIdentity',
698
+ 'sts:TagSession'
699
+ ],
700
+ Resource: '*'
701
+ }
702
+ ]
703
+ }
704
+ }
705
+ ]
706
+ };
707
+ }
708
+
323
709
  /**
324
710
  * Get help text for OIDC command
325
711
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paulduvall/claude-dev-toolkit",
3
- "version": "0.0.1-alpha.10",
3
+ "version": "0.0.1-alpha.12",
4
4
  "description": "Custom commands toolkit for Claude Code - streamline your development workflow",
5
5
  "author": "Paul Duvall",
6
6
  "license": "MIT",