pombus 0.1.0 → 0.1.2

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.
@@ -1,344 +0,0 @@
1
- import { test } from "node:test";
2
- import assert from "node:assert/strict";
3
- import { mkdtempSync, readFileSync, writeFileSync, rmSync, statSync } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
6
- import { runInit } from "./init.js";
7
- import { runDoctor } from "./doctor.js";
8
- import { clientTargets, writeJsonConfig, isInsideHome, printableJson, printableCodexToml, ConfigWriteError, } from "./clients.js";
9
- import { FakeCore, authMeInbox, authMePod, createdInbox } from "./testutil.js";
10
- // Obviously-fake tokens — never a real secret (AGENTS.md §9).
11
- const BROAD_KEY = "pk_test_broad_pod_scope_FAKE";
12
- const BASE = "https://core.test/v0";
13
- /** A temp HOME, cleaned up after each use. */
14
- function tempHome() {
15
- const home = mkdtempSync(join(tmpdir(), "pombus-home-"));
16
- return { home, cleanup: () => rmSync(home, { recursive: true, force: true }) };
17
- }
18
- /** A line sink that records init/doctor human output for assertions. */
19
- function sink() {
20
- const lines = [];
21
- return { lines, out: (l) => lines.push(l) };
22
- }
23
- // ---------------------------------------------------------------------------------------------------
24
- // clients.ts — the write is the sharp edge: merge, idempotent, atomic-ish, 0600, never outside HOME.
25
- // ---------------------------------------------------------------------------------------------------
26
- test("writeJsonConfig MERGES — preserves every existing key, adds only mcpServers.pombus", () => {
27
- const { home, cleanup } = tempHome();
28
- try {
29
- const path = join(home, ".claude.json");
30
- writeFileSync(path, JSON.stringify({
31
- projects: { "/some/proj": { history: [1, 2, 3] } },
32
- theme: "dark",
33
- mcpServers: { other: { command: "node", args: ["x.js"] } },
34
- }));
35
- writeJsonConfig(path, home, BROAD_KEY);
36
- const after = JSON.parse(readFileSync(path, "utf8"));
37
- // Untouched keys survive byte-for-value.
38
- assert.deepEqual(after["projects"], { "/some/proj": { history: [1, 2, 3] } });
39
- assert.equal(after["theme"], "dark");
40
- // The pre-existing OTHER server survives; ours is added alongside.
41
- assert.deepEqual(after["mcpServers"]["other"], { command: "node", args: ["x.js"] });
42
- assert.deepEqual(after["mcpServers"]["pombus"], {
43
- command: "npx",
44
- args: ["-y", "pombus", "mcp"],
45
- env: { POMBUS_API_KEY: BROAD_KEY },
46
- });
47
- }
48
- finally {
49
- cleanup();
50
- }
51
- });
52
- test("writeJsonConfig is IDEMPOTENT — running twice yields exactly one pombus entry", () => {
53
- const { home, cleanup } = tempHome();
54
- try {
55
- const path = join(home, ".cursor", "mcp.json");
56
- writeJsonConfig(path, home, BROAD_KEY);
57
- writeJsonConfig(path, home, BROAD_KEY);
58
- const after = JSON.parse(readFileSync(path, "utf8"));
59
- const servers = after["mcpServers"];
60
- assert.deepEqual(Object.keys(servers), ["pombus"]);
61
- }
62
- finally {
63
- cleanup();
64
- }
65
- });
66
- test("writeJsonConfig creates a fresh file when none exists (and its parent dir)", () => {
67
- const { home, cleanup } = tempHome();
68
- try {
69
- const path = join(home, ".gemini", "settings.json");
70
- writeJsonConfig(path, home, BROAD_KEY);
71
- const after = JSON.parse(readFileSync(path, "utf8"));
72
- assert.equal(after["mcpServers"]["pombus"]["env"]["POMBUS_API_KEY"], BROAD_KEY);
73
- }
74
- finally {
75
- cleanup();
76
- }
77
- });
78
- test("writeJsonConfig REFUSES to clobber a corrupt existing config (never destroys real state)", () => {
79
- const { home, cleanup } = tempHome();
80
- try {
81
- const path = join(home, ".claude.json");
82
- writeFileSync(path, "{ this is not valid json ");
83
- assert.throws(() => writeJsonConfig(path, home, BROAD_KEY), (e) => e instanceof ConfigWriteError && e.code === "corrupt_config");
84
- // The broken file is left exactly as it was — not overwritten.
85
- assert.equal(readFileSync(path, "utf8"), "{ this is not valid json ");
86
- }
87
- finally {
88
- cleanup();
89
- }
90
- });
91
- test("writeJsonConfig REFUSES a target outside HOME (never writes into a project repo)", () => {
92
- const { home, cleanup } = tempHome();
93
- try {
94
- const outside = join(tmpdir(), "not-home-" + process.pid, "mcp.json");
95
- assert.throws(() => writeJsonConfig(outside, home, BROAD_KEY), (e) => e instanceof ConfigWriteError && e.code === "outside_home");
96
- }
97
- finally {
98
- cleanup();
99
- }
100
- });
101
- test("isInsideHome accepts HOME and children, rejects siblings and the project tree", () => {
102
- const home = join(tmpdir(), "h");
103
- assert.ok(isInsideHome(join(home, ".cursor", "mcp.json"), home));
104
- assert.ok(isInsideHome(home, home));
105
- assert.ok(!isInsideHome(join(tmpdir(), "hother", "x.json"), home));
106
- assert.ok(!isInsideHome("/etc/mcp.json", home));
107
- });
108
- test("the config file carrying the key is 0600 on POSIX (owner-only)", { skip: process.platform === "win32" }, () => {
109
- const { home, cleanup } = tempHome();
110
- try {
111
- const path = join(home, ".cursor", "mcp.json");
112
- writeJsonConfig(path, home, BROAD_KEY);
113
- const mode = statSync(path).mode & 0o777;
114
- assert.equal(mode, 0o600, `expected 0600, got ${mode.toString(8)}`);
115
- }
116
- finally {
117
- cleanup();
118
- }
119
- });
120
- test("the client table: claude-code/cursor/gemini are WRITE (json), codex is PRINT (toml)", () => {
121
- const home = join(tmpdir(), "h");
122
- const t = clientTargets(home);
123
- const by = Object.fromEntries(t.map((c) => [c.id, c]));
124
- assert.equal(by["claude-code"].mode, "json");
125
- assert.equal(by["cursor"].mode, "json");
126
- assert.equal(by["gemini"].mode, "json");
127
- assert.equal(by["codex"].mode, "print");
128
- // Every target carries a cited source and a path under HOME.
129
- for (const c of t) {
130
- assert.ok(c.source.length > 0, `${c.id} missing source`);
131
- assert.ok(isInsideHome(c.path, home), `${c.id} path escapes HOME`);
132
- }
133
- });
134
- test("printable blocks: JSON has the standard mcpServers wrapper; Codex TOML is a [mcp_servers.pombus] table", () => {
135
- const j = JSON.parse(printableJson("<KEY>"));
136
- assert.deepEqual(j.mcpServers.pombus, {
137
- command: "npx",
138
- args: ["-y", "pombus", "mcp"],
139
- env: { POMBUS_API_KEY: "<KEY>" },
140
- });
141
- const toml = printableCodexToml("<KEY>");
142
- assert.ok(toml.includes("[mcp_servers.pombus]"));
143
- assert.ok(toml.includes('command = "npx"'));
144
- assert.ok(toml.includes("POMBUS_API_KEY = '<KEY>'"));
145
- });
146
- // ---------------------------------------------------------------------------------------------------
147
- // runInit key contract (option A): the key the user provided is persisted verbatim — no mint, no
148
- // narrowing. The agent is the account's full operator and runs with the key the user chose to give.
149
- // ---------------------------------------------------------------------------------------------------
150
- test("init persists EXACTLY the provided key — never mints a new one (no POST /api-keys)", async () => {
151
- const { home, cleanup } = tempHome();
152
- try {
153
- // --create-inbox on a pod key: an inbox is created, but the key written is still the given one.
154
- const fake = new FakeCore()
155
- .on("GET", (u) => u.pathname.endsWith("/auth/me"), authMePod())
156
- .on("POST", (u) => u.pathname.endsWith("/inboxes"), createdInbox("inb_new", "agent@pombus.com"));
157
- const { out } = sink();
158
- const res = await runInit({
159
- apiKey: BROAD_KEY,
160
- createInbox: true,
161
- client: "cursor",
162
- home,
163
- baseUrl: BASE,
164
- fetchImpl: fake.fetch,
165
- locale: "en-US",
166
- out,
167
- });
168
- assert.deepEqual(res.written, ["cursor"]);
169
- assert.equal(res.inboxId, "inb_new"); // --create-inbox still works, independent of the key
170
- const cfg = JSON.parse(readFileSync(join(home, ".cursor", "mcp.json"), "utf8"));
171
- assert.equal(cfg.mcpServers.pombus.env.POMBUS_API_KEY, BROAD_KEY, "the provided key is persisted as-is");
172
- // No key was minted — the installer never hits POST /api-keys.
173
- assert.ok(!fake.requests.some((r) => r.method === "POST" && r.url.includes("/api-keys")));
174
- }
175
- finally {
176
- cleanup();
177
- }
178
- });
179
- test("init with an inbox-scoped key persists it as-is (no /api-keys call)", async () => {
180
- const { home, cleanup } = tempHome();
181
- try {
182
- const fake = new FakeCore().on("GET", (u) => u.pathname.endsWith("/auth/me"), authMeInbox("inb_fixed"));
183
- const { out } = sink();
184
- const res = await runInit({
185
- apiKey: BROAD_KEY, // the value is opaque to init; it is written verbatim
186
- client: "cursor",
187
- home,
188
- baseUrl: BASE,
189
- fetchImpl: fake.fetch,
190
- locale: "en-US",
191
- out,
192
- });
193
- assert.deepEqual(res.written, ["cursor"]);
194
- assert.ok(!fake.requests.some((r) => r.url.includes("/api-keys")));
195
- const cfg = JSON.parse(readFileSync(join(home, ".cursor", "mcp.json"), "utf8"));
196
- assert.equal(cfg.mcpServers.pombus.env.POMBUS_API_KEY, BROAD_KEY);
197
- }
198
- finally {
199
- cleanup();
200
- }
201
- });
202
- // ---------------------------------------------------------------------------------------------------
203
- // --print writes NOTHING.
204
- // ---------------------------------------------------------------------------------------------------
205
- test("init --print writes NO file and emits the block", async () => {
206
- const { home, cleanup } = tempHome();
207
- try {
208
- const fake = new FakeCore().on("GET", (u) => u.pathname.endsWith("/auth/me"), authMeInbox("inb_fixed"));
209
- const { lines, out } = sink();
210
- const res = await runInit({
211
- apiKey: BROAD_KEY,
212
- print: true,
213
- client: "cursor",
214
- home,
215
- baseUrl: BASE,
216
- fetchImpl: fake.fetch,
217
- locale: "en-US",
218
- out,
219
- });
220
- assert.equal(res.printed, true);
221
- assert.deepEqual(res.written, []);
222
- // No config file was created.
223
- assert.throws(() => readFileSync(join(home, ".cursor", "mcp.json"), "utf8"));
224
- assert.ok(lines.some((l) => l.includes('"pombus"')));
225
- }
226
- finally {
227
- cleanup();
228
- }
229
- });
230
- test("init --print with NO key emits a placeholder block, makes no network call, writes nothing", async () => {
231
- const { home, cleanup } = tempHome();
232
- try {
233
- const fake = new FakeCore().on("GET", () => true, { json: {} });
234
- const { lines, out } = sink();
235
- const res = await runInit({
236
- print: true,
237
- client: "cursor",
238
- home,
239
- baseUrl: BASE,
240
- fetchImpl: fake.fetch,
241
- locale: "en-US",
242
- out,
243
- });
244
- assert.equal(res.printed, true);
245
- assert.equal(fake.requests.length, 0, "no network call without a key");
246
- assert.ok(lines.some((l) => l.includes("<YOUR_POMBUS_API_KEY>")));
247
- }
248
- finally {
249
- cleanup();
250
- }
251
- });
252
- test("init on a PRINT-only client (codex) prints TOML and writes no file", async () => {
253
- const { home, cleanup } = tempHome();
254
- try {
255
- const fake = new FakeCore().on("GET", (u) => u.pathname.endsWith("/auth/me"), authMeInbox("inb_fixed"));
256
- const { lines, out } = sink();
257
- const res = await runInit({
258
- apiKey: BROAD_KEY,
259
- client: "codex",
260
- home,
261
- baseUrl: BASE,
262
- fetchImpl: fake.fetch,
263
- locale: "en-US",
264
- out,
265
- });
266
- assert.deepEqual(res.written, []);
267
- assert.deepEqual(res.printedFor, ["codex"]);
268
- assert.throws(() => readFileSync(join(home, ".codex", "config.toml"), "utf8"));
269
- assert.ok(lines.some((l) => l.includes("[mcp_servers.pombus]")));
270
- }
271
- finally {
272
- cleanup();
273
- }
274
- });
275
- // ---------------------------------------------------------------------------------------------------
276
- // doctor — reports scope / errors, read-only.
277
- // ---------------------------------------------------------------------------------------------------
278
- test("doctor reports the scope of a valid key", async () => {
279
- const fake = new FakeCore().on("GET", (u) => u.pathname.endsWith("/auth/me"), authMePod());
280
- const { lines, out } = sink();
281
- const res = await runDoctor({ apiKey: BROAD_KEY, baseUrl: BASE, fetchImpl: fake.fetch, locale: "en-US", out });
282
- assert.equal(res.ok, true);
283
- assert.equal(res.scope, "pod");
284
- assert.ok(lines.some((l) => l.includes("pod")));
285
- // Read-only: the single request was the /auth/me GET.
286
- assert.equal(fake.requests.length, 1);
287
- assert.equal(fake.requests[0].method, "GET");
288
- });
289
- test("doctor with no key reports no_api_key and makes no network call", async () => {
290
- const fake = new FakeCore().on("GET", () => true, { json: {} });
291
- const { out } = sink();
292
- const res = await runDoctor({ baseUrl: BASE, fetchImpl: fake.fetch, locale: "en-US", out });
293
- assert.equal(res.ok, false);
294
- assert.equal(res.code, "no_api_key");
295
- assert.equal(fake.requests.length, 0);
296
- });
297
- test("doctor with an invalid key reports auth_failed (401/403/404 collapse to not_found -> auth_failed)", async () => {
298
- const fake = new FakeCore().on("GET", (u) => u.pathname.endsWith("/auth/me"), { status: 401, json: { code: "unauthorized" } });
299
- const { out } = sink();
300
- const res = await runDoctor({ apiKey: "pk_test_bad_FAKE", baseUrl: BASE, fetchImpl: fake.fetch, locale: "en-US", out });
301
- assert.equal(res.ok, false);
302
- assert.equal(res.code, "auth_failed");
303
- });
304
- test("doctor with an unreachable core reports network", async () => {
305
- const fetchImpl = async () => {
306
- throw new TypeError("fetch failed");
307
- };
308
- const { out } = sink();
309
- const res = await runDoctor({ apiKey: "pk_test_x_FAKE", baseUrl: BASE, fetchImpl, locale: "en-US", out });
310
- assert.equal(res.ok, false);
311
- assert.equal(res.code, "network");
312
- });
313
- // ---------------------------------------------------------------------------------------------------
314
- // the key never leaks into human output (log hygiene, AGENTS.md §5).
315
- // ---------------------------------------------------------------------------------------------------
316
- test("no human line printed by init or doctor ever contains the BROAD key", async () => {
317
- const { home, cleanup } = tempHome();
318
- try {
319
- const fake = new FakeCore()
320
- .on("GET", (u) => u.pathname.endsWith("/auth/me"), authMePod())
321
- .on("POST", (u) => u.pathname.endsWith("/inboxes"), createdInbox("inb_new", "agent@pombus.com"));
322
- const initSink = sink();
323
- await runInit({
324
- apiKey: BROAD_KEY,
325
- createInbox: true,
326
- client: "cursor",
327
- home,
328
- baseUrl: BASE,
329
- fetchImpl: fake.fetch,
330
- locale: "pt-BR",
331
- out: initSink.out,
332
- });
333
- for (const l of initSink.lines)
334
- assert.ok(!l.includes(BROAD_KEY), `broad key leaked: ${l}`);
335
- const docSink = sink();
336
- const fake2 = new FakeCore().on("GET", (u) => u.pathname.endsWith("/auth/me"), authMePod());
337
- await runDoctor({ apiKey: BROAD_KEY, baseUrl: BASE, fetchImpl: fake2.fetch, locale: "es-419", out: docSink.out });
338
- for (const l of docSink.lines)
339
- assert.ok(!l.includes(BROAD_KEY), `broad key leaked (doctor): ${l}`);
340
- }
341
- finally {
342
- cleanup();
343
- }
344
- });
@@ -1,111 +0,0 @@
1
- import { test } from "node:test";
2
- import assert from "node:assert/strict";
3
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4
- import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
5
- import { CoreClient, CoreError } from "./client.js";
6
- import { ScopeBinding } from "./scope.js";
7
- import { specByName, callTool } from "./tools.js";
8
- import { buildServer } from "./server.js";
9
- import { FakeCore, authMeInbox, authMePod } from "./testutil.js";
10
- const KEY = "pk_live_secret_do_not_log_abcdef";
11
- async function ctxFor(fake) {
12
- const client = new CoreClient({ apiKey: KEY, baseUrl: "https://core.test/v0", fetchImpl: fake.fetch });
13
- const scope = await ScopeBinding.resolve(client);
14
- return { client, scope };
15
- }
16
- test("the API key rides ONLY in the Authorization header — never in a URL", async () => {
17
- const fake = new FakeCore()
18
- .on("GET", (u) => u.pathname.endsWith("/auth/me"), authMeInbox("inb_x"))
19
- .on("GET", () => true, { json: { ok: true } });
20
- const ctx = await ctxFor(fake);
21
- await callTool(specByName("list_messages", ctx.scope), {}, ctx);
22
- await callTool(specByName("get_message", ctx.scope), { message_id: "m1" }, ctx);
23
- assert.ok(fake.requests.length >= 2);
24
- for (const r of fake.requests) {
25
- assert.ok(!r.url.includes(KEY), `key leaked into URL: ${r.url}`);
26
- assert.equal(r.headers["authorization"], `Bearer ${KEY}`);
27
- // No other header carries it.
28
- for (const [k, v] of Object.entries(r.headers)) {
29
- if (k !== "authorization")
30
- assert.ok(!v.includes(KEY), `key leaked into header ${k}`);
31
- }
32
- }
33
- });
34
- test("a transport error message never contains the key or the URL", async () => {
35
- const fetchImpl = async () => {
36
- throw new TypeError("fetch failed: connect ECONNREFUSED https://core.test/v0?api_key=" + KEY);
37
- };
38
- const client = new CoreClient({ apiKey: KEY, baseUrl: "https://core.test/v0", fetchImpl });
39
- await assert.rejects(() => client.get("/inboxes"), (e) => {
40
- assert.ok(e instanceof CoreError);
41
- assert.ok(!e.message.includes(KEY), "key must not appear in the error");
42
- assert.ok(!e.message.includes("core.test"), "URL must not appear in the error");
43
- assert.equal(e.code, "network");
44
- return true;
45
- });
46
- });
47
- test("auth failure does NOT reveal whether an inbox exists (403 and 404 collapse to not_found)", async () => {
48
- const forbidden = new FakeCore()
49
- .on("GET", (u) => u.pathname.endsWith("/auth/me"), authMePod())
50
- .on("GET", (u) => /\/inboxes\/inb_q$/.test(u.pathname), { status: 403, json: { code: "forbidden" } });
51
- const missing = new FakeCore()
52
- .on("GET", (u) => u.pathname.endsWith("/auth/me"), authMePod())
53
- .on("GET", (u) => /\/inboxes\/inb_q$/.test(u.pathname), { status: 404, json: { code: "not_found" } });
54
- const errOf = async (fake) => {
55
- const ctx = await ctxFor(fake);
56
- try {
57
- await callTool(specByName("get_thread", ctx.scope), { inbox_id: "inb_q", thread_id: "t1" }, ctx);
58
- return "no-error";
59
- }
60
- catch (e) {
61
- return e.code ?? "unknown";
62
- }
63
- };
64
- // A key denied the inbox (403) and a key facing a missing inbox (404) get the IDENTICAL answer, so
65
- // the model can never use the error to enumerate which inboxes exist.
66
- assert.equal(await errOf(forbidden), "not_found");
67
- assert.equal(await errOf(missing), "not_found");
68
- });
69
- test("end-to-end over MCP: list tools and call one through the real server adapter", async () => {
70
- const fake = new FakeCore()
71
- .on("GET", (u) => u.pathname.endsWith("/auth/me"), authMeInbox("inb_e2e"))
72
- .on("GET", (u) => /\/inboxes\/inb_e2e\/threads$/.test(u.pathname), {
73
- json: { threads: [{ tid: "thr_1" }] },
74
- });
75
- const core = new CoreClient({ apiKey: KEY, baseUrl: "https://core.test/v0", fetchImpl: fake.fetch });
76
- const server = await buildServer(core);
77
- const [clientT, serverT] = InMemoryTransport.createLinkedPair();
78
- const client = new Client({ name: "test-client", version: "0" });
79
- await Promise.all([server.connect(serverT), client.connect(clientT)]);
80
- const listed = await client.listTools();
81
- assert.ok(listed.tools.some((t) => t.name === "list_threads"));
82
- // Single-inbox key: no tool exposes inbox_id.
83
- for (const t of listed.tools) {
84
- const props = (t.inputSchema?.properties ?? {});
85
- assert.ok(!("inbox_id" in props));
86
- }
87
- const res = await client.callTool({ name: "list_threads", arguments: {} });
88
- assert.equal(res.isError ?? false, false);
89
- const content = res.content;
90
- const payload = JSON.parse(content[0].text);
91
- assert.equal(payload.threads[0].tid, "thr_1");
92
- await client.close();
93
- await server.close();
94
- });
95
- test("end-to-end: a core error surfaces as an isError tool result with a stable code (no leak)", async () => {
96
- const fake = new FakeCore()
97
- .on("GET", (u) => u.pathname.endsWith("/auth/me"), authMeInbox("inb_e2e"))
98
- .on("GET", () => true, { status: 404, json: { code: "not_found" } });
99
- const core = new CoreClient({ apiKey: KEY, baseUrl: "https://core.test/v0", fetchImpl: fake.fetch });
100
- const server = await buildServer(core);
101
- const [clientT, serverT] = InMemoryTransport.createLinkedPair();
102
- const client = new Client({ name: "test-client", version: "0" });
103
- await Promise.all([server.connect(serverT), client.connect(clientT)]);
104
- const res = await client.callTool({ name: "get_message", arguments: { message_id: "m_x" } });
105
- assert.equal(res.isError, true);
106
- const content = res.content;
107
- assert.equal(content[0].text, "error: not_found");
108
- assert.ok(!content[0].text.includes(KEY));
109
- await client.close();
110
- await server.close();
111
- });
package/dist/testutil.js DELETED
@@ -1,117 +0,0 @@
1
- // Test doubles — a fake core (a FetchLike that records every request and returns programmed replies)
2
- // and a captured-request shape the tests assert against. Not a *.test.ts, so the test runner does not
3
- // execute it as a suite. All fixtures are written from scratch (AGENTS.md §9): invented ids, no real
4
- // data.
5
- export class FakeCore {
6
- requests = [];
7
- routes = [];
8
- /** Register a route. Routes are matched in order; the first match wins. */
9
- on(method, match, reply) {
10
- this.routes.push({ method, match, reply });
11
- return this;
12
- }
13
- /** A FetchLike bound to this fake. Records the request, then returns the first matching route's
14
- * reply, or a 404 when nothing matches (so an unexpected call fails closed, like the real core). */
15
- fetch = async (rawUrl, init) => {
16
- const url = new URL(rawUrl);
17
- const headers = lowerHeaders(init.headers);
18
- let body;
19
- if (typeof init.body === "string" && init.body.length > 0) {
20
- try {
21
- body = JSON.parse(init.body);
22
- }
23
- catch {
24
- body = init.body;
25
- }
26
- }
27
- const captured = { method: init.method ?? "GET", url: rawUrl, headers, body };
28
- this.requests.push(captured);
29
- const route = this.routes.find((r) => r.method === captured.method && r.match(url));
30
- const reply = route
31
- ? typeof route.reply === "function"
32
- ? route.reply(captured, url)
33
- : route.reply
34
- : { status: 404, json: { code: "not_found" } };
35
- return makeResponse(reply);
36
- };
37
- }
38
- function lowerHeaders(h) {
39
- const out = {};
40
- if (!h)
41
- return out;
42
- if (h instanceof Headers) {
43
- h.forEach((v, k) => (out[k.toLowerCase()] = v));
44
- }
45
- else if (Array.isArray(h)) {
46
- for (const pair of h) {
47
- const k = pair[0];
48
- const v = pair[1];
49
- if (k !== undefined && v !== undefined)
50
- out[k.toLowerCase()] = v;
51
- }
52
- }
53
- else {
54
- for (const [k, v] of Object.entries(h))
55
- out[k.toLowerCase()] = String(v);
56
- }
57
- return out;
58
- }
59
- function makeResponse(reply) {
60
- const status = reply.status ?? 200;
61
- const hasBody = reply.json !== undefined;
62
- // A 204/205 MUST have a null body (undici throws on any body, even ""), matching the real core's
63
- // no-content replies (e.g. a DELETE).
64
- const nullBodyStatus = status === 204 || status === 205 || status === 304;
65
- const body = hasBody ? JSON.stringify(reply.json) : nullBodyStatus ? null : "";
66
- return new Response(body, {
67
- status,
68
- headers: hasBody ? { "content-type": "application/json" } : {},
69
- });
70
- }
71
- /** An /auth/me reply for an inbox-scoped key (single implied inbox). */
72
- export function authMeInbox(inboxId) {
73
- return {
74
- json: {
75
- scope_type: "inbox",
76
- scope_id: inboxId,
77
- organization_id: "org_test",
78
- pod_id: "pod_test",
79
- inbox_id: inboxId,
80
- api_key_id: "key_test",
81
- permissions: null,
82
- },
83
- };
84
- }
85
- /** An /auth/me reply for a pod-scoped key (reaches many inboxes). */
86
- export function authMePod() {
87
- return {
88
- json: {
89
- scope_type: "pod",
90
- scope_id: "pod_test",
91
- organization_id: "org_test",
92
- pod_id: "pod_test",
93
- inbox_id: null,
94
- api_key_id: "key_test",
95
- permissions: null,
96
- },
97
- };
98
- }
99
- /** A POST /v0/inboxes reply (the shape the core returns: inbox_id + email). Invented ids only (§9). */
100
- export function createdInbox(inboxId, email) {
101
- return { json: { inbox_id: inboxId, email } };
102
- }
103
- /** An /auth/me reply for a SCOPED pod key — a real permissions object drives the catalog gate. Pass
104
- * the permissions the human granted the key; everything else uses invented ids (§9). */
105
- export function authMeScoped(permissions, overrides = {}) {
106
- return {
107
- json: {
108
- scope_type: overrides.scope_type ?? "pod",
109
- scope_id: "pod_test",
110
- organization_id: "org_test",
111
- pod_id: overrides.pod_id === undefined ? "pod_test" : overrides.pod_id,
112
- inbox_id: overrides.inbox_id ?? null,
113
- api_key_id: "key_test",
114
- permissions,
115
- },
116
- };
117
- }