@vibe-commit/capture 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 (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +66 -0
  3. package/dist/agents/claude_code.d.ts +11 -0
  4. package/dist/agents/claude_code.js +162 -0
  5. package/dist/agents/claude_code.js.map +1 -0
  6. package/dist/agents/codex_cli.d.ts +15 -0
  7. package/dist/agents/codex_cli.js +67 -0
  8. package/dist/agents/codex_cli.js.map +1 -0
  9. package/dist/agents/cursor.d.ts +21 -0
  10. package/dist/agents/cursor.js +89 -0
  11. package/dist/agents/cursor.js.map +1 -0
  12. package/dist/agents/registry.d.ts +184 -0
  13. package/dist/agents/registry.js +212 -0
  14. package/dist/agents/registry.js.map +1 -0
  15. package/dist/agents/types.d.ts +279 -0
  16. package/dist/agents/types.js +67 -0
  17. package/dist/agents/types.js.map +1 -0
  18. package/dist/commands/connect.d.ts +36 -0
  19. package/dist/commands/connect.js +499 -0
  20. package/dist/commands/connect.js.map +1 -0
  21. package/dist/commands/context.d.ts +45 -0
  22. package/dist/commands/context.js +14 -0
  23. package/dist/commands/context.js.map +1 -0
  24. package/dist/commands/report.d.ts +105 -0
  25. package/dist/commands/report.js +430 -0
  26. package/dist/commands/report.js.map +1 -0
  27. package/dist/commands/status.d.ts +29 -0
  28. package/dist/commands/status.js +236 -0
  29. package/dist/commands/status.js.map +1 -0
  30. package/dist/commands/why.d.ts +349 -0
  31. package/dist/commands/why.js +937 -0
  32. package/dist/commands/why.js.map +1 -0
  33. package/dist/consent.d.ts +59 -0
  34. package/dist/consent.js +111 -0
  35. package/dist/consent.js.map +1 -0
  36. package/dist/copy/claims.d.ts +79 -0
  37. package/dist/copy/claims.js +343 -0
  38. package/dist/copy/claims.js.map +1 -0
  39. package/dist/copy/grades.d.ts +117 -0
  40. package/dist/copy/grades.js +96 -0
  41. package/dist/copy/grades.js.map +1 -0
  42. package/dist/copy/index.d.ts +12 -0
  43. package/dist/copy/index.js +13 -0
  44. package/dist/copy/index.js.map +1 -0
  45. package/dist/copy/strings.d.ts +1328 -0
  46. package/dist/copy/strings.js +1243 -0
  47. package/dist/copy/strings.js.map +1 -0
  48. package/dist/copy/time.d.ts +27 -0
  49. package/dist/copy/time.js +43 -0
  50. package/dist/copy/time.js.map +1 -0
  51. package/dist/credential.d.ts +211 -0
  52. package/dist/credential.js +298 -0
  53. package/dist/credential.js.map +1 -0
  54. package/dist/exit.d.ts +59 -0
  55. package/dist/exit.js +60 -0
  56. package/dist/exit.js.map +1 -0
  57. package/dist/git.d.ts +299 -0
  58. package/dist/git.js +653 -0
  59. package/dist/git.js.map +1 -0
  60. package/dist/hooks/entry.d.ts +277 -0
  61. package/dist/hooks/entry.js +1092 -0
  62. package/dist/hooks/entry.js.map +1 -0
  63. package/dist/hooks/post_commit.d.ts +59 -0
  64. package/dist/hooks/post_commit.js +160 -0
  65. package/dist/hooks/post_commit.js.map +1 -0
  66. package/dist/hooks/post_rewrite.d.ts +59 -0
  67. package/dist/hooks/post_rewrite.js +161 -0
  68. package/dist/hooks/post_rewrite.js.map +1 -0
  69. package/dist/hooks/session_end.d.ts +101 -0
  70. package/dist/hooks/session_end.js +120 -0
  71. package/dist/hooks/session_end.js.map +1 -0
  72. package/dist/index.d.ts +34 -0
  73. package/dist/index.js +360 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/install.d.ts +153 -0
  76. package/dist/install.js +284 -0
  77. package/dist/install.js.map +1 -0
  78. package/dist/json.d.ts +100 -0
  79. package/dist/json.js +94 -0
  80. package/dist/json.js.map +1 -0
  81. package/dist/oauth/discovery.d.ts +96 -0
  82. package/dist/oauth/discovery.js +163 -0
  83. package/dist/oauth/discovery.js.map +1 -0
  84. package/dist/oauth/lock.d.ts +100 -0
  85. package/dist/oauth/lock.js +287 -0
  86. package/dist/oauth/lock.js.map +1 -0
  87. package/dist/oauth/loopback.d.ts +85 -0
  88. package/dist/oauth/loopback.js +124 -0
  89. package/dist/oauth/loopback.js.map +1 -0
  90. package/dist/oauth/pkce.d.ts +58 -0
  91. package/dist/oauth/pkce.js +77 -0
  92. package/dist/oauth/pkce.js.map +1 -0
  93. package/dist/oauth/read.d.ts +120 -0
  94. package/dist/oauth/read.js +144 -0
  95. package/dist/oauth/read.js.map +1 -0
  96. package/dist/oauth/session.d.ts +80 -0
  97. package/dist/oauth/session.js +112 -0
  98. package/dist/oauth/session.js.map +1 -0
  99. package/dist/oauth/signin.d.ts +178 -0
  100. package/dist/oauth/signin.js +225 -0
  101. package/dist/oauth/signin.js.map +1 -0
  102. package/dist/oauth/token.d.ts +139 -0
  103. package/dist/oauth/token.js +183 -0
  104. package/dist/oauth/token.js.map +1 -0
  105. package/dist/paths.d.ts +366 -0
  106. package/dist/paths.js +460 -0
  107. package/dist/paths.js.map +1 -0
  108. package/dist/policy.d.ts +195 -0
  109. package/dist/policy.js +244 -0
  110. package/dist/policy.js.map +1 -0
  111. package/dist/post.d.ts +302 -0
  112. package/dist/post.js +384 -0
  113. package/dist/post.js.map +1 -0
  114. package/dist/project.d.ts +99 -0
  115. package/dist/project.js +192 -0
  116. package/dist/project.js.map +1 -0
  117. package/dist/redact.d.ts +175 -0
  118. package/dist/redact.js +700 -0
  119. package/dist/redact.js.map +1 -0
  120. package/dist/runtime.d.ts +30 -0
  121. package/dist/runtime.js +37 -0
  122. package/dist/runtime.js.map +1 -0
  123. package/dist/spawn_budget.d.ts +101 -0
  124. package/dist/spawn_budget.js +124 -0
  125. package/dist/spawn_budget.js.map +1 -0
  126. package/dist/spool.d.ts +390 -0
  127. package/dist/spool.js +556 -0
  128. package/dist/spool.js.map +1 -0
  129. package/dist/state.d.ts +109 -0
  130. package/dist/state.js +217 -0
  131. package/dist/state.js.map +1 -0
  132. package/dist/system_message.d.ts +101 -0
  133. package/dist/system_message.js +141 -0
  134. package/dist/system_message.js.map +1 -0
  135. package/dist/term.d.ts +112 -0
  136. package/dist/term.js +134 -0
  137. package/dist/term.js.map +1 -0
  138. package/dist/version.d.ts +16 -0
  139. package/dist/version.js +17 -0
  140. package/dist/version.js.map +1 -0
  141. package/package.json +50 -0
@@ -0,0 +1,85 @@
1
+ /**
2
+ * The one-shot loopback listener that catches the authorization redirect.
3
+ *
4
+ * ## ⚠ THE HOST IS THE LITERAL `127.0.0.1`, NEVER `localhost`
5
+ *
6
+ * The server's redirect-URI policy (`vibecommit-mcp/src/oauth/loopback.ts`,
7
+ * read for its RULE and not imported — closed source, D60 §D1a) accepts
8
+ * `http://127.0.0.1:<any port>` and `http://[::1]:<any port>` per RFC 8252 §7.3,
9
+ * and REJECTS the literal string `localhost` with `localhost_disallowed`,
10
+ * because DNS resolution of `localhost` is OS-configurable and could point at a
11
+ * different host. So `redirect_uri` is built from the literal below, and the
12
+ * socket is bound to that same literal.
13
+ *
14
+ * **A client that builds `http://localhost:<port>` fails in a way that looks
15
+ * like a config problem rather than a spelling one** — the browser round-trip
16
+ * works, the listener receives nothing, and the visible symptom is a hang. That
17
+ * is why `test/oauth-loopback.test.ts` asserts the host rather than asserting
18
+ * that the flow completes.
19
+ *
20
+ * ## What this listener refuses
21
+ *
22
+ * `state` (RFC 6749 §10.12) and `iss` (RFC 9207) are both checked, and a request
23
+ * failing either is answered 400 and **does not end the wait**. Ending the wait
24
+ * on a bad request would let anything able to reach the port cancel a sign-in;
25
+ * accepting it would be a CSRF hole with a green happy-path suite over it, which
26
+ * is the specific vacuity this task was briefed on.
27
+ *
28
+ * @provenance vibecommit-mcp src/oauth/loopback.ts — redirect-URI policy, retyped
29
+ * @provenance vibecommit-mcp src/transport/server.ts — the code+state+iss handoff, retyped
30
+ */
31
+ /**
32
+ * The literal the server's policy admits. **Not a default — a requirement.**
33
+ * Changing this to `localhost` is refused by the authorization server.
34
+ */
35
+ export declare const LOOPBACK_HOST = "127.0.0.1";
36
+ /** The path the redirect lands on. Any path is legal; a fixed one is readable. */
37
+ export declare const CALLBACK_PATH = "/callback";
38
+ export type Callback =
39
+ /** The authorization code, with `state` and `iss` already verified. */
40
+ {
41
+ readonly kind: "code";
42
+ readonly code: string;
43
+ }
44
+ /** The AS redirected with an OAuth error — `access_denied` when a user declines. */
45
+ | {
46
+ readonly kind: "denied";
47
+ readonly error: string;
48
+ }
49
+ /** The deadline passed with no acceptable callback. */
50
+ | {
51
+ readonly kind: "timeout";
52
+ };
53
+ export interface LoopbackOptions {
54
+ /** The exact `state` this attempt sent. */
55
+ readonly expectedState: string;
56
+ /**
57
+ * The issuer discovered from RFC 8414 metadata. RFC 9207 requires the
58
+ * authorization response to carry it, and D26 makes the comparison
59
+ * EXACT-STRING — scheme, host, apex-vs-www and trailing slash all count.
60
+ */
61
+ readonly expectedIssuer: string;
62
+ /** How long to wait before giving up. The browser round-trip includes a login. */
63
+ readonly deadlineMs: number;
64
+ /** What the browser tab shows. Copy lives in `src/copy/`, never here. */
65
+ readonly page: {
66
+ readonly done: string;
67
+ readonly refused: string;
68
+ };
69
+ }
70
+ export interface Loopback {
71
+ /** `http://127.0.0.1:<ephemeral>/callback` — presented as `redirect_uri`. */
72
+ readonly redirectUri: string;
73
+ /** Resolves once, on the first acceptable callback, a denial, or the deadline. */
74
+ readonly result: Promise<Callback>;
75
+ /** Idempotent. Always call it — a listening socket outliving the flow is a bug. */
76
+ close(): void;
77
+ }
78
+ /**
79
+ * Bind an ephemeral loopback port and wait for one authorization redirect.
80
+ *
81
+ * Port 0 rather than a fixed port: RFC 8252 §7.3 requires the AS to accept any
82
+ * port on the loopback for exactly this reason, and a fixed port collides with a
83
+ * second terminal and with whatever else on the machine wanted it.
84
+ */
85
+ export declare function listenForCallback(opts: LoopbackOptions): Promise<Loopback>;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The one-shot loopback listener that catches the authorization redirect.
3
+ *
4
+ * ## ⚠ THE HOST IS THE LITERAL `127.0.0.1`, NEVER `localhost`
5
+ *
6
+ * The server's redirect-URI policy (`vibecommit-mcp/src/oauth/loopback.ts`,
7
+ * read for its RULE and not imported — closed source, D60 §D1a) accepts
8
+ * `http://127.0.0.1:<any port>` and `http://[::1]:<any port>` per RFC 8252 §7.3,
9
+ * and REJECTS the literal string `localhost` with `localhost_disallowed`,
10
+ * because DNS resolution of `localhost` is OS-configurable and could point at a
11
+ * different host. So `redirect_uri` is built from the literal below, and the
12
+ * socket is bound to that same literal.
13
+ *
14
+ * **A client that builds `http://localhost:<port>` fails in a way that looks
15
+ * like a config problem rather than a spelling one** — the browser round-trip
16
+ * works, the listener receives nothing, and the visible symptom is a hang. That
17
+ * is why `test/oauth-loopback.test.ts` asserts the host rather than asserting
18
+ * that the flow completes.
19
+ *
20
+ * ## What this listener refuses
21
+ *
22
+ * `state` (RFC 6749 §10.12) and `iss` (RFC 9207) are both checked, and a request
23
+ * failing either is answered 400 and **does not end the wait**. Ending the wait
24
+ * on a bad request would let anything able to reach the port cancel a sign-in;
25
+ * accepting it would be a CSRF hole with a green happy-path suite over it, which
26
+ * is the specific vacuity this task was briefed on.
27
+ *
28
+ * @provenance vibecommit-mcp src/oauth/loopback.ts — redirect-URI policy, retyped
29
+ * @provenance vibecommit-mcp src/transport/server.ts — the code+state+iss handoff, retyped
30
+ */
31
+ import { createServer } from "node:http";
32
+ import { statesMatch } from "./pkce.js";
33
+ /**
34
+ * The literal the server's policy admits. **Not a default — a requirement.**
35
+ * Changing this to `localhost` is refused by the authorization server.
36
+ */
37
+ export const LOOPBACK_HOST = "127.0.0.1";
38
+ /** The path the redirect lands on. Any path is legal; a fixed one is readable. */
39
+ export const CALLBACK_PATH = "/callback";
40
+ /**
41
+ * Bind an ephemeral loopback port and wait for one authorization redirect.
42
+ *
43
+ * Port 0 rather than a fixed port: RFC 8252 §7.3 requires the AS to accept any
44
+ * port on the loopback for exactly this reason, and a fixed port collides with a
45
+ * second terminal and with whatever else on the machine wanted it.
46
+ */
47
+ export async function listenForCallback(opts) {
48
+ let settle;
49
+ const result = new Promise((resolve) => {
50
+ settle = resolve;
51
+ });
52
+ const server = createServer((req, res) => {
53
+ // `req.url` is a path with a query, so it needs a base to parse. The base is
54
+ // this listener's own address and is never used for anything else.
55
+ const url = new URL(req.url ?? "/", `http://${LOOPBACK_HOST}`);
56
+ const verdict = classify(url.searchParams, opts);
57
+ if (verdict === null) {
58
+ // Neither acceptable nor a recognised denial: answer, and keep waiting.
59
+ // A stray request must not be able to cancel somebody's sign-in.
60
+ res.writeHead(400, { "content-type": "text/plain; charset=utf-8" });
61
+ res.end(opts.page.refused);
62
+ return;
63
+ }
64
+ res.writeHead(200, { "content-type": "text/plain; charset=utf-8" });
65
+ res.end(verdict.kind === "code" ? opts.page.done : opts.page.refused);
66
+ finish(verdict);
67
+ });
68
+ const timer = setTimeout(() => finish({ kind: "timeout" }), opts.deadlineMs);
69
+ // The deadline must not by itself hold the process open — the flow is awaited
70
+ // elsewhere, and a stray timer keeping Node alive after a `close()` is the
71
+ // same class of bug as the socket this module is careful to shut.
72
+ timer.unref();
73
+ let closed = false;
74
+ const close = () => {
75
+ if (closed)
76
+ return;
77
+ closed = true;
78
+ clearTimeout(timer);
79
+ // ⚠ `close()` alone waits for KEEP-ALIVE sockets to go idle, and a browser
80
+ // holds one open after the tab renders. Without this the CLI sits there with
81
+ // the code already exchanged, which reads to a user as a hang at the last
82
+ // step. `closeAllConnections` is why the listener actually goes away.
83
+ server.closeAllConnections();
84
+ server.close();
85
+ };
86
+ const finish = (value) => {
87
+ close();
88
+ settle(value);
89
+ };
90
+ await new Promise((resolve, reject) => {
91
+ server.once("error", reject);
92
+ // The LITERAL, both here and in the redirect_uri below. See the module note.
93
+ server.listen(0, LOOPBACK_HOST, resolve);
94
+ });
95
+ const { port } = server.address();
96
+ return { redirectUri: `http://${LOOPBACK_HOST}:${port}${CALLBACK_PATH}`, result, close };
97
+ }
98
+ /**
99
+ * Decide what one inbound request is: the callback, a denial, or noise.
100
+ *
101
+ * Null means "not this attempt's callback" — the caller keeps waiting. The
102
+ * ORDER matters: `state` is checked before anything is read out of the query,
103
+ * so an unauthenticated caller cannot use the `error` branch to end a wait it
104
+ * does not own.
105
+ */
106
+ function classify(params, opts) {
107
+ const state = params.get("state");
108
+ if (state === null || !statesMatch(opts.expectedState, state))
109
+ return null;
110
+ // RFC 9207 issuer identification, D26's exact-string match. Applied to the
111
+ // ERROR redirect too: a mix-up attack is not less of one because the response
112
+ // it forged happens to be a refusal.
113
+ const iss = params.get("iss");
114
+ if (iss !== opts.expectedIssuer)
115
+ return null;
116
+ const error = params.get("error");
117
+ if (error !== null && error !== "")
118
+ return { kind: "denied", error };
119
+ const code = params.get("code");
120
+ if (code === null || code === "")
121
+ return null;
122
+ return { kind: "code", code };
123
+ }
124
+ //# sourceMappingURL=loopback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopback.js","sourceRoot":"","sources":["../../src/oauth/loopback.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * PKCE (RFC 7636) and the authorization-request `state` — `CR-084d`.
3
+ *
4
+ * Two secrets are minted here and both are one-shot: the `code_verifier`, which
5
+ * is what stops an attacker who steals the authorization code from exchanging
6
+ * it, and `state`, which is what stops an attacker who can reach the loopback
7
+ * listener from injecting THEIR code into OUR flow.
8
+ *
9
+ * **`S256` is the only method, and it is a TYPE and not a default.** The server
10
+ * advertises `code_challenge_methods_supported: ["S256"]` and rejects `plain`
11
+ * (its `verifyPkce` recomputes the SHA-256 unconditionally), so a client that
12
+ * downgraded would not be refused by an obvious error — it would send a
13
+ * challenge the server hashes anyway and fail as `invalid_grant`, which reads as
14
+ * a config problem. Making the literal the field's type means the downgrade
15
+ * cannot compile, and `test/oauth-pkce.test.ts` proves the challenge really is
16
+ * the digest rather than the verifier copied across.
17
+ *
18
+ * @provenance vibecommit-mcp src/oauth/token.ts — `verifyPkce` base64url-of-SHA-256 rule, retyped
19
+ * @provenance vibecommit-mcp src/oauth/discovery.ts — `code_challenge_methods_supported`, retyped
20
+ */
21
+ /**
22
+ * The one supported challenge method. Declared `const` so it is a literal type:
23
+ * `method: "S256"` cannot be assigned `"plain"` anywhere downstream.
24
+ */
25
+ export declare const CHALLENGE_METHOD: "S256";
26
+ export type ChallengeMethod = typeof CHALLENGE_METHOD;
27
+ export interface PkcePair {
28
+ /** Secret. Sent ONLY to the token endpoint, never on the authorize URL. */
29
+ readonly verifier: string;
30
+ /** Public. Sent on the authorize URL; safe to log. */
31
+ readonly challenge: string;
32
+ readonly method: ChallengeMethod;
33
+ }
34
+ /** RFC 4648 §5 base64url, unpadded — the encoding both ends of PKCE use. */
35
+ export declare function base64url(bytes: Buffer): string;
36
+ /**
37
+ * Mint a fresh verifier and its challenge.
38
+ *
39
+ * `randomBytes` and not `Math.random`: this is the value an attacker must guess
40
+ * to exchange a stolen code, so it comes from the CSPRNG or it is decoration.
41
+ */
42
+ export declare function generatePkce(): PkcePair;
43
+ /** base64url(SHA-256(verifier)) — the server recomputes exactly this. */
44
+ export declare function challengeFor(verifier: string): string;
45
+ /** A fresh per-attempt `state`. Never reused across sign-ins. */
46
+ export declare function generateState(): string;
47
+ /**
48
+ * Compare two `state` values without leaking the position of the first
49
+ * difference.
50
+ *
51
+ * The timing channel here is thin — an attacker would need many attempts against
52
+ * a listener that closes after one — but the comparison is one line either way,
53
+ * and `===` on a secret is the habit that matters. Lengths are compared first
54
+ * because `timingSafeEqual` THROWS on a length mismatch rather than returning
55
+ * false, which would turn a wrong-length callback into an unhandled exception on
56
+ * the sign-in path.
57
+ */
58
+ export declare function statesMatch(expected: string, presented: string): boolean;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * PKCE (RFC 7636) and the authorization-request `state` — `CR-084d`.
3
+ *
4
+ * Two secrets are minted here and both are one-shot: the `code_verifier`, which
5
+ * is what stops an attacker who steals the authorization code from exchanging
6
+ * it, and `state`, which is what stops an attacker who can reach the loopback
7
+ * listener from injecting THEIR code into OUR flow.
8
+ *
9
+ * **`S256` is the only method, and it is a TYPE and not a default.** The server
10
+ * advertises `code_challenge_methods_supported: ["S256"]` and rejects `plain`
11
+ * (its `verifyPkce` recomputes the SHA-256 unconditionally), so a client that
12
+ * downgraded would not be refused by an obvious error — it would send a
13
+ * challenge the server hashes anyway and fail as `invalid_grant`, which reads as
14
+ * a config problem. Making the literal the field's type means the downgrade
15
+ * cannot compile, and `test/oauth-pkce.test.ts` proves the challenge really is
16
+ * the digest rather than the verifier copied across.
17
+ *
18
+ * @provenance vibecommit-mcp src/oauth/token.ts — `verifyPkce` base64url-of-SHA-256 rule, retyped
19
+ * @provenance vibecommit-mcp src/oauth/discovery.ts — `code_challenge_methods_supported`, retyped
20
+ */
21
+ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
22
+ /**
23
+ * The one supported challenge method. Declared `const` so it is a literal type:
24
+ * `method: "S256"` cannot be assigned `"plain"` anywhere downstream.
25
+ */
26
+ export const CHALLENGE_METHOD = "S256";
27
+ /**
28
+ * 32 random bytes, which base64url-encode to exactly 43 characters — the FLOOR
29
+ * of RFC 7636 §4.1's 43..128 range, and the server enforces that range verbatim
30
+ * (`codeVerifier.length < 43 || > 128` → PKCE mismatch). 32 bytes is also the
31
+ * size the digest itself is, so a longer verifier buys no entropy the challenge
32
+ * can carry.
33
+ */
34
+ const VERIFIER_BYTES = 32;
35
+ /** `state` needs unguessability, not length. 32 bytes matches the verifier. */
36
+ const STATE_BYTES = 32;
37
+ /** RFC 4648 §5 base64url, unpadded — the encoding both ends of PKCE use. */
38
+ export function base64url(bytes) {
39
+ return bytes.toString("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
40
+ }
41
+ /**
42
+ * Mint a fresh verifier and its challenge.
43
+ *
44
+ * `randomBytes` and not `Math.random`: this is the value an attacker must guess
45
+ * to exchange a stolen code, so it comes from the CSPRNG or it is decoration.
46
+ */
47
+ export function generatePkce() {
48
+ const verifier = base64url(randomBytes(VERIFIER_BYTES));
49
+ return { verifier, challenge: challengeFor(verifier), method: CHALLENGE_METHOD };
50
+ }
51
+ /** base64url(SHA-256(verifier)) — the server recomputes exactly this. */
52
+ export function challengeFor(verifier) {
53
+ return base64url(createHash("sha256").update(verifier).digest());
54
+ }
55
+ /** A fresh per-attempt `state`. Never reused across sign-ins. */
56
+ export function generateState() {
57
+ return base64url(randomBytes(STATE_BYTES));
58
+ }
59
+ /**
60
+ * Compare two `state` values without leaking the position of the first
61
+ * difference.
62
+ *
63
+ * The timing channel here is thin — an attacker would need many attempts against
64
+ * a listener that closes after one — but the comparison is one line either way,
65
+ * and `===` on a secret is the habit that matters. Lengths are compared first
66
+ * because `timingSafeEqual` THROWS on a length mismatch rather than returning
67
+ * false, which would turn a wrong-length callback into an unhandled exception on
68
+ * the sign-in path.
69
+ */
70
+ export function statesMatch(expected, presented) {
71
+ const a = Buffer.from(expected, "utf8");
72
+ const b = Buffer.from(presented, "utf8");
73
+ if (a.length !== b.length)
74
+ return false;
75
+ return timingSafeEqual(a, b);
76
+ }
77
+ //# sourceMappingURL=pkce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/oauth/pkce.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEvE;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAe,CAAC;AAGhD;;;;;;GAMG;AACH,MAAM,cAAc,GAAG,EAAE,CAAC;AAE1B,+EAA+E;AAC/E,MAAM,WAAW,GAAG,EAAE,CAAC;AAUvB,4EAA4E;AAC5E,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC7F,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,QAAQ,GAAG,SAAS,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC;IACxD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;AACnF,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,OAAO,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AACnE,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,aAAa;IAC3B,OAAO,SAAS,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,SAAiB;IAC7D,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * The read transport: ONE `tools/call` POST to the EXISTING `/mcp` endpoint.
3
+ *
4
+ * ## What this is not
5
+ *
6
+ * There is no REST `/read` route, no second Fly app, no second hostname and no
7
+ * second PRM document (D73, which closed `CR-083` as unnecessary and recorded
8
+ * the "2nd Fly app (read lane)" provisioning item as CLOSED — NOT NEEDED). A
9
+ * read is the same endpoint the agent already talks to, with a user-principal
10
+ * bearer instead of a machine one.
11
+ *
12
+ * There is also no third audience set. The access token is minted at
13
+ * `mcpAcceptedAudiences`, which is typed `readonly [string]` and populated as a
14
+ * ONE-element tuple. `readAcceptedAudiences` is GONE — `CR-084b` deleted it
15
+ * (merged after this client was written, re-verified against it), and the claim
16
+ * it encoded (that a machine credential can read) was false as built anyway: a
17
+ * `vcik_` is opaque base64url with no dots, so the JWT verifier rejects it
18
+ * before any audience check runs.
19
+ *
20
+ * ## The envelope
21
+ *
22
+ * A successful `tools/call` returns `result.content[0].text` carrying a JSON
23
+ * DOCUMENT — every tool in the server ends
24
+ * `{ content: [{ type: "text", text: JSON.stringify(payload) }] }`. So the
25
+ * caller parses that string; it does not read a typed result off the JSON-RPC
26
+ * envelope directly.
27
+ *
28
+ * ⚠ A tool's OWN failures travel the same way — one text part with a JSON body,
29
+ * plus `isError: true`. They are not JSON-RPC errors and not HTTP errors, and a
30
+ * caller that only branched on the transport would read one as success. So
31
+ * `document` is returned uninterpreted with `isError` beside it: parse ONCE,
32
+ * branch after, which is exactly what the server's seam says this client does.
33
+ *
34
+ * `CR-084b` pins both forms with a golden test and names this client as the
35
+ * envelope's external consumer, so the shape is a contract with an owner rather
36
+ * than a convention that happens to hold.
37
+ *
38
+ * @provenance vibecommit-mcp src/handshake/router.ts — JSON-RPC methods, retyped
39
+ * @provenance vibecommit-mcp src/handshake/tools.ts — `tools/call` params, retyped
40
+ * @provenance vibecommit-mcp src/read/envelope.ts — `content[0].text` envelope, retyped
41
+ *
42
+ * ⚠ THAT THIRD CITATION WAS RE-POINTED, NOT ADDED (`CR-163`, D136 §2). It named
43
+ * `src/tools/query_history.ts`, and **no such file exists**: `query_history` was
44
+ * retired by `CR-076b` and now has an entry in `vibecommit-mcp/src/tools/
45
+ * retired.ts` that returns a migration error. A dangling citation is worse than
46
+ * none (D54) — it points a reader at a file they cannot open and stops them
47
+ * looking further.
48
+ *
49
+ * ⚠ AND THE TAG NAME IS NOT SPELLED OUT IN PROSE ANYWHERE BELOW THIS LINE.
50
+ * `test/provenance.test.ts` counts the tags in this file by scanning for the
51
+ * literal token, so writing it inside a sentence adds a fourth "tag" that cannot
52
+ * parse and turns the gate RED against correct prose. It did, on the first
53
+ * wording of this note. Same species as the linkage wall reading `from` plus a
54
+ * quoted phrase as an import — see `src/json.ts` and `src/commands/why.ts`.
55
+ *
56
+ * The envelope is still real and still has a source: `readEnvelope()` in
57
+ * `vibecommit-mcp/src/read/envelope.ts` is the single function that builds
58
+ * `{ content: [{ type: "text", text: JSON.stringify(payload) }] }`, and
59
+ * `readErrorEnvelope()` beside it builds the `isError` form. Both live read
60
+ * tools return them — verified 2026-08-16 against `vibecommit-mcp` `main`:
61
+ * `src/tools/blame_commit.ts` and `src/tools/commit_coverage.ts` each import
62
+ * from that module and return it. The citation is MUTUAL: that file names this
63
+ * client as the envelope's external consumer by name.
64
+ */
65
+ import type { AccessToken } from "../credential.js";
66
+ export type ReadOutcome =
67
+ /**
68
+ * `content[0].text`, JSON-parsed.
69
+ *
70
+ * `isError` is MCP's flag for a failed call and is part of the SAME envelope:
71
+ * the error form carries one text part with a JSON body, plus `isError: true`.
72
+ * That is what lets this client `JSON.parse` ONCE and branch afterwards,
73
+ * rather than needing to know which shape it is about to receive.
74
+ *
75
+ * ⚠ Surfaced rather than dropped because it is a documented field of a
76
+ * contract with an external owner. `document` alone would still carry
77
+ * `{ error, detail }`, so a caller COULD infer the failure by sniffing for an
78
+ * `error` key — and inferring a fact the wire states outright is how a caller
79
+ * ends up treating a legitimate payload that has an `error` field as a failure.
80
+ */
81
+ {
82
+ readonly kind: "ok";
83
+ readonly document: unknown;
84
+ readonly isError: boolean;
85
+ }
86
+ /**
87
+ * The bearer was rejected. A FIRST-CLASS OUTCOME so the caller can refresh
88
+ * ONCE and retry ONCE — never in a loop. A loop here would present a rotated
89
+ * refresh token repeatedly and end in a family revoke, which is a signed-out
90
+ * user produced by the error handling rather than by the error.
91
+ */
92
+ | {
93
+ readonly kind: "unauthorized";
94
+ }
95
+ /** A JSON-RPC error object. Data, not an exception (`CR-084d` scope). */
96
+ | {
97
+ readonly kind: "rpc-error";
98
+ readonly code: number;
99
+ readonly message: string;
100
+ }
101
+ /** No response at all — DNS, refused, TLS, timeout. */
102
+ | {
103
+ readonly kind: "unreachable";
104
+ }
105
+ /** A 200 whose body is not the envelope. Never treated as success. */
106
+ | {
107
+ readonly kind: "malformed";
108
+ };
109
+ export interface ReadDeps {
110
+ readonly fetch: typeof fetch;
111
+ readonly timeoutMs: number;
112
+ }
113
+ /**
114
+ * Call one read tool.
115
+ *
116
+ * `id` is fixed at 1: this is a single request/response over a fresh connection
117
+ * with no batching and no notifications, so a counter would be state kept for
118
+ * nobody. The server echoes it and nothing correlates on it.
119
+ */
120
+ export declare function callTool(mcpEndpoint: string, access: AccessToken, name: string, args: Record<string, unknown>, deps: ReadDeps): Promise<ReadOutcome>;
@@ -0,0 +1,144 @@
1
+ /**
2
+ * The read transport: ONE `tools/call` POST to the EXISTING `/mcp` endpoint.
3
+ *
4
+ * ## What this is not
5
+ *
6
+ * There is no REST `/read` route, no second Fly app, no second hostname and no
7
+ * second PRM document (D73, which closed `CR-083` as unnecessary and recorded
8
+ * the "2nd Fly app (read lane)" provisioning item as CLOSED — NOT NEEDED). A
9
+ * read is the same endpoint the agent already talks to, with a user-principal
10
+ * bearer instead of a machine one.
11
+ *
12
+ * There is also no third audience set. The access token is minted at
13
+ * `mcpAcceptedAudiences`, which is typed `readonly [string]` and populated as a
14
+ * ONE-element tuple. `readAcceptedAudiences` is GONE — `CR-084b` deleted it
15
+ * (merged after this client was written, re-verified against it), and the claim
16
+ * it encoded (that a machine credential can read) was false as built anyway: a
17
+ * `vcik_` is opaque base64url with no dots, so the JWT verifier rejects it
18
+ * before any audience check runs.
19
+ *
20
+ * ## The envelope
21
+ *
22
+ * A successful `tools/call` returns `result.content[0].text` carrying a JSON
23
+ * DOCUMENT — every tool in the server ends
24
+ * `{ content: [{ type: "text", text: JSON.stringify(payload) }] }`. So the
25
+ * caller parses that string; it does not read a typed result off the JSON-RPC
26
+ * envelope directly.
27
+ *
28
+ * ⚠ A tool's OWN failures travel the same way — one text part with a JSON body,
29
+ * plus `isError: true`. They are not JSON-RPC errors and not HTTP errors, and a
30
+ * caller that only branched on the transport would read one as success. So
31
+ * `document` is returned uninterpreted with `isError` beside it: parse ONCE,
32
+ * branch after, which is exactly what the server's seam says this client does.
33
+ *
34
+ * `CR-084b` pins both forms with a golden test and names this client as the
35
+ * envelope's external consumer, so the shape is a contract with an owner rather
36
+ * than a convention that happens to hold.
37
+ *
38
+ * @provenance vibecommit-mcp src/handshake/router.ts — JSON-RPC methods, retyped
39
+ * @provenance vibecommit-mcp src/handshake/tools.ts — `tools/call` params, retyped
40
+ * @provenance vibecommit-mcp src/read/envelope.ts — `content[0].text` envelope, retyped
41
+ *
42
+ * ⚠ THAT THIRD CITATION WAS RE-POINTED, NOT ADDED (`CR-163`, D136 §2). It named
43
+ * `src/tools/query_history.ts`, and **no such file exists**: `query_history` was
44
+ * retired by `CR-076b` and now has an entry in `vibecommit-mcp/src/tools/
45
+ * retired.ts` that returns a migration error. A dangling citation is worse than
46
+ * none (D54) — it points a reader at a file they cannot open and stops them
47
+ * looking further.
48
+ *
49
+ * ⚠ AND THE TAG NAME IS NOT SPELLED OUT IN PROSE ANYWHERE BELOW THIS LINE.
50
+ * `test/provenance.test.ts` counts the tags in this file by scanning for the
51
+ * literal token, so writing it inside a sentence adds a fourth "tag" that cannot
52
+ * parse and turns the gate RED against correct prose. It did, on the first
53
+ * wording of this note. Same species as the linkage wall reading `from` plus a
54
+ * quoted phrase as an import — see `src/json.ts` and `src/commands/why.ts`.
55
+ *
56
+ * The envelope is still real and still has a source: `readEnvelope()` in
57
+ * `vibecommit-mcp/src/read/envelope.ts` is the single function that builds
58
+ * `{ content: [{ type: "text", text: JSON.stringify(payload) }] }`, and
59
+ * `readErrorEnvelope()` beside it builds the `isError` form. Both live read
60
+ * tools return them — verified 2026-08-16 against `vibecommit-mcp` `main`:
61
+ * `src/tools/blame_commit.ts` and `src/tools/commit_coverage.ts` each import
62
+ * from that module and return it. The citation is MUTUAL: that file names this
63
+ * client as the envelope's external consumer by name.
64
+ */
65
+ /** JSON-RPC 2.0. The server rejects anything else at `validateRequest`. */
66
+ const JSONRPC_VERSION = "2.0";
67
+ /**
68
+ * Call one read tool.
69
+ *
70
+ * `id` is fixed at 1: this is a single request/response over a fresh connection
71
+ * with no batching and no notifications, so a counter would be state kept for
72
+ * nobody. The server echoes it and nothing correlates on it.
73
+ */
74
+ export async function callTool(mcpEndpoint, access, name, args, deps) {
75
+ const controller = new AbortController();
76
+ const timer = setTimeout(() => controller.abort(), deps.timeoutMs);
77
+ let res;
78
+ try {
79
+ res = await deps.fetch(mcpEndpoint, {
80
+ method: "POST",
81
+ headers: {
82
+ // The one place this token's plaintext is used — the request boundary,
83
+ // the same discipline `post.ts` states for the ingest credential.
84
+ authorization: `Bearer ${access.expose()}`,
85
+ "content-type": "application/json",
86
+ accept: "application/json",
87
+ },
88
+ body: JSON.stringify({
89
+ jsonrpc: JSONRPC_VERSION,
90
+ id: 1,
91
+ method: "tools/call",
92
+ params: { name, arguments: args },
93
+ }),
94
+ signal: controller.signal,
95
+ // undici strips Authorization across origins, so a followed redirect would
96
+ // arrive unauthenticated and read as a mysterious 401 — and one that did
97
+ // NOT cross origins would carry the bearer somewhere unconfigured.
98
+ redirect: "error",
99
+ });
100
+ }
101
+ catch {
102
+ return { kind: "unreachable" };
103
+ }
104
+ finally {
105
+ clearTimeout(timer);
106
+ }
107
+ if (res.status === 401)
108
+ return { kind: "unauthorized" };
109
+ let body;
110
+ try {
111
+ body = (await res.json());
112
+ }
113
+ catch {
114
+ return { kind: "malformed" };
115
+ }
116
+ const envelope = body;
117
+ if (envelope.error !== undefined && envelope.error !== null) {
118
+ const { code, message } = envelope.error;
119
+ return {
120
+ kind: "rpc-error",
121
+ code: typeof code === "number" ? code : 0,
122
+ message: typeof message === "string" ? message : "",
123
+ };
124
+ }
125
+ const content = envelope.result?.content;
126
+ if (!Array.isArray(content) || content.length === 0)
127
+ return { kind: "malformed" };
128
+ const text = content[0].text;
129
+ if (typeof text !== "string")
130
+ return { kind: "malformed" };
131
+ try {
132
+ return {
133
+ kind: "ok",
134
+ document: JSON.parse(text),
135
+ // Strictly `=== true`: the field is OPTIONAL on the wire, so an absent one
136
+ // must read as "not an error" rather than as anything truthy-ish.
137
+ isError: envelope.result?.isError === true,
138
+ };
139
+ }
140
+ catch {
141
+ return { kind: "malformed" };
142
+ }
143
+ }
144
+ //# sourceMappingURL=read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/oauth/read.ts"],"names":[],"mappings":"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"}