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 +94 -0
- package/bin/krexel +10 -0
- package/dist/cli.d.ts +21 -0
- package/dist/cli.js +167 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-patchers.d.ts +108 -0
- package/dist/config-patchers.js +231 -0
- package/dist/config-patchers.js.map +1 -0
- package/dist/init.d.ts +103 -0
- package/dist/init.js +312 -0
- package/dist/init.js.map +1 -0
- package/dist/login.d.ts +29 -0
- package/dist/login.js +106 -0
- package/dist/login.js.map +1 -0
- package/dist/logs.d.ts +10 -0
- package/dist/logs.js +26 -0
- package/dist/logs.js.map +1 -0
- package/dist/mcp-client.d.ts +168 -0
- package/dist/mcp-client.js +379 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/output.d.ts +44 -0
- package/dist/output.js +90 -0
- package/dist/output.js.map +1 -0
- package/dist/paths.d.ts +69 -0
- package/dist/paths.js +115 -0
- package/dist/paths.js.map +1 -0
- package/dist/rollback.d.ts +10 -0
- package/dist/rollback.js +34 -0
- package/dist/rollback.js.map +1 -0
- package/dist/ship.d.ts +13 -0
- package/dist/ship.js +49 -0
- package/dist/ship.js.map +1 -0
- package/dist/whoami.d.ts +7 -0
- package/dist/whoami.js +25 -0
- package/dist/whoami.js.map +1 -0
- package/package.json +44 -0
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
|
package/dist/cli.js.map
ADDED
|
@@ -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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"}
|