authflow-cli 0.4.1 → 0.6.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 (79) hide show
  1. package/README.md +62 -341
  2. package/dist/checks.d.ts +30 -0
  3. package/dist/checks.js +36 -0
  4. package/dist/checks.js.map +1 -0
  5. package/dist/client-conformance.d.ts +5 -1
  6. package/dist/client-conformance.js +9 -1
  7. package/dist/client-conformance.js.map +1 -1
  8. package/dist/commands/account.d.ts +2 -0
  9. package/dist/commands/account.js +105 -0
  10. package/dist/commands/account.js.map +1 -0
  11. package/dist/commands/doctor.js +5 -1
  12. package/dist/commands/doctor.js.map +1 -1
  13. package/dist/commands/mcp.js +11 -6
  14. package/dist/commands/mcp.js.map +1 -1
  15. package/dist/commands/plan.js +7 -7
  16. package/dist/commands/plan.js.map +1 -1
  17. package/dist/commands/register.js +30 -2
  18. package/dist/commands/register.js.map +1 -1
  19. package/dist/commands/set-domain.d.ts +34 -0
  20. package/dist/commands/set-domain.js +162 -0
  21. package/dist/commands/set-domain.js.map +1 -0
  22. package/dist/commands/signup.d.ts +1 -0
  23. package/dist/commands/signup.js +4 -50
  24. package/dist/commands/signup.js.map +1 -1
  25. package/dist/config.d.ts +36 -1
  26. package/dist/config.js +1 -1
  27. package/dist/config.js.map +1 -1
  28. package/dist/credentials.d.ts +22 -0
  29. package/dist/credentials.js +206 -0
  30. package/dist/credentials.js.map +1 -0
  31. package/dist/custom-domain.d.ts +96 -0
  32. package/dist/custom-domain.js +289 -0
  33. package/dist/custom-domain.js.map +1 -0
  34. package/dist/doctor.d.ts +31 -23
  35. package/dist/doctor.js +271 -57
  36. package/dist/doctor.js.map +1 -1
  37. package/dist/google-redirect.d.ts +5 -1
  38. package/dist/google-redirect.js +11 -5
  39. package/dist/google-redirect.js.map +1 -1
  40. package/dist/integration-plans.json +80 -0
  41. package/dist/integration.d.ts +17 -23
  42. package/dist/integration.js +29 -236
  43. package/dist/integration.js.map +1 -1
  44. package/dist/login.d.ts +11 -0
  45. package/dist/login.js +181 -0
  46. package/dist/login.js.map +1 -0
  47. package/dist/management.d.ts +17 -0
  48. package/dist/management.js +67 -0
  49. package/dist/management.js.map +1 -0
  50. package/dist/mcp/management-server.d.ts +4 -0
  51. package/dist/mcp/management-server.js +103 -0
  52. package/dist/mcp/management-server.js.map +1 -0
  53. package/dist/mcp/server.d.ts +8 -1
  54. package/dist/mcp/server.js +137 -7
  55. package/dist/mcp/server.js.map +1 -1
  56. package/dist/program.js +6 -1
  57. package/dist/program.js.map +1 -1
  58. package/dist/rail.d.ts +16 -0
  59. package/dist/rail.js +43 -0
  60. package/dist/rail.js.map +1 -1
  61. package/dist/resource-urls.d.ts +27 -0
  62. package/dist/resource-urls.js +56 -0
  63. package/dist/resource-urls.js.map +1 -0
  64. package/dist/secrets.d.ts +29 -14
  65. package/dist/secrets.js +201 -90
  66. package/dist/secrets.js.map +1 -1
  67. package/dist/status.d.ts +15 -1
  68. package/dist/status.js +68 -6
  69. package/dist/status.js.map +1 -1
  70. package/dist/surface.d.ts +1 -0
  71. package/dist/surface.js +1 -0
  72. package/dist/surface.js.map +1 -1
  73. package/dist/tenant-host.d.ts +49 -0
  74. package/dist/tenant-host.js +367 -0
  75. package/dist/tenant-host.js.map +1 -0
  76. package/dist/version.d.ts +10 -0
  77. package/dist/version.js +27 -0
  78. package/dist/version.js.map +1 -0
  79. package/package.json +6 -5
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Everything about a tenant-owned hostname that more than one front end needs: validating what the
3
+ * user typed, showing the DNS records they have to create, and waiting for the rail to confirm them.
4
+ * `set-domain`, the `authflow_set_custom_domain` tool, `status`, and `register` all render the same
5
+ * records, so they render them from here; a table that differs between the CLI and the agent path is
6
+ * how a tenant ends up adding the wrong record.
7
+ */
8
+ /** The public MCP path when a tenant does not choose one. */
9
+ export const defaultPublicPath = "/mcp";
10
+ /** How often verification is re-checked while waiting. Each call makes the rail do real DNS lookups. */
11
+ export const waitIntervalMs = 15_000;
12
+ /** Default `set-domain --wait` budget: DNS propagation plus certificate issuance. */
13
+ export const defaultWaitTimeoutSeconds = 1800;
14
+ /**
15
+ * How long the MCP tool waits. A tool call that outlives the client's request timeout (60 s is the
16
+ * SDK default and common in hosts) is dropped along with its result, so the tool waits less than
17
+ * that, reports where things stand, and tells the agent to call again. The client's clock also
18
+ * covers the request that sets the host and the last verification still in flight when the wait
19
+ * ends, which is why this is well short of 60 s rather than just under it.
20
+ */
21
+ export const mcpWaitTimeoutMs = 45_000;
22
+ const failureStates = new Set(["moved", "blocked", "removed"]);
23
+ /** Consecutive verification errors tolerated while waiting before the error is surfaced. */
24
+ const maxConsecutiveFailures = 3;
25
+ /** While nothing changes, say so about once a minute at the default interval. */
26
+ const heartbeatEveryPolls = 4;
27
+ /**
28
+ * Normalises a hostname the user typed and rejects the things that are not one. The rail validates
29
+ * too; this exists because the likeliest mistake is pasting the MCP URL, and the rail's 400 does not
30
+ * say that.
31
+ */
32
+ export function parsePublicHost(input, flag = "--host",
33
+ /** Where the path goes, for the error. Null when the caller has no path to offer. */
34
+ pathFlag = "--path") {
35
+ const host = input.trim().toLowerCase().replace(/\.$/, "");
36
+ if (host.length === 0 || /[/:?#@*\s]/.test(host)) {
37
+ throw new Error(`${flag} must be a bare hostname such as mcp.example.com, with no scheme, path, port or ` +
38
+ `wildcard (got '${input.trim()}').${pathFlag ? ` The path goes in ${pathFlag}.` : ""}`);
39
+ }
40
+ if (!/^[a-z0-9_]([a-z0-9_.-]*[a-z0-9_])?$/.test(host)) {
41
+ throw new Error(`${flag} '${input.trim()}' is not a plain ASCII hostname. Use the punycode (xn--) form for a ` +
42
+ "non-ASCII name.");
43
+ }
44
+ if (!host.includes(".")) {
45
+ throw new Error(`${flag} must be the full hostname, such as mcp.example.com, not just '${host}'. ` +
46
+ "(The short form belongs in your registrar's Name column, not here.)");
47
+ }
48
+ return host;
49
+ }
50
+ export function parsePublicPath(input, flag = "--path") {
51
+ const path = input.trim();
52
+ if (!path.startsWith("/")) {
53
+ throw new Error(`${flag} must start with '/', for example ${defaultPublicPath} (got '${path}').`);
54
+ }
55
+ return path;
56
+ }
57
+ /**
58
+ * A 200 from the rail that carries no `custom_domain` has nothing to show or wait on. It means the
59
+ * rail predates custom domains or something upstream dropped the object; either way, say so rather
60
+ * than print an empty table and report success.
61
+ */
62
+ export function assertCustomDomainReturned(receipt, host, slug) {
63
+ if (!receipt.custom_domain) {
64
+ throw new Error(`The rail returned no custom_domain for ${host} on ${slug}, so there are no records to show. ` +
65
+ "Is the rail new enough to support custom domains?");
66
+ }
67
+ }
68
+ /**
69
+ * Re-verifying means re-checking a host that is already there. A resource with none has nothing to
70
+ * verify, and the bare error from asking the rail anyway would not say how to fix it, so say that
71
+ * plainly and name the argument that sets one. Returns the host the resource is registered on.
72
+ */
73
+ export function assertCustomDomainSet(receipt, slug, hostFlag = "--host") {
74
+ const host = receipt.public_host?.trim();
75
+ if (!host) {
76
+ throw new Error(`no custom domain set on ${slug}; pass ${hostFlag}`);
77
+ }
78
+ return host;
79
+ }
80
+ export function customDomainState(receipt) {
81
+ return receipt.custom_domain?.state?.trim().toLowerCase() || undefined;
82
+ }
83
+ export function isCustomDomainFailure(state) {
84
+ return state !== undefined && failureStates.has(state);
85
+ }
86
+ /** One line on what a state means, for the person staring at it. */
87
+ export function describeCustomDomainState(state) {
88
+ switch (state) {
89
+ case "awaiting_ownership":
90
+ return "waiting for the ownership TXT record";
91
+ case "provisioning":
92
+ return "ownership verified; provisioning the hostname and certificate";
93
+ case "awaiting_traffic":
94
+ return "waiting for the traffic and certificate CNAMEs";
95
+ case "ready":
96
+ return "the hostname is serving traffic";
97
+ case "moved":
98
+ return "DNS no longer points at Authflow";
99
+ case "blocked":
100
+ return "the hostname is blocked";
101
+ case "removed":
102
+ return "the hostname was removed";
103
+ case undefined:
104
+ return "the rail reported no custom-domain state";
105
+ default:
106
+ return "a state this CLI does not know; see last_error";
107
+ }
108
+ }
109
+ /** `awaiting_traffic (waiting for the traffic and certificate CNAMEs)`, plus the rail's last error. */
110
+ export function summarizeCustomDomain(receipt) {
111
+ const state = customDomainState(receipt);
112
+ const error = receipt.custom_domain?.last_error?.trim();
113
+ return `${state ?? "unknown"} (${describeCustomDomainState(state)})${error ? ` - ${error}` : ""}`;
114
+ }
115
+ /** The records as an aligned TYPE / NAME / VALUE / PURPOSE table, in the order the rail listed them. */
116
+ export function formatDnsRecords(records) {
117
+ const rows = [
118
+ ["TYPE", "NAME", "VALUE", "PURPOSE"],
119
+ ...records.map((record) => [
120
+ String(record.type ?? ""),
121
+ String(record.name ?? ""),
122
+ String(record.value ?? ""),
123
+ String(record.purpose ?? ""),
124
+ ]),
125
+ ];
126
+ const widths = [0, 1, 2].map((column) => Math.max(...rows.map((row) => row[column].length)));
127
+ return rows.map((row) => `${row[0].padEnd(widths[0])} ${row[1].padEnd(widths[1])} ${row[2].padEnd(widths[2])} ${row[3]}`.trimEnd());
128
+ }
129
+ /**
130
+ * Registrars disagree about the Name column. GoDaddy and several others append the domain
131
+ * themselves, so a full name there produces `mcp.example.com.example.com` and a record that
132
+ * silently never matches.
133
+ */
134
+ export function registrarNameHint(host) {
135
+ const labels = host.split(".");
136
+ const intro = "Registrars such as GoDaddy usually want only the host part of the name, not the full name";
137
+ if (labels.length < 3) {
138
+ return [`${intro}: leave your domain off.`];
139
+ }
140
+ const relative = labels.slice(0, -2).join(".");
141
+ return [
142
+ `${intro}:`,
143
+ `for ${host} enter "${relative}", and for _authflow-verify.${host} enter "_authflow-verify.${relative}".`,
144
+ ];
145
+ }
146
+ /** The "add these records" block shared by every front end. Empty when the rail listed none. */
147
+ export function dnsInstructions(receipt) {
148
+ const records = receipt.custom_domain?.records ?? [];
149
+ if (records.length === 0) {
150
+ return [];
151
+ }
152
+ const lines = [
153
+ "Add these DNS records at your registrar:",
154
+ "",
155
+ ...formatDnsRecords(records).map((line) => ` ${line}`),
156
+ "",
157
+ ];
158
+ const host = receipt.public_host?.trim();
159
+ if (host) {
160
+ lines.push(...registrarNameHint(host));
161
+ }
162
+ // The certificate record only exists once provisioning has begun, which is after ownership is
163
+ // verified. Without saying so, a tenant who sees two records adds two and wonders about the third.
164
+ const state = customDomainState(receipt);
165
+ if (!records.some((record) => record.purpose === "certificate") &&
166
+ state !== "ready" &&
167
+ !isCustomDomainFailure(state)) {
168
+ lines.push("", "The certificate record (_acme-challenge) is listed once provisioning begins, after ownership", "is verified. Check again once the ownership TXT record is in place.");
169
+ }
170
+ return lines;
171
+ }
172
+ /**
173
+ * The command that re-checks the host already set on a resource. It names no host and no path: the
174
+ * rail keeps both, so there is nothing to retype and nothing to get wrong on a re-run.
175
+ */
176
+ export function setDomainCommandLine(slug) {
177
+ return `authflow set-domain --slug ${slug} --wait`;
178
+ }
179
+ /**
180
+ * Polls verification until the domain is ready, fails, or the budget runs out. Verifies straight
181
+ * away and then every interval: a tenant who re-runs after adding their records should not wait a
182
+ * full interval to hear it worked.
183
+ *
184
+ * `ready` and the failure states settle it. Anything else, including a state this CLI has never
185
+ * heard of, keeps waiting, so a rail that adds a state does not break an old CLI.
186
+ */
187
+ export async function waitForCustomDomain(initial, options) {
188
+ const intervalMs = options.intervalMs ?? waitIntervalMs;
189
+ const sleep = options.sleep ?? defaultSleep;
190
+ const now = options.now ?? Date.now;
191
+ const started = now();
192
+ const deadline = started + options.timeoutMs;
193
+ const progress = [];
194
+ const emit = (line) => {
195
+ progress.push(line);
196
+ options.onProgress?.(line);
197
+ };
198
+ const stamp = () => `[${formatClock(now() - started)}]`;
199
+ const finish = (outcome, receipt, polls) => ({
200
+ outcome,
201
+ receipt,
202
+ polls,
203
+ elapsedMs: now() - started,
204
+ progress,
205
+ });
206
+ let receipt = initial;
207
+ const already = verdict(receipt);
208
+ if (already) {
209
+ return finish(already, receipt, 0);
210
+ }
211
+ emit(`Checking every ${formatDuration(intervalMs)}, giving up after ${formatDuration(options.timeoutMs)}.`);
212
+ const seen = new Set((receipt.custom_domain?.records ?? []).map(recordKey));
213
+ let lastSummary = summarizeCustomDomain(receipt);
214
+ let polls = 0;
215
+ let failures = 0;
216
+ for (;;) {
217
+ let fresh;
218
+ try {
219
+ fresh = await options.verify();
220
+ }
221
+ catch (err) {
222
+ // A 30-minute wait should not die on one dropped connection, but a persistent error (a bad
223
+ // key, a rail that is down) must not be retried until the deadline either.
224
+ failures += 1;
225
+ if (failures >= maxConsecutiveFailures) {
226
+ throw err;
227
+ }
228
+ emit(`${stamp()} verification failed (${errorMessage(err)}); will retry`);
229
+ }
230
+ if (fresh) {
231
+ failures = 0;
232
+ polls += 1;
233
+ receipt = fresh;
234
+ const summary = summarizeCustomDomain(receipt);
235
+ if (summary !== lastSummary || polls === 1 || polls % heartbeatEveryPolls === 0) {
236
+ emit(`${stamp()} ${summary}`);
237
+ lastSummary = summary;
238
+ }
239
+ // The certificate record appears mid-wait, after ownership verifies. Saying so here is the
240
+ // difference between "waiting on DNS" and "waiting on a record nobody knows to add".
241
+ const appeared = (receipt.custom_domain?.records ?? []).filter((record) => !seen.has(recordKey(record)));
242
+ if (appeared.length > 0) {
243
+ for (const record of appeared) {
244
+ seen.add(recordKey(record));
245
+ }
246
+ emit(`${stamp()} new DNS record${appeared.length === 1 ? "" : "s"} to add:`);
247
+ for (const line of formatDnsRecords(appeared)) {
248
+ emit(` ${line}`);
249
+ }
250
+ }
251
+ const outcome = verdict(receipt);
252
+ if (outcome) {
253
+ return finish(outcome, receipt, polls);
254
+ }
255
+ }
256
+ const remaining = deadline - now();
257
+ if (remaining <= 0) {
258
+ return finish("timeout", receipt, polls);
259
+ }
260
+ await sleep(Math.min(intervalMs, remaining));
261
+ }
262
+ }
263
+ function verdict(receipt) {
264
+ const state = customDomainState(receipt);
265
+ if (state === "ready") {
266
+ return "ready";
267
+ }
268
+ return isCustomDomainFailure(state) ? "failed" : undefined;
269
+ }
270
+ function recordKey(record) {
271
+ return `${record.type}|${record.name}|${record.value}`;
272
+ }
273
+ function errorMessage(err) {
274
+ return err instanceof Error ? err.message : String(err);
275
+ }
276
+ function defaultSleep(ms) {
277
+ return new Promise((resolve) => setTimeout(resolve, ms));
278
+ }
279
+ /** `1:05` for 65 seconds. */
280
+ function formatClock(ms) {
281
+ const total = Math.max(0, Math.floor(ms / 1000));
282
+ return `${Math.floor(total / 60)}:${String(total % 60).padStart(2, "0")}`;
283
+ }
284
+ /** `15s`, `50s`, `30m`. */
285
+ export function formatDuration(ms) {
286
+ const seconds = Math.round(ms / 1000);
287
+ return seconds >= 120 && seconds % 60 === 0 ? `${seconds / 60}m` : `${seconds}s`;
288
+ }
289
+ //# sourceMappingURL=custom-domain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-domain.js","sourceRoot":"","sources":["../src/custom-domain.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,6DAA6D;AAC7D,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAExC,wGAAwG;AACxG,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC;AAErC,qFAAqF;AACrF,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEvC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;AAE/D,4FAA4F;AAC5F,MAAM,sBAAsB,GAAG,CAAC,CAAC;AAEjC,iFAAiF;AACjF,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAE9B;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAa,EACb,IAAI,GAAG,QAAQ;AACf,qFAAqF;AACrF,WAA0B,QAAQ;IAElC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAE3D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,kFAAkF;YACvF,kBAAkB,KAAK,CAAC,IAAI,EAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,qBAAqB,QAAQ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACzF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,qCAAqC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,KAAK,KAAK,CAAC,IAAI,EAAE,sEAAsE;YAC5F,iBAAiB,CACpB,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,kEAAkE,IAAI,KAAK;YAChF,qEAAqE,CACxE,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,IAAI,GAAG,QAAQ;IAC5D,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC1B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,qCAAqC,iBAAiB,UAAU,IAAI,KAAK,CAAC,CAAC;IACpG,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAA4B,EAAE,IAAY,EAAE,IAAY;IACjG,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,0CAA0C,IAAI,OAAO,IAAI,qCAAqC;YAC5F,mDAAmD,CACtD,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAA4B,EAAE,IAAY,EAAE,QAAQ,GAAG,QAAQ;IACnG,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;IACzC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,2BAA2B,IAAI,UAAU,QAAQ,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAA4B;IAC5D,OAAO,OAAO,CAAC,aAAa,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,SAAS,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAyB;IAC7D,OAAO,KAAK,KAAK,SAAS,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,yBAAyB,CAAC,KAAyB;IACjE,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,oBAAoB;YACvB,OAAO,sCAAsC,CAAC;QAChD,KAAK,cAAc;YACjB,OAAO,+DAA+D,CAAC;QACzE,KAAK,kBAAkB;YACrB,OAAO,gDAAgD,CAAC;QAC1D,KAAK,OAAO;YACV,OAAO,iCAAiC,CAAC;QAC3C,KAAK,OAAO;YACV,OAAO,kCAAkC,CAAC;QAC5C,KAAK,SAAS;YACZ,OAAO,yBAAyB,CAAC;QACnC,KAAK,SAAS;YACZ,OAAO,0BAA0B,CAAC;QACpC,KAAK,SAAS;YACZ,OAAO,0CAA0C,CAAC;QACpD;YACE,OAAO,gDAAgD,CAAC;IAC5D,CAAC;AACH,CAAC;AAED,uGAAuG;AACvG,MAAM,UAAU,qBAAqB,CAAC,OAA4B;IAChE,MAAM,KAAK,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IACxD,OAAO,GAAG,KAAK,IAAI,SAAS,KAAK,yBAAyB,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AACpG,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,gBAAgB,CAAC,OAAsC;IACrE,MAAM,IAAI,GAAG;QACX,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC;QACpC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;YACzB,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACzB,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACzB,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;YAC1B,MAAM,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC;SAC7B,CAAC;KACH,CAAC;IACF,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAE7F,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CACtB,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,CAC7G,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAG,2FAA2F,CAAC;IAC1G,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,KAAK,0BAA0B,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,OAAO;QACL,GAAG,KAAK,GAAG;QACX,OAAO,IAAI,WAAW,QAAQ,+BAA+B,IAAI,4BAA4B,QAAQ,IAAI;KAC1G,CAAC;AACJ,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,eAAe,CAAC,OAA4B;IAC1D,MAAM,OAAO,GAAG,OAAO,CAAC,aAAa,EAAE,OAAO,IAAI,EAAE,CAAC;IACrD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAAG;QACZ,0CAA0C;QAC1C,EAAE;QACF,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;QACvD,EAAE;KACH,CAAC;IAEF,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;IACzC,IAAI,IAAI,EAAE,CAAC;QACT,KAAK,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC;IACzC,CAAC;IAED,8FAA8F;IAC9F,mGAAmG;IACnG,MAAM,KAAK,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACzC,IACE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,KAAK,aAAa,CAAC;QAC3D,KAAK,KAAK,OAAO;QACjB,CAAC,qBAAqB,CAAC,KAAK,CAAC,EAC7B,CAAC;QACD,KAAK,CAAC,IAAI,CACR,EAAE,EACF,8FAA8F,EAC9F,qEAAqE,CACtE,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,OAAO,8BAA8B,IAAI,SAAS,CAAC;AACrD,CAAC;AA0BD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAA4B,EAC5B,OAAoB;IAEpB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,cAAc,CAAC;IACxD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAAC;IAC5C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC;IACtB,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC;IAC7C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,MAAM,IAAI,GAAG,CAAC,IAAY,EAAQ,EAAE;QAClC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpB,OAAO,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,GAAW,EAAE,CAAC,IAAI,WAAW,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;IAChE,MAAM,MAAM,GAAG,CAAC,OAAoB,EAAE,OAA4B,EAAE,KAAa,EAAc,EAAE,CAAC,CAAC;QACjG,OAAO;QACP,OAAO;QACP,KAAK;QACL,SAAS,EAAE,GAAG,EAAE,GAAG,OAAO;QAC1B,QAAQ;KACT,CAAC,CAAC;IAEH,IAAI,OAAO,GAAG,OAAO,CAAC;IACtB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,CAAC,kBAAkB,cAAc,CAAC,UAAU,CAAC,qBAAqB,cAAc,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAE5G,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;IAC5E,IAAI,WAAW,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACjD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,SAAS,CAAC;QACR,IAAI,KAAsC,CAAC;QAC3C,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;QACjC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,2FAA2F;YAC3F,2EAA2E;YAC3E,QAAQ,IAAI,CAAC,CAAC;YACd,IAAI,QAAQ,IAAI,sBAAsB,EAAE,CAAC;gBACvC,MAAM,GAAG,CAAC;YACZ,CAAC;YACD,IAAI,CAAC,GAAG,KAAK,EAAE,yBAAyB,YAAY,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QAC5E,CAAC;QAED,IAAI,KAAK,EAAE,CAAC;YACV,QAAQ,GAAG,CAAC,CAAC;YACb,KAAK,IAAI,CAAC,CAAC;YACX,OAAO,GAAG,KAAK,CAAC;YAEhB,MAAM,OAAO,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;YAC/C,IAAI,OAAO,KAAK,WAAW,IAAI,KAAK,KAAK,CAAC,IAAI,KAAK,GAAG,mBAAmB,KAAK,CAAC,EAAE,CAAC;gBAChF,IAAI,CAAC,GAAG,KAAK,EAAE,IAAI,OAAO,EAAE,CAAC,CAAC;gBAC9B,WAAW,GAAG,OAAO,CAAC;YACxB,CAAC;YAED,2FAA2F;YAC3F,qFAAqF;YACrF,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAC5D,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CACzC,CAAC;YACF,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACxB,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;oBAC9B,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;gBAC9B,CAAC;gBACD,IAAI,CAAC,GAAG,KAAK,EAAE,kBAAkB,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC;gBAC7E,KAAK,MAAM,IAAI,IAAI,gBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC9C,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;gBACpB,CAAC;YACH,CAAC;YAED,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;YACjC,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;YACzC,CAAC;QACH,CAAC;QAED,MAAM,SAAS,GAAG,QAAQ,GAAG,GAAG,EAAE,CAAC;QACnC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YACnB,OAAO,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QAC3C,CAAC;QAED,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAC,OAA4B;IAC3C,MAAM,KAAK,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;QACtB,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7D,CAAC;AAED,SAAS,SAAS,CAAC,MAA0B;IAC3C,OAAO,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;AACzD,CAAC;AAED,SAAS,YAAY,CAAC,GAAY;IAChC,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,YAAY,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,6BAA6B;AAC7B,SAAS,WAAW,CAAC,EAAU;IAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IACjD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AAC5E,CAAC;AAED,2BAA2B;AAC3B,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;IACtC,OAAO,OAAO,IAAI,GAAG,IAAI,OAAO,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC;AACnF,CAAC"}
package/dist/doctor.d.ts CHANGED
@@ -1,3 +1,8 @@
1
+ import { fetchOrNull, type DoctorCheck } from "./checks.js";
2
+ import { type ResourceEndpoints } from "./resource-urls.js";
3
+ import { type HostProbes } from "./tenant-host.js";
4
+ export { fetchOrNull };
5
+ export type { DoctorCheck, FetchOutcome } from "./checks.js";
1
6
  /**
2
7
  * Preflight against a rail deployment, from a tenant's point of view.
3
8
  *
@@ -8,41 +13,44 @@
8
13
  * showed up again as 500 HTML on /login (and the other consumer pages) when returnUrl is
9
14
  * missing — it looks like an outage, it is an unhandled throw. These checks do.
10
15
  */
11
- export type DoctorCheck = {
12
- name: string;
13
- pass: boolean;
14
- detail: string;
15
- /** What to do about it. Empty when the check passed. */
16
- remedy?: string;
17
- };
18
16
  export type DoctorReport = {
19
17
  issuer: string;
20
18
  slug?: string;
19
+ /** Tenant hostname whose DNS, certificate and discovery documents were checked (`--host`). */
20
+ host?: string;
21
+ /**
22
+ * Where the resource's PRM and 401 challenge were probed, so the checks that follow (the Google
23
+ * redirect) look at the same URLs. Only present when a slug was given.
24
+ */
25
+ endpoints?: ResourceEndpoints;
21
26
  /** MCP clients whose OAuth registration was replayed; absent when `--client` was not passed. */
22
27
  clients?: string[];
23
28
  dryRun?: boolean;
29
+ /** True when the slug is a correct draft: paywall 404s are expected, not a rail outage. */
30
+ draft?: boolean;
24
31
  pass: boolean;
25
32
  checks: DoctorCheck[];
26
33
  };
27
- export declare function runDoctor(issuer: string, slug?: string): Promise<DoctorReport>;
34
+ export type DoctorOptions = HostProbes & {
35
+ adminKey?: string;
36
+ /**
37
+ * A tenant's own hostname to verify end to end (DNS, TLS, redirect, PRM, 401). It supersedes the
38
+ * slug's own PRM and challenge checks, which would probe the same URLs a second time; the slug
39
+ * then only selects the receipt the host is compared against.
40
+ */
41
+ host?: string;
42
+ };
43
+ export declare function runDoctor(issuer: string, slug?: string, options?: DoctorOptions): Promise<DoctorReport>;
28
44
  export declare function checkConsumerPages(base: string): Promise<DoctorCheck>;
29
45
  /**
30
46
  * The paywall is a transport-level 401 carrying `resource_metadata`. If this is not exactly right,
31
47
  * MCP clients never prompt for auth, which presents as "the server just does not work in Claude".
48
+ *
49
+ * `target` is where the resource actually answers (from the receipt). Without it the slug URL on
50
+ * the issuer is assumed, which is only true for a resource on the gateway host.
32
51
  */
33
- /**
34
- * The paywall is a transport-level 401 carrying `resource_metadata`. If this is not exactly right,
35
- * MCP clients never prompt for auth, which presents as "the server just does not work in Claude".
36
- */
37
- export declare function checkGatewayChallenge(base: string, slug: string): Promise<DoctorCheck>;
38
- export type FetchOutcome = {
39
- ok: boolean;
40
- status?: number;
41
- body?: string;
42
- headers?: Headers;
43
- location?: string;
44
- error?: string;
45
- };
46
- /** Network failures are a diagnosis here, not an exception — every one of them is a real finding. */
47
- export declare function fetchOrNull(url: string, init?: RequestInit): Promise<FetchOutcome>;
52
+ export declare function checkGatewayChallenge(base: string, slug: string, draft?: boolean, target?: {
53
+ url: string;
54
+ publicHost?: string;
55
+ }): Promise<DoctorCheck>;
48
56
  export declare function formatDoctorReport(report: DoctorReport): string;