context101-cli 0.1.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.
- package/bin/context101.js +11 -0
- package/package.json +26 -0
- package/src/amplify-repo.js +25 -0
- package/src/aws-profiles.js +128 -0
- package/src/bedrock-access.js +129 -0
- package/src/cdk-invoke.js +231 -0
- package/src/checks.js +165 -0
- package/src/clone.js +63 -0
- package/src/config.js +102 -0
- package/src/defaults.js +28 -0
- package/src/deploy-env-load.js +74 -0
- package/src/deploy.js +138 -0
- package/src/docker.js +120 -0
- package/src/embedding-models.js +184 -0
- package/src/env-file.js +140 -0
- package/src/exec.js +34 -0
- package/src/hosted-url.js +35 -0
- package/src/init.js +435 -0
- package/src/main.js +41 -0
- package/src/parse-args.js +320 -0
- package/src/plan.js +111 -0
- package/src/prompt.js +104 -0
- package/src/redact.js +28 -0
- package/src/repo.js +79 -0
- package/src/secrets.js +9 -0
- package/src/stacks.js +187 -0
- package/src/style.js +54 -0
package/src/stacks.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { DEPLOY_CLI, DESTROY_CLI, LIST_CLI, SMOOTH_REGION } from "./defaults.js";
|
|
2
|
+
import { runCdk } from "./cdk-invoke.js";
|
|
3
|
+
import { createExec } from "./exec.js";
|
|
4
|
+
import { findRepoRoot } from "./repo.js";
|
|
5
|
+
import { banner, writers } from "./style.js";
|
|
6
|
+
|
|
7
|
+
export function parseStackSummaries(payload) {
|
|
8
|
+
const rows = payload?.StackSummaries;
|
|
9
|
+
return Array.isArray(rows) ? rows : [];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function isContext101Deployment(stack) {
|
|
13
|
+
if (!stack || stack.ParentId) return false;
|
|
14
|
+
if (String(stack.StackStatus || "") === "DELETE_COMPLETE") return false;
|
|
15
|
+
const name = String(stack.StackName || "");
|
|
16
|
+
const desc = String(stack.TemplateDescription || "");
|
|
17
|
+
return /context101/i.test(name) || /context101/i.test(desc);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function listDeployments({ exec, env, region = SMOOTH_REGION } = {}) {
|
|
21
|
+
if (!exec) {
|
|
22
|
+
return { ok: false, stacks: [], error: "aws cli not available" };
|
|
23
|
+
}
|
|
24
|
+
const result = exec({
|
|
25
|
+
command: "aws",
|
|
26
|
+
args: ["cloudformation", "list-stacks", "--region", region, "--output", "json"],
|
|
27
|
+
env,
|
|
28
|
+
});
|
|
29
|
+
if (!result.ok) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
stacks: [],
|
|
33
|
+
error: result.stderr || result.stdout || "aws cloudformation list-stacks failed",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const stacks = parseStackSummaries(JSON.parse(result.stdout || "{}")).filter(
|
|
38
|
+
isContext101Deployment
|
|
39
|
+
);
|
|
40
|
+
stacks.sort((a, b) => String(a.StackName).localeCompare(String(b.StackName)));
|
|
41
|
+
return { ok: true, stacks, error: null };
|
|
42
|
+
} catch {
|
|
43
|
+
return { ok: false, stacks: [], error: "could not parse cloudformation list-stacks" };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function formatDeployments(stacks, { region = SMOOTH_REGION } = {}) {
|
|
48
|
+
if (!stacks.length) {
|
|
49
|
+
return [`No Context101 deployments in ${region}.`, `Next: ${DEPLOY_CLI}`].join("\n");
|
|
50
|
+
}
|
|
51
|
+
const nameW = Math.max(4, ...stacks.map((s) => String(s.StackName).length));
|
|
52
|
+
const statusW = Math.max(6, ...stacks.map((s) => String(s.StackStatus).length));
|
|
53
|
+
const lines = [
|
|
54
|
+
`Context101 deployments in ${region}`,
|
|
55
|
+
"",
|
|
56
|
+
`${"NAME".padEnd(nameW)} ${"STATUS".padEnd(statusW)} UPDATED`,
|
|
57
|
+
];
|
|
58
|
+
for (const stack of stacks) {
|
|
59
|
+
const updated = stack.LastUpdatedTime || stack.CreationTime || "";
|
|
60
|
+
lines.push(
|
|
61
|
+
`${String(stack.StackName).padEnd(nameW)} ${String(stack.StackStatus).padEnd(statusW)} ${updated}`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function runList(opts, ctx) {
|
|
68
|
+
const io = writers(ctx);
|
|
69
|
+
const exec = ctx.exec ?? createExec(ctx.env);
|
|
70
|
+
|
|
71
|
+
banner(ctx);
|
|
72
|
+
|
|
73
|
+
const repoRoot = findRepoRoot(ctx.cwd);
|
|
74
|
+
if (!repoRoot) {
|
|
75
|
+
io.err("run this from a Context101 checkout (needs cdk/ and web/).");
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const env = withAwsAuth(ctx.env ?? {}, {
|
|
80
|
+
profile: opts.awsProfile,
|
|
81
|
+
accessKeyId: opts.awsAccessKeyId,
|
|
82
|
+
secretAccessKey: opts.awsSecretAccessKey,
|
|
83
|
+
});
|
|
84
|
+
const listed = listDeployments({ exec, env, region: SMOOTH_REGION });
|
|
85
|
+
if (!listed.ok) {
|
|
86
|
+
io.err(listed.error);
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
io.write(formatDeployments(listed.stacks, { region: SMOOTH_REGION }));
|
|
90
|
+
io.write("");
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function runDestroy(opts, ctx) {
|
|
95
|
+
const io = writers(ctx);
|
|
96
|
+
const exec = ctx.exec ?? createExec(ctx.env);
|
|
97
|
+
|
|
98
|
+
banner(ctx);
|
|
99
|
+
if (opts.dryRun) {
|
|
100
|
+
io.dim("dry-run — destroy nothing");
|
|
101
|
+
io.write("");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const repoRoot = findRepoRoot(ctx.cwd);
|
|
105
|
+
if (!repoRoot) {
|
|
106
|
+
io.err("run this from a Context101 checkout (needs cdk/ and web/).");
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const stackName = String(opts.stackName || "").trim();
|
|
111
|
+
if (!stackName) {
|
|
112
|
+
io.err(`destroy needs a stack name from \`${LIST_CLI}\`.`);
|
|
113
|
+
io.write(`Next: ${LIST_CLI}`);
|
|
114
|
+
return 1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const env = withAwsAuth(ctx.env ?? {}, {
|
|
118
|
+
profile: opts.awsProfile,
|
|
119
|
+
accessKeyId: opts.awsAccessKeyId,
|
|
120
|
+
secretAccessKey: opts.awsSecretAccessKey,
|
|
121
|
+
});
|
|
122
|
+
const listed = listDeployments({ exec, env, region: SMOOTH_REGION });
|
|
123
|
+
if (!listed.ok) {
|
|
124
|
+
io.err(listed.error);
|
|
125
|
+
return 1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
io.write(formatDeployments(listed.stacks, { region: SMOOTH_REGION }));
|
|
129
|
+
io.write("");
|
|
130
|
+
|
|
131
|
+
const known = listed.stacks.some((stack) => stack.StackName === stackName);
|
|
132
|
+
if (!known) {
|
|
133
|
+
io.err(`unknown stack ${stackName}. Use a name from \`${LIST_CLI}\`.`);
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (opts.dryRun) {
|
|
138
|
+
io.write(`Would destroy ${stackName}`);
|
|
139
|
+
io.write(`Next: ${DESTROY_CLI} ${stackName} --yes`);
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const tty = Boolean(ctx.stdin && ctx.stdin.isTTY && ctx.stdout && ctx.stdout.isTTY);
|
|
144
|
+
if (!opts.yes) {
|
|
145
|
+
if (!tty) {
|
|
146
|
+
io.err("not a TTY. Re-run with --yes to destroy.");
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
const confirm = ctx.confirmDestroy ?? confirmDestroyPrompt;
|
|
150
|
+
const ok = await confirm(stackName);
|
|
151
|
+
if (!ok) {
|
|
152
|
+
io.write("Cancelled.");
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
io.warn(
|
|
158
|
+
"Non-default brains are not in CloudFormation — delete them from /brains first."
|
|
159
|
+
);
|
|
160
|
+
io.write(`Destroying ${stackName}…`);
|
|
161
|
+
return (ctx.runDeploy ?? runCdk)({
|
|
162
|
+
repoRoot,
|
|
163
|
+
action: "destroy",
|
|
164
|
+
stackName,
|
|
165
|
+
env,
|
|
166
|
+
home: opts.home,
|
|
167
|
+
envFile: opts.envFile,
|
|
168
|
+
cwd: ctx.cwd,
|
|
169
|
+
exec,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function confirmDestroyPrompt(stackName) {
|
|
174
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
175
|
+
return confirm({
|
|
176
|
+
message: `Destroy ${stackName}? This cannot be undone.`,
|
|
177
|
+
default: false,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function withAwsAuth(env, { profile, accessKeyId, secretAccessKey } = {}) {
|
|
182
|
+
const next = { ...env };
|
|
183
|
+
if (profile) next.AWS_PROFILE = profile;
|
|
184
|
+
if (accessKeyId) next.AWS_ACCESS_KEY_ID = accessKeyId;
|
|
185
|
+
if (secretAccessKey) next.AWS_SECRET_ACCESS_KEY = secretAccessKey;
|
|
186
|
+
return next;
|
|
187
|
+
}
|
package/src/style.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
function enabled(stream) {
|
|
2
|
+
if (process.env.NO_COLOR) return false;
|
|
3
|
+
if (process.env.FORCE_COLOR === "0") return false;
|
|
4
|
+
return Boolean(stream && stream.isTTY);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function palette(stream = process.stdout) {
|
|
8
|
+
if (!enabled(stream)) {
|
|
9
|
+
return { red: "", green: "", yellow: "", bold: "", dim: "", reset: "" };
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
red: "\x1b[31m",
|
|
13
|
+
green: "\x1b[32m",
|
|
14
|
+
yellow: "\x1b[33m",
|
|
15
|
+
bold: "\x1b[1m",
|
|
16
|
+
dim: "\x1b[2m",
|
|
17
|
+
reset: "\x1b[0m",
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function writers(io) {
|
|
22
|
+
const out = io.stdout;
|
|
23
|
+
const err = io.stderr;
|
|
24
|
+
const c = palette(out);
|
|
25
|
+
return {
|
|
26
|
+
c,
|
|
27
|
+
write(line = "") {
|
|
28
|
+
out.write(`${line}\n`);
|
|
29
|
+
},
|
|
30
|
+
ok(msg) {
|
|
31
|
+
out.write(`${c.green}✓${c.reset} ${msg}\n`);
|
|
32
|
+
},
|
|
33
|
+
warn(msg) {
|
|
34
|
+
err.write(`${c.yellow}!${c.reset} ${msg}\n`);
|
|
35
|
+
},
|
|
36
|
+
err(msg) {
|
|
37
|
+
err.write(`${c.red}✗${c.reset} ${msg}\n`);
|
|
38
|
+
},
|
|
39
|
+
dim(msg) {
|
|
40
|
+
out.write(`${c.dim}${msg}${c.reset}\n`);
|
|
41
|
+
},
|
|
42
|
+
bold(msg) {
|
|
43
|
+
out.write(`${c.bold}${msg}${c.reset}\n`);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function banner(io) {
|
|
49
|
+
const { c, write } = writers(io);
|
|
50
|
+
write();
|
|
51
|
+
write(`${c.bold}Context101${c.reset}`);
|
|
52
|
+
write(`${c.dim}self-host setup — web/ + CDK + MCP on your AWS account${c.reset}`);
|
|
53
|
+
write();
|
|
54
|
+
}
|