@serviceme/devtools-core 0.1.9 → 0.2.1

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 (47) hide show
  1. package/dist/auth.d.mts +592 -0
  2. package/dist/auth.d.ts +592 -0
  3. package/dist/auth.js +886 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/auth.mjs +848 -0
  6. package/dist/auth.mjs.map +1 -0
  7. package/dist/device.d.mts +456 -0
  8. package/dist/device.d.ts +456 -0
  9. package/dist/device.js +696 -0
  10. package/dist/device.js.map +1 -0
  11. package/dist/device.mjs +647 -0
  12. package/dist/device.mjs.map +1 -0
  13. package/dist/index-CrNC-aao.d.ts +277 -0
  14. package/dist/index-Dmyy4urr.d.mts +277 -0
  15. package/dist/index.d.mts +1376 -27
  16. package/dist/index.d.ts +1376 -27
  17. package/dist/index.js +4707 -414
  18. package/dist/index.js.map +1 -0
  19. package/dist/index.mjs +4586 -414
  20. package/dist/index.mjs.map +1 -0
  21. package/dist/skill-linker.d.mts +188 -0
  22. package/dist/skill-linker.d.ts +188 -0
  23. package/dist/skill-linker.js +374 -0
  24. package/dist/skill-linker.js.map +1 -0
  25. package/dist/skill-linker.mjs +330 -0
  26. package/dist/skill-linker.mjs.map +1 -0
  27. package/dist/skill-store.d.mts +35 -0
  28. package/dist/skill-store.d.ts +35 -0
  29. package/dist/skill-store.js +284 -0
  30. package/dist/skill-store.js.map +1 -0
  31. package/dist/skill-store.mjs +247 -0
  32. package/dist/skill-store.mjs.map +1 -0
  33. package/dist/submit.d.mts +3 -0
  34. package/dist/submit.d.ts +3 -0
  35. package/dist/submit.js +166 -0
  36. package/dist/submit.js.map +1 -0
  37. package/dist/submit.mjs +130 -0
  38. package/dist/submit.mjs.map +1 -0
  39. package/dist/toolbox.d.mts +240 -0
  40. package/dist/toolbox.d.ts +240 -0
  41. package/dist/toolbox.js +530 -0
  42. package/dist/toolbox.js.map +1 -0
  43. package/dist/toolbox.mjs +482 -0
  44. package/dist/toolbox.mjs.map +1 -0
  45. package/dist/types-B9gk3dXH.d.mts +62 -0
  46. package/dist/types-B9gk3dXH.d.ts +62 -0
  47. package/package.json +51 -5
@@ -0,0 +1,592 @@
1
+ import { AuthProvider, AuthAccountMeta, AuthStatus, AuthLoginResult, AuthWhoamiResult, AuthSwitchResult } from '@serviceme/devtools-protocol';
2
+
3
+ /**
4
+ * AccessControl — Pure logic for org-membership gating.
5
+ *
6
+ * Ported from `apps/extension/src/services/auth/AccessControlService.ts`
7
+ * but stripped of all VSCode dependencies:
8
+ * - No `vscode.window.showWarningMessage` — callers render the notice.
9
+ * - No `vscode.context.globalState` — callers inject a `KeyValueStore`.
10
+ * - No `vscode.Event` — uses a plain `onDidChange` callback.
11
+ *
12
+ * The class is generic over the membership-fetcher function so it can
13
+ * run identically with the CLI's `@serviceme/shared` `getGitHubOrgMembership`
14
+ * or with the Extension's bundled copy. ADL-003 forbids Core from
15
+ * importing `@serviceme/shared` directly, so the org membership
16
+ * result type is defined locally and the runtime adapter wraps the
17
+ * shared helper at the boundary (Phase 5.3+).
18
+ *
19
+ * Refs:
20
+ * - 4.功能规划.md §2.1 — `AccessControl.ts 从 AccessControlService 改写`
21
+ * - ADL-003 — `@serviceme/shared` boundary (Core MUST NOT import shared)
22
+ */
23
+
24
+ /** Core-local mirror of `GitHubOrgMembershipCheckResult` (defined in `@serviceme/shared`). */
25
+ interface CoreOrgMembershipResult {
26
+ status: "active" | "pending" | "not_member" | "unknown";
27
+ httpStatus?: number;
28
+ role?: string;
29
+ directMembership?: boolean;
30
+ }
31
+ /**
32
+ * Upstream org-membership fetcher. CLI / Extension adapters wrap
33
+ * `getGitHubOrgMembership` (or the local extension copy) to match
34
+ * this signature.
35
+ */
36
+ type OrgMembershipFetcher = (token: string, org: string) => Promise<CoreOrgMembershipResult>;
37
+ /** Pluggable key/value store. Extension injects `globalState`-backed impl; CLI injects a file-backed impl. */
38
+ interface KeyValueStore {
39
+ get<T>(key: string): T | undefined;
40
+ update<T>(key: string, value: T): Promise<void>;
41
+ }
42
+ interface AccessCheckResult {
43
+ allowed: boolean;
44
+ reason?: "not_authenticated" | "not_member";
45
+ username?: string;
46
+ }
47
+ interface AccessControlOptions {
48
+ org: string;
49
+ /** Domains that auto-qualify Microsoft users without a GitHub link. */
50
+ microsoftEmailDomains?: readonly string[];
51
+ /** TTL for in-memory membership cache (ms). */
52
+ cacheTtlMs?: number;
53
+ /** TTL for persisted membership cache (ms). */
54
+ persistentCacheTtlMs?: number;
55
+ /** Tokens whose bearer can be supplied to `OrgMembershipFetcher`. */
56
+ tokenFetcher: (provider: AuthProvider) => Promise<string | null>;
57
+ /** Upstream org-membership fetcher (typically `getGitHubOrgMembership` from `@serviceme/shared`). */
58
+ orgFetcher: OrgMembershipFetcher;
59
+ /** Clock seam for tests. */
60
+ now?: () => number;
61
+ }
62
+ declare class AccessControl {
63
+ private readonly kv;
64
+ private readonly memCache;
65
+ private readonly serverInOrgMem;
66
+ private readonly listeners;
67
+ private readonly opts;
68
+ constructor(kv: KeyValueStore, options: AccessControlOptions);
69
+ /** Subscribe to inOrg changes (used by extension to refresh access-gated UI). */
70
+ onDidChange(listener: () => void): () => void;
71
+ /** Full check: requires authentication + org membership / admin grant. */
72
+ checkAccess(user: AuthAccountMeta | null): Promise<AccessCheckResult>;
73
+ /** Admin: grant access to a GitHub username (bypasses org check). */
74
+ grantAccess(username: string): Promise<void>;
75
+ /** Admin: revoke a previously granted access. */
76
+ revokeAccess(username: string): Promise<void>;
77
+ /** Snapshot of all admin-granted usernames (lowercased). */
78
+ getGrantedUsers(): string[];
79
+ /** Cached membership lookup with both in-memory + persistent layers. */
80
+ checkOrgMembership(username: string): Promise<boolean>;
81
+ /** Server-confirmed inOrg status (set by device claim flow). */
82
+ setServerInOrg(username: string, inOrg: boolean): void;
83
+ /** Inspect server-confirmed inOrg for a username; returns undefined when unknown. */
84
+ getServerInOrg(username: string): boolean | undefined;
85
+ private resolveDecision;
86
+ private updateCache;
87
+ private notifyListeners;
88
+ }
89
+
90
+ /**
91
+ * AuthStateManager — In-memory mirror of the persisted auth state.
92
+ *
93
+ * Holds the multi-account map (provider + accountId keyed) plus the
94
+ * "active" provider/account pair. Emits change events via Node's
95
+ * built-in `events.EventEmitter` so listeners stay decoupled.
96
+ *
97
+ * Important: this class does NOT touch `vscode.SecretStorage` or any
98
+ * file — it only keeps the metadata (`AuthAccountMeta`) and the
99
+ * `activeProvider` selection. Token bytes are looked up via the
100
+ * `KeychainAuthTokenStore` on demand. This separation is what lets
101
+ * `AuthCore` run identically in CLI + Extension.
102
+ *
103
+ * Refs:
104
+ * - 4.功能规划.md §2.1 — `AuthStateManager.ts events.EventEmitter, NO VSCode dep`
105
+ * - ADL-004 — token bytes never enter Core state
106
+ */
107
+
108
+ interface AuthStateManagerOptions {
109
+ /** EventEmitter listener cap; defaults to 32 (Node default) but raised for hot test paths. */
110
+ maxListeners?: number;
111
+ }
112
+ declare class AuthStateManager {
113
+ private readonly emitter;
114
+ private accounts;
115
+ private activeProvider;
116
+ private activeAccountId;
117
+ private lastError;
118
+ constructor(opts?: AuthStateManagerOptions);
119
+ /** Subscribe to state-change events. Returns a disposer. */
120
+ onDidChange(listener: () => void): () => void;
121
+ /** Snapshot of all known accounts (immutable copy). */
122
+ listAccounts(): AuthAccountMeta[];
123
+ /** Find an account by `(provider, accountId)` tuple; returns `null` when absent. */
124
+ findAccount(provider: AuthProvider, accountId: string): AuthAccountMeta | null;
125
+ /** First account for the requested provider — used for "switch to GitHub" UX. */
126
+ findFirstForProvider(provider: AuthProvider): AuthAccountMeta | null;
127
+ /** Insert or update an account entry. New accounts land at the head of the list. */
128
+ upsertAccount(meta: AuthAccountMeta): void;
129
+ /** Remove an account entry. Returns `true` when an entry was removed. */
130
+ removeAccount(provider: AuthProvider, accountId: string): boolean;
131
+ /** Set the active provider. When `accountId` is omitted, picks the first account for that provider. */
132
+ setActive(provider: AuthProvider, accountId?: string): boolean;
133
+ /** Currently active provider — `null` when no session is active. */
134
+ getActiveProvider(): AuthProvider | null;
135
+ /** Currently active account metadata — `null` when none. */
136
+ getActiveAccount(): AuthAccountMeta | null;
137
+ /** True when at least one provider has an account entry. */
138
+ hasAnySession(): boolean;
139
+ /** Snapshot of the state for `auth.status` and bridge serialization. */
140
+ getStatus(): AuthStatus;
141
+ /** Record a non-fatal error from the last login/refresh attempt. */
142
+ recordError(message: string): void;
143
+ /** Clear the recorded error (e.g. after a successful login). */
144
+ clearError(): void;
145
+ /** Drop every account — used by `auth.logout` with no provider. */
146
+ clearAll(): void;
147
+ private fire;
148
+ }
149
+
150
+ /**
151
+ * KeychainAuthTokenStore — abstract adapter for token byte persistence.
152
+ *
153
+ * Per ADL-004 the OAuth token bytes live in one of three places:
154
+ * (a) VSCode `SecretStorage` (Extension process)
155
+ * (b) `@napi-rs/keyring` Entry (CLI process)
156
+ * (c) HTTP request `Authorization` header
157
+ *
158
+ * `AuthCore` MUST NOT call any of these directly. Instead it receives
159
+ * a `KeychainAuthTokenStore` via DI; CLI provides a `@napi-rs/keyring`
160
+ * adapter, Extension provides a `SecretStorage` adapter. This keeps
161
+ * Core free of native-binding concerns and lets the same business
162
+ * logic power both runtimes.
163
+ *
164
+ * Implementations:
165
+ * - CLI: `apps/serviceme-cli/src/.../KeyringTokenStore.ts` (Phase 5.3)
166
+ * - Extension: `apps/extension/src/.../SecretStorageTokenStore.ts` (Phase 5.5)
167
+ *
168
+ * Failure semantics — `get()` returns `null` when no token is stored
169
+ * (cold-start), throws when the underlying keychain is unavailable
170
+ * (rare on Linux without libsecret). Callers should surface the throw
171
+ * as `AUTH_KEYRING_UNAVAILABLE` rather than silently falling back to
172
+ * in-memory storage (per ADL-004 §不变量).
173
+ *
174
+ * Refs:
175
+ * - ADL-004 — CLI 端 Auth Token 存储选型
176
+ * - 4.功能规划.md §2.1 — "Core MUST NOT directly depend on `@napi-rs/keyring`"
177
+ */
178
+ /** Opaque account identifier (provider + upstream user id). */
179
+ interface KeychainAccountKey {
180
+ provider: string;
181
+ accountId: string;
182
+ }
183
+ /** Non-secret metadata returned alongside a `get()` so callers can audit. */
184
+ interface KeychainTokenMetadata {
185
+ provider: string;
186
+ accountId: string;
187
+ expiresAt?: number | null;
188
+ storedAt?: number;
189
+ }
190
+ /**
191
+ * Result envelope — callers receive the token bytes (for immediate use
192
+ * in an HTTP `Authorization` header) plus optional non-secret metadata.
193
+ *
194
+ * IMPORTANT: the `token` field is plain `string` here, not the
195
+ * `SecretToken` brand. The provider -> store -> HTTP-header pipeline
196
+ * runs inside a single trust boundary; crossing that boundary requires
197
+ * `KeychainTokenEnvelope<SecretToken>` and the `auth.tokenRead`
198
+ * capability gate (see ADL-004 §不变量).
199
+ */
200
+ interface KeychainTokenEnvelope {
201
+ token: string;
202
+ metadata: KeychainTokenMetadata;
203
+ }
204
+ /**
205
+ * Abstract token-storage adapter. All methods are async because the
206
+ * keyring / SecretStorage backends are async-by-nature.
207
+ *
208
+ * Thread-safety: implementations MUST be safe for concurrent calls;
209
+ * `AuthCore` will multiplex over multiple providers on the same
210
+ * runtime.
211
+ */
212
+ interface KeychainAuthTokenStore {
213
+ /** Persist token bytes for the given provider + account pair. */
214
+ set(key: KeychainAccountKey, token: string, opts?: {
215
+ expiresAt?: number | null;
216
+ }): Promise<void>;
217
+ /** Fetch the token bytes; returns `null` when none is stored. */
218
+ get(key: KeychainAccountKey): Promise<KeychainTokenEnvelope | null>;
219
+ /** Remove the token entry. Idempotent — removing a missing entry is not an error. */
220
+ delete(key: KeychainAccountKey): Promise<void>;
221
+ /**
222
+ * List all stored token keys (metadata only — never the token bytes).
223
+ * Useful for multi-account enumeration and bridge `auth.status`.
224
+ */
225
+ list(): Promise<KeychainAccountKey[]>;
226
+ /**
227
+ * Health probe — returns `false` when the keychain is unreachable
228
+ * (e.g. Linux without libsecret). Callers SHOULD probe on first
229
+ * use and surface `AUTH_KEYRING_UNAVAILABLE` rather than degrade.
230
+ */
231
+ isAvailable(): Promise<boolean>;
232
+ }
233
+ /**
234
+ * Sentinel error thrown by `KeychainAuthTokenStore.get()` / `.set()`
235
+ * when the underlying keychain is unavailable. Callers map this to
236
+ * `AUTH_KEYRING_UNAVAILABLE` (Phase 5.3 error code).
237
+ */
238
+ declare class KeychainUnavailableError extends Error {
239
+ constructor(message: string, cause?: unknown);
240
+ }
241
+ /**
242
+ * In-memory `KeychainAuthTokenStore` — test/dev fallback. NEVER use
243
+ * in production: tokens live in process memory and disappear on exit.
244
+ * Production wiring is `KeyringTokenStore` (CLI) / `SecretStorageTokenStore`
245
+ * (Extension).
246
+ */
247
+ declare class InMemoryKeychainAuthTokenStore implements KeychainAuthTokenStore {
248
+ private readonly entries;
249
+ private compositeKey;
250
+ set(key: KeychainAccountKey, token: string, opts?: {
251
+ expiresAt?: number | null;
252
+ }): Promise<void>;
253
+ get(key: KeychainAccountKey): Promise<KeychainTokenEnvelope | null>;
254
+ delete(key: KeychainAccountKey): Promise<void>;
255
+ list(): Promise<KeychainAccountKey[]>;
256
+ isAvailable(): Promise<boolean>;
257
+ }
258
+
259
+ /**
260
+ * IAuthProvider — Provider abstraction used by `AuthCore`.
261
+ *
262
+ * Each provider (GitHub / Microsoft / future) exposes the same minimal
263
+ * surface so `AuthCore` can drive device-flow style handshakes without
264
+ * coupling to a specific OAuth server. Implementations MUST NOT touch
265
+ * `vscode`, `globalState`, or `SecretStorage` — those concerns live in
266
+ * the CLI / Extension adapters (see ADL-004).
267
+ *
268
+ * Per ADL-004 the **token bytes** never leave the provider's runtime
269
+ * — `requestDeviceFlow()` returns enough metadata for the caller to
270
+ * display the verification URL + user code; the actual `token` is
271
+ * persisted via the `KeychainAuthTokenStore` interface (DI) immediately
272
+ * after `completeDeviceFlow()` resolves.
273
+ *
274
+ * Refs:
275
+ * - 4.功能规划.md §2.1 — `providers/IAuthProvider.ts`
276
+ * - ADL-002 — Device Flow OAuth (locked)
277
+ * - ADL-004 — token storage boundary
278
+ */
279
+
280
+ /** Public, non-secret user metadata fetched right after device-flow success. */
281
+ interface IAuthProviderUserInfo {
282
+ id: string;
283
+ login?: string;
284
+ name?: string;
285
+ email?: string | null;
286
+ avatarUrl?: string;
287
+ }
288
+ /**
289
+ * Outcome of `completeDeviceFlow()`. The token is returned ONLY so the
290
+ * caller can immediately persist it via `KeychainAuthTokenStore`; it
291
+ * must not be logged, JSON.stringify'd, or stored on disk by callers.
292
+ */
293
+ interface IAuthProviderSession {
294
+ token: string;
295
+ refreshToken?: string;
296
+ expiresIn?: number;
297
+ user: IAuthProviderUserInfo;
298
+ }
299
+ /**
300
+ * Abstraction for any auth provider that supports OAuth Device Flow
301
+ * (ADL-002). The provider is responsible for:
302
+ * 1. Talking to the upstream `POST <deviceCodeUrl>` to obtain the
303
+ * user-visible code + verification URL.
304
+ * 2. Polling the upstream `POST <tokenUrl>` until the user grants
305
+ * access (or the device code expires).
306
+ * 3. Fetching user metadata so the account is recognizable on the
307
+ * bridge side.
308
+ */
309
+ interface IAuthProvider {
310
+ readonly providerId: AuthProvider;
311
+ /**
312
+ * Step 1: ask the upstream for a fresh device-flow code.
313
+ * Returns the user-visible code, verification URL, and the
314
+ * caller-supplied callback for the polling loop.
315
+ */
316
+ requestDeviceFlow(opts?: {
317
+ scope?: string;
318
+ }): Promise<AuthLoginResult>;
319
+ /**
320
+ * Step 2: poll until the user authorizes (or the code expires).
321
+ * Returns the session including the token bytes — callers MUST
322
+ * immediately hand the token to a `KeychainAuthTokenStore` and
323
+ * discard the returned reference.
324
+ *
325
+ * @param shouldContinue Aborts the poll loop when it returns false
326
+ * (used by callers to wire up Ctrl-C, QuickPick cancel, etc.).
327
+ */
328
+ completeDeviceFlow(deviceCode: string, shouldContinue?: () => boolean): Promise<IAuthProviderSession>;
329
+ /**
330
+ * Refresh an expiring access token. Throws when the provider has no
331
+ * refresh-token grant (GitHub user-to-server tokens, for instance,
332
+ * cannot be refreshed and must be re-issued via device flow).
333
+ */
334
+ refreshAccessToken(refreshToken: string): Promise<IAuthProviderSession>;
335
+ /** Validate a token by hitting the provider's `GET /user`-style endpoint. */
336
+ validateToken(token: string): Promise<boolean>;
337
+ /** Fetch non-secret user metadata (used by `auth.whoami` and `auth.switch`). */
338
+ fetchAccountMeta(token: string): Promise<AuthAccountMeta>;
339
+ }
340
+
341
+ /**
342
+ * ProviderRegistry — Maps `AuthProvider` ids to `IAuthProvider` instances.
343
+ *
344
+ * `AuthCore` looks up providers by `AuthProvider` enum (string union
345
+ * per the protocol). The registry is mutable so that callers can
346
+ * replace a provider (e.g. swap a real keyring-backed implementation
347
+ * in tests), but providers must be registered before `AuthCore.login()`
348
+ * is called.
349
+ *
350
+ * Refs:
351
+ * - 4.功能规划.md §2.1 — `ProviderRegistry.ts`
352
+ */
353
+
354
+ declare class ProviderRegistry {
355
+ private readonly providers;
356
+ /** Register or replace a provider implementation. */
357
+ register(provider: IAuthProvider): void;
358
+ /** Look up a registered provider by id; throws when missing. */
359
+ get(providerId: AuthProvider): IAuthProvider;
360
+ /** Non-throwing lookup; returns `undefined` when the provider is unknown. */
361
+ tryGet(providerId: AuthProvider): IAuthProvider | undefined;
362
+ /** Return all registered provider ids — used by `auth.status` and bridge capability hints. */
363
+ list(): AuthProvider[];
364
+ /** True when a provider has been registered for this id. */
365
+ has(providerId: AuthProvider): boolean;
366
+ }
367
+
368
+ /**
369
+ * AuthCore — Main entry for the auth domain.
370
+ *
371
+ * Owns the `ProviderRegistry` + `AuthStateManager` + `KeychainAuthTokenStore`.
372
+ * Provides a small, side-effectful surface that CLI / Extension / Bridge
373
+ * handlers can call:
374
+ *
375
+ * - `login(provider)` → drives Device Flow, persists token via store.
376
+ * - `logout(provider?)` → clears the active session (and token bytes).
377
+ * - `status()` → snapshot for `auth.status`.
378
+ * - `whoami()` → minimal identity for `auth.whoami`.
379
+ * - `switchProvider(p)` → flip the active provider (multi-account).
380
+ *
381
+ * Core NEVER holds token bytes past the `KeychainAuthTokenStore.set()`
382
+ * boundary — callers pass the token to the store immediately, then
383
+ * drop the local reference. Per ADL-004 the token bytes only live in
384
+ * (a) SecretStorage / (b) keyring / (c) HTTP Authorization header.
385
+ *
386
+ * Refs:
387
+ * - 4.功能规划.md §2.1 — `AuthCore.ts 主入口`
388
+ * - ADL-002 — Device Flow OAuth
389
+ * - ADL-004 — Auth token storage
390
+ */
391
+
392
+ interface AuthCoreOptions {
393
+ providers: IAuthProvider[];
394
+ tokenStore: KeychainAuthTokenStore;
395
+ accessControl?: AccessControl;
396
+ stateManager?: AuthStateManager;
397
+ }
398
+ /**
399
+ * Callback invoked once the user finishes the device-flow handshake but
400
+ * BEFORE the token is written to the keychain. Lets the caller display
401
+ * the `userCode` + `verificationUrl` to the user (Phase 5.3 CLI prints to
402
+ * stdout; Phase 5.5 Extension pops a webview notification).
403
+ */
404
+ type DeviceFlowUiCallback = (result: AuthLoginResult) => void | Promise<void>;
405
+ /**
406
+ * Optional cancellation handle — when `shouldContinue()` returns false,
407
+ * the polling loop exits with a clear error so callers can render
408
+ * "Login cancelled" UX.
409
+ */
410
+ type CancellationCheck = () => boolean;
411
+ declare class AuthCore {
412
+ private readonly registry;
413
+ private readonly state;
414
+ private readonly tokenStore;
415
+ private readonly accessControl?;
416
+ constructor(opts: AuthCoreOptions);
417
+ /** Snapshot of every account, the active provider, and the last error. */
418
+ status(): AuthStatus;
419
+ /** List accounts (immutable copy). */
420
+ listAccounts(): AuthAccountMeta[];
421
+ /**
422
+ * Drive the device-flow login for `provider`.
423
+ *
424
+ * Sequence:
425
+ * 1. Ask the provider for the user code + verification URL.
426
+ * 2. Surface the code to the user (via `ui`).
427
+ * 3. Poll until the user authorizes (or `shouldContinue` aborts).
428
+ * 4. Persist the token via `KeychainAuthTokenStore.set()`.
429
+ * 5. Insert the resulting `AuthAccountMeta` into state and mark active.
430
+ */
431
+ login(provider: AuthProvider, ui: DeviceFlowUiCallback, shouldContinue?: CancellationCheck): Promise<AuthLoginResult>;
432
+ /**
433
+ * Complete the device-flow handshake given a pre-fetched user code.
434
+ * Useful when the caller (Phase 5.3 CLI) wants to fetch the code,
435
+ * print the URL, and then poll on a subsequent invocation.
436
+ */
437
+ completeLogin(provider: AuthProvider, deviceCode: string, shouldContinue?: CancellationCheck): Promise<AuthLoginResult>;
438
+ /** Resolve the provider + token for the active session; returns null when no session is active. */
439
+ resolveActiveToken(): Promise<{
440
+ provider: AuthProvider;
441
+ account: AuthAccountMeta;
442
+ token: string;
443
+ } | null>;
444
+ /**
445
+ * Logout: removes the token bytes from the keychain + drops the
446
+ * account entry from state. When `provider` is omitted, clears
447
+ * every account.
448
+ */
449
+ logout(provider?: AuthProvider): Promise<{
450
+ provider: AuthProvider | null;
451
+ success: boolean;
452
+ }>;
453
+ /** Minimal identity for `auth.whoami`. */
454
+ whoami(): Promise<AuthWhoamiResult>;
455
+ /** Switch the active provider. Returns the resulting active account. */
456
+ switchProvider(provider: AuthProvider, accountId?: string): AuthSwitchResult;
457
+ /** Run the access-control check against the active account (optional, requires AccessControl). */
458
+ checkAccess(): Promise<AccessCheckResult | null>;
459
+ /** Expose the state manager for test inspection (not for mutation). */
460
+ getStateManager(): AuthStateManager;
461
+ /** Expose the provider registry for test inspection. */
462
+ getProviderRegistry(): ProviderRegistry;
463
+ /** Expose the access control (or undefined when not configured). */
464
+ getAccessControl(): AccessControl | undefined;
465
+ private persistSession;
466
+ }
467
+
468
+ /**
469
+ * GitHubAuthProvider — Device Flow OAuth implementation.
470
+ *
471
+ * Implements the GitHub OAuth Device Flow per the official spec
472
+ * (https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow).
473
+ *
474
+ * Per ADL-002 the device flow is the locked authentication strategy for
475
+ * SERVICEME — no localhost callback server, no PAT. This class is a
476
+ * pure HTTP client (uses native `fetch`, no vscode dependency) that
477
+ * returns the token bytes for the caller's `KeychainAuthTokenStore` to
478
+ * persist immediately.
479
+ *
480
+ * Token bytes NEVER enter logs / errors / debug output. The provider
481
+ * exposes only the user-visible code + verification URL during the
482
+ * device-flow handshake.
483
+ *
484
+ * Refs:
485
+ * - ADL-002 — Device Flow OAuth (locked)
486
+ * - 4.功能规划.md §2.1 — `providers/GitHubAuthProvider.ts`
487
+ */
488
+
489
+ /** Tunable provider config — exposed for tests + forks. */
490
+ interface GitHubAuthProviderConfig {
491
+ clientId: string;
492
+ deviceCodeUrl?: string;
493
+ tokenUrl?: string;
494
+ userUrl?: string;
495
+ scope?: string;
496
+ /** Polling interval (ms) floor. The device-flow response's `interval` wins when larger. */
497
+ minPollIntervalMs?: number;
498
+ /** Maximum poll interval after exponential back-off. */
499
+ maxPollIntervalMs?: number;
500
+ /** Fetch override (for tests + DI). */
501
+ fetchImpl?: typeof fetch;
502
+ /** Maximum wall-clock time to wait for the user before giving up (ms). Defaults to `expires_in * 1000`. */
503
+ maxWaitMs?: number;
504
+ /** Base delay (ms) before retrying the device-code request after a transient network error. */
505
+ deviceCodeRetryBaseDelayMs?: number;
506
+ }
507
+ declare class GitHubAuthProvider implements IAuthProvider {
508
+ readonly providerId: AuthProvider;
509
+ private readonly cfg;
510
+ constructor(config: GitHubAuthProviderConfig);
511
+ requestDeviceFlow(opts?: {
512
+ scope?: string;
513
+ }): Promise<AuthLoginResult>;
514
+ completeDeviceFlow(deviceCode: string, shouldContinue?: () => boolean): Promise<IAuthProviderSession>;
515
+ refreshAccessToken(refreshToken: string): Promise<IAuthProviderSession>;
516
+ validateToken(token: string): Promise<boolean>;
517
+ fetchAccountMeta(token: string): Promise<AuthAccountMeta>;
518
+ private fetchGitHubUser;
519
+ /**
520
+ * GitHub's `/user` endpoint returns `null` when the user kept their
521
+ * email private. The `/user/emails` endpoint reveals verified emails;
522
+ * we pick the primary one, falling back to the synthetic
523
+ * `<login>@github.local` form so the account is never email-less.
524
+ */
525
+ private resolveEmail;
526
+ }
527
+
528
+ /**
529
+ * MicrosoftAuthProvider — Microsoft Account (MSA) OAuth Device Flow stub.
530
+ *
531
+ * The Extension's existing `MicrosoftAuthProvider` (`apps/extension/src/services/auth/providers/MicrosoftAuthProvider.ts`)
532
+ * delegates to VSCode's built-in `vscode.authentication.getSession()` API
533
+ * — there's no direct Microsoft device-flow endpoint exposed for
534
+ * first-party apps. To keep Core usable from the CLI without VSCode,
535
+ * we expose a minimal stub that throws "not implemented in Core" so
536
+ * callers can detect and route back to the Extension adapter.
537
+ *
538
+ * The reason this lives in Core at all (instead of being purely
539
+ * Extension-side) is the cross-runtime registry: `AuthCore` needs to
540
+ * know which providers it MIGHT support, even if only the Extension
541
+ * process can actually fulfil the Microsoft login.
542
+ *
543
+ * Refs:
544
+ * - 4.功能规划.md §2.1 — `providers/MicrosoftAuthProvider.ts 从 extension 整体迁移`
545
+ * - ADL-002 — Device Flow OAuth (locked, GitHub-only)
546
+ */
547
+
548
+ /**
549
+ * Error thrown when callers invoke Microsoft auth from a non-Extension
550
+ * runtime (CLI / Bridge). The CLI maps this to `AUTH_PROVIDER_REQUIRES_HOST`
551
+ * so the user is prompted to retry inside VSCode.
552
+ */
553
+ declare class MicrosoftProviderNotHostedError extends Error {
554
+ constructor(message?: string);
555
+ }
556
+ declare class MicrosoftAuthProvider implements IAuthProvider {
557
+ readonly providerId: AuthProvider;
558
+ requestDeviceFlow(): Promise<AuthLoginResult>;
559
+ completeDeviceFlow(_deviceCode: string, _shouldContinue?: () => boolean): Promise<IAuthProviderSession>;
560
+ refreshAccessToken(_refreshToken: string): Promise<IAuthProviderSession>;
561
+ validateToken(_token: string): Promise<boolean>;
562
+ fetchAccountMeta(_token: string): Promise<AuthAccountMeta>;
563
+ }
564
+
565
+ /**
566
+ * AuthCore — Pure local utilities for GitHub local email handling.
567
+ *
568
+ * Copied verbatim from `@serviceme/shared/src/github-user-email.ts` (15 LOC)
569
+ * because ADL-003 forbids Core from depending on `@serviceme/shared`. These
570
+ * helpers are pure functions with zero side effects, so the duplication is
571
+ * trivial to keep in sync.
572
+ *
573
+ * Refs:
574
+ * - 4.功能规划.md §2.1 — "utils/githubUserEmail.ts 纯函数,直接搬"
575
+ * - ADL-003 — `@serviceme/shared` boundary decision
576
+ */
577
+ /**
578
+ * Returns true when the supplied address is a synthetic GitHub "local" email
579
+ * (suffixed with `@github.local`). Real GitHub OAuth clients often return
580
+ * `null` for `email` (user kept it private) and the application substitutes
581
+ * `<login>@github.local` to keep the field non-null.
582
+ */
583
+ declare function isGitHubLocalEmail(email: string | null | undefined): boolean;
584
+ /** Build a synthetic `<login>@github.local` address. */
585
+ declare function buildGitHubLocalEmail(login: string): string;
586
+ /**
587
+ * Resolve the user's primary email address — falling back to the synthetic
588
+ * `<login>@github.local` form when the upstream email is missing or blank.
589
+ */
590
+ declare function resolvePrimaryEmail(login: string, email: string | null | undefined): string;
591
+
592
+ export { type AccessCheckResult, AccessControl, type AccessControlOptions, AuthCore, type AuthCoreOptions, AuthStateManager, type AuthStateManagerOptions, type CancellationCheck, type DeviceFlowUiCallback, GitHubAuthProvider, type GitHubAuthProviderConfig, type IAuthProvider, type IAuthProviderSession, type IAuthProviderUserInfo, InMemoryKeychainAuthTokenStore, type KeyValueStore, type KeychainAccountKey, type KeychainAuthTokenStore, type KeychainTokenEnvelope, type KeychainTokenMetadata, KeychainUnavailableError, MicrosoftAuthProvider, MicrosoftProviderNotHostedError, type OrgMembershipFetcher, ProviderRegistry, buildGitHubLocalEmail, isGitHubLocalEmail, resolvePrimaryEmail };