authflow-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +260 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +33 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/connect.d.ts +7 -0
  7. package/dist/commands/connect.js +84 -0
  8. package/dist/commands/connect.js.map +1 -0
  9. package/dist/commands/doctor.d.ts +2 -0
  10. package/dist/commands/doctor.js +16 -0
  11. package/dist/commands/doctor.js.map +1 -0
  12. package/dist/commands/init.d.ts +12 -0
  13. package/dist/commands/init.js +182 -0
  14. package/dist/commands/init.js.map +1 -0
  15. package/dist/commands/mcp.d.ts +2 -0
  16. package/dist/commands/mcp.js +24 -0
  17. package/dist/commands/mcp.js.map +1 -0
  18. package/dist/commands/mint-header.d.ts +2 -0
  19. package/dist/commands/mint-header.js +69 -0
  20. package/dist/commands/mint-header.js.map +1 -0
  21. package/dist/commands/probe.d.ts +2 -0
  22. package/dist/commands/probe.js +49 -0
  23. package/dist/commands/probe.js.map +1 -0
  24. package/dist/commands/register.d.ts +2 -0
  25. package/dist/commands/register.js +78 -0
  26. package/dist/commands/register.js.map +1 -0
  27. package/dist/commands/scaffold.d.ts +2 -0
  28. package/dist/commands/scaffold.js +72 -0
  29. package/dist/commands/scaffold.js.map +1 -0
  30. package/dist/commands/set-origin.d.ts +6 -0
  31. package/dist/commands/set-origin.js +38 -0
  32. package/dist/commands/set-origin.js.map +1 -0
  33. package/dist/commands/signup.d.ts +2 -0
  34. package/dist/commands/signup.js +53 -0
  35. package/dist/commands/signup.js.map +1 -0
  36. package/dist/config.d.ts +47 -0
  37. package/dist/config.js +56 -0
  38. package/dist/config.js.map +1 -0
  39. package/dist/constants.d.ts +12 -0
  40. package/dist/constants.js +13 -0
  41. package/dist/constants.js.map +1 -0
  42. package/dist/doctor.d.ts +24 -0
  43. package/dist/doctor.js +230 -0
  44. package/dist/doctor.js.map +1 -0
  45. package/dist/integration.d.ts +44 -0
  46. package/dist/integration.js +247 -0
  47. package/dist/integration.js.map +1 -0
  48. package/dist/mcp/server.d.ts +3 -0
  49. package/dist/mcp/server.js +362 -0
  50. package/dist/mcp/server.js.map +1 -0
  51. package/dist/rail.d.ts +80 -0
  52. package/dist/rail.js +115 -0
  53. package/dist/rail.js.map +1 -0
  54. package/dist/secrets.d.ts +28 -0
  55. package/dist/secrets.js +117 -0
  56. package/dist/secrets.js.map +1 -0
  57. package/package.json +56 -0
@@ -0,0 +1,53 @@
1
+ import { requireIssuer } from "../config.js";
2
+ import { signupTenant } from "../rail.js";
3
+ import { writeAdminApiKey } from "../secrets.js";
4
+ export function signupCommand(program) {
5
+ program
6
+ .command("signup")
7
+ .description("Mint a per-tenant admin API key (shown once)")
8
+ .requiredOption("--email <address>", "Email that owns the tenant")
9
+ .option("--issuer <url>", "Authflow issuer (default: AUTHFLOW_ISSUER)")
10
+ .option("--write-secrets", "Write the key to an env file (default: .env.local)", true)
11
+ .option("--no-write-secrets", "Print the key only; do not write it to disk")
12
+ .option("--secret-destination <path>", "Env-file path for --write-secrets")
13
+ .addHelpText("after", [
14
+ "",
15
+ "The key is scoped to slugs you register with it. Paste it into Cursor as",
16
+ "AUTHFLOW_ADMIN_API_KEY, then run `authflow mcp` or `authflow init`.",
17
+ ].join("\n"))
18
+ .action(async (opts) => {
19
+ const issuer = requireIssuer(opts.issuer);
20
+ const receipt = await signupTenant(issuer, opts.email);
21
+ let storedAt;
22
+ if (opts.writeSecrets !== false) {
23
+ storedAt = await writeAdminApiKey({
24
+ value: receipt.admin_api_key,
25
+ destination: opts.secretDestination,
26
+ });
27
+ }
28
+ const mcpConfig = {
29
+ mcpServers: {
30
+ authflow: {
31
+ command: "npx",
32
+ args: ["-y", "authflow-cli", "mcp"],
33
+ env: {
34
+ AUTHFLOW_ISSUER: receipt.issuer,
35
+ AUTHFLOW_ADMIN_API_KEY: storedAt ? `<from ${storedAt}>` : receipt.admin_api_key,
36
+ },
37
+ },
38
+ },
39
+ };
40
+ process.stdout.write(JSON.stringify({
41
+ ...receipt,
42
+ admin_api_key: storedAt ? `<stored in ${storedAt}>` : receipt.admin_api_key,
43
+ mcp_config: mcpConfig,
44
+ next_steps: [
45
+ storedAt
46
+ ? `Load ${storedAt} (or copy the key into Cursor's MCP env) and run: npx -y authflow-cli init --slug <your-slug>`
47
+ : "Store the admin_api_key, then: npx -y authflow-cli init --slug <your-slug>",
48
+ "Or paste mcp_config into Cursor and let the agent finish onboarding.",
49
+ ],
50
+ }, null, 2) + "\n");
51
+ });
52
+ }
53
+ //# sourceMappingURL=signup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signup.js","sourceRoot":"","sources":["../../src/commands/signup.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AASjD,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,8CAA8C,CAAC;SAC3D,cAAc,CAAC,mBAAmB,EAAE,4BAA4B,CAAC;SACjE,MAAM,CAAC,gBAAgB,EAAE,4CAA4C,CAAC;SACtE,MAAM,CAAC,iBAAiB,EAAE,oDAAoD,EAAE,IAAI,CAAC;SACrF,MAAM,CAAC,oBAAoB,EAAE,6CAA6C,CAAC;SAC3E,MAAM,CAAC,6BAA6B,EAAE,mCAAmC,CAAC;SAC1E,WAAW,CACV,OAAO,EACP;QACE,EAAE;QACF,0EAA0E;QAC1E,qEAAqE;KACtE,CAAC,IAAI,CAAC,IAAI,CAAC,CACb;SACA,MAAM,CAAC,KAAK,EAAE,IAAmB,EAAE,EAAE;QACpC,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAEvD,IAAI,QAA4B,CAAC;QACjC,IAAI,IAAI,CAAC,YAAY,KAAK,KAAK,EAAE,CAAC;YAChC,QAAQ,GAAG,MAAM,gBAAgB,CAAC;gBAChC,KAAK,EAAE,OAAO,CAAC,aAAa;gBAC5B,WAAW,EAAE,IAAI,CAAC,iBAAiB;aACpC,CAAC,CAAC;QACL,CAAC;QAED,MAAM,SAAS,GAAG;YAChB,UAAU,EAAE;gBACV,QAAQ,EAAE;oBACR,OAAO,EAAE,KAAK;oBACd,IAAI,EAAE,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,CAAC;oBACnC,GAAG,EAAE;wBACH,eAAe,EAAE,OAAO,CAAC,MAAM;wBAC/B,sBAAsB,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,QAAQ,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa;qBAChF;iBACF;aACF;SACF,CAAC;QAEF,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,IAAI,CAAC,SAAS,CACZ;YACE,GAAG,OAAO;YACV,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC,cAAc,QAAQ,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa;YAC3E,UAAU,EAAE,SAAS;YACrB,UAAU,EAAE;gBACV,QAAQ;oBACN,CAAC,CAAC,QAAQ,QAAQ,+FAA+F;oBACjH,CAAC,CAAC,4EAA4E;gBAChF,sEAAsE;aACvE;SACF,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI,CACT,CAAC;IACJ,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,47 @@
1
+ export type CliConfig = {
2
+ issuer: string;
3
+ adminApiKey?: string;
4
+ originApiKey?: string;
5
+ resource?: string;
6
+ gatewayUrl?: string;
7
+ };
8
+ export declare function requireIssuer(flag?: string): string;
9
+ export declare function requireAdminKey(flag?: string): string;
10
+ export declare function requireOriginKey(flag?: string): string;
11
+ /** Registration receipt fields (snake_case) from RegistryAdminEndpoints. */
12
+ export type RegistrationReceipt = {
13
+ slug?: string;
14
+ display_name?: string;
15
+ resource?: string;
16
+ gateway_url?: string;
17
+ prm_url?: string;
18
+ issuer?: string;
19
+ keys_url?: string;
20
+ usage_url?: string;
21
+ origin_base_uri?: string;
22
+ connected_account_id?: string;
23
+ currency?: string;
24
+ price_amount_minor?: number;
25
+ credits_per_period?: number;
26
+ price_version?: number;
27
+ application_fee_percent?: number;
28
+ origin_api_key?: string;
29
+ gateway_egress_cidrs?: string[];
30
+ status?: string;
31
+ metering_mode?: string;
32
+ /** What still blocks go-live: origin_base_uri, connected_account_id, stripe_onboarding_complete. */
33
+ pending_requirements?: string[];
34
+ };
35
+ export declare function readReceipt(path: string): Promise<RegistrationReceipt>;
36
+ export declare function resolveGatewayUrl(opts: {
37
+ gatewayUrl?: string;
38
+ receipt?: RegistrationReceipt;
39
+ }): string | undefined;
40
+ export declare function apiJson<T>(url: string, init?: RequestInit & {
41
+ expectedStatuses?: number[];
42
+ }): Promise<{
43
+ status: number;
44
+ body: T;
45
+ raw: string;
46
+ }>;
47
+ export declare function bearer(token: string): Record<string, string>;
package/dist/config.js ADDED
@@ -0,0 +1,56 @@
1
+ import { readFile } from "node:fs/promises";
2
+ export function requireIssuer(flag) {
3
+ const issuer = (flag ?? process.env.AUTHFLOW_ISSUER ?? "").trim().replace(/\/+$/, "");
4
+ if (!issuer) {
5
+ throw new Error("Set AUTHFLOW_ISSUER or pass --issuer.");
6
+ }
7
+ return issuer;
8
+ }
9
+ export function requireAdminKey(flag) {
10
+ const key = (flag ?? process.env.AUTHFLOW_ADMIN_API_KEY ?? "").trim();
11
+ if (!key) {
12
+ throw new Error("Set AUTHFLOW_ADMIN_API_KEY or pass --admin-key.");
13
+ }
14
+ return key;
15
+ }
16
+ export function requireOriginKey(flag) {
17
+ const key = (flag ?? process.env.AUTHFLOW_ORIGIN_API_KEY ?? "").trim();
18
+ if (!key) {
19
+ throw new Error("Set AUTHFLOW_ORIGIN_API_KEY or pass --origin-key.");
20
+ }
21
+ return key;
22
+ }
23
+ export async function readReceipt(path) {
24
+ const raw = (await readFile(path, "utf8")).replace(/^\uFEFF/, "");
25
+ return JSON.parse(raw);
26
+ }
27
+ export function resolveGatewayUrl(opts) {
28
+ return (opts.gatewayUrl?.trim() ||
29
+ process.env.AUTHFLOW_GATEWAY_URL?.trim() ||
30
+ process.env.AUTHFLOW_RESOURCE?.trim() ||
31
+ opts.receipt?.gateway_url?.trim() ||
32
+ opts.receipt?.resource?.trim() ||
33
+ undefined);
34
+ }
35
+ export async function apiJson(url, init = {}) {
36
+ const { expectedStatuses, ...fetchInit } = init;
37
+ const response = await fetch(url, fetchInit);
38
+ const raw = await response.text();
39
+ let body;
40
+ try {
41
+ body = raw ? JSON.parse(raw) : {};
42
+ }
43
+ catch {
44
+ throw new Error(`Non-JSON response from ${url} (${response.status}): ${raw.slice(0, 200)}`);
45
+ }
46
+ if (expectedStatuses && !expectedStatuses.includes(response.status)) {
47
+ const err = body;
48
+ const detail = err.error_description ?? err.error ?? raw.slice(0, 300);
49
+ throw new Error(`${url} returned ${response.status}: ${detail}`);
50
+ }
51
+ return { status: response.status, body, raw };
52
+ }
53
+ export function bearer(token) {
54
+ return { Authorization: `Bearer ${token}` };
55
+ }
56
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Origin Protocol wire constants, mirrored from `Rail.Kernel.OriginProtocol.OriginProtocolConstants`.
3
+ * These are published contract: version, never mutate.
4
+ */
5
+ export declare const OriginProtocol: {
6
+ readonly version1: "v1";
7
+ readonly identityHeaderName: "X-Authflow-Identity";
8
+ readonly verificationKeysPath: "/.well-known/authflow-origin-keys";
9
+ readonly usagePath: "/origin/v1/usage";
10
+ readonly conformancePath: "/origin/v1/conformance";
11
+ readonly devMintHeaderPath: "/origin/v1/dev/mint-header";
12
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Origin Protocol wire constants, mirrored from `Rail.Kernel.OriginProtocol.OriginProtocolConstants`.
3
+ * These are published contract: version, never mutate.
4
+ */
5
+ export const OriginProtocol = {
6
+ version1: "v1",
7
+ identityHeaderName: "X-Authflow-Identity",
8
+ verificationKeysPath: "/.well-known/authflow-origin-keys",
9
+ usagePath: "/origin/v1/usage",
10
+ conformancePath: "/origin/v1/conformance",
11
+ devMintHeaderPath: "/origin/v1/dev/mint-header",
12
+ };
13
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,QAAQ,EAAE,IAAI;IACd,kBAAkB,EAAE,qBAAqB;IACzC,oBAAoB,EAAE,mCAAmC;IACzD,SAAS,EAAE,kBAAkB;IAC7B,eAAe,EAAE,wBAAwB;IACzC,iBAAiB,EAAE,4BAA4B;CACvC,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Preflight against a rail deployment, from a tenant's point of view.
3
+ *
4
+ * This exists because of a real onboarding failure: tenant #1 was given a staging hostname whose
5
+ * DNS resolved but which was never bound to the App Service, so Azure answered `404 Site Not Found`
6
+ * on every path. They read the 404 on the keys document as "the rail's keys endpoint is broken" and
7
+ * filed it, while the rail was healthy the whole time on a different hostname. Nothing in the
8
+ * onboarding path told either side which of the two it was. These checks do.
9
+ */
10
+ export type DoctorCheck = {
11
+ name: string;
12
+ pass: boolean;
13
+ detail: string;
14
+ /** What to do about it. Empty when the check passed. */
15
+ remedy?: string;
16
+ };
17
+ export type DoctorReport = {
18
+ issuer: string;
19
+ slug?: string;
20
+ pass: boolean;
21
+ checks: DoctorCheck[];
22
+ };
23
+ export declare function runDoctor(issuer: string, slug?: string): Promise<DoctorReport>;
24
+ export declare function formatDoctorReport(report: DoctorReport): string;
package/dist/doctor.js ADDED
@@ -0,0 +1,230 @@
1
+ import { OriginProtocol } from "./constants.js";
2
+ const timeoutMs = 15_000;
3
+ export async function runDoctor(issuer, slug) {
4
+ const base = issuer.trim().replace(/\/+$/, "");
5
+ const checks = [];
6
+ const reachable = await checkHostBound(base);
7
+ checks.push(reachable);
8
+ // Every later check reads a document from this host. If the host is not the rail, they would all
9
+ // fail with misleading messages that each look like a separate rail bug — which is exactly the
10
+ // pile of bugs this whole function exists to prevent.
11
+ if (!reachable.pass) {
12
+ return { issuer: base, slug, pass: false, checks };
13
+ }
14
+ checks.push(...(await checkOriginKeys(base)));
15
+ if (slug) {
16
+ checks.push(await checkProtectedResourceMetadata(base, slug));
17
+ checks.push(await checkGatewayChallenge(base, slug));
18
+ }
19
+ return { issuer: base, slug, pass: checks.every((c) => c.pass), checks };
20
+ }
21
+ /**
22
+ * An unbound Azure custom domain answers `404 Site Not Found` on every path, `/healthz` included.
23
+ * That is the signature that separates "wrong hostname" from "endpoint is broken", so it is worth
24
+ * one dedicated check with an unambiguous remedy.
25
+ */
26
+ async function checkHostBound(base) {
27
+ const url = `${base}/healthz`;
28
+ const response = await fetchOrNull(url);
29
+ if (!response.ok && response.error) {
30
+ return {
31
+ name: "issuer reachable",
32
+ pass: false,
33
+ detail: `${url} could not be reached: ${response.error}`,
34
+ remedy: "Check the hostname and DNS. A TLS name mismatch usually means the hostname is not bound " +
35
+ "to the deployment that answers on its IP.",
36
+ };
37
+ }
38
+ const status = response.status ?? 0;
39
+ if (status === 404) {
40
+ return {
41
+ name: "issuer reachable",
42
+ pass: false,
43
+ detail: `${url} returned 404. /healthz exists on every rail deployment, so this host is not serving the rail.`,
44
+ remedy: "The hostname is almost certainly not bound to the rail. Use the issuer from your " +
45
+ "registration receipt rather than a hostname copied from prose, and confirm with " +
46
+ "GET {issuer}/healthz before anything else.",
47
+ };
48
+ }
49
+ if (status !== 200) {
50
+ return {
51
+ name: "issuer reachable",
52
+ pass: false,
53
+ detail: `${url} returned ${status}, expected 200.`,
54
+ remedy: "The host is serving something, but not a healthy rail. Check the deployment.",
55
+ };
56
+ }
57
+ return { name: "issuer reachable", pass: true, detail: `${url} returned 200.` };
58
+ }
59
+ async function checkOriginKeys(base) {
60
+ const url = `${base}${OriginProtocol.verificationKeysPath}`;
61
+ const response = await fetchOrNull(url);
62
+ const checks = [];
63
+ if (!response.ok || response.status !== 200) {
64
+ checks.push({
65
+ name: "origin keys document",
66
+ pass: false,
67
+ detail: `${url} returned ${response.status ?? response.error}, expected 200.`,
68
+ remedy: "The host is a healthy rail but is not serving Origin Protocol keys. That is a rail-side " +
69
+ "bug — file it. Origins cannot verify identity headers until it is fixed.",
70
+ });
71
+ return checks;
72
+ }
73
+ let document;
74
+ try {
75
+ document = JSON.parse(response.body ?? "");
76
+ }
77
+ catch {
78
+ checks.push({
79
+ name: "origin keys document",
80
+ pass: false,
81
+ detail: `${url} did not return JSON.`,
82
+ remedy: "File this against the rail.",
83
+ });
84
+ return checks;
85
+ }
86
+ checks.push({ name: "origin keys document", pass: true, detail: `${url} returned 200.` });
87
+ // Byte-compare, per spec §5. A mismatch here is the tell-tale of configuring the origin from a
88
+ // hostname that redirects, or of pointing at the wrong environment entirely.
89
+ const documentIssuer = (document.issuer ?? "").trim();
90
+ checks.push(documentIssuer === base
91
+ ? {
92
+ name: "issuer matches",
93
+ pass: true,
94
+ detail: `Document issuer is ${documentIssuer}.`,
95
+ }
96
+ : {
97
+ name: "issuer matches",
98
+ pass: false,
99
+ detail: `Document issuer is '${documentIssuer}', but you configured '${base}'.`,
100
+ remedy: `Set AUTHFLOW_ISSUER to '${documentIssuer}' — origins MUST reject a keys document whose ` +
101
+ "issuer does not match their configured issuer, so this mismatch fails every request.",
102
+ });
103
+ const version = document.protocol_version ?? "";
104
+ checks.push(version === OriginProtocol.version1
105
+ ? { name: "protocol version", pass: true, detail: `Document advertises ${version}.` }
106
+ : {
107
+ name: "protocol version",
108
+ pass: false,
109
+ detail: `Document advertises '${version}', expected '${OriginProtocol.version1}'.`,
110
+ remedy: "This CLI implements v1. Upgrade the CLI, or file a rail bug.",
111
+ });
112
+ const keys = document.keys ?? [];
113
+ const signing = keys.filter((k) => k.kty === "OKP" && k.crv === "Ed25519" && k.x);
114
+ checks.push(signing.length > 0
115
+ ? {
116
+ name: "ed25519 verification key",
117
+ pass: true,
118
+ detail: `${signing.length} key(s): ${signing.map((k) => k.kid ?? "?").join(", ")}.`,
119
+ }
120
+ : {
121
+ name: "ed25519 verification key",
122
+ pass: false,
123
+ detail: "No usable Ed25519 JWK in the document.",
124
+ remedy: "File this against the rail. Identity headers cannot be verified without one.",
125
+ });
126
+ // A private key in a public document would be a total compromise of the identity header, so it
127
+ // is worth failing loudly rather than trusting that it never happens.
128
+ const leaked = keys.filter((k) => k.d !== undefined);
129
+ if (leaked.length > 0) {
130
+ checks.push({
131
+ name: "no private key material",
132
+ pass: false,
133
+ detail: `${leaked.length} key(s) contain a 'd' member.`,
134
+ remedy: "Stop. The rail is publishing private key material. Report this immediately and treat the " +
135
+ "signing key as compromised.",
136
+ });
137
+ }
138
+ return checks;
139
+ }
140
+ async function checkProtectedResourceMetadata(base, slug) {
141
+ const url = `${base}/.well-known/oauth-protected-resource/${encodeURIComponent(slug)}/mcp`;
142
+ const response = await fetchOrNull(url);
143
+ if (response.status === 200) {
144
+ return { name: "protected resource metadata", pass: true, detail: `${url} returned 200.` };
145
+ }
146
+ if (response.status === 404) {
147
+ return {
148
+ name: "protected resource metadata",
149
+ pass: false,
150
+ detail: `${url} returned 404.`,
151
+ remedy: `No live resource is registered at slug '${slug}'. A draft resource 404s here by design — ` +
152
+ "check its pending_requirements.",
153
+ };
154
+ }
155
+ return {
156
+ name: "protected resource metadata",
157
+ pass: false,
158
+ detail: `${url} returned ${response.status ?? response.error}, expected 200.`,
159
+ remedy: "MCP clients discover the authorization server from this document. File a rail bug.",
160
+ };
161
+ }
162
+ /**
163
+ * The paywall is a transport-level 401 carrying `resource_metadata`. If this is not exactly right,
164
+ * MCP clients never prompt for auth, which presents as "the server just does not work in Claude".
165
+ */
166
+ async function checkGatewayChallenge(base, slug) {
167
+ const url = `${base}/${encodeURIComponent(slug)}/mcp`;
168
+ const response = await fetchOrNull(url, {
169
+ method: "POST",
170
+ headers: { "Content-Type": "application/json", Accept: "application/json, text/event-stream" },
171
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} }),
172
+ });
173
+ if (response.status !== 401) {
174
+ return {
175
+ name: "gateway 401 challenge",
176
+ pass: false,
177
+ detail: `Unauthenticated POST ${url} returned ${response.status ?? response.error}, expected 401.`,
178
+ remedy: response.status === 404
179
+ ? `No live resource at slug '${slug}'. Check pending_requirements on the receipt.`
180
+ : "An unauthenticated connect that does not 401 means clients will never start OAuth.",
181
+ };
182
+ }
183
+ const challenge = response.headers?.get("www-authenticate") ?? "";
184
+ if (!/resource_metadata=/i.test(challenge)) {
185
+ return {
186
+ name: "gateway 401 challenge",
187
+ pass: false,
188
+ detail: `401 returned, but WWW-Authenticate has no resource_metadata parameter: '${challenge}'.`,
189
+ remedy: "MCP clients cannot discover the authorization server. File a rail bug.",
190
+ };
191
+ }
192
+ return {
193
+ name: "gateway 401 challenge",
194
+ pass: true,
195
+ detail: "401 with a resource_metadata challenge.",
196
+ };
197
+ }
198
+ /** Network failures are a diagnosis here, not an exception — every one of them is a real finding. */
199
+ async function fetchOrNull(url, init = {}) {
200
+ try {
201
+ const response = await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
202
+ return {
203
+ ok: true,
204
+ status: response.status,
205
+ body: await response.text(),
206
+ headers: response.headers,
207
+ };
208
+ }
209
+ catch (err) {
210
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
211
+ }
212
+ }
213
+ export function formatDoctorReport(report) {
214
+ const lines = [`issuer: ${report.issuer}`];
215
+ if (report.slug) {
216
+ lines.push(`slug: ${report.slug}`);
217
+ }
218
+ lines.push("");
219
+ for (const check of report.checks) {
220
+ lines.push(`${check.pass ? "PASS" : "FAIL"} ${check.name}`);
221
+ lines.push(` ${check.detail}`);
222
+ if (!check.pass && check.remedy) {
223
+ lines.push(` -> ${check.remedy}`);
224
+ }
225
+ }
226
+ lines.push("");
227
+ lines.push(report.pass ? "Overall: PASS" : "Overall: FAIL");
228
+ return lines.join("\n");
229
+ }
230
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import type { RegistrationReceipt } from "./config.js";
2
+ /**
3
+ * Framework-specific integration instructions, emitted as structured data rather than prose.
4
+ *
5
+ * The consumer is usually not a human: it is the creator's own coding agent, which already has the
6
+ * codebase open. The rail cannot see their files and should not try to — it supplies the plan, the
7
+ * agent applies it. That split is also what keeps this honest as a public contract: everything here
8
+ * is derived from the receipt and the published spec, never from rail internals.
9
+ */
10
+ export declare const integrationFrameworks: readonly ["dotnet", "typescript", "python", "proxy"];
11
+ export type IntegrationFramework = (typeof integrationFrameworks)[number];
12
+ export type IntegrationStep = {
13
+ title: string;
14
+ /** What the agent should do. Imperative, one action. */
15
+ action: string;
16
+ /** Shell command to run, when the step is a command. */
17
+ command?: string;
18
+ /** File content or snippet to apply, when the step is an edit. */
19
+ snippet?: string;
20
+ /** Where the snippet goes. */
21
+ file?: string;
22
+ };
23
+ export type IntegrationPlan = {
24
+ framework: IntegrationFramework;
25
+ summary: string;
26
+ configuration: Record<string, string>;
27
+ secrets: {
28
+ key: string;
29
+ envVar: string;
30
+ note: string;
31
+ };
32
+ steps: IntegrationStep[];
33
+ verification: string[];
34
+ invariants: string[];
35
+ metering: string;
36
+ };
37
+ export declare function isIntegrationFramework(value: string): value is IntegrationFramework;
38
+ export declare function buildIntegrationPlan(framework: IntegrationFramework, receipt: RegistrationReceipt): IntegrationPlan;
39
+ /**
40
+ * Origins that bill exactly one unit per tool call can skip debit/compensate entirely by declaring
41
+ * per-tool costs at registration. It is the largest and subtlest part of the integration, so the
42
+ * plan should say so rather than leaving it to be discovered in §9 of the quickstart.
43
+ */
44
+ export declare function meteringAdvice(gatewayMetered: boolean): string;