@serviceme/devtools-core 2.0.0 → 2.0.2

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