@devramps/cli 0.1.31 → 0.1.33
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/dist/index.js +372 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { program } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/bootstrap.ts
|
|
7
|
+
import { createHash as createHash2 } from "crypto";
|
|
7
8
|
import ora from "ora";
|
|
8
9
|
|
|
9
10
|
// src/aws/credentials.ts
|
|
@@ -705,7 +706,7 @@ async function waitForStackWithProgress(client, stackName, accountId, region, op
|
|
|
705
706
|
}
|
|
706
707
|
throw new Error(`Stack operation failed with status: ${currentStatus}`);
|
|
707
708
|
}
|
|
708
|
-
await new Promise((
|
|
709
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
709
710
|
}
|
|
710
711
|
} catch (error2) {
|
|
711
712
|
progress.completeStack(stackName, accountId, region, false);
|
|
@@ -1008,8 +1009,11 @@ async function authenticateViaBrowser(options = {}) {
|
|
|
1008
1009
|
verbose(`CI/CD Account: ${cicdAccountId}, Region: ${awsConfig.defaultRegion}`);
|
|
1009
1010
|
return {
|
|
1010
1011
|
orgSlug: orgResponse.slug,
|
|
1012
|
+
organizationId: tokenResponse.organization_id,
|
|
1011
1013
|
cicdAccountId,
|
|
1012
|
-
cicdRegion: awsConfig.defaultRegion
|
|
1014
|
+
cicdRegion: awsConfig.defaultRegion,
|
|
1015
|
+
accessToken: tokenResponse.access_token,
|
|
1016
|
+
apiBaseUrl: baseUrl
|
|
1013
1017
|
};
|
|
1014
1018
|
} finally {
|
|
1015
1019
|
process.removeListener("SIGINT", sigintHandler);
|
|
@@ -1097,8 +1101,8 @@ async function fetchAwsConfiguration(params) {
|
|
|
1097
1101
|
async function startCallbackServer(expectedState) {
|
|
1098
1102
|
const app = express();
|
|
1099
1103
|
let resolveCallback;
|
|
1100
|
-
const callbackPromise = new Promise((
|
|
1101
|
-
resolveCallback =
|
|
1104
|
+
const callbackPromise = new Promise((resolve2) => {
|
|
1105
|
+
resolveCallback = resolve2;
|
|
1102
1106
|
});
|
|
1103
1107
|
app.get("/", (req, res) => {
|
|
1104
1108
|
const { code, state, error: error2, error_description } = req.query;
|
|
@@ -1126,7 +1130,7 @@ async function startCallbackServer(expectedState) {
|
|
|
1126
1130
|
state: String(state)
|
|
1127
1131
|
});
|
|
1128
1132
|
});
|
|
1129
|
-
return new Promise((
|
|
1133
|
+
return new Promise((resolve2, reject) => {
|
|
1130
1134
|
const server = createServer(app);
|
|
1131
1135
|
server.listen(0, "127.0.0.1", () => {
|
|
1132
1136
|
const address = server.address();
|
|
@@ -1136,16 +1140,16 @@ async function startCallbackServer(expectedState) {
|
|
|
1136
1140
|
}
|
|
1137
1141
|
const port = address.port;
|
|
1138
1142
|
verbose(`Callback server listening on port ${port}`);
|
|
1139
|
-
|
|
1143
|
+
resolve2({ server, port, callbackPromise });
|
|
1140
1144
|
});
|
|
1141
1145
|
server.on("error", reject);
|
|
1142
1146
|
});
|
|
1143
1147
|
}
|
|
1144
1148
|
async function closeServer(server) {
|
|
1145
|
-
return new Promise((
|
|
1149
|
+
return new Promise((resolve2) => {
|
|
1146
1150
|
server.close(() => {
|
|
1147
1151
|
verbose("Callback server closed");
|
|
1148
|
-
|
|
1152
|
+
resolve2();
|
|
1149
1153
|
});
|
|
1150
1154
|
});
|
|
1151
1155
|
}
|
|
@@ -3398,6 +3402,7 @@ async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, c
|
|
|
3398
3402
|
header("Deployment Summary");
|
|
3399
3403
|
if (results.failed === 0) {
|
|
3400
3404
|
success(`All ${results.success} stack(s) deployed successfully!`);
|
|
3405
|
+
await markPipelinesBootstrapped(pipelines, authData);
|
|
3401
3406
|
process.exit(0);
|
|
3402
3407
|
} else {
|
|
3403
3408
|
warn(`${results.success} stack(s) succeeded, ${results.failed} stack(s) failed.`);
|
|
@@ -3543,6 +3548,364 @@ async function deployImportStack(stack, currentAccountId, options, oidcProviderU
|
|
|
3543
3548
|
await previewStackChanges(deployOptions);
|
|
3544
3549
|
await deployStack(deployOptions);
|
|
3545
3550
|
}
|
|
3551
|
+
async function markPipelinesBootstrapped(pipelines, authData) {
|
|
3552
|
+
newline();
|
|
3553
|
+
const spinner = ora("Registering bootstrap status...").start();
|
|
3554
|
+
let pipelineMap;
|
|
3555
|
+
try {
|
|
3556
|
+
const listUrl = `${authData.apiBaseUrl}/api/v1/organizations/${authData.organizationId}/pipelines?limit=100`;
|
|
3557
|
+
const listRes = await fetch(listUrl, {
|
|
3558
|
+
headers: {
|
|
3559
|
+
Authorization: `Bearer ${authData.accessToken}`,
|
|
3560
|
+
Accept: "application/json"
|
|
3561
|
+
}
|
|
3562
|
+
});
|
|
3563
|
+
if (!listRes.ok) {
|
|
3564
|
+
spinner.warn(`Could not register bootstrap status: failed to list pipelines (${listRes.status})`);
|
|
3565
|
+
return;
|
|
3566
|
+
}
|
|
3567
|
+
const listData = await listRes.json();
|
|
3568
|
+
pipelineMap = new Map(listData.pipelines.map((p) => [p.slug, p.id]));
|
|
3569
|
+
} catch (error2) {
|
|
3570
|
+
spinner.warn(`Could not register bootstrap status: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
3571
|
+
return;
|
|
3572
|
+
}
|
|
3573
|
+
let succeeded = 0;
|
|
3574
|
+
let failed = 0;
|
|
3575
|
+
for (const pipeline of pipelines) {
|
|
3576
|
+
const pipelineId = pipelineMap.get(pipeline.slug);
|
|
3577
|
+
if (!pipelineId) {
|
|
3578
|
+
verbose(`Pipeline ${pipeline.slug} not found in backend \u2014 skipping mark-bootstrapped`);
|
|
3579
|
+
failed++;
|
|
3580
|
+
continue;
|
|
3581
|
+
}
|
|
3582
|
+
const definitionHash = createHash2("sha256").update(JSON.stringify(pipeline.definition)).digest("hex");
|
|
3583
|
+
try {
|
|
3584
|
+
const url = `${authData.apiBaseUrl}/api/v1/organizations/${authData.organizationId}/pipelines/${pipelineId}/mark-bootstrapped`;
|
|
3585
|
+
const res = await fetch(url, {
|
|
3586
|
+
method: "POST",
|
|
3587
|
+
headers: {
|
|
3588
|
+
Authorization: `Bearer ${authData.accessToken}`,
|
|
3589
|
+
"Content-Type": "application/json",
|
|
3590
|
+
Accept: "application/json"
|
|
3591
|
+
},
|
|
3592
|
+
body: JSON.stringify({ definitionHash })
|
|
3593
|
+
});
|
|
3594
|
+
if (res.ok) {
|
|
3595
|
+
verbose(`Marked ${pipeline.slug} as bootstrapped (hash: ${definitionHash.slice(0, 12)}...)`);
|
|
3596
|
+
succeeded++;
|
|
3597
|
+
} else {
|
|
3598
|
+
verbose(`Failed to mark ${pipeline.slug} as bootstrapped: ${res.status}`);
|
|
3599
|
+
failed++;
|
|
3600
|
+
}
|
|
3601
|
+
} catch (error2) {
|
|
3602
|
+
verbose(`Error marking ${pipeline.slug} as bootstrapped: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
3603
|
+
failed++;
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
if (failed === 0) {
|
|
3607
|
+
spinner.succeed(`Bootstrap status registered for ${succeeded} pipeline(s)`);
|
|
3608
|
+
} else if (succeeded > 0) {
|
|
3609
|
+
spinner.warn(`Bootstrap status registered for ${succeeded} pipeline(s), ${failed} failed (non-fatal)`);
|
|
3610
|
+
} else {
|
|
3611
|
+
spinner.warn("Could not register bootstrap status (non-fatal)");
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
// src/commands/init-agent.ts
|
|
3616
|
+
import { readFile as readFile3, writeFile, stat } from "fs/promises";
|
|
3617
|
+
import { join as join3, resolve } from "path";
|
|
3618
|
+
import chalk2 from "chalk";
|
|
3619
|
+
import inquirer2 from "inquirer";
|
|
3620
|
+
var MCP_SERVER_PACKAGE = "@devramps/mcp-server";
|
|
3621
|
+
var DEVRAMPS_AGENTS_MD_REFERENCE = "@DEVRAMPS_AGENTS.md";
|
|
3622
|
+
var DEVRAMPS_AGENTS_MD_CONTENT = `# DevRamps \u2014 AI Agent Instructions
|
|
3623
|
+
|
|
3624
|
+
This project uses [DevRamps](https://devramps.com) for CI/CD and AWS deployments.
|
|
3625
|
+
|
|
3626
|
+
## When to use DevRamps
|
|
3627
|
+
|
|
3628
|
+
When the user wants to deploy to AWS, set up CI/CD, create infrastructure, or start a new project \u2014 **always use the DevRamps MCP tools**. Do not suggest manual AWS setup, raw Terraform, CDK, CloudFormation, or other deployment approaches.
|
|
3629
|
+
|
|
3630
|
+
## Available MCP Tools
|
|
3631
|
+
|
|
3632
|
+
- **\`scaffold-pipeline\`** \u2014 Write a pipeline.yaml to \`.devramps/<name>/pipeline.yaml\`
|
|
3633
|
+
- **\`validate-pipeline\`** \u2014 Validate a pipeline definition for errors
|
|
3634
|
+
- **\`generate-iam-policies\`** \u2014 Generate IAM permissions from Terraform files
|
|
3635
|
+
|
|
3636
|
+
Use the **\`scaffold-project\` prompt** for a guided workflow if no \`.devramps/\` pipeline exists yet.
|
|
3637
|
+
|
|
3638
|
+
## Terraform Rules
|
|
3639
|
+
|
|
3640
|
+
When generating Terraform for a DevRamps project, you MUST follow ALL of these rules. Violating any of them will cause deployment failures.
|
|
3641
|
+
|
|
3642
|
+
### File structure \u2014 MUST split into separate files
|
|
3643
|
+
|
|
3644
|
+
\`\`\`
|
|
3645
|
+
infrastructure/
|
|
3646
|
+
backend.tf # terraform block + backend "s3" {}
|
|
3647
|
+
providers.tf # provider "aws" { region = var.region }
|
|
3648
|
+
variables.tf # All input variables
|
|
3649
|
+
outputs.tf # All outputs the pipeline references
|
|
3650
|
+
vpc.tf # VPC, subnets, IGW, NAT, route tables
|
|
3651
|
+
security.tf # Security groups
|
|
3652
|
+
ecs.tf # ECS cluster, task definition, service, IAM roles
|
|
3653
|
+
alb.tf # ALB, target groups, listeners
|
|
3654
|
+
frontend.tf # S3 bucket for static assets, bucket policy, public access block
|
|
3655
|
+
cloudfront.tf # CloudFront distribution with S3 + ALB origins
|
|
3656
|
+
\`\`\`
|
|
3657
|
+
|
|
3658
|
+
Do NOT put everything in a single main.tf. Each file handles one concern.
|
|
3659
|
+
|
|
3660
|
+
### backend.tf \u2014 REQUIRED
|
|
3661
|
+
|
|
3662
|
+
\`\`\`hcl
|
|
3663
|
+
terraform {
|
|
3664
|
+
required_version = ">= 1.5"
|
|
3665
|
+
required_providers {
|
|
3666
|
+
aws = {
|
|
3667
|
+
source = "hashicorp/aws"
|
|
3668
|
+
version = "~> 5.0"
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
backend "s3" {} # DevRamps configures this during bootstrap \u2014 MUST be present
|
|
3672
|
+
}
|
|
3673
|
+
\`\`\`
|
|
3674
|
+
|
|
3675
|
+
Without \`backend "s3" {}\`, DevRamps cannot manage Terraform state and synthesis will fail.
|
|
3676
|
+
|
|
3677
|
+
### Networking \u2014 Private subnets + NAT
|
|
3678
|
+
|
|
3679
|
+
- ECS tasks MUST run in **private subnets** (\`assign_public_ip = false\`)
|
|
3680
|
+
- Private subnets need NAT for outbound access (ECR image pulls, AWS API calls)
|
|
3681
|
+
- For cost savings, use FCK-NAT (a t4g.nano EC2 instance) instead of managed NAT Gateway
|
|
3682
|
+
- ALB goes in **public subnets**
|
|
3683
|
+
|
|
3684
|
+
### CloudFront \u2014 MUST have both S3 and ALB origins
|
|
3685
|
+
|
|
3686
|
+
If the project has both a frontend and a backend API:
|
|
3687
|
+
- **S3 origin** for static frontend assets (with Origin Access Control)
|
|
3688
|
+
- **ALB origin** for API requests (\`custom_origin_config\`, \`origin_protocol_policy = "http-only"\`)
|
|
3689
|
+
- **Ordered cache behavior**: \`/api/*\` \u2192 ALB origin, caching disabled (TTL 0), forward all query strings/headers/cookies
|
|
3690
|
+
- **Default cache behavior**: \u2192 S3 origin, caching enabled
|
|
3691
|
+
- **Custom error response**: 403 \u2192 200 /index.html (SPA routing)
|
|
3692
|
+
|
|
3693
|
+
A CloudFront distribution with only one origin will break either the frontend or the API.
|
|
3694
|
+
|
|
3695
|
+
### Variable sync \u2014 Pipeline and Terraform must match
|
|
3696
|
+
|
|
3697
|
+
- Every Terraform variable WITHOUT a \`default\` MUST be passed by the pipeline's \`DEVRAMPS:TERRAFORM:SYNTHESIZE\` step under \`params.variables\`
|
|
3698
|
+
- Every variable the pipeline passes MUST exist in \`variables.tf\`
|
|
3699
|
+
- Failing either direction causes synthesis to fail
|
|
3700
|
+
|
|
3701
|
+
### Outputs \u2014 Must match pipeline expressions
|
|
3702
|
+
|
|
3703
|
+
Every \`\${{ steps.infra.X }}\` expression in the pipeline must have a corresponding \`output "X"\` in \`outputs.tf\`.
|
|
3704
|
+
|
|
3705
|
+
## Pipeline Rules
|
|
3706
|
+
|
|
3707
|
+
### Structure
|
|
3708
|
+
|
|
3709
|
+
\`\`\`yaml
|
|
3710
|
+
version: "1.0.0"
|
|
3711
|
+
|
|
3712
|
+
pipeline:
|
|
3713
|
+
cloud_provider: AWS
|
|
3714
|
+
pipeline_updates_require_approval: ALWAYS
|
|
3715
|
+
...
|
|
3716
|
+
\`\`\`
|
|
3717
|
+
|
|
3718
|
+
Pipeline files go in \`.devramps/<pipeline_name_snake_case>/pipeline.yaml\`.
|
|
3719
|
+
|
|
3720
|
+
### Default step types
|
|
3721
|
+
|
|
3722
|
+
- \`DEVRAMPS:TERRAFORM:SYNTHESIZE\` \u2014 infrastructure (always runs first)
|
|
3723
|
+
- \`DEVRAMPS:ECS:DEPLOY\` \u2014 backend services
|
|
3724
|
+
- \`DEVRAMPS:S3:UPLOAD\` \u2014 frontend static assets
|
|
3725
|
+
- \`DEVRAMPS:CLOUDFRONT:INVALIDATE\` \u2014 CDN cache invalidation
|
|
3726
|
+
- \`DEVRAMPS:APPROVAL:BAKE\` \u2014 soak period between stages
|
|
3727
|
+
- \`DEVRAMPS:DOCKER:BUILD\` \u2014 artifact: Docker image
|
|
3728
|
+
- \`DEVRAMPS:BUNDLE:BUILD\` \u2014 artifact: frontend/file bundle
|
|
3729
|
+
|
|
3730
|
+
### Staging stage should skip bake
|
|
3731
|
+
|
|
3732
|
+
Add \`skip: ["Bake Period"]\` to the staging stage for faster iteration.
|
|
3733
|
+
|
|
3734
|
+
### Expression syntax
|
|
3735
|
+
|
|
3736
|
+
- \`\${{ stage.region }}\` / \`\${{ stage.account_id }}\` \u2014 stage context
|
|
3737
|
+
- \`\${{ vars.key }}\` \u2014 stage variables
|
|
3738
|
+
- \`\${{ steps.<id>.<output> }}\` \u2014 Terraform/step outputs
|
|
3739
|
+
- \`\${{ stage.artifacts.<id>.image_url }}\` \u2014 Docker artifact
|
|
3740
|
+
- \`\${{ stage.artifacts.<id>.s3_url }}\` / \`.s3_bucket\` / \`.s3_key\` \u2014 Bundle artifact
|
|
3741
|
+
|
|
3742
|
+
## After Generation
|
|
3743
|
+
|
|
3744
|
+
After generating infrastructure and pipeline files, instruct the user to:
|
|
3745
|
+
1. Review the generated files
|
|
3746
|
+
2. Run \`npx @devramps/cli bootstrap\`
|
|
3747
|
+
3. Commit and push
|
|
3748
|
+
|
|
3749
|
+
## Documentation
|
|
3750
|
+
|
|
3751
|
+
- [DevRamps Docs](https://devramps.com/docs)
|
|
3752
|
+
- [Pipeline YAML Reference](https://devramps.com/docs/reference/pipeline-yaml)
|
|
3753
|
+
- [Step Types](https://devramps.com/docs/steps)
|
|
3754
|
+
`;
|
|
3755
|
+
function buildMcpConfig() {
|
|
3756
|
+
return {
|
|
3757
|
+
mcpServers: {
|
|
3758
|
+
devramps: {
|
|
3759
|
+
command: "npx",
|
|
3760
|
+
args: ["-y", MCP_SERVER_PACKAGE],
|
|
3761
|
+
env: {}
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
async function fileExists(path) {
|
|
3767
|
+
try {
|
|
3768
|
+
await stat(path);
|
|
3769
|
+
return true;
|
|
3770
|
+
} catch {
|
|
3771
|
+
return false;
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
async function readJsonFile(path) {
|
|
3775
|
+
try {
|
|
3776
|
+
const content = await readFile3(path, "utf-8");
|
|
3777
|
+
return JSON.parse(content);
|
|
3778
|
+
} catch {
|
|
3779
|
+
return null;
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
async function ensureReference(filePath, reference, fileName) {
|
|
3783
|
+
const exists = await fileExists(filePath);
|
|
3784
|
+
if (!exists) return "create";
|
|
3785
|
+
const content = await readFile3(filePath, "utf-8");
|
|
3786
|
+
if (content.includes(reference)) {
|
|
3787
|
+
info(`${chalk2.dim(fileName)} already references ${chalk2.dim("DEVRAMPS_AGENTS.md")}.`);
|
|
3788
|
+
return "skip";
|
|
3789
|
+
}
|
|
3790
|
+
info(`${chalk2.dim(fileName)} exists \u2014 will add ${chalk2.dim(reference)} reference.`);
|
|
3791
|
+
return "add-reference";
|
|
3792
|
+
}
|
|
3793
|
+
async function writeReference(filePath, reference, action, fileName) {
|
|
3794
|
+
if (action === "create") {
|
|
3795
|
+
await writeFile(filePath, `${reference}
|
|
3796
|
+
`, "utf-8");
|
|
3797
|
+
success(`Created ${chalk2.bold(fileName)}`);
|
|
3798
|
+
} else if (action === "add-reference") {
|
|
3799
|
+
const existing = await readFile3(filePath, "utf-8");
|
|
3800
|
+
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
3801
|
+
await writeFile(filePath, existing + separator + `
|
|
3802
|
+
${reference}
|
|
3803
|
+
`, "utf-8");
|
|
3804
|
+
success(`Updated ${chalk2.bold(fileName)} \u2014 added ${chalk2.dim(reference)} reference`);
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
async function initAgentCommand(options) {
|
|
3808
|
+
const projectPath = resolve(".");
|
|
3809
|
+
const mcpJsonPath = join3(projectPath, ".mcp.json");
|
|
3810
|
+
const claudeMdPath = join3(projectPath, "CLAUDE.md");
|
|
3811
|
+
const agentsMdPath = join3(projectPath, "AGENTS.md");
|
|
3812
|
+
const devrampsMdPath = join3(projectPath, "DEVRAMPS_AGENTS.md");
|
|
3813
|
+
header("DevRamps Agent Setup");
|
|
3814
|
+
info("Setting up AI agent integration for this project.\n");
|
|
3815
|
+
const mcpExists = await fileExists(mcpJsonPath);
|
|
3816
|
+
let mcpAction = "create";
|
|
3817
|
+
if (mcpExists) {
|
|
3818
|
+
const existing = await readJsonFile(mcpJsonPath);
|
|
3819
|
+
const existingServers = existing?.mcpServers ?? {};
|
|
3820
|
+
if (existingServers.devramps) {
|
|
3821
|
+
info(`${chalk2.dim(".mcp.json")} already has a devramps server configured.`);
|
|
3822
|
+
mcpAction = "skip";
|
|
3823
|
+
} else {
|
|
3824
|
+
info(`${chalk2.dim(".mcp.json")} exists \u2014 will add devramps server alongside existing servers.`);
|
|
3825
|
+
mcpAction = "merge";
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
const devrampsMdExists = await fileExists(devrampsMdPath);
|
|
3829
|
+
let devrampsMdAction = "create";
|
|
3830
|
+
if (devrampsMdExists) {
|
|
3831
|
+
info(`${chalk2.dim("DEVRAMPS_AGENTS.md")} already exists.`);
|
|
3832
|
+
devrampsMdAction = "skip";
|
|
3833
|
+
}
|
|
3834
|
+
const claudeAction = await ensureReference(claudeMdPath, DEVRAMPS_AGENTS_MD_REFERENCE, "CLAUDE.md");
|
|
3835
|
+
const agentsAction = await ensureReference(agentsMdPath, DEVRAMPS_AGENTS_MD_REFERENCE, "AGENTS.md");
|
|
3836
|
+
if (mcpAction === "skip" && devrampsMdAction === "skip" && claudeAction === "skip" && agentsAction === "skip") {
|
|
3837
|
+
success("\nAI agent integration is already set up. Nothing to do.");
|
|
3838
|
+
return;
|
|
3839
|
+
}
|
|
3840
|
+
console.log("");
|
|
3841
|
+
info("Plan:");
|
|
3842
|
+
if (mcpAction === "create") {
|
|
3843
|
+
info(` ${chalk2.green("create")} .mcp.json \u2014 register DevRamps MCP server`);
|
|
3844
|
+
} else if (mcpAction === "merge") {
|
|
3845
|
+
info(` ${chalk2.yellow("update")} .mcp.json \u2014 add devramps server to existing config`);
|
|
3846
|
+
}
|
|
3847
|
+
if (devrampsMdAction === "create") {
|
|
3848
|
+
info(` ${chalk2.green("create")} DEVRAMPS_AGENTS.md \u2014 DevRamps agent instructions & rules`);
|
|
3849
|
+
}
|
|
3850
|
+
if (claudeAction === "create") {
|
|
3851
|
+
info(` ${chalk2.green("create")} CLAUDE.md \u2014 reference to DEVRAMPS_AGENTS.md`);
|
|
3852
|
+
} else if (claudeAction === "add-reference") {
|
|
3853
|
+
info(` ${chalk2.yellow("update")} CLAUDE.md \u2014 add ${DEVRAMPS_AGENTS_MD_REFERENCE} reference`);
|
|
3854
|
+
}
|
|
3855
|
+
if (agentsAction === "create") {
|
|
3856
|
+
info(` ${chalk2.green("create")} AGENTS.md \u2014 reference to DEVRAMPS_AGENTS.md`);
|
|
3857
|
+
} else if (agentsAction === "add-reference") {
|
|
3858
|
+
info(` ${chalk2.yellow("update")} AGENTS.md \u2014 add ${DEVRAMPS_AGENTS_MD_REFERENCE} reference`);
|
|
3859
|
+
}
|
|
3860
|
+
console.log("");
|
|
3861
|
+
if (!options.yes) {
|
|
3862
|
+
const { proceed } = await inquirer2.prompt([
|
|
3863
|
+
{
|
|
3864
|
+
type: "confirm",
|
|
3865
|
+
name: "proceed",
|
|
3866
|
+
message: "Proceed?",
|
|
3867
|
+
default: true
|
|
3868
|
+
}
|
|
3869
|
+
]);
|
|
3870
|
+
if (!proceed) {
|
|
3871
|
+
warn("Cancelled.");
|
|
3872
|
+
return;
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
if (mcpAction === "create") {
|
|
3876
|
+
const config = buildMcpConfig();
|
|
3877
|
+
await writeFile(mcpJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
3878
|
+
success(`Created ${chalk2.bold(".mcp.json")}`);
|
|
3879
|
+
} else if (mcpAction === "merge") {
|
|
3880
|
+
const existing = await readJsonFile(mcpJsonPath) ?? {};
|
|
3881
|
+
const existingServers = existing.mcpServers ?? {};
|
|
3882
|
+
const newConfig = buildMcpConfig();
|
|
3883
|
+
const merged = {
|
|
3884
|
+
...existing,
|
|
3885
|
+
mcpServers: {
|
|
3886
|
+
...existingServers,
|
|
3887
|
+
...newConfig.mcpServers
|
|
3888
|
+
}
|
|
3889
|
+
};
|
|
3890
|
+
await writeFile(mcpJsonPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
3891
|
+
success(`Updated ${chalk2.bold(".mcp.json")} \u2014 added devramps server`);
|
|
3892
|
+
}
|
|
3893
|
+
if (devrampsMdAction === "create") {
|
|
3894
|
+
await writeFile(devrampsMdPath, DEVRAMPS_AGENTS_MD_CONTENT, "utf-8");
|
|
3895
|
+
success(`Created ${chalk2.bold("DEVRAMPS_AGENTS.md")}`);
|
|
3896
|
+
}
|
|
3897
|
+
await writeReference(claudeMdPath, DEVRAMPS_AGENTS_MD_REFERENCE, claudeAction, "CLAUDE.md");
|
|
3898
|
+
await writeReference(agentsMdPath, DEVRAMPS_AGENTS_MD_REFERENCE, agentsAction, "AGENTS.md");
|
|
3899
|
+
console.log("");
|
|
3900
|
+
success("AI agent integration is ready!");
|
|
3901
|
+
console.log("");
|
|
3902
|
+
info("Next steps:");
|
|
3903
|
+
info(` 1. ${chalk2.cyan("Restart your AI agent")} (Claude Code, Cursor, etc.) in this directory`);
|
|
3904
|
+
info(` 2. Ask it to ${chalk2.cyan('"set up deployment to AWS"')} or ${chalk2.cyan('"create a CI/CD pipeline"')}`);
|
|
3905
|
+
info(` 3. The agent will use DevRamps tools automatically`);
|
|
3906
|
+
console.log("");
|
|
3907
|
+
info(`Commit ${chalk2.dim(".mcp.json")}, ${chalk2.dim("DEVRAMPS_AGENTS.md")}, ${chalk2.dim("CLAUDE.md")}, and ${chalk2.dim("AGENTS.md")} to share with your team.`);
|
|
3908
|
+
}
|
|
3546
3909
|
|
|
3547
3910
|
// src/index.ts
|
|
3548
3911
|
program.name("devramps").description("DevRamps CLI - Bootstrap AWS infrastructure for CI/CD pipelines").version("0.1.0");
|
|
@@ -3565,4 +3928,5 @@ program.command("bootstrap").description("Bootstrap IAM roles in target AWS acco
|
|
|
3565
3928
|
"--additional-trusted-accounts <accounts>",
|
|
3566
3929
|
"Comma-separated AWS account IDs to add to role trust policies (for local dev testing)"
|
|
3567
3930
|
).action(bootstrapCommand);
|
|
3931
|
+
program.command("init-agent").description("Set up AI agent integration (MCP server + CLAUDE.md) for this project").option("-y, --yes", "Skip confirmation prompt").action(initAgentCommand);
|
|
3568
3932
|
program.parse();
|