context101-cli 0.1.1 → 0.1.3

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 ADDED
@@ -0,0 +1,52 @@
1
+ # context101-cli
2
+
3
+ your context. every agent.
4
+
5
+ Thin self-host CLI for [Context101](https://github.com/jginorio/context101) — a wrapper around Amazon Bedrock Knowledge Bases. Self-host now; hosted later (not there yet). Alpha / trusted-team.
6
+
7
+ This is the AWS front door: init, deploy, list, destroy, config. Not a wiki app.
8
+
9
+ **`npx context101` (unscoped) is Context7's MCP — not this tool.** Use `context101-cli`.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm i -g context101-cli
15
+ context101 <cmd>
16
+ ```
17
+
18
+ or
19
+
20
+ ```bash
21
+ npx context101-cli@latest <cmd>
22
+ ```
23
+
24
+ ## Commands
25
+
26
+ | Command | What it does |
27
+ | --- | --- |
28
+ | `context101 init` | write deploy-env; TTY asks to deploy |
29
+ | `context101 deploy` | deploy the AWS stack |
30
+ | `context101 list` | list Context101 CloudFormation stacks |
31
+ | `context101 destroy <name>` | tear down a listed stack |
32
+ | `context101 config` | show deploy-env keys (values redacted) |
33
+ | `context101 config set KEY=value` | write one key (chmod 600; value is not printed) |
34
+ | `context101 help` | list commands |
35
+
36
+ `list`, `help`, and `destroy --dry-run` work without a checkout.
37
+
38
+ ```bash
39
+ context101 list
40
+ context101 help
41
+ context101 destroy Context101Stack --dry-run
42
+ ```
43
+
44
+ ## Name collision
45
+
46
+ The publishable package is **context101-cli**. The bin name is `context101`.
47
+
48
+ `npx context101` downloads Context7's MCP from npm. Unrelated.
49
+
50
+ ## Repo
51
+
52
+ https://github.com/jginorio/context101
package/bin/context101.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "context101-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "private": false,
5
- "description": "Context101 self-host CLI. Use `npx context101-cli` or the `context101` bin. The public npm package named context101 is Context7's MCP, not this tool.",
5
+ "description": "Context101 self-host CLI — your context. every agent. Use `npx context101-cli` or the `context101` bin. Unscoped `npx context101` is Context7's MCP, not this tool.",
6
+ "license": "MIT",
6
7
  "type": "module",
7
8
  "bin": {
8
9
  "context101": "./bin/context101.js"
@@ -11,6 +12,22 @@
11
12
  "bin",
12
13
  "src"
13
14
  ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/jginorio/context101.git"
18
+ },
19
+ "homepage": "https://github.com/jginorio/context101",
20
+ "bugs": {
21
+ "url": "https://github.com/jginorio/context101/issues"
22
+ },
23
+ "keywords": [
24
+ "context101",
25
+ "cli",
26
+ "knowledge-base",
27
+ "bedrock",
28
+ "mcp",
29
+ "self-host"
30
+ ],
14
31
  "scripts": {
15
32
  "test": "node --test test/*.test.js"
16
33
  },
@@ -19,8 +36,5 @@
19
36
  },
20
37
  "dependencies": {
21
38
  "@inquirer/prompts": "^7.8.4"
22
- },
23
- "publishConfig": {
24
- "access": "public"
25
39
  }
26
40
  }
package/src/clone.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { existsSync, mkdirSync } from "node:fs";
2
+ import { homedir } from "node:os";
2
3
  import path from "node:path";
3
- import { DEFAULT_AMPLIFY_REPO } from "./defaults.js";
4
+ import { DEFAULT_AMPLIFY_REPO, HOME_SRC_REL } from "./defaults.js";
4
5
  import { findRepoRoot } from "./repo.js";
5
6
 
6
7
  export const CLONE_URL = DEFAULT_AMPLIFY_REPO;
@@ -10,6 +11,10 @@ export function resolveCloneDir(cwd, dir) {
10
11
  return path.resolve(cwd, dir || DEFAULT_CLONE_DIR);
11
12
  }
12
13
 
14
+ export function homeSrcDir(homeDir = homedir()) {
15
+ return path.join(homeDir, HOME_SRC_REL);
16
+ }
17
+
13
18
  export function ensureRepoRoot({
14
19
  cwd,
15
20
  dir,
@@ -17,16 +22,27 @@ export function ensureRepoRoot({
17
22
  io,
18
23
  dryRun = false,
19
24
  exists = existsSync,
25
+ homeDir,
26
+ preferHomeClone = false,
20
27
  } = {}) {
21
28
  const existing = findRepoRoot(cwd, exists);
22
29
  if (existing) return { repoRoot: existing, cloned: false };
23
30
 
24
- const target = resolveCloneDir(cwd, dir);
25
- const already = findRepoRoot(target, exists);
26
- if (already) return { repoRoot: already, cloned: false };
31
+ const localTarget = resolveCloneDir(cwd, dir);
32
+ const alreadyLocal = findRepoRoot(localTarget, exists);
33
+ if (alreadyLocal) return { repoRoot: alreadyLocal, cloned: false };
34
+
35
+ const resolvedHome = homeDir ?? homedir();
36
+ const homeTarget = homeSrcDir(resolvedHome);
37
+ if (preferHomeClone) {
38
+ const alreadyHome = findRepoRoot(homeTarget, exists);
39
+ if (alreadyHome) return { repoRoot: alreadyHome, cloned: false };
40
+ }
41
+
42
+ const target = preferHomeClone && !dir ? homeTarget : localTarget;
27
43
 
28
44
  if (dryRun) {
29
- io?.write?.(`Would clone ${CLONE_URL} into ${path.relative(cwd, target) || target}`);
45
+ io?.write?.(`Would clone ${CLONE_URL} into ${displayCloneTarget(cwd, target, resolvedHome)}`);
30
46
  return { repoRoot: target, cloned: false, wouldClone: true };
31
47
  }
32
48
 
@@ -58,6 +74,13 @@ export function ensureRepoRoot({
58
74
  error: `cloned ${target} but it is not a Context101 checkout (needs cdk/ and web/)`,
59
75
  };
60
76
  }
61
- io?.ok?.(`cloned into ${path.relative(cwd, cloned) || cloned}`);
77
+ io?.ok?.(`cloned into ${displayCloneTarget(cwd, cloned, resolvedHome)}`);
62
78
  return { repoRoot: cloned, cloned: true };
63
79
  }
80
+
81
+ function displayCloneTarget(cwd, target, homeDir) {
82
+ if (homeDir && (target === homeDir || target.startsWith(homeDir + path.sep))) {
83
+ return `~${target.slice(homeDir.length)}`;
84
+ }
85
+ return path.relative(cwd, target) || target;
86
+ }
package/src/config.js CHANGED
@@ -52,6 +52,7 @@ export async function runConfig(opts, ctx) {
52
52
  envFile: opts.envFile,
53
53
  home: opts.home,
54
54
  cwd: ctx.cwd,
55
+ homeDir: ctx.homeDir,
55
56
  });
56
57
 
57
58
  if (opts.configAction === "set") {
@@ -85,7 +86,7 @@ async function writeConfig(opts, { io, filePath }) {
85
86
  return 1;
86
87
  }
87
88
  if (!filePath) {
88
- io.err("no deploy-env path. Re-run from a checkout or pass --deploy-env.");
89
+ io.err("no deploy-env path. Pass --home or --deploy-env.");
89
90
  return 1;
90
91
  }
91
92
 
package/src/defaults.js CHANGED
@@ -10,6 +10,7 @@ export const BILLING_ENABLED = "false";
10
10
  export const EXAMPLE_ENV_REL = "cdk/.deploy-env.example";
11
11
  export const REPO_ENV_REL = "cdk/.deploy-env";
12
12
  export const HOME_ENV_REL = ".context101/deploy-env";
13
+ export const HOME_SRC_REL = ".context101/src";
13
14
  export const DEPLOY_CLI = "context101 deploy";
14
15
  export const LIST_CLI = "context101 list";
15
16
  export const DESTROY_CLI = "context101 destroy";
@@ -39,23 +39,22 @@ export function findDeployEnvPath({
39
39
  home = false,
40
40
  cwd,
41
41
  exists = existsSync,
42
+ homeDir,
42
43
  } = {}) {
44
+ const resolvedHome = homeDir ?? homedir();
43
45
  if (envFile) {
44
46
  return path.isAbsolute(envFile)
45
47
  ? envFile
46
48
  : path.resolve(cwd ?? repoRoot ?? process.cwd(), envFile);
47
49
  }
48
- if (home) {
49
- const homePath = path.join(homedir(), HOME_ENV_REL);
50
- return exists(homePath) ? homePath : homePath;
51
- }
50
+ const homePath = path.join(resolvedHome, HOME_ENV_REL);
51
+ if (home) return homePath;
52
52
  if (repoRoot) {
53
53
  const repoPath = path.join(repoRoot, ...REPO_ENV_REL.split("/"));
54
54
  if (exists(repoPath)) return repoPath;
55
55
  }
56
- const homePath = path.join(homedir(), HOME_ENV_REL);
57
56
  if (exists(homePath)) return homePath;
58
- return repoRoot ? path.join(repoRoot, ...REPO_ENV_REL.split("/")) : null;
57
+ return repoRoot ? path.join(repoRoot, ...REPO_ENV_REL.split("/")) : homePath;
59
58
  }
60
59
 
61
60
  export function readDeployEnvFile(filePath, { readFile = readFileSync, exists = existsSync } = {}) {
package/src/main.js CHANGED
@@ -3,7 +3,7 @@ import { runConfig } from "./config.js";
3
3
  import { runDeploy } from "./deploy.js";
4
4
  import { runInit } from "./init.js";
5
5
  import { runDestroy, runList } from "./stacks.js";
6
- import { writers } from "./style.js";
6
+ import { banner, writers } from "./style.js";
7
7
 
8
8
  export async function main(argv, ctx) {
9
9
  const io = writers(ctx);
@@ -19,8 +19,11 @@ export async function main(argv, ctx) {
19
19
  throw error;
20
20
  }
21
21
 
22
- if (opts.help) {
23
- io.write(helpText());
22
+ if (opts.help || opts.command === "help") {
23
+ const topic =
24
+ opts.helpTopic ?? (opts.command !== "help" ? opts.command : null);
25
+ banner(ctx);
26
+ io.write(helpText(topic));
24
27
  return 0;
25
28
  }
26
29
 
package/src/parse-args.js CHANGED
@@ -1,85 +1,90 @@
1
1
  import { DRIVER_NEON, DRIVER_POSTGRES } from "./defaults.js";
2
2
 
3
- const FLAG_HELP = `
4
- Usage: context101 <command> [options]
5
-
6
- init write a local secrets file (default); TTY asks to deploy
7
- deploy deploy the AWS stack (loads deploy-env, invokes cdk)
8
- diff cdk diff with the same context flags
9
- synth cdk synth with the same context flags
10
- list, ls list Context101 CloudFormation deployments
11
- destroy, remove, rm tear down a listed stack (name required)
12
- config show deploy-env keys (values redacted)
13
- config set KEY=value write one key (chmod 600; value is not printed)
14
-
15
- CDK fails closed: a bare \`cdk deploy\` without \`-c token=\` throws
16
- instead of deleting MCP / Amplify. The CLI is the front door.
17
-
18
- context101 init
19
- context101 deploy
20
- npx context101-cli init
21
- npx context101-cli deploy
22
-
23
- --dry-run print the plan; write nothing, deploy nothing
3
+ const COMMAND_LINES = [
4
+ ["init", "write deploy-env (default); TTY asks to deploy"],
5
+ ["deploy", "deploy the AWS stack"],
6
+ ["diff", "cdk diff with the same context"],
7
+ ["synth", "cdk synth with the same context"],
8
+ ["list", "list Context101 CloudFormation stacks"],
9
+ ["destroy", "tear down a listed stack (name required)"],
10
+ ["config", "show deploy-env keys (values redacted)"],
11
+ ["config set", "write one key (chmod 600; value is not printed)"],
12
+ ["help", "list commands"],
13
+ ];
14
+
15
+ const TOPIC_HELP = {
16
+ init: `init write deploy-env (default); TTY asks to deploy
17
+
18
+ --dry-run
24
19
  --yes, -y accept defaults (creates RDS if no --database-url);
25
- does not deploy unless --deploy is also passed
20
+ required --aws-profile when several exist
26
21
  --force overwrite an existing env file
27
- --dir <path> clone into this directory when not in a checkout
22
+ --dir <path> clone here when not in a checkout
28
23
  --deploy-env <path> default: <repo>/cdk/.deploy-env
29
- --home write ~/.context101/deploy-env instead
30
- --database-url <url> Postgres URL (also reads DATABASE_URL)
31
- --create-rds CDK provisions RDS Postgres (default when no URL)
24
+ --home ~/.context101/deploy-env
25
+ --database-url <url>
26
+ --create-rds default when no URL
32
27
  --database-driver ${DRIVER_NEON} | ${DRIVER_POSTGRES}
33
28
  --database-prepare true | false
34
- --aws-profile <name> also reads AWS_PROFILE; required with --yes
35
- when more than one profile exists
36
- --aws-access-key-id used when no profile exists (also AWS_ACCESS_KEY_ID)
29
+ --aws-profile <name>
30
+ --aws-access-key-id
37
31
  --aws-secret-access-key
38
- used when no profile exists (also AWS_SECRET_ACCESS_KEY)
39
- --repo <url> watch this GitHub repo with Amplify
40
- (default: skip Amplify, unless gh login is jginorio)
41
- --embed-model <id> optional CDK default embedding model
42
- (brains still pick any Titan/Cohere model in the app)
43
- --skip-bedrock-access do not request Bedrock model access during init
44
- --seed first deploy uploads knowledge/ once
45
- --deploy deploy after writing without asking
46
-
47
- deploy / diff / synth:
48
- --seed upload knowledge/ once (first deploy only)
32
+ --repo <url> Amplify watch (skipped unless gh login is jginorio)
33
+ --embed-model <id>
34
+ --skip-bedrock-access
35
+ --seed
36
+ --deploy deploy after writing without asking`,
37
+
38
+ deploy: `deploy deploy the AWS stack
39
+
40
+ --seed
49
41
  --deploy-env <path>
50
42
  --home
51
- --dry-run print the command; invoke nothing
43
+ --dry-run`,
44
+
45
+ diff: `diff — cdk diff with the same context as deploy
46
+
47
+ --seed
48
+ --deploy-env <path>
49
+ --home
50
+ --dry-run`,
51
+
52
+ synth: `synth — cdk synth with the same context as deploy
53
+
54
+ --seed
55
+ --deploy-env <path>
56
+ --home
57
+ --dry-run`,
58
+
59
+ list: `list — list Context101 CloudFormation stacks (no checkout)
52
60
 
53
- list:
54
61
  --aws-profile <name>
55
62
  --aws-access-key-id
56
- --aws-secret-access-key
63
+ --aws-secret-access-key`,
57
64
 
58
- destroy <StackName>:
59
- --yes, -y skip the confirmation prompt
65
+ destroy: `destroy <name> — tear down a listed stack (clones if needed)
66
+
67
+ --yes, -y
60
68
  --aws-profile <name>
61
69
  --aws-access-key-id
62
70
  --aws-secret-access-key
71
+ --dir <path>
63
72
  --deploy-env <path>
64
73
  --home
65
- --dry-run print the plan; destroy nothing
74
+ --dry-run`,
75
+
76
+ config: `config — show deploy-env keys (values redacted)
66
77
 
67
- config:
68
78
  --deploy-env <path>
69
- --home
79
+ --home`,
70
80
 
71
- From this checkout (after npm install):
72
- npm run context101 -- init
73
- npm run context101 -- deploy
74
- npx context101-cli init
75
- npx context101-cli deploy
76
- context101 list
77
- context101 destroy Context101Stack
78
- context101 config
81
+ "config set": `config set KEY=value — write one key (chmod 600; value is not printed)
79
82
 
80
- npx context101 (unscoped) downloads Context7's MCP from npm — unrelated.
81
- The publishable CLI is context101-cli; the bin name is context101.
82
- `.trim();
83
+ --deploy-env <path>
84
+ --home`,
85
+
86
+ help: `help [command] — list commands, or flags for one command`,
87
+ };
83
88
 
84
89
  const INIT_ONLY = new Set([
85
90
  "--yes",
@@ -102,6 +107,7 @@ const INIT_ONLY = new Set([
102
107
  const DESTROY_FROM_INIT = new Set([
103
108
  "--yes",
104
109
  "-y",
110
+ "--dir",
105
111
  "--aws-profile",
106
112
  "--aws-access-key-id",
107
113
  "--aws-secret-access-key",
@@ -126,10 +132,22 @@ const COMMANDS = {
126
132
  remove: "destroy",
127
133
  rm: "destroy",
128
134
  config: "config",
135
+ help: "help",
129
136
  };
130
137
 
131
- export function helpText() {
132
- return FLAG_HELP;
138
+ export function helpText(topic) {
139
+ if (topic) {
140
+ const key = COMMANDS[topic] ?? topic;
141
+ return TOPIC_HELP[key] ?? helpText();
142
+ }
143
+ const nameW = Math.max(...COMMAND_LINES.map(([name]) => name.length));
144
+ return [
145
+ "Usage: context101 <command>",
146
+ "",
147
+ ...COMMAND_LINES.map(([name, desc]) => ` ${name.padEnd(nameW)} ${desc}`),
148
+ "",
149
+ "npx context101 (unscoped) is Context7's MCP — use context101-cli.",
150
+ ].join("\n");
133
151
  }
134
152
 
135
153
  export function parseArgs(argv) {
@@ -158,24 +176,30 @@ export function parseArgs(argv) {
158
176
  configAction: "show",
159
177
  configKey: null,
160
178
  configValue: null,
179
+ helpTopic: null,
161
180
  };
162
181
 
163
182
  const args = [...argv];
164
183
  if (args.length === 0) return opts;
165
184
 
166
185
  const first = args[0];
167
- if (COMMANDS[first]) {
186
+ if (first === "--help" || first === "-h") {
187
+ opts.command = "help";
188
+ opts.help = true;
189
+ args.shift();
190
+ } else if (COMMANDS[first]) {
168
191
  opts.command = COMMANDS[first];
169
192
  args.shift();
170
- } else if (first === "help" || first === "--help" || first === "-h") {
171
- opts.help = true;
172
- return opts;
173
193
  } else if (!first.startsWith("-")) {
174
194
  const err = new Error(`unknown command: ${first}`);
175
195
  err.code = "USAGE";
176
196
  throw err;
177
197
  }
178
198
 
199
+ if (opts.command === "help") {
200
+ return parseHelpArgs(opts, args);
201
+ }
202
+
179
203
  if (opts.command === "config" && args[0] === "set") {
180
204
  args.shift();
181
205
  const pair = args.shift();
@@ -284,6 +308,33 @@ export function parseArgs(argv) {
284
308
  return opts;
285
309
  }
286
310
 
311
+ function parseHelpArgs(opts, args) {
312
+ opts.help = true;
313
+ if (args[0] && !args[0].startsWith("-")) {
314
+ const topic = args.shift();
315
+ if (topic === "config" && args[0] === "set") {
316
+ args.shift();
317
+ opts.helpTopic = "config set";
318
+ } else if (COMMANDS[topic] && topic !== "help") {
319
+ opts.helpTopic = COMMANDS[topic];
320
+ } else if (topic === "help") {
321
+ opts.helpTopic = "help";
322
+ } else {
323
+ const err = new Error(`unknown command: ${topic}`);
324
+ err.code = "USAGE";
325
+ throw err;
326
+ }
327
+ }
328
+ while (args.length) {
329
+ const arg = args.shift();
330
+ if (arg === "--help" || arg === "-h") continue;
331
+ const err = new Error(`unknown flag: ${arg}`);
332
+ err.code = "USAGE";
333
+ throw err;
334
+ }
335
+ return opts;
336
+ }
337
+
287
338
  function flagAllowed(command, arg) {
288
339
  if (!INIT_ONLY.has(arg)) return true;
289
340
  if (command === "init") return true;
package/src/stacks.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { DEPLOY_CLI, DESTROY_CLI, LIST_CLI, SMOOTH_REGION } from "./defaults.js";
2
2
  import { runCdk } from "./cdk-invoke.js";
3
+ import { ensureRepoRoot } from "./clone.js";
3
4
  import { createExec } from "./exec.js";
4
- import { findRepoRoot } from "./repo.js";
5
5
  import { banner, writers } from "./style.js";
6
6
 
7
7
  export function parseStackSummaries(payload) {
@@ -44,21 +44,31 @@ export function listDeployments({ exec, env, region = SMOOTH_REGION } = {}) {
44
44
  }
45
45
  }
46
46
 
47
- export function formatDeployments(stacks, { region = SMOOTH_REGION } = {}) {
47
+ export function statusTone(status, colors = {}) {
48
+ const s = String(status);
49
+ if (/FAIL|ROLLBACK/i.test(s)) return colors.red ?? "";
50
+ if (/IN_PROGRESS|PENDING|REVIEW/i.test(s)) return colors.violet ?? "";
51
+ if (/COMPLETE/i.test(s)) return colors.magenta ?? "";
52
+ return colors.violet ?? "";
53
+ }
54
+
55
+ export function formatDeployments(stacks, { region = SMOOTH_REGION, colors } = {}) {
56
+ const c = colors ?? { magenta: "", violet: "", dim: "", red: "", bold: "", reset: "" };
48
57
  if (!stacks.length) {
49
58
  return [`No Context101 deployments in ${region}.`, `Next: ${DEPLOY_CLI}`].join("\n");
50
59
  }
51
60
  const nameW = Math.max(4, ...stacks.map((s) => String(s.StackName).length));
52
61
  const statusW = Math.max(6, ...stacks.map((s) => String(s.StackStatus).length));
53
62
  const lines = [
54
- `Context101 deployments in ${region}`,
63
+ `${c.dim}Context101 deployments in ${region}${c.reset}`,
55
64
  "",
56
- `${"NAME".padEnd(nameW)} ${"STATUS".padEnd(statusW)} UPDATED`,
65
+ `${c.dim}${"NAME".padEnd(nameW)} ${"STATUS".padEnd(statusW)} UPDATED${c.reset}`,
57
66
  ];
58
67
  for (const stack of stacks) {
59
68
  const updated = stack.LastUpdatedTime || stack.CreationTime || "";
69
+ const tone = statusTone(stack.StackStatus, c);
60
70
  lines.push(
61
- `${String(stack.StackName).padEnd(nameW)} ${String(stack.StackStatus).padEnd(statusW)} ${updated}`
71
+ `${String(stack.StackName).padEnd(nameW)} ${tone}${String(stack.StackStatus).padEnd(statusW)}${c.reset} ${c.dim}${updated}${c.reset}`
62
72
  );
63
73
  }
64
74
  return lines.join("\n");
@@ -70,12 +80,6 @@ export async function runList(opts, ctx) {
70
80
 
71
81
  banner(ctx);
72
82
 
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
83
  const env = withAwsAuth(ctx.env ?? {}, {
80
84
  profile: opts.awsProfile,
81
85
  accessKeyId: opts.awsAccessKeyId,
@@ -86,7 +90,7 @@ export async function runList(opts, ctx) {
86
90
  io.err(listed.error);
87
91
  return 1;
88
92
  }
89
- io.write(formatDeployments(listed.stacks, { region: SMOOTH_REGION }));
93
+ io.write(formatDeployments(listed.stacks, { region: SMOOTH_REGION, colors: io.c }));
90
94
  io.write("");
91
95
  return 0;
92
96
  }
@@ -101,12 +105,6 @@ export async function runDestroy(opts, ctx) {
101
105
  io.write("");
102
106
  }
103
107
 
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
108
  const stackName = String(opts.stackName || "").trim();
111
109
  if (!stackName) {
112
110
  io.err(`destroy needs a stack name from \`${LIST_CLI}\`.`);
@@ -125,7 +123,7 @@ export async function runDestroy(opts, ctx) {
125
123
  return 1;
126
124
  }
127
125
 
128
- io.write(formatDeployments(listed.stacks, { region: SMOOTH_REGION }));
126
+ io.write(formatDeployments(listed.stacks, { region: SMOOTH_REGION, colors: io.c }));
129
127
  io.write("");
130
128
 
131
129
  const known = listed.stacks.some((stack) => stack.StackName === stackName);
@@ -157,6 +155,25 @@ export async function runDestroy(opts, ctx) {
157
155
  io.warn(
158
156
  "Non-default brains are not in CloudFormation — delete them from /brains first."
159
157
  );
158
+
159
+ const checkout = ensureRepoRoot({
160
+ cwd: ctx.cwd,
161
+ dir: opts.dir,
162
+ exec,
163
+ io,
164
+ preferHomeClone: true,
165
+ homeDir: ctx.homeDir,
166
+ });
167
+ if (checkout.error) {
168
+ io.err(checkout.error);
169
+ return 1;
170
+ }
171
+ const repoRoot = checkout.repoRoot;
172
+ if (!repoRoot) {
173
+ io.err("could not find or clone a Context101 checkout (needs cdk/ and web/).");
174
+ return 1;
175
+ }
176
+
160
177
  io.write(`Destroying ${stackName}…`);
161
178
  return (ctx.runDeploy ?? runCdk)({
162
179
  repoRoot,
package/src/style.js CHANGED
@@ -1,19 +1,40 @@
1
+ export const TAGLINE = "your context. every agent.";
2
+
3
+ const MAGENTA = [184, 85, 201];
4
+ const VIOLET = [139, 92, 246];
5
+ const DUSTY = [168, 158, 180];
6
+ const MAGENTA_256 = 170;
7
+ const VIOLET_256 = 99;
8
+ const DUSTY_256 = 139;
9
+
1
10
  function enabled(stream) {
2
11
  if (process.env.NO_COLOR) return false;
3
12
  if (process.env.FORCE_COLOR === "0") return false;
4
13
  return Boolean(stream && stream.isTTY);
5
14
  }
6
15
 
16
+ function truecolor() {
17
+ const colorterm = String(process.env.COLORTERM || "").toLowerCase();
18
+ if (colorterm === "truecolor" || colorterm === "24bit") return true;
19
+ const term = String(process.env.TERM || "").toLowerCase();
20
+ return term.includes("truecolor") || term.includes("direct");
21
+ }
22
+
23
+ function fg(rgb, fallback256) {
24
+ if (truecolor()) return `\x1b[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m`;
25
+ return `\x1b[38;5;${fallback256}m`;
26
+ }
27
+
7
28
  export function palette(stream = process.stdout) {
8
29
  if (!enabled(stream)) {
9
- return { red: "", green: "", yellow: "", bold: "", dim: "", reset: "" };
30
+ return { magenta: "", violet: "", dim: "", red: "", bold: "", reset: "" };
10
31
  }
11
32
  return {
33
+ magenta: fg(MAGENTA, MAGENTA_256),
34
+ violet: fg(VIOLET, VIOLET_256),
35
+ dim: fg(DUSTY, DUSTY_256),
12
36
  red: "\x1b[31m",
13
- green: "\x1b[32m",
14
- yellow: "\x1b[33m",
15
37
  bold: "\x1b[1m",
16
- dim: "\x1b[2m",
17
38
  reset: "\x1b[0m",
18
39
  };
19
40
  }
@@ -28,10 +49,10 @@ export function writers(io) {
28
49
  out.write(`${line}\n`);
29
50
  },
30
51
  ok(msg) {
31
- out.write(`${c.green}✓${c.reset} ${msg}\n`);
52
+ out.write(`${c.magenta}✓${c.reset} ${msg}\n`);
32
53
  },
33
54
  warn(msg) {
34
- err.write(`${c.yellow}!${c.reset} ${msg}\n`);
55
+ err.write(`${c.violet}!${c.reset} ${msg}\n`);
35
56
  },
36
57
  err(msg) {
37
58
  err.write(`${c.red}✗${c.reset} ${msg}\n`);
@@ -48,7 +69,7 @@ export function writers(io) {
48
69
  export function banner(io) {
49
70
  const { c, write } = writers(io);
50
71
  write();
51
- write(`${c.bold}Context101${c.reset}`);
52
- write(`${c.dim}self-host setup — web/ + CDK + MCP on your AWS account${c.reset}`);
72
+ write(`${c.bold}${c.magenta}Context101${c.reset}`);
73
+ write(`${c.dim}${TAGLINE}${c.reset}`);
53
74
  write();
54
75
  }