@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,80 @@
1
+ /**
2
+ * The signed-in session on disk — the human lane's persistence.
3
+ *
4
+ * Lands in `~/.vibecommit/session.json` and therefore inherits the two checks
5
+ * `credential.ts`'s `readSecretFile` makes: the DIRECTORY must not be writable
6
+ * by group or other (`/cso` finding 3 — a writable `~/.vibecommit` lets a second
7
+ * local account unlink the file and drop in its own), and the FILE must not be
8
+ * readable by group or other, checked with `lstat` so a symlink cannot report
9
+ * its target's mode instead.
10
+ *
11
+ * ## What is stored, and what deliberately is not
12
+ *
13
+ * The refresh token, the access token, the access token's expiry as an epoch
14
+ * millisecond, and the two discovered endpoints. **Not** the code verifier or
15
+ * the `state` — both are per-attempt secrets that die with the sign-in that
16
+ * minted them, and a verifier surviving on disk would let a stolen code be
17
+ * exchanged later by whoever could read the file.
18
+ *
19
+ * The endpoints are cached because a read verb that re-ran discovery on every
20
+ * invocation would put two network round trips in front of every command, and
21
+ * because the ISSUER is the value RFC 9207's `iss` check compares against — so
22
+ * it has to be the one this session was established with, not whatever the
23
+ * server says today.
24
+ */
25
+ import { type TokenSet } from "./token.js";
26
+ export interface Session {
27
+ readonly tokens: TokenSet;
28
+ /** The RFC 8414 issuer this session was established against. */
29
+ readonly issuer: string;
30
+ readonly tokenEndpoint: string;
31
+ }
32
+ export type SessionLoad = {
33
+ readonly kind: "ok";
34
+ readonly session: Session;
35
+ }
36
+ /** Not signed in. The overwhelmingly common case, and not an error. */
37
+ | {
38
+ readonly kind: "absent";
39
+ } | {
40
+ readonly kind: "insecure-file";
41
+ readonly path: string;
42
+ readonly mode: string;
43
+ } | {
44
+ readonly kind: "unreadable";
45
+ readonly path: string;
46
+ }
47
+ /**
48
+ * A secret of the wrong class in one of the two slots — an ingest credential
49
+ * pasted into `refresh_token`, or a JWT where the opaque token belongs.
50
+ * Refused CLIENT-side and never sent, the mirror of `loadCredential`'s
51
+ * `wrong-class`: a credential is only meaningful on the lane it was minted for.
52
+ */
53
+ | {
54
+ readonly kind: "wrong-class";
55
+ readonly path: string;
56
+ };
57
+ /** Has the access token expired, allowing for the refresh margin? */
58
+ export declare function isFresh(session: Session, nowMs: number): boolean;
59
+ /**
60
+ * Read the session.
61
+ *
62
+ * Returns a discriminated union rather than throwing, for the same reason
63
+ * `loadCredential` does: the caller wants to distinguish "not signed in" from
64
+ * "signed in but the file is unsafe" so it can print two different fixes.
65
+ */
66
+ export declare function loadSession(home: string): SessionLoad;
67
+ /**
68
+ * Write the session. 0700 on the directory, 0600 on the file.
69
+ *
70
+ * `chmod` after `writeFileSync` for both, because `writeFileSync`'s `mode` is
71
+ * masked by the umask and is IGNORED ENTIRELY when the file already exists — so
72
+ * a session rewritten after a rotation would silently inherit whatever mode the
73
+ * previous write left. `consent.ts`'s `grantProject` learned this first; this is
74
+ * the same two lines for the same reason.
75
+ *
76
+ * The two `expose()` calls here are the only ones outside the request boundary,
77
+ * and they are unavoidable: persisting a secret means writing its bytes. They
78
+ * are greppable, which is the property the wrapper buys.
79
+ */
80
+ export declare function saveSession(home: string, session: Session): void;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * The signed-in session on disk — the human lane's persistence.
3
+ *
4
+ * Lands in `~/.vibecommit/session.json` and therefore inherits the two checks
5
+ * `credential.ts`'s `readSecretFile` makes: the DIRECTORY must not be writable
6
+ * by group or other (`/cso` finding 3 — a writable `~/.vibecommit` lets a second
7
+ * local account unlink the file and drop in its own), and the FILE must not be
8
+ * readable by group or other, checked with `lstat` so a symlink cannot report
9
+ * its target's mode instead.
10
+ *
11
+ * ## What is stored, and what deliberately is not
12
+ *
13
+ * The refresh token, the access token, the access token's expiry as an epoch
14
+ * millisecond, and the two discovered endpoints. **Not** the code verifier or
15
+ * the `state` — both are per-attempt secrets that die with the sign-in that
16
+ * minted them, and a verifier surviving on disk would let a stolen code be
17
+ * exchanged later by whoever could read the file.
18
+ *
19
+ * The endpoints are cached because a read verb that re-ran discovery on every
20
+ * invocation would put two network round trips in front of every command, and
21
+ * because the ISSUER is the value RFC 9207's `iss` check compares against — so
22
+ * it has to be the one this session was established with, not whatever the
23
+ * server says today.
24
+ */
25
+ import { chmodSync, mkdirSync, writeFileSync } from "node:fs";
26
+ import { dirname } from "node:path";
27
+ import { AccessToken, RefreshToken, isAccessTokenShape, isRefreshTokenShape, readSecretFile, } from "../credential.js";
28
+ import { rootDir, sessionPath } from "../paths.js";
29
+ import { EXPIRY_SKEW_MS } from "./token.js";
30
+ /** Has the access token expired, allowing for the refresh margin? */
31
+ export function isFresh(session, nowMs) {
32
+ return session.tokens.expiresAtMs - EXPIRY_SKEW_MS > nowMs;
33
+ }
34
+ /**
35
+ * Read the session.
36
+ *
37
+ * Returns a discriminated union rather than throwing, for the same reason
38
+ * `loadCredential` does: the caller wants to distinguish "not signed in" from
39
+ * "signed in but the file is unsafe" so it can print two different fixes.
40
+ */
41
+ export function loadSession(home) {
42
+ const path = sessionPath(home);
43
+ const read = readSecretFile(home, path);
44
+ if (read.kind !== "ok")
45
+ return read;
46
+ let doc;
47
+ try {
48
+ doc = JSON.parse(read.raw);
49
+ }
50
+ catch {
51
+ return { kind: "unreadable", path };
52
+ }
53
+ const { refresh_token: refresh, access_token: access } = doc;
54
+ if (typeof refresh !== "string" || typeof access !== "string") {
55
+ return { kind: "unreadable", path };
56
+ }
57
+ // Class before shape-of-envelope: a `vcik_` here is the interesting mistake,
58
+ // and reporting it as "unreadable" would send the user to fix the wrong thing.
59
+ if (!isRefreshTokenShape(refresh) || !isAccessTokenShape(access)) {
60
+ return { kind: "wrong-class", path };
61
+ }
62
+ if (typeof doc.expires_at_ms !== "number" ||
63
+ !Number.isFinite(doc.expires_at_ms) ||
64
+ typeof doc.issuer !== "string" ||
65
+ doc.issuer === "" ||
66
+ typeof doc.token_endpoint !== "string" ||
67
+ doc.token_endpoint === "") {
68
+ return { kind: "unreadable", path };
69
+ }
70
+ return {
71
+ kind: "ok",
72
+ session: {
73
+ tokens: {
74
+ access: new AccessToken(access),
75
+ refresh: new RefreshToken(refresh),
76
+ expiresAtMs: doc.expires_at_ms,
77
+ scope: typeof doc.scope === "string" ? doc.scope : "",
78
+ },
79
+ issuer: doc.issuer,
80
+ tokenEndpoint: doc.token_endpoint,
81
+ },
82
+ };
83
+ }
84
+ /**
85
+ * Write the session. 0700 on the directory, 0600 on the file.
86
+ *
87
+ * `chmod` after `writeFileSync` for both, because `writeFileSync`'s `mode` is
88
+ * masked by the umask and is IGNORED ENTIRELY when the file already exists — so
89
+ * a session rewritten after a rotation would silently inherit whatever mode the
90
+ * previous write left. `consent.ts`'s `grantProject` learned this first; this is
91
+ * the same two lines for the same reason.
92
+ *
93
+ * The two `expose()` calls here are the only ones outside the request boundary,
94
+ * and they are unavoidable: persisting a secret means writing its bytes. They
95
+ * are greppable, which is the property the wrapper buys.
96
+ */
97
+ export function saveSession(home, session) {
98
+ const path = sessionPath(home);
99
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
100
+ chmodSync(rootDir(home), 0o700);
101
+ const body = {
102
+ refresh_token: session.tokens.refresh.expose(),
103
+ access_token: session.tokens.access.expose(),
104
+ expires_at_ms: session.tokens.expiresAtMs,
105
+ scope: session.tokens.scope,
106
+ issuer: session.issuer,
107
+ token_endpoint: session.tokenEndpoint,
108
+ };
109
+ writeFileSync(path, `${JSON.stringify(body, null, 2)}\n`, { mode: 0o600 });
110
+ chmodSync(path, 0o600);
111
+ }
112
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/oauth/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * The sign-in flow, and the refresh that keeps it alive.
3
+ *
4
+ * ```
5
+ * discover ──► PKCE + state ──► loopback listener on 127.0.0.1:0
6
+ * │ │
7
+ * │ browser ────┘ code + state + iss
8
+ * ▼ ▼
9
+ * authorize URL ─────────────────────► exchange ──► session.json
10
+ * ```
11
+ *
12
+ * Every step's failure is a NAMED outcome rather than an exception, because each
13
+ * one wants a different fix line: an unreachable discovery document is "the
14
+ * server is down", a denied grant is "you declined", and a malformed token
15
+ * response is ours to fix, not the user's.
16
+ *
17
+ * ## `authorizedAccessToken` is the part to read carefully
18
+ *
19
+ * It is the only caller of `refreshGrant`, and it takes `withRefreshLock` around
20
+ * a **re-read of the session from disk**. That order is the whole control: by
21
+ * the time a waiter gets the lock, the winner has rotated and saved, so the tail
22
+ * the waiter loaded before waiting is already a parent row — and presenting it
23
+ * is the replay that revokes the family and signs the user out everywhere. See
24
+ * `lock.ts`'s module note for why that is a Tuesday and not an edge case.
25
+ */
26
+ import type { AccessToken, RefreshToken } from "../credential.js";
27
+ import { type DiscoveryFault } from "./discovery.js";
28
+ import { type ReadDeps, type ReadOutcome } from "./read.js";
29
+ import { type Session, type SessionLoad } from "./session.js";
30
+ /**
31
+ * How long the browser round trip may take. It contains a full identity-provider
32
+ * login — reading an email, a password manager, possibly a second factor — so
33
+ * the bound is a human's attention span rather than a network budget. Five
34
+ * minutes is long enough not to punish a slow login and short enough that an
35
+ * abandoned attempt does not leave a listener bound for the rest of the session.
36
+ */
37
+ export declare const SIGNIN_DEADLINE_MS: number;
38
+ /** How long each HTTP step gets. Discovery and the code exchange are both fast. */
39
+ export declare const HTTP_TIMEOUT_MS = 10000;
40
+ export interface SignInDeps {
41
+ readonly home: string;
42
+ readonly mcpEndpoint: string;
43
+ readonly fetch: typeof fetch;
44
+ readonly nowMs: () => number;
45
+ /** Injected so a test never launches a browser. Returns false if it could not. */
46
+ readonly openBrowser: (url: string) => boolean;
47
+ /** Called with the authorize URL so the caller can print it. Copy stays in `src/copy/`. */
48
+ readonly onAuthorizeUrl: (url: string, opened: boolean) => void;
49
+ /** The browser tab's text. Supplied by the caller from `src/copy/`. */
50
+ readonly page: {
51
+ readonly done: string;
52
+ readonly refused: string;
53
+ };
54
+ readonly deadlineMs?: number;
55
+ readonly timeoutMs?: number;
56
+ }
57
+ export type SignIn = {
58
+ readonly kind: "ok";
59
+ readonly session: Session;
60
+ }
61
+ /** Discovery failed. Carries the cause so the fix line can name it. */
62
+ | {
63
+ readonly kind: "no-server";
64
+ readonly detail: DiscoveryFault | "unreachable";
65
+ }
66
+ /** The user declined at the authorize screen, or the AS refused. */
67
+ | {
68
+ readonly kind: "denied";
69
+ readonly error: string;
70
+ }
71
+ /** Nobody came back before the deadline. */
72
+ | {
73
+ readonly kind: "timeout";
74
+ }
75
+ /** The code exchange failed. `unauthorized_client` lands here — see `token.ts`. */
76
+ | {
77
+ readonly kind: "rejected";
78
+ readonly error: string;
79
+ } | {
80
+ readonly kind: "unreachable";
81
+ } | {
82
+ readonly kind: "malformed";
83
+ };
84
+ /**
85
+ * Open a URL in the platform browser.
86
+ *
87
+ * Detached and with its streams discarded: the browser is not this process's
88
+ * child in any meaningful sense, and leaving a pipe attached would keep the CLI
89
+ * alive until the browser exits — which on a long-running browser is forever.
90
+ * Returns false rather than throwing when there is nothing to open with, because
91
+ * a headless machine is a perfectly ordinary place to run this and the URL
92
+ * printed to the terminal is a complete fallback.
93
+ */
94
+ export declare function openBrowser(url: string): boolean;
95
+ /**
96
+ * Run one sign-in, end to end.
97
+ *
98
+ * The listener is started BEFORE the browser is opened and closed in a
99
+ * `finally`, so there is no window in which the redirect can arrive at a port
100
+ * nobody is listening on, and no path on which a socket outlives the attempt.
101
+ */
102
+ export declare function signIn(deps: SignInDeps): Promise<SignIn>;
103
+ export type Authorized =
104
+ /**
105
+ * `refresh` travels with the token so a caller that gets a 401 anyway can say
106
+ * WHICH session failed. That is what lets the forced refresh below separate a
107
+ * stale token of our own from one another process has already replaced;
108
+ * without it the retry path would rotate a token that had just been rotated,
109
+ * which is the family revoke arriving through the error handling.
110
+ *
111
+ * `expiresAtMs` rides along for callers that want to reason about lifetime.
112
+ * It is deliberately NOT the identity used for that comparison — see
113
+ * `rotateUnderLock`, where using it was a `/cso` finding.
114
+ *
115
+ * (Phrasing note: those sentences avoid putting a quoted phrase immediately
116
+ * after the word f-r-o-m. The linkage scan in `test/provenance.test.ts` is a
117
+ * REGEX over raw text rather than an AST, so prose of that shape inside a
118
+ * comment is read as an import specifier and reported as escaping the package.
119
+ * Worth knowing before writing the obvious sentence.)
120
+ */
121
+ {
122
+ readonly kind: "ok";
123
+ readonly access: AccessToken;
124
+ readonly expiresAtMs: number;
125
+ readonly refresh: RefreshToken;
126
+ }
127
+ /** Not signed in, or the session file is unusable. Carries the load for the fix line. */
128
+ | {
129
+ readonly kind: "no-session";
130
+ readonly load: SessionLoad;
131
+ }
132
+ /** The refresh grant was refused — the session is over and sign-in must run again. */
133
+ | {
134
+ readonly kind: "expired";
135
+ readonly error: string;
136
+ }
137
+ /** Another process held the refresh lock for longer than a healthy holder can. */
138
+ | {
139
+ readonly kind: "busy";
140
+ } | {
141
+ readonly kind: "unreachable";
142
+ } | {
143
+ readonly kind: "malformed";
144
+ };
145
+ export interface AuthorizeDeps {
146
+ readonly home: string;
147
+ readonly fetch: typeof fetch;
148
+ readonly nowMs: () => number;
149
+ readonly timeoutMs?: number;
150
+ }
151
+ /**
152
+ * A usable access token, refreshing under the cross-process lock if needed.
153
+ *
154
+ * The fast path takes no lock at all: a fresh access token is the common case
155
+ * and serialising every read behind a lock file would be a needless cost on
156
+ * every command.
157
+ */
158
+ export declare function authorizedAccessToken(deps: AuthorizeDeps): Promise<Authorized>;
159
+ /** What a read produces: a transport outcome, or the reason there was no token. */
160
+ export type Read = ReadOutcome | {
161
+ readonly kind: "not-authorized";
162
+ readonly authorized: Authorized;
163
+ };
164
+ /**
165
+ * Call a read tool with the signed-in user's token: refresh ONCE, retry ONCE.
166
+ *
167
+ * The bound is STRUCTURAL rather than a counter — two `callTool` statements, no
168
+ * loop — because "retry until it works" against an endpoint that keeps answering
169
+ * 401 is how a client turns one expired token into a revoked family.
170
+ *
171
+ * The second attempt exists because a token can be fresh by the clock and
172
+ * rejected anyway: a revoked family, a rotated signing key, or a machine whose
173
+ * clock is simply wrong. Those look identical from here, and one retry after a
174
+ * genuine rotation resolves all three or fails honestly.
175
+ */
176
+ export declare function readWithSession(mcpEndpoint: string, name: string, args: Record<string, unknown>, deps: AuthorizeDeps & {
177
+ readonly read: ReadDeps;
178
+ }): Promise<Read>;
@@ -0,0 +1,225 @@
1
+ /**
2
+ * The sign-in flow, and the refresh that keeps it alive.
3
+ *
4
+ * ```
5
+ * discover ──► PKCE + state ──► loopback listener on 127.0.0.1:0
6
+ * │ │
7
+ * │ browser ────┘ code + state + iss
8
+ * ▼ ▼
9
+ * authorize URL ─────────────────────► exchange ──► session.json
10
+ * ```
11
+ *
12
+ * Every step's failure is a NAMED outcome rather than an exception, because each
13
+ * one wants a different fix line: an unreachable discovery document is "the
14
+ * server is down", a denied grant is "you declined", and a malformed token
15
+ * response is ours to fix, not the user's.
16
+ *
17
+ * ## `authorizedAccessToken` is the part to read carefully
18
+ *
19
+ * It is the only caller of `refreshGrant`, and it takes `withRefreshLock` around
20
+ * a **re-read of the session from disk**. That order is the whole control: by
21
+ * the time a waiter gets the lock, the winner has rotated and saved, so the tail
22
+ * the waiter loaded before waiting is already a parent row — and presenting it
23
+ * is the replay that revokes the family and signs the user out everywhere. See
24
+ * `lock.ts`'s module note for why that is a Tuesday and not an edge case.
25
+ */
26
+ import { spawn } from "node:child_process";
27
+ import { discover } from "./discovery.js";
28
+ import { listenForCallback } from "./loopback.js";
29
+ import { REFRESH_REQUEST_TIMEOUT_MS, withRefreshLock } from "./lock.js";
30
+ import { generatePkce, generateState } from "./pkce.js";
31
+ import { callTool } from "./read.js";
32
+ import { isFresh, loadSession, saveSession } from "./session.js";
33
+ import { buildAuthorizeUrl, exchangeCode, refreshGrant } from "./token.js";
34
+ /**
35
+ * How long the browser round trip may take. It contains a full identity-provider
36
+ * login — reading an email, a password manager, possibly a second factor — so
37
+ * the bound is a human's attention span rather than a network budget. Five
38
+ * minutes is long enough not to punish a slow login and short enough that an
39
+ * abandoned attempt does not leave a listener bound for the rest of the session.
40
+ */
41
+ export const SIGNIN_DEADLINE_MS = 5 * 60_000;
42
+ /** How long each HTTP step gets. Discovery and the code exchange are both fast. */
43
+ export const HTTP_TIMEOUT_MS = 10_000;
44
+ /**
45
+ * Open a URL in the platform browser.
46
+ *
47
+ * Detached and with its streams discarded: the browser is not this process's
48
+ * child in any meaningful sense, and leaving a pipe attached would keep the CLI
49
+ * alive until the browser exits — which on a long-running browser is forever.
50
+ * Returns false rather than throwing when there is nothing to open with, because
51
+ * a headless machine is a perfectly ordinary place to run this and the URL
52
+ * printed to the terminal is a complete fallback.
53
+ */
54
+ export function openBrowser(url) {
55
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
56
+ try {
57
+ const child = spawn(command, [url], { detached: true, stdio: "ignore" });
58
+ // An ENOENT from `spawn` arrives asynchronously as an 'error' event, not as
59
+ // a throw. Without this handler it becomes an unhandled exception on a
60
+ // machine that simply has no `xdg-open` — a crash instead of a fallback.
61
+ child.on("error", () => { });
62
+ child.unref();
63
+ return true;
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
69
+ /** Discovery's failure, flattened to the shape the caller renders. */
70
+ function noServer(d) {
71
+ return { kind: "no-server", detail: d.kind === "unreachable" ? "unreachable" : d.detail };
72
+ }
73
+ /** A token-endpoint failure, flattened the same way. */
74
+ function fromToken(outcome) {
75
+ if (outcome.kind === "denied")
76
+ return { kind: "rejected", error: outcome.error };
77
+ return outcome.kind === "unreachable" ? { kind: "unreachable" } : { kind: "malformed" };
78
+ }
79
+ /**
80
+ * Run one sign-in, end to end.
81
+ *
82
+ * The listener is started BEFORE the browser is opened and closed in a
83
+ * `finally`, so there is no window in which the redirect can arrive at a port
84
+ * nobody is listening on, and no path on which a socket outlives the attempt.
85
+ */
86
+ export async function signIn(deps) {
87
+ const timeoutMs = deps.timeoutMs ?? HTTP_TIMEOUT_MS;
88
+ const discovery = await discover(deps.mcpEndpoint, { fetch: deps.fetch, timeoutMs });
89
+ if (discovery.kind !== "ok")
90
+ return noServer(discovery);
91
+ const { issuer, authorization_endpoint, token_endpoint } = discovery.metadata;
92
+ const pkce = generatePkce();
93
+ const state = generateState();
94
+ const loopback = await listenForCallback({
95
+ expectedState: state,
96
+ expectedIssuer: issuer,
97
+ deadlineMs: deps.deadlineMs ?? SIGNIN_DEADLINE_MS,
98
+ page: deps.page,
99
+ });
100
+ let callback;
101
+ try {
102
+ const url = buildAuthorizeUrl(authorization_endpoint, {
103
+ challenge: pkce.challenge,
104
+ state,
105
+ redirectUri: loopback.redirectUri,
106
+ });
107
+ deps.onAuthorizeUrl(url, deps.openBrowser(url));
108
+ callback = await loopback.result;
109
+ }
110
+ finally {
111
+ loopback.close();
112
+ }
113
+ if (callback.kind === "timeout")
114
+ return { kind: "timeout" };
115
+ if (callback.kind === "denied")
116
+ return { kind: "denied", error: callback.error };
117
+ const exchanged = await exchangeCode(token_endpoint, {
118
+ code: callback.code,
119
+ codeVerifier: pkce.verifier,
120
+ // Byte-identical to the one presented at authorize — the server compares
121
+ // them and answers `invalid_grant` on any difference.
122
+ redirectUri: loopback.redirectUri,
123
+ }, { fetch: deps.fetch, timeoutMs, nowMs: deps.nowMs() });
124
+ if (exchanged.kind !== "ok")
125
+ return fromToken(exchanged);
126
+ const session = { tokens: exchanged.tokens, issuer, tokenEndpoint: token_endpoint };
127
+ saveSession(deps.home, session);
128
+ return { kind: "ok", session };
129
+ }
130
+ /**
131
+ * A usable access token, refreshing under the cross-process lock if needed.
132
+ *
133
+ * The fast path takes no lock at all: a fresh access token is the common case
134
+ * and serialising every read behind a lock file would be a needless cost on
135
+ * every command.
136
+ */
137
+ export async function authorizedAccessToken(deps) {
138
+ const load = loadSession(deps.home);
139
+ if (load.kind !== "ok")
140
+ return { kind: "no-session", load };
141
+ const { access, expiresAtMs, refresh } = load.session.tokens;
142
+ if (isFresh(load.session, deps.nowMs()))
143
+ return { kind: "ok", access, expiresAtMs, refresh };
144
+ return await rotateUnderLock(deps, refresh);
145
+ }
146
+ /**
147
+ * Take the cross-process lock and rotate — unless somebody else already did.
148
+ *
149
+ * `superseded` is the refresh token the caller found unusable. Under the lock
150
+ * the session is re-read, and if the stored token is no longer that one, another
151
+ * process rotated while we waited: its token is the live one, and rotating again
152
+ * would present a token that is already a parent row. That is the family revoke,
153
+ * so the check is the point of the function rather than an optimisation.
154
+ *
155
+ * ⚠ `/cso` finding, HIGH: this compares the TOKEN, not the expiry it used to
156
+ * compare. `expiresAtMs` is `nowMs + expires_in * 1000`, so two rotations
157
+ * sampling the same millisecond with the same lifetime produce the SAME number —
158
+ * and the guard would then read "nobody rotated" immediately after somebody did,
159
+ * present the dead token, and revoke the family. Over a real network the
160
+ * round trip makes that collision vanishingly unlikely; over loopback it is
161
+ * merely unlikely. The refresh token is 32 random bytes and cannot collide, so
162
+ * the narrow window closes rather than shrinking.
163
+ */
164
+ async function rotateUnderLock(deps, superseded) {
165
+ const locked = await withRefreshLock(deps.home, async () => {
166
+ // ⚠ RE-READ INSIDE THE LOCK, ALWAYS. See the module note: the session was
167
+ // loaded before waiting, and if we waited at all, the winner has already
168
+ // rotated and saved.
169
+ const current = loadSession(deps.home);
170
+ if (current.kind !== "ok")
171
+ return { kind: "no-session", load: current };
172
+ if (current.session.tokens.refresh.expose() !== superseded.expose()) {
173
+ const { access, expiresAtMs, refresh } = current.session.tokens;
174
+ return { kind: "ok", access, expiresAtMs, refresh };
175
+ }
176
+ const rotated = await refreshGrant(current.session.tokenEndpoint, current.session.tokens.refresh, {
177
+ fetch: deps.fetch,
178
+ timeoutMs: deps.timeoutMs ?? REFRESH_REQUEST_TIMEOUT_MS,
179
+ nowMs: deps.nowMs(),
180
+ });
181
+ if (rotated.kind === "denied")
182
+ return { kind: "expired", error: rotated.error };
183
+ if (rotated.kind === "unreachable")
184
+ return { kind: "unreachable" };
185
+ if (rotated.kind === "malformed")
186
+ return { kind: "malformed" };
187
+ // Saved BEFORE returning, and before any read uses it: the old token is dead
188
+ // the instant the server answered, so a crash between here and the save
189
+ // would lose the only live token in the family.
190
+ const next = { ...current.session, tokens: rotated.tokens };
191
+ saveSession(deps.home, next);
192
+ return {
193
+ kind: "ok",
194
+ access: next.tokens.access,
195
+ expiresAtMs: next.tokens.expiresAtMs,
196
+ refresh: next.tokens.refresh,
197
+ };
198
+ });
199
+ return locked.kind === "ok" ? locked.value : { kind: "busy" };
200
+ }
201
+ /**
202
+ * Call a read tool with the signed-in user's token: refresh ONCE, retry ONCE.
203
+ *
204
+ * The bound is STRUCTURAL rather than a counter — two `callTool` statements, no
205
+ * loop — because "retry until it works" against an endpoint that keeps answering
206
+ * 401 is how a client turns one expired token into a revoked family.
207
+ *
208
+ * The second attempt exists because a token can be fresh by the clock and
209
+ * rejected anyway: a revoked family, a rotated signing key, or a machine whose
210
+ * clock is simply wrong. Those look identical from here, and one retry after a
211
+ * genuine rotation resolves all three or fails honestly.
212
+ */
213
+ export async function readWithSession(mcpEndpoint, name, args, deps) {
214
+ const first = await authorizedAccessToken(deps);
215
+ if (first.kind !== "ok")
216
+ return { kind: "not-authorized", authorized: first };
217
+ const outcome = await callTool(mcpEndpoint, first.access, name, args, deps.read);
218
+ if (outcome.kind !== "unauthorized")
219
+ return outcome;
220
+ const second = await rotateUnderLock(deps, first.refresh);
221
+ if (second.kind !== "ok")
222
+ return { kind: "not-authorized", authorized: second };
223
+ return await callTool(mcpEndpoint, second.access, name, args, deps.read);
224
+ }
225
+ //# sourceMappingURL=signin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signin.js","sourceRoot":"","sources":["../../src/oauth/signin.ts"],"names":[],"mappings":"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"}