krexel 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/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # Krexel CLI
2
+
3
+ The `krexel` command ships websites via AI assistants. This CLI is the local entry point — it spawns the Krexel MCP server (`mcp-server/`) over stdio and calls its tools on your behalf.
4
+
5
+ ## Install
6
+
7
+ From the monorepo root:
8
+
9
+ ```bash
10
+ npm install
11
+ npm run build --workspace krexel
12
+ npm link --workspace krexel
13
+ ```
14
+
15
+ From npm (once published):
16
+
17
+ ```bash
18
+ npm i -g krexel
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```bash
24
+ # 1. Log in once (opens your browser, OAuths into Cloudflare)
25
+ krexel login
26
+
27
+ # 2. Ship a folder to a custom domain
28
+ krexel ship ./my-site mydomain.com --framework=auto
29
+
30
+ # 3. Watch what happened
31
+ krexel logs dep_xxxxxxxx_xxxx
32
+
33
+ # 4. Or roll back
34
+ krexel rollback mydomain.com dep_yyyyyyyy_yyyy
35
+
36
+ # 5. Confirm who you're signed in as
37
+ krexel whoami
38
+ ```
39
+
40
+ ## Subcommands
41
+
42
+ | Command | What it does |
43
+ |---|---|
44
+ | `krexel login [--provider=cloudflare]` | OAuth into Cloudflare (Phase 2: Vercel + Netlify throw a clear "Phase 2" error) |
45
+ | `krexel ship <folder> <domain> [--framework=auto\|nextjs\|astro\|vite\|static]` | Ship a built folder to a domain |
46
+ | `krexel logs <deploy_id>` | Print build logs for a deploy |
47
+ | `krexel rollback <domain> <deploy_id>` | Pin a previous deploy as current/last-good |
48
+ | `krexel whoami` | Print account + deploy count + state dir |
49
+ | `krexel version` | Print version |
50
+ | `krexel --json ...` | Global flag for machine-readable JSON |
51
+
52
+ ## How it works under the hood
53
+
54
+ - The CLI is a thin commander program. Every subcommand calls into the Krexel MCP server's tool surface (`ship_site`, `list_deploys`, `get_logs`, `rollback`, `set_env`, `get_status`).
55
+ - It spawns `mcp-server/dist/index.js` over stdio, sends an MCP `initialize` + `tools/call` JSON-RPC, parses the response, formats it.
56
+ - It does NOT call the orchestrator (the Hono worker) directly. That stays an implementation detail of the MCP layer.
57
+
58
+ ## State directory
59
+
60
+ For v1, account state lives at `$KREXEL_HOME` (defaults to `~/.krexel/`):
61
+
62
+ - `state.json` — encrypted env vars + customer config
63
+ - `uploads/<deploy_id>/` — staged deploy folders (zipped + manifest)
64
+
65
+ The master key lives in `$KREXEL_MASTER_KEY` (32+ chars hex). It never touches disk.
66
+
67
+ ## Errors
68
+
69
+ The CLI fails loud and early:
70
+
71
+ - Missing CLI binary → `Install the Krexel MCP server first: npm i -g krexel-mcp` (no fake success)
72
+ - Missing folder → `Folder does not exist: <path>`
73
+ - MCP server returns error → surfaces upstream message verbatim
74
+ - Network failure to orchestrator → bubbles up, never swallowed
75
+
76
+ ## Development
77
+
78
+ ```bash
79
+ # Watch src, run tsx
80
+ npm run dev
81
+
82
+ # Type-check + emit dist
83
+ npm run build
84
+
85
+ # Run the test suite
86
+ npm test
87
+
88
+ # Lint (type-check only)
89
+ npm run lint
90
+ ```
91
+
92
+ ## License
93
+
94
+ MIT
package/bin/krexel ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ // Krexel CLI — `npm i -g krexel` installs this onto $PATH.
3
+ // All real work happens in dist/cli.js (compiled from src/cli.ts).
4
+
5
+ import("../dist/cli.js").catch((err) => {
6
+ // Last-resort error handler — keeps process.exit predictable.
7
+ // eslint-disable-next-line no-console
8
+ console.error("Error:", err instanceof Error ? err.message : String(err));
9
+ process.exit(1);
10
+ });
package/dist/cli.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli.ts — Krexel CLI entry point.
4
+ *
5
+ * Subcommands:
6
+ * login [--provider=cloudflare|vercel|netlify]
7
+ * ship <folder> <domain> [--framework=auto|nextjs|astro|vite|static]
8
+ * logs <deploy_id>
9
+ * rollback <domain> <deploy_id>
10
+ * whoami
11
+ * version
12
+ * help (built into commander)
13
+ *
14
+ * Conventions:
15
+ * - `--json` is a global flag — emits machine-readable output for piping.
16
+ * - All errors go through output.error() which exits 1.
17
+ * - Every subcommand's async handler swallows its own failures and forwards
18
+ * them to process.exit(1) so the user always sees a clear, actionable
19
+ * message rather than a raw stack trace.
20
+ */
21
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli.ts — Krexel CLI entry point.
4
+ *
5
+ * Subcommands:
6
+ * login [--provider=cloudflare|vercel|netlify]
7
+ * ship <folder> <domain> [--framework=auto|nextjs|astro|vite|static]
8
+ * logs <deploy_id>
9
+ * rollback <domain> <deploy_id>
10
+ * whoami
11
+ * version
12
+ * help (built into commander)
13
+ *
14
+ * Conventions:
15
+ * - `--json` is a global flag — emits machine-readable output for piping.
16
+ * - All errors go through output.error() which exits 1.
17
+ * - Every subcommand's async handler swallows its own failures and forwards
18
+ * them to process.exit(1) so the user always sees a clear, actionable
19
+ * message rather than a raw stack trace.
20
+ */
21
+ import { Command, Option } from "commander";
22
+ import { readFileSync } from "node:fs";
23
+ import * as path from "node:path";
24
+ import { fileURLToPath } from "node:url";
25
+ import { setJsonMode, error, printError } from "./output.js";
26
+ import { runLogin } from "./login.js";
27
+ import { runShip } from "./ship.js";
28
+ import { runLogs } from "./logs.js";
29
+ import { runRollback } from "./rollback.js";
30
+ import { runWhoami } from "./whoami.js";
31
+ import { runInit } from "./init.js";
32
+ // ---------------------------------------------------------------------------
33
+ // Version — read from package.json so we never drift from "npm view krexel".
34
+ // ---------------------------------------------------------------------------
35
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
36
+ // dist/cli.js → ../../package.json
37
+ function readVersion() {
38
+ // 1. dist/cli.js → ../../package.json (production)
39
+ const candidates = [
40
+ path.resolve(HERE, "../../package.json"),
41
+ // 2. src/cli.ts → ../../package.json (dev — tsx)
42
+ path.resolve(HERE, "../../package.json"),
43
+ ];
44
+ for (const p of candidates) {
45
+ try {
46
+ const json = JSON.parse(readFileSync(p, "utf8"));
47
+ if (json.version)
48
+ return json.version;
49
+ }
50
+ catch {
51
+ // ignore — try next
52
+ }
53
+ }
54
+ return "0.0.0";
55
+ }
56
+ const VERSION = readVersion();
57
+ function safe(fn) {
58
+ return async (...args) => {
59
+ try {
60
+ await fn(...args);
61
+ }
62
+ catch (err) {
63
+ const msg = err instanceof Error ? err.message : String(err);
64
+ printError(msg);
65
+ process.exit(1);
66
+ }
67
+ };
68
+ }
69
+ // ---------------------------------------------------------------------------
70
+ // Program
71
+ // ---------------------------------------------------------------------------
72
+ const program = new Command();
73
+ program
74
+ .name("krexel")
75
+ .description("Krexel CLI — login, ship, and manage deploys. Spawns the Krexel MCP server over stdio.")
76
+ .version(VERSION)
77
+ // Built-in --help output via commander.
78
+ .helpCommand("help [command]", "display help for command")
79
+ // Treat unrecognized subcommands the way every well-behaved CLI does.
80
+ .showHelpAfterError()
81
+ .option("--json", "emit machine-readable JSON output (for piping to jq)", false);
82
+ // Per-subcommand: --version is provided by commander; we add our own `version`
83
+ // subcommand so users get the same answer regardless of how they ask.
84
+ program
85
+ .command("version")
86
+ .description("Print the CLI version")
87
+ .action(() => {
88
+ // eslint-disable-next-line no-console
89
+ console.log(`krexel ${VERSION}`);
90
+ });
91
+ program
92
+ .command("login")
93
+ .description("Log in to Krexel via OAuth (Cloudflare is the only wired provider in v1)")
94
+ .addOption(new Option("--provider <name>", "OAuth provider to log in with")
95
+ .choices(["cloudflare", "vercel", "netlify"])
96
+ .default("cloudflare"))
97
+ .option("--api-key <key>", "Reuse an existing API key (skips re-minting)")
98
+ .action(safe(async (opts) => {
99
+ await runLogin({ provider: opts.provider, apiKey: opts.apiKey });
100
+ }));
101
+ program
102
+ .command("ship <folder> <domain>")
103
+ .description("Ship a built site folder to a custom domain")
104
+ .addOption(new Option("--framework <name>", "Framework hint (default: auto-detect from package.json)")
105
+ .choices(["auto", "nextjs", "astro", "vite", "static"])
106
+ .default("auto"))
107
+ .action(safe(async (folder, domain, opts) => {
108
+ await runShip({ folder, domain, framework: opts.framework });
109
+ }));
110
+ program
111
+ .command("logs <deploy_id>")
112
+ .description("Print build logs for a deploy")
113
+ .action(safe(async (deployId) => {
114
+ await runLogs({ deployId });
115
+ }));
116
+ program
117
+ .command("rollback <domain> <deploy_id>")
118
+ .description("Mark a previous deploy as the current/last-good for a domain")
119
+ .action(safe(async (domain, deployId) => {
120
+ await runRollback({ domain, deployId });
121
+ }));
122
+ program
123
+ .command("whoami")
124
+ .description("Print account + deploy counts + state directory")
125
+ .action(safe(runWhoami));
126
+ program
127
+ .command("init")
128
+ .description("Set up Krexel in one command: install + configure all AI clients + OAuth")
129
+ .option("--dry-run", "show what would change without writing any files", false)
130
+ .option("--skip-login", "skip the OAuth step (for CI / scripted installs)", false)
131
+ .action(safe(async (opts) => {
132
+ await runInit({ dryRun: Boolean(opts.dryRun), skipLogin: Boolean(opts.skipLogin) });
133
+ }));
134
+ // ---------------------------------------------------------------------------
135
+ // Pre-parse — turn on JSON mode if --json was passed before commander can see it
136
+ // (otherwise subcommands start before the global flag toggles).
137
+ // ---------------------------------------------------------------------------
138
+ const rawArgs = process.argv.slice(2);
139
+ if (rawArgs.includes("--json")) {
140
+ setJsonMode(true);
141
+ }
142
+ // `version`/`help` overrides so we own their output rather than letting
143
+ // commander route the user off to the package.json version (which is fine but
144
+ // we'd rather be explicit and consistent).
145
+ if (rawArgs[0] === "version") {
146
+ // eslint-disable-next-line no-console
147
+ console.log(VERSION);
148
+ process.exit(0);
149
+ }
150
+ // Silence the commander `--version` route so our own `version` subcommand is canonical.
151
+ program.exitOverride();
152
+ try {
153
+ await program.parseAsync(process.argv);
154
+ }
155
+ catch (err) {
156
+ // commander throws on parse failure (e.g. missing required arg). The error
157
+ // already contains a helpful message; surface it and exit non-zero.
158
+ const msg = err instanceof Error ? err.message : String(err);
159
+ // Don't double-print "krexel version" etc. for help exits.
160
+ if (/help/i.test(msg) || /usage/i.test(msg)) {
161
+ // eslint-disable-next-line no-console
162
+ console.log(msg);
163
+ process.exit(0);
164
+ }
165
+ error(msg);
166
+ }
167
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * config-patchers.ts — typed, idempotent merge functions for each AI
3
+ * client's MCP server config.
4
+ *
5
+ * Every patcher follows the same contract:
6
+ * 1. Read the existing file; treat ENOENT as "no existing config".
7
+ * 2. Parse with the right format (JSON.parse or smol-toml.parse).
8
+ * 3. If a `krexel` entry already exists under `mcpServers` (or its
9
+ * equivalent schema), LEAVE IT TOUCHED-IS — idempotent re-runs.
10
+ * 4. Add the `krexel` entry pointing at the resolved binary + env vars.
11
+ * 5. Write back atomically (write to `<path>.tmp`, then rename).
12
+ * 6. Re-parse after write to confirm round-trip validity.
13
+ *
14
+ * Each patcher throws on unrecoverable failure (bad JSON the user has
15
+ * authored themselves, perms denial). The orchestrator in init.ts
16
+ * catches per-patcher failures and continues with the others.
17
+ */
18
+ import type { ClientId } from "./paths.js";
19
+ /** The env vars every MCP server entry needs to point at us. */
20
+ export declare const REQUIRED_ENV_KEYS: readonly ["KREXEL_API_URL", "KREXEL_MASTER_KEY"];
21
+ /** A single MCP server entry, in the Claude/Codex-flavoured JSON shape. */
22
+ export interface McpServerEntry {
23
+ command: string;
24
+ args: string[];
25
+ env: Record<string, string>;
26
+ }
27
+ export interface PatchResult {
28
+ /** Did we actually change anything? false = already configured. */
29
+ changed: boolean;
30
+ /** Absolute path to the file we wrote (or would write, in dry-run). */
31
+ path: string;
32
+ /** The parsed JSON/TOML content post-patch (caller may serialize differently). */
33
+ next: unknown;
34
+ /** Human-readable note — "added", "already present", "skipped (read-only)", etc. */
35
+ note: string;
36
+ }
37
+ /** Inputs every patcher shares. */
38
+ export interface PatchInput {
39
+ /** Absolute path to the config file. */
40
+ filePath: string;
41
+ /** Absolute command path of the MCP server binary. */
42
+ binaryCommand: string;
43
+ /** Args the binary takes (e.g. ["./dist/index.js"] in dev). */
44
+ binaryArgs: string[];
45
+ /** Env vars to inject — caller is responsible for masking on log. */
46
+ env: Record<string, string>;
47
+ /** When true, compute the patch but DON'T touch the disk. */
48
+ dryRun: boolean;
49
+ /** Override fs ops for tests. */
50
+ fsAdapter?: FsAdapter;
51
+ }
52
+ export interface FsAdapter {
53
+ readFile(p: string): Promise<string>;
54
+ writeFile(p: string, contents: string): Promise<void>;
55
+ rename(p: string, newPath: string): Promise<void>;
56
+ exists(p: string): Promise<boolean>;
57
+ }
58
+ /**
59
+ * Pure merge helper — given an existing mcpServers object (may be missing)
60
+ * and a new entry, return `{changed, next}`. Public so unit tests can
61
+ * verify merge semantics without touching the disk.
62
+ */
63
+ export declare function mergeIntoConfig(existing: Record<string, unknown> | undefined, client: ClientId, entry: McpServerEntry): {
64
+ changed: boolean;
65
+ next: Record<string, unknown>;
66
+ };
67
+ /** Build the krexel MCP entry that all clients share. */
68
+ export declare function buildEntry(input: PatchInput): McpServerEntry;
69
+ /** Mask a secret for safe log output: "abcd…wxyz". */
70
+ export declare function maskSecret(secret: string): string;
71
+ /**
72
+ * Read+merge+write for any client whose config is JSON. Parses, runs
73
+ * `mergeIntoConfig`, validates by re-parsing the written bytes.
74
+ */
75
+ export declare function patchJsonConfig(input: PatchInput, client: ClientId): Promise<PatchResult>;
76
+ /**
77
+ * Codex's `~/.codex/config.toml` uses a nested `[mcp_servers.krexel]`
78
+ * table. We preserve unknown keys, add `krexel` only if missing, and
79
+ * stringify back with smol-toml.
80
+ *
81
+ * Note: Codex historically used snake_case (`mcp_servers`); we support
82
+ * both spellings because the schema has drifted across releases.
83
+ */
84
+ export declare function patchTomlConfig(input: PatchInput): Promise<PatchResult>;
85
+ /** Result of persisting the master key. */
86
+ export interface MasterKeyResult {
87
+ /** The key value that ended up on disk (either pre-existing or newly minted). */
88
+ value: string;
89
+ /** Paths that were inspected / written. */
90
+ files: string[];
91
+ /** True iff we had to mint a new key (false when one already existed). */
92
+ minted: boolean;
93
+ }
94
+ /**
95
+ * Persist the master key into a shell rc file, creating the file if it
96
+ * doesn't exist. Refuses to overwrite a pre-existing value.
97
+ *
98
+ * - If the file has `export KREXEL_MASTER_KEY=...` already, returns that
99
+ * value (does NOT touch the file).
100
+ * - Otherwise appends a single line with the new value.
101
+ *
102
+ * Quoting: we always wrap in single-quotes, escaping any embedded `'`
103
+ * via the POSIX idiom `'\''`. The result is shell-safe by construction.
104
+ */
105
+ export declare function persistMasterKeyToRc(rcPath: string, newValue: string, fsAdapter?: FsAdapter): Promise<{
106
+ value: string;
107
+ written: boolean;
108
+ }>;
@@ -0,0 +1,231 @@
1
+ /**
2
+ * config-patchers.ts — typed, idempotent merge functions for each AI
3
+ * client's MCP server config.
4
+ *
5
+ * Every patcher follows the same contract:
6
+ * 1. Read the existing file; treat ENOENT as "no existing config".
7
+ * 2. Parse with the right format (JSON.parse or smol-toml.parse).
8
+ * 3. If a `krexel` entry already exists under `mcpServers` (or its
9
+ * equivalent schema), LEAVE IT TOUCHED-IS — idempotent re-runs.
10
+ * 4. Add the `krexel` entry pointing at the resolved binary + env vars.
11
+ * 5. Write back atomically (write to `<path>.tmp`, then rename).
12
+ * 6. Re-parse after write to confirm round-trip validity.
13
+ *
14
+ * Each patcher throws on unrecoverable failure (bad JSON the user has
15
+ * authored themselves, perms denial). The orchestrator in init.ts
16
+ * catches per-patcher failures and continues with the others.
17
+ */
18
+ import * as fs from "node:fs/promises";
19
+ import * as path from "node:path";
20
+ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
21
+ /** The env vars every MCP server entry needs to point at us. */
22
+ export const REQUIRED_ENV_KEYS = ["KREXEL_API_URL", "KREXEL_MASTER_KEY"];
23
+ const realFs = {
24
+ readFile: (p) => fs.readFile(p, "utf8"),
25
+ writeFile: (p, c) => fs.writeFile(p, c, "utf8"),
26
+ rename: (p, np) => fs.rename(p, np),
27
+ exists: async (p) => {
28
+ try {
29
+ await fs.access(p);
30
+ return true;
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ },
36
+ };
37
+ /** Atomic write: write to `<path>.tmp`, then rename. */
38
+ async function atomicWrite(fsAdapter, filePath, contents) {
39
+ const tmp = `${filePath}.tmp`;
40
+ await fsAdapter.writeFile(tmp, contents);
41
+ await fsAdapter.rename(tmp, filePath);
42
+ }
43
+ /**
44
+ * Pure merge helper — given an existing mcpServers object (may be missing)
45
+ * and a new entry, return `{changed, next}`. Public so unit tests can
46
+ * verify merge semantics without touching the disk.
47
+ */
48
+ export function mergeIntoConfig(existing, client, entry) {
49
+ // Schema differences per client — only Continue uses the experimental key.
50
+ const key = client === "continue" ? "experimental.modelContextProtocolServers" : "mcpServers";
51
+ const root = { ...(existing ?? {}) };
52
+ const current = root[key] ?? {};
53
+ if (current["krexel"] !== undefined) {
54
+ // Idempotent: existing entry untouched.
55
+ return { changed: false, next: root };
56
+ }
57
+ const nextServers = { ...current, krexel: entry };
58
+ root[key] = nextServers;
59
+ return { changed: true, next: root };
60
+ }
61
+ /** Build the krexel MCP entry that all clients share. */
62
+ export function buildEntry(input) {
63
+ // Filter env to only what we know about — drop unknown keys the caller
64
+ // might have forwarded.
65
+ const env = {};
66
+ for (const k of REQUIRED_ENV_KEYS) {
67
+ const v = input.env[k];
68
+ if (typeof v === "string")
69
+ env[k] = v;
70
+ }
71
+ return {
72
+ command: input.binaryCommand,
73
+ args: [...input.binaryArgs],
74
+ env,
75
+ };
76
+ }
77
+ /** Mask a secret for safe log output: "abcd…wxyz". */
78
+ export function maskSecret(secret) {
79
+ if (secret.length <= 8)
80
+ return "****";
81
+ return `${secret.slice(0, 4)}…${secret.slice(-4)}`;
82
+ }
83
+ // ---------------------------------------------------------------------------
84
+ // JSON patchers (claude-code, cursor, claude-desktop, continue)
85
+ // ---------------------------------------------------------------------------
86
+ /**
87
+ * Read+merge+write for any client whose config is JSON. Parses, runs
88
+ * `mergeIntoConfig`, validates by re-parsing the written bytes.
89
+ */
90
+ export async function patchJsonConfig(input, client) {
91
+ const fsAdapter = input.fsAdapter ?? realFs;
92
+ const exists = await fsAdapter.exists(input.filePath);
93
+ let existing = {};
94
+ if (exists) {
95
+ const raw = await fsAdapter.readFile(input.filePath);
96
+ const trimmed = raw.trim();
97
+ if (trimmed.length > 0) {
98
+ try {
99
+ const parsed = JSON.parse(trimmed);
100
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
101
+ existing = parsed;
102
+ }
103
+ }
104
+ catch (err) {
105
+ throw new Error(`Cannot parse ${path.basename(input.filePath)} as JSON: ${err instanceof Error ? err.message : String(err)}`);
106
+ }
107
+ }
108
+ }
109
+ const entry = buildEntry(input);
110
+ const { changed, next } = mergeIntoConfig(existing, client, entry);
111
+ if (!changed) {
112
+ return {
113
+ changed: false,
114
+ path: input.filePath,
115
+ next,
116
+ note: "krexel entry already present",
117
+ };
118
+ }
119
+ if (input.dryRun) {
120
+ return { changed: true, path: input.filePath, next, note: "dry-run (not written)" };
121
+ }
122
+ const serialized = JSON.stringify(next, null, 2) + "\n";
123
+ await atomicWrite(fsAdapter, input.filePath, serialized);
124
+ // Validate round-trip.
125
+ const reread = await fsAdapter.readFile(input.filePath);
126
+ JSON.parse(reread); // throws on invalid
127
+ return { changed: true, path: input.filePath, next, note: "patched" };
128
+ }
129
+ // ---------------------------------------------------------------------------
130
+ // TOML patcher (codex)
131
+ // ---------------------------------------------------------------------------
132
+ /**
133
+ * Codex's `~/.codex/config.toml` uses a nested `[mcp_servers.krexel]`
134
+ * table. We preserve unknown keys, add `krexel` only if missing, and
135
+ * stringify back with smol-toml.
136
+ *
137
+ * Note: Codex historically used snake_case (`mcp_servers`); we support
138
+ * both spellings because the schema has drifted across releases.
139
+ */
140
+ export async function patchTomlConfig(input) {
141
+ const fsAdapter = input.fsAdapter ?? realFs;
142
+ const exists = await fsAdapter.exists(input.filePath);
143
+ let existing = {};
144
+ if (exists) {
145
+ const raw = await fsAdapter.readFile(input.filePath);
146
+ if (raw.trim().length > 0) {
147
+ try {
148
+ const parsed = parseToml(raw);
149
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
150
+ existing = parsed;
151
+ }
152
+ }
153
+ catch (err) {
154
+ throw new Error(`Cannot parse ${path.basename(input.filePath)} as TOML: ${err instanceof Error ? err.message : String(err)}`);
155
+ }
156
+ }
157
+ }
158
+ // Locate the server-collection key — support both spellings.
159
+ const serverKey = existing["mcpServers"] !== undefined
160
+ ? "mcpServers"
161
+ : existing["mcp_servers"] !== undefined
162
+ ? "mcp_servers"
163
+ : "mcp_servers"; // default for new files
164
+ const current = existing[serverKey] ?? {};
165
+ const root = { ...existing };
166
+ if (current["krexel"] !== undefined) {
167
+ return {
168
+ changed: false,
169
+ path: input.filePath,
170
+ next: root,
171
+ note: "krexel entry already present",
172
+ };
173
+ }
174
+ const entry = buildEntry(input);
175
+ // smol-toml stringifies nested tables cleanly; we pass the entry as-is.
176
+ root[serverKey] = { ...current, krexel: entry };
177
+ if (input.dryRun) {
178
+ return {
179
+ changed: true,
180
+ path: input.filePath,
181
+ next: root,
182
+ note: "dry-run (not written)",
183
+ };
184
+ }
185
+ const serialized = stringifyToml(root);
186
+ await atomicWrite(fsAdapter, input.filePath, serialized);
187
+ // Validate round-trip.
188
+ const reread = await fsAdapter.readFile(input.filePath);
189
+ parseToml(reread);
190
+ return { changed: true, path: input.filePath, next: root, note: "patched" };
191
+ }
192
+ const MASTER_KEY_LINE = (v) => `export KREXEL_MASTER_KEY=${v}`;
193
+ const MASTER_KEY_RE = /^export\s+KREXEL_MASTER_KEY=(.*)$/m;
194
+ /**
195
+ * Persist the master key into a shell rc file, creating the file if it
196
+ * doesn't exist. Refuses to overwrite a pre-existing value.
197
+ *
198
+ * - If the file has `export KREXEL_MASTER_KEY=...` already, returns that
199
+ * value (does NOT touch the file).
200
+ * - Otherwise appends a single line with the new value.
201
+ *
202
+ * Quoting: we always wrap in single-quotes, escaping any embedded `'`
203
+ * via the POSIX idiom `'\''`. The result is shell-safe by construction.
204
+ */
205
+ export async function persistMasterKeyToRc(rcPath, newValue, fsAdapter = realFs) {
206
+ const quoted = `'${newValue.replace(/'/g, `'\\''`)}'`;
207
+ const line = MASTER_KEY_LINE(quoted);
208
+ const exists = await fsAdapter.exists(rcPath);
209
+ let content = "";
210
+ if (exists) {
211
+ content = await fsAdapter.readFile(rcPath);
212
+ const match = MASTER_KEY_RE.exec(content);
213
+ if (match) {
214
+ // Pre-existing — strip surrounding quotes if present so we return
215
+ // the raw value.
216
+ const raw = (match[1] ?? "").trim();
217
+ const stripped = raw.replace(/^'(.*)'$/, "$1");
218
+ return { value: stripped, written: false };
219
+ }
220
+ }
221
+ // Append. Ensure exactly one blank line between existing content and us.
222
+ const prefix = content.length === 0
223
+ ? ""
224
+ : content.endsWith("\n")
225
+ ? ""
226
+ : "\n";
227
+ const next = `${content}${prefix}\n# Added by \`krexel init\`\n${line}\n`;
228
+ await atomicWrite(fsAdapter, rcPath, next);
229
+ return { value: newValue, written: true };
230
+ }
231
+ //# sourceMappingURL=config-patchers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-patchers.js","sourceRoot":"","sources":["../src/config-patchers.ts"],"names":[],"mappings":"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"}