@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 +0 -7
- package/bin/claude-commands +15 -2
- package/lib/oidc-command.js +391 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
# Claude Dev Toolkit
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@paulduvall/claude-dev-toolkit)
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-

|
|
6
|
-

|
|
7
|
-

|
|
8
|
-

|
|
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
|
package/bin/claude-commands
CHANGED
|
@@ -266,11 +266,24 @@ program
|
|
|
266
266
|
const oidcCmd = new OidcCommand();
|
|
267
267
|
try {
|
|
268
268
|
const result = await oidcCmd.execute(options);
|
|
269
|
-
if (
|
|
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(
|
|
286
|
+
console.error(`ā OIDC setup failed: ${error.message}`);
|
|
274
287
|
process.exit(1);
|
|
275
288
|
}
|
|
276
289
|
});
|
package/lib/oidc-command.js
CHANGED
|
@@ -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
|
-
|
|
295
|
+
await this.showDryRunWithDetection(options);
|
|
296
|
+
return {
|
|
297
|
+
message: 'ā
Dry run completed successfully',
|
|
298
|
+
dryRun: true
|
|
299
|
+
};
|
|
296
300
|
}
|
|
297
301
|
|
|
298
|
-
//
|
|
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