@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.
- package/dist/auth.d.mts +592 -0
- package/dist/auth.d.ts +592 -0
- package/dist/auth.js +886 -0
- package/dist/auth.js.map +1 -0
- package/dist/auth.mjs +848 -0
- package/dist/auth.mjs.map +1 -0
- package/dist/device.d.mts +456 -0
- package/dist/device.d.ts +456 -0
- package/dist/device.js +696 -0
- package/dist/device.js.map +1 -0
- package/dist/device.mjs +647 -0
- package/dist/device.mjs.map +1 -0
- package/dist/index-CrNC-aao.d.ts +277 -0
- package/dist/index-Dmyy4urr.d.mts +277 -0
- package/dist/index.d.mts +1376 -27
- package/dist/index.d.ts +1376 -27
- package/dist/index.js +4707 -414
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +4586 -414
- package/dist/index.mjs.map +1 -0
- package/dist/skill-linker.d.mts +188 -0
- package/dist/skill-linker.d.ts +188 -0
- package/dist/skill-linker.js +374 -0
- package/dist/skill-linker.js.map +1 -0
- package/dist/skill-linker.mjs +330 -0
- package/dist/skill-linker.mjs.map +1 -0
- package/dist/skill-store.d.mts +35 -0
- package/dist/skill-store.d.ts +35 -0
- package/dist/skill-store.js +284 -0
- package/dist/skill-store.js.map +1 -0
- package/dist/skill-store.mjs +247 -0
- package/dist/skill-store.mjs.map +1 -0
- package/dist/submit.d.mts +3 -0
- package/dist/submit.d.ts +3 -0
- package/dist/submit.js +166 -0
- package/dist/submit.js.map +1 -0
- package/dist/submit.mjs +130 -0
- package/dist/submit.mjs.map +1 -0
- package/dist/toolbox.d.mts +240 -0
- package/dist/toolbox.d.ts +240 -0
- package/dist/toolbox.js +530 -0
- package/dist/toolbox.js.map +1 -0
- package/dist/toolbox.mjs +482 -0
- package/dist/toolbox.mjs.map +1 -0
- package/dist/types-B9gk3dXH.d.mts +62 -0
- package/dist/types-B9gk3dXH.d.ts +62 -0
- package/package.json +51 -5
package/dist/auth.d.mts
ADDED
|
@@ -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 };
|