@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
package/dist/auth.js CHANGED
@@ -1,1029 +1,14 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/auth/index.ts
21
- var auth_exports = {};
22
- __export(auth_exports, {
23
- AccessControl: () => AccessControl,
24
- AuthCore: () => AuthCore,
25
- AuthStateManager: () => AuthStateManager,
26
- GitHubAuthProvider: () => GitHubAuthProvider,
27
- InMemoryKeychainAuthTokenStore: () => InMemoryKeychainAuthTokenStore,
28
- KeychainUnavailableError: () => KeychainUnavailableError,
29
- MicrosoftAuthProvider: () => MicrosoftAuthProvider,
30
- MicrosoftProviderNotHostedError: () => MicrosoftProviderNotHostedError,
31
- ProviderRegistry: () => ProviderRegistry,
32
- buildGitHubLocalEmail: () => buildGitHubLocalEmail,
33
- isGitHubLocalEmail: () => isGitHubLocalEmail,
34
- resolvePrimaryEmail: () => resolvePrimaryEmail
35
- });
36
- module.exports = __toCommonJS(auth_exports);
37
-
38
- // src/auth/AccessControl.ts
39
- var DEFAULT_CACHE_TTL_MS = 10 * 60 * 1e3;
40
- var DEFAULT_PERSISTENT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
41
- var KEY_GRANTED_USERS = "msDevTools.accessControl.grantedUsers";
42
- var KEY_MEMBERSHIP_CACHE = "msDevTools.accessControl.membershipCache";
43
- var KEY_SERVER_IN_ORG = "msDevTools.accessControl.serverInOrg";
44
- var AccessControl = class {
45
- constructor(kv, options) {
46
- this.kv = kv;
47
- this.memCache = /* @__PURE__ */ new Map();
48
- this.serverInOrgMem = /* @__PURE__ */ new Map();
49
- this.listeners = /* @__PURE__ */ new Set();
50
- this.opts = {
51
- org: options.org,
52
- tokenFetcher: options.tokenFetcher,
53
- orgFetcher: options.orgFetcher,
54
- microsoftEmailDomains: options.microsoftEmailDomains ?? [],
55
- cacheTtlMs: options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS,
56
- persistentCacheTtlMs: options.persistentCacheTtlMs ?? DEFAULT_PERSISTENT_CACHE_TTL_MS,
57
- now: options.now ?? (() => Date.now())
58
- };
59
- const persistedServerInOrg = this.kv.get(KEY_SERVER_IN_ORG);
60
- if (persistedServerInOrg) {
61
- for (const [key, value] of Object.entries(persistedServerInOrg)) {
62
- this.serverInOrgMem.set(key, value);
63
- }
64
- }
65
- }
66
- /** Subscribe to inOrg changes (used by extension to refresh access-gated UI). */
67
- onDidChange(listener) {
68
- this.listeners.add(listener);
69
- return () => this.listeners.delete(listener);
70
- }
71
- /** Full check: requires authentication + org membership / admin grant. */
72
- async checkAccess(user) {
73
- if (!user) {
74
- return { allowed: false, reason: "not_authenticated" };
75
- }
76
- if (user.provider === "microsoft" && user.login) {
77
- const inOrg = this.serverInOrgMem.get(user.login.toLowerCase());
78
- if (inOrg === true) return { allowed: true };
79
- if (inOrg === false) {
80
- return { allowed: false, reason: "not_member", username: user.login };
81
- }
82
- }
83
- const githubAccount = user.provider === "github" ? user : null;
84
- if (!githubAccount && user.provider === "microsoft" && user.login) {
85
- const [, domain = ""] = user.login.split("@");
86
- const isDomainMember = domain.length > 0 && this.opts.microsoftEmailDomains.includes(domain.toLowerCase());
87
- if (isDomainMember) return { allowed: true };
88
- return { allowed: false, reason: "not_member", username: user.login };
89
- }
90
- if (!githubAccount) {
91
- return { allowed: true };
92
- }
93
- const accessUsername = githubAccount.login ?? user.login ?? "";
94
- if (accessUsername.length === 0) {
95
- return { allowed: false, reason: "not_member", username: "" };
96
- }
97
- const granted = this.getGrantedUsers();
98
- if (granted.includes(accessUsername.toLowerCase())) {
99
- return { allowed: true };
100
- }
101
- const isMember = await this.checkOrgMembership(accessUsername);
102
- return isMember ? { allowed: true } : { allowed: false, reason: "not_member", username: accessUsername };
103
- }
104
- /** Admin: grant access to a GitHub username (bypasses org check). */
105
- async grantAccess(username) {
106
- const list = this.getGrantedUsers();
107
- const key = username.toLowerCase();
108
- if (!list.includes(key)) {
109
- list.push(key);
110
- await this.kv.update(KEY_GRANTED_USERS, list);
111
- }
112
- }
113
- /** Admin: revoke a previously granted access. */
114
- async revokeAccess(username) {
115
- const updated = this.getGrantedUsers().filter((u) => u !== username.toLowerCase());
116
- await this.kv.update(KEY_GRANTED_USERS, updated);
117
- }
118
- /** Snapshot of all admin-granted usernames (lowercased). */
119
- getGrantedUsers() {
120
- return this.kv.get(KEY_GRANTED_USERS) ?? [];
121
- }
122
- /** Cached membership lookup with both in-memory + persistent layers. */
123
- async checkOrgMembership(username) {
124
- const now = this.opts.now();
125
- const inMem = this.memCache.get(username);
126
- if (inMem && now - inMem.at < this.opts.cacheTtlMs) {
127
- return inMem.isMember;
128
- }
129
- const persistent = this.kv.get(KEY_MEMBERSHIP_CACHE) ?? {};
130
- const persistentEntry = persistent[username];
131
- if (persistentEntry && now - persistentEntry.at < this.opts.persistentCacheTtlMs) {
132
- this.memCache.set(username, persistentEntry);
133
- return persistentEntry.isMember;
134
- }
135
- const token = await this.opts.tokenFetcher("github");
136
- if (!token) return false;
137
- try {
138
- const result = await this.opts.orgFetcher(token, this.opts.org);
139
- const decision = this.resolveDecision(username, result);
140
- if (typeof decision === "boolean") {
141
- this.updateCache(username, decision);
142
- return decision;
143
- }
144
- return true;
145
- } catch {
146
- return true;
147
- }
148
- }
149
- /** Server-confirmed inOrg status (set by device claim flow). */
150
- setServerInOrg(username, inOrg) {
151
- const key = username.toLowerCase();
152
- this.serverInOrgMem.set(key, inOrg);
153
- const persisted = this.kv.get(KEY_SERVER_IN_ORG) ?? {};
154
- persisted[key] = inOrg;
155
- void this.kv.update(KEY_SERVER_IN_ORG, persisted);
156
- this.notifyListeners();
157
- }
158
- /** Inspect server-confirmed inOrg for a username; returns undefined when unknown. */
159
- getServerInOrg(username) {
160
- return this.serverInOrgMem.get(username.toLowerCase());
161
- }
162
- resolveDecision(_username, membership) {
163
- if (membership.status === "active" || membership.status === "pending") return true;
164
- if (membership.status === "not_member") return false;
165
- return void 0;
166
- }
167
- updateCache(username, isMember) {
168
- const at = this.opts.now();
169
- this.memCache.set(username, { isMember, at });
170
- const persistent = this.kv.get(KEY_MEMBERSHIP_CACHE) ?? {};
171
- persistent[username] = { isMember, at };
172
- void this.kv.update(KEY_MEMBERSHIP_CACHE, persistent);
173
- }
174
- notifyListeners() {
175
- for (const listener of this.listeners) {
176
- try {
177
- listener();
178
- } catch {
179
- }
180
- }
181
- }
182
- };
183
-
184
- // src/logger.ts
185
- var noopLogger = {
186
- debug() {
187
- },
188
- info() {
189
- },
190
- warn() {
191
- },
192
- error() {
193
- }
194
- };
195
-
196
- // src/auth/AuthStateManager.ts
197
- var import_node_events = require("events");
198
- var AuthStateManager = class {
199
- constructor(opts = {}) {
200
- this.emitter = new import_node_events.EventEmitter();
201
- this.accounts = [];
202
- this.activeProvider = null;
203
- this.activeAccountId = null;
204
- this.lastError = null;
205
- if (opts.maxListeners !== void 0) {
206
- this.emitter.setMaxListeners(opts.maxListeners);
207
- }
208
- }
209
- /** Subscribe to state-change events. Returns a disposer. */
210
- onDidChange(listener) {
211
- this.emitter.on("change", listener);
212
- return () => this.emitter.off("change", listener);
213
- }
214
- /** Snapshot of all known accounts (immutable copy). */
215
- listAccounts() {
216
- return [...this.accounts];
217
- }
218
- /** Find an account by `(provider, accountId)` tuple; returns `null` when absent. */
219
- findAccount(provider, accountId) {
220
- return this.accounts.find((a) => a.provider === provider && a.id === accountId) ?? null;
221
- }
222
- /** First account for the requested provider — used for "switch to GitHub" UX. */
223
- findFirstForProvider(provider) {
224
- return this.accounts.find((a) => a.provider === provider) ?? null;
225
- }
226
- /** Insert or update an account entry. New accounts land at the head of the list. */
227
- upsertAccount(meta) {
228
- const idx = this.accounts.findIndex((a) => a.provider === meta.provider && a.id === meta.id);
229
- if (idx >= 0) {
230
- this.accounts[idx] = meta;
231
- } else {
232
- this.accounts.unshift(meta);
233
- }
234
- this.fire();
235
- }
236
- /** Remove an account entry. Returns `true` when an entry was removed. */
237
- removeAccount(provider, accountId) {
238
- const before = this.accounts.length;
239
- this.accounts = this.accounts.filter((a) => !(a.provider === provider && a.id === accountId));
240
- const removed = this.accounts.length !== before;
241
- if (removed && this.activeProvider === provider && this.activeAccountId === accountId) {
242
- const stillHasSameProvider = this.accounts.some((a) => a.provider === provider);
243
- if (!stillHasSameProvider) {
244
- this.activeProvider = null;
245
- this.activeAccountId = null;
246
- } else {
247
- const next = this.accounts.find((a) => a.provider === provider);
248
- if (next) {
249
- this.activeAccountId = next.id;
250
- }
251
- }
252
- }
253
- if (removed) this.fire();
254
- return removed;
255
- }
256
- /** Set the active provider. When `accountId` is omitted, picks the first account for that provider. */
257
- setActive(provider, accountId) {
258
- const match = accountId !== void 0 ? this.accounts.find((a) => a.provider === provider && a.id === accountId) : this.accounts.find((a) => a.provider === provider);
259
- if (!match) return false;
260
- this.activeProvider = provider;
261
- this.activeAccountId = match.id;
262
- this.fire();
263
- return true;
264
- }
265
- /** Currently active provider — `null` when no session is active. */
266
- getActiveProvider() {
267
- return this.activeProvider;
268
- }
269
- /** Currently active account metadata — `null` when none. */
270
- getActiveAccount() {
271
- if (!this.activeProvider || !this.activeAccountId) return null;
272
- return this.accounts.find(
273
- (a) => a.provider === this.activeProvider && a.id === this.activeAccountId
274
- ) ?? null;
275
- }
276
- /** True when at least one provider has an account entry. */
277
- hasAnySession() {
278
- return this.accounts.length > 0 && this.activeProvider !== null;
279
- }
280
- /** Snapshot of the state for `auth.status` and bridge serialization. */
281
- getStatus() {
282
- const grouped = {};
283
- for (const account of this.accounts) {
284
- grouped[account.provider] = account;
285
- }
286
- return {
287
- activeProvider: this.activeProvider,
288
- accounts: grouped,
289
- hasAnySession: this.hasAnySession(),
290
- lastError: this.lastError ?? void 0
291
- };
292
- }
293
- /** Record a non-fatal error from the last login/refresh attempt. */
294
- recordError(message) {
295
- this.lastError = message;
296
- this.fire();
297
- }
298
- /** Clear the recorded error (e.g. after a successful login). */
299
- clearError() {
300
- if (this.lastError === null) return;
301
- this.lastError = null;
302
- this.fire();
303
- }
304
- /** Drop every account — used by `auth.logout` with no provider. */
305
- clearAll() {
306
- this.accounts = [];
307
- this.activeProvider = null;
308
- this.activeAccountId = null;
309
- this.lastError = null;
310
- this.fire();
311
- }
312
- fire() {
313
- this.emitter.emit("change");
314
- }
315
- };
316
-
317
- // src/auth/ProviderRegistry.ts
318
- var ProviderRegistry = class {
319
- constructor() {
320
- this.providers = /* @__PURE__ */ new Map();
321
- }
322
- /** Register or replace a provider implementation. */
323
- register(provider) {
324
- this.providers.set(provider.providerId, provider);
325
- }
326
- /** Look up a registered provider by id; throws when missing. */
327
- get(providerId) {
328
- const p = this.providers.get(providerId);
329
- if (!p) {
330
- throw new Error(`Auth provider not registered: ${providerId}`);
331
- }
332
- return p;
333
- }
334
- /** Non-throwing lookup; returns `undefined` when the provider is unknown. */
335
- tryGet(providerId) {
336
- return this.providers.get(providerId);
337
- }
338
- /** Return all registered provider ids — used by `auth.status` and bridge capability hints. */
339
- list() {
340
- return [...this.providers.keys()];
341
- }
342
- /** True when a provider has been registered for this id. */
343
- has(providerId) {
344
- return this.providers.has(providerId);
345
- }
346
- };
347
-
348
- // src/auth/AuthCore.ts
349
- var AuthCore = class {
350
- constructor(opts) {
351
- this.registry = new ProviderRegistry();
352
- for (const provider of opts.providers) {
353
- this.registry.register(provider);
354
- }
355
- this.state = opts.stateManager ?? new AuthStateManager();
356
- this.tokenStore = opts.tokenStore;
357
- this.accessControl = opts.accessControl;
358
- this.logger = opts.logger ?? noopLogger;
359
- }
360
- /** Snapshot of every account, the active provider, and the last error. */
361
- status() {
362
- return this.state.getStatus();
363
- }
364
- /** List accounts (immutable copy). */
365
- listAccounts() {
366
- return this.state.listAccounts();
367
- }
368
- /**
369
- * Drive the device-flow login for `provider`.
370
- *
371
- * Sequence:
372
- * 1. Ask the provider for the user code + verification URL.
373
- * 2. Surface the code to the user (via `ui`).
374
- * 3. Poll until the user authorizes (or `shouldContinue` aborts).
375
- * 4. Persist the token via `KeychainAuthTokenStore.set()`.
376
- * 5. Insert the resulting `AuthAccountMeta` into state and mark active.
377
- */
378
- async login(provider, ui, shouldContinue) {
379
- const providerImpl = this.registry.get(provider);
380
- this.logger.info("[AuthCore] Starting device-flow login", { provider });
381
- try {
382
- const initial = await providerImpl.requestDeviceFlow();
383
- await ui(initial);
384
- if (!initial.deviceCode) {
385
- this.logger.error("[AuthCore] Provider did not return deviceCode", provider);
386
- throw new Error("Auth provider did not return deviceCode for device flow completion");
387
- }
388
- const session = await providerImpl.completeDeviceFlow(
389
- initial.deviceCode,
390
- shouldContinue,
391
- initial.pollIntervalMs
392
- );
393
- const result = await this.persistSession(providerImpl, session);
394
- this.logger.info("[AuthCore] Device-flow login completed", { provider });
395
- return result;
396
- } catch (err) {
397
- const message = err instanceof Error ? err.message : String(err);
398
- this.logger.error("[AuthCore] Device-flow login failed", message, {
399
- provider
400
- });
401
- this.state.recordError(message);
402
- throw err;
403
- }
404
- }
405
- /**
406
- * Complete the device-flow handshake given a pre-fetched user code.
407
- * Useful when the caller (Phase 5.3 CLI) wants to fetch the code,
408
- * print the URL, and then poll on a subsequent invocation.
409
- */
410
- async completeLogin(provider, deviceCode, shouldContinue) {
411
- const providerImpl = this.registry.get(provider);
412
- try {
413
- const session = await providerImpl.completeDeviceFlow(deviceCode, shouldContinue);
414
- return await this.persistSession(providerImpl, session);
415
- } catch (err) {
416
- this.state.recordError(err instanceof Error ? err.message : String(err));
417
- throw err;
418
- }
419
- }
420
- /** Resolve the provider + token for the active session; returns null when no session is active. */
421
- async resolveActiveToken() {
422
- const provider = this.state.getActiveProvider();
423
- if (!provider) return null;
424
- const account = this.state.getActiveAccount();
425
- if (!account) return null;
426
- const envelope = await this.tokenStore.get({
427
- provider,
428
- accountId: account.id
429
- });
430
- if (!envelope) return null;
431
- return { provider, account, token: envelope.token };
432
- }
433
- /**
434
- * Logout: removes the token bytes from the keychain + drops the
435
- * account entry from state. When `provider` is omitted, clears
436
- * every account.
437
- */
438
- async logout(provider) {
439
- this.logger.info("[AuthCore] Logout requested", {
440
- provider: provider ?? "all"
441
- });
442
- if (!provider) {
443
- for (const account of this.state.listAccounts()) {
444
- await this.tokenStore.delete({
445
- provider: account.provider,
446
- accountId: account.id
447
- });
448
- this.state.removeAccount(account.provider, account.id);
449
- }
450
- this.state.clearAll();
451
- return { provider: null, success: true };
452
- }
453
- const accountsForProvider = this.state.listAccounts().filter((a) => a.provider === provider);
454
- let removed = false;
455
- for (const account of accountsForProvider) {
456
- await this.tokenStore.delete({ provider, accountId: account.id });
457
- const didRemove = this.state.removeAccount(provider, account.id);
458
- removed = removed || didRemove;
459
- }
460
- if (this.state.getActiveProvider() === provider) {
461
- const firstRemaining = this.state.listAccounts()[0];
462
- if (firstRemaining) {
463
- this.state.setActive(firstRemaining.provider);
464
- }
465
- }
466
- return { provider, success: removed };
467
- }
468
- /** Minimal identity for `auth.whoami`. */
469
- async whoami() {
470
- const provider = this.state.getActiveProvider();
471
- const account = this.state.getActiveAccount();
472
- if (!provider || !account) {
473
- return { provider: null };
474
- }
475
- return {
476
- provider,
477
- login: account.login,
478
- name: account.displayName,
479
- avatarUrl: account.avatarUrl,
480
- email: account.email
481
- };
482
- }
483
- /** Switch the active provider. Returns the resulting active account. */
484
- switchProvider(provider, accountId) {
485
- const ok = this.state.setActive(provider, accountId);
486
- const account = this.state.getActiveAccount();
487
- return {
488
- activeProvider: ok ? provider : this.state.getActiveProvider(),
489
- account
490
- };
491
- }
492
- /** Run the access-control check against the active account (optional, requires AccessControl). */
493
- async checkAccess() {
494
- if (!this.accessControl) return null;
495
- const account = this.state.getActiveAccount();
496
- return this.accessControl.checkAccess(account);
497
- }
498
- /** Expose the state manager for test inspection (not for mutation). */
499
- getStateManager() {
500
- return this.state;
501
- }
502
- /** Expose the provider registry for test inspection. */
503
- getProviderRegistry() {
504
- return this.registry;
505
- }
506
- /** Expose the access control (or undefined when not configured). */
507
- getAccessControl() {
508
- return this.accessControl;
509
- }
510
- async persistSession(providerImpl, session) {
511
- const expiresAt = session.expiresIn ? Date.now() + session.expiresIn * 1e3 : null;
512
- const meta = await providerImpl.fetchAccountMeta(session.token);
513
- const accountMeta = {
514
- id: meta.id,
515
- provider: meta.provider,
516
- displayName: meta.displayName,
517
- login: meta.login,
518
- email: meta.email,
519
- avatarUrl: meta.avatarUrl,
520
- expiresAt: meta.expiresAt ?? expiresAt
521
- };
522
- this.logger.debug("[AuthCore] Persisting session", {
523
- provider: meta.provider,
524
- accountId: meta.id
525
- });
526
- await this.tokenStore.set({ provider: meta.provider, accountId: meta.id }, session.token, {
527
- expiresAt
528
- });
529
- this.state.upsertAccount(accountMeta);
530
- this.state.setActive(meta.provider, meta.id);
531
- this.state.clearError();
532
- return {
533
- provider: meta.provider,
534
- message: `Logged in as ${meta.login ?? meta.id}`
535
- };
536
- }
537
- };
538
-
539
- // src/auth/KeychainAuthTokenStore.ts
540
- var KeychainUnavailableError = class extends Error {
541
- constructor(message, cause) {
542
- super(message, cause !== void 0 ? { cause } : void 0);
543
- this.name = "KeychainUnavailableError";
544
- }
545
- };
546
- var InMemoryKeychainAuthTokenStore = class {
547
- constructor() {
548
- this.entries = /* @__PURE__ */ new Map();
549
- }
550
- compositeKey(key) {
551
- return `${key.provider}:${key.accountId}`;
552
- }
553
- async set(key, token, opts) {
554
- this.entries.set(this.compositeKey(key), {
555
- token,
556
- expiresAt: opts?.expiresAt ?? null,
557
- storedAt: Date.now()
558
- });
559
- }
560
- async get(key) {
561
- const entry = this.entries.get(this.compositeKey(key));
562
- if (!entry) return null;
563
- return {
564
- token: entry.token,
565
- metadata: {
566
- provider: key.provider,
567
- accountId: key.accountId,
568
- expiresAt: entry.expiresAt,
569
- storedAt: entry.storedAt
570
- }
571
- };
572
- }
573
- async delete(key) {
574
- this.entries.delete(this.compositeKey(key));
575
- }
576
- async list() {
577
- const out = [];
578
- for (const composite of this.entries.keys()) {
579
- const sepIdx = composite.indexOf(":");
580
- if (sepIdx <= 0) continue;
581
- out.push({
582
- provider: composite.slice(0, sepIdx),
583
- accountId: composite.slice(sepIdx + 1)
584
- });
585
- }
586
- return out;
587
- }
588
- async isAvailable() {
589
- return true;
590
- }
591
- };
592
-
593
- // src/auth/utils/githubUserEmail.ts
594
- function isGitHubLocalEmail(email) {
595
- return typeof email === "string" && email.trim().toLowerCase().endsWith("@github.local");
596
- }
597
- function buildGitHubLocalEmail(login) {
598
- return `${login}@github.local`;
599
- }
600
- function resolvePrimaryEmail(login, email) {
601
- const normalizedEmail = email?.trim();
602
- return normalizedEmail || buildGitHubLocalEmail(login);
603
- }
604
-
605
- // src/auth/providers/GitHubAuthProvider.ts
606
- var DEFAULT_DEVICE_CODE_URL = "https://github.com/login/device/code";
607
- var DEFAULT_TOKEN_URL = "https://github.com/login/oauth/access_token";
608
- var DEFAULT_USER_URL = "https://api.github.com/user";
609
- var DEFAULT_SCOPE = "read:user user:email";
610
- var DEVICE_CODE_MAX_ATTEMPTS = 3;
611
- var DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS = 500;
612
- function isTransientNetworkError(error) {
613
- return error instanceof TypeError;
614
- }
615
- var GitHubAuthProvider = class {
616
- constructor(config) {
617
- this.providerId = "github";
618
- if (!config.clientId || config.clientId.length === 0) {
619
- throw new Error("GitHubAuthProvider requires a non-empty `clientId`");
620
- }
621
- this.cfg = {
622
- clientId: config.clientId,
623
- deviceCodeUrl: config.deviceCodeUrl ?? DEFAULT_DEVICE_CODE_URL,
624
- tokenUrl: config.tokenUrl ?? DEFAULT_TOKEN_URL,
625
- userUrl: config.userUrl ?? DEFAULT_USER_URL,
626
- scope: config.scope ?? DEFAULT_SCOPE,
627
- minPollIntervalMs: config.minPollIntervalMs ?? 1e3,
628
- maxPollIntervalMs: config.maxPollIntervalMs ?? 6e4,
629
- maxWaitMs: config.maxWaitMs,
630
- fetchImpl: config.fetchImpl ?? fetch,
631
- deviceCodeRetryBaseDelayMs: config.deviceCodeRetryBaseDelayMs ?? DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS
632
- };
633
- this.logger = config.logger ?? noopLogger;
634
- this.sleepImpl = config.sleepImpl ?? sleep;
635
- }
636
- async requestDeviceFlow(opts) {
637
- const body = JSON.stringify({
638
- client_id: this.cfg.clientId,
639
- scope: opts?.scope ?? this.cfg.scope
640
- });
641
- let lastNetworkError;
642
- for (let attempt = 1; attempt <= DEVICE_CODE_MAX_ATTEMPTS; attempt++) {
643
- this.logger.debug("[GitHubAuthProvider] Requesting device code", {
644
- attempt,
645
- maxAttempts: DEVICE_CODE_MAX_ATTEMPTS
646
- });
647
- let resp;
648
- try {
649
- resp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {
650
- method: "POST",
651
- headers: {
652
- Accept: "application/json",
653
- "Content-Type": "application/json",
654
- "User-Agent": "serviceme-core"
655
- },
656
- body
657
- });
658
- } catch (error) {
659
- if (!isTransientNetworkError(error) || attempt === DEVICE_CODE_MAX_ATTEMPTS) {
660
- this.logger.error(
661
- "[GitHubAuthProvider] Device-code request failed (non-retryable)",
662
- error instanceof Error ? error.message : String(error),
663
- { attempt }
664
- );
665
- throw error;
666
- }
667
- const delayMs = this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1);
668
- this.logger.warn(
669
- "[GitHubAuthProvider] Transient network error requesting device code, retrying",
670
- {
671
- attempt,
672
- delayMs,
673
- error: error instanceof Error ? error.message : String(error)
674
- }
675
- );
676
- lastNetworkError = error;
677
- await this.sleepImpl(delayMs);
678
- continue;
679
- }
680
- if (!resp.ok) {
681
- this.logger.error(
682
- "[GitHubAuthProvider] Device-code request rejected by GitHub",
683
- `HTTP ${resp.status}`
684
- );
685
- throw new Error(`GitHub device-code request failed: HTTP ${resp.status}`);
686
- }
687
- const data = await resp.json();
688
- if (!data.device_code || !data.user_code || !data.verification_uri) {
689
- this.logger.error(
690
- "[GitHubAuthProvider] Device-code response missing required fields",
691
- JSON.stringify(Object.keys(data))
692
- );
693
- throw new Error("GitHub device-code response missing required fields");
694
- }
695
- this.logger.info("[GitHubAuthProvider] Device code obtained", {
696
- userCode: data.user_code,
697
- verificationUri: data.verification_uri,
698
- expiresInSec: data.expires_in,
699
- pollIntervalSec: data.interval
700
- });
701
- return {
702
- provider: this.providerId,
703
- deviceCode: data.device_code,
704
- userCode: data.user_code,
705
- verificationUrl: data.verification_uri,
706
- expiresAt: Date.now() + data.expires_in * 1e3,
707
- pollIntervalMs: data.interval * 1e3,
708
- message: `Open ${data.verification_uri} and enter ${data.user_code}`
709
- };
710
- }
711
- throw lastNetworkError ?? new Error("GitHub device-code request failed");
712
- }
713
- async completeDeviceFlow(deviceCode, shouldContinue, initialPollIntervalMs) {
714
- const start = Date.now();
715
- let pollIntervalMs = Math.max(this.cfg.minPollIntervalMs, initialPollIntervalMs ?? 0);
716
- let consecutiveSlowDown = 0;
717
- let pollCount = 0;
718
- this.logger.info("[GitHubAuthProvider] Starting device-flow polling", {
719
- initialPollIntervalMs: pollIntervalMs,
720
- maxWaitMs: this.cfg.maxWaitMs
721
- });
722
- while (true) {
723
- if (shouldContinue && !shouldContinue()) {
724
- this.logger.info("[GitHubAuthProvider] Device flow cancelled by caller", {
725
- elapsedMs: Date.now() - start,
726
- pollCount
727
- });
728
- throw new Error("GitHub device flow cancelled by caller");
729
- }
730
- const elapsed = Date.now() - start;
731
- if (this.cfg.maxWaitMs !== void 0 && elapsed > this.cfg.maxWaitMs) {
732
- this.logger.warn("[GitHubAuthProvider] Device flow exceeded max wait time", {
733
- elapsedMs: elapsed,
734
- maxWaitMs: this.cfg.maxWaitMs,
735
- pollCount
736
- });
737
- throw new Error("GitHub device flow exceeded max wait time");
738
- }
739
- await this.sleepImpl(pollIntervalMs);
740
- if (shouldContinue && !shouldContinue()) {
741
- this.logger.info("[GitHubAuthProvider] Device flow cancelled by caller", {
742
- elapsedMs: Date.now() - start,
743
- pollCount
744
- });
745
- throw new Error("GitHub device flow cancelled by caller");
746
- }
747
- pollCount++;
748
- this.logger.debug("[GitHubAuthProvider] Polling for authorization", {
749
- pollCount,
750
- elapsedMs: Date.now() - start,
751
- pollIntervalMs
752
- });
753
- let resp;
754
- try {
755
- resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {
756
- method: "POST",
757
- headers: {
758
- Accept: "application/json",
759
- "Content-Type": "application/json",
760
- "User-Agent": "serviceme-core"
761
- },
762
- body: JSON.stringify({
763
- client_id: this.cfg.clientId,
764
- device_code: deviceCode,
765
- grant_type: "urn:ietf:params:oauth:grant-type:device_code"
766
- })
767
- });
768
- } catch (error) {
769
- if (!isTransientNetworkError(error)) {
770
- this.logger.error(
771
- "[GitHubAuthProvider] Non-retryable error while polling token endpoint",
772
- error instanceof Error ? error.message : String(error),
773
- { pollCount }
774
- );
775
- throw error;
776
- }
777
- const nextPollIntervalMs = Math.min(
778
- Math.round(pollIntervalMs * 1.5),
779
- this.cfg.maxPollIntervalMs
780
- );
781
- this.logger.warn(
782
- "[GitHubAuthProvider] Transient network error while polling, backing off",
783
- {
784
- pollCount,
785
- error: error instanceof Error ? error.message : String(error),
786
- previousPollIntervalMs: pollIntervalMs,
787
- nextPollIntervalMs
788
- }
789
- );
790
- pollIntervalMs = nextPollIntervalMs;
791
- continue;
792
- }
793
- if (!resp.ok) {
794
- const nextPollIntervalMs = Math.min(
795
- Math.round(pollIntervalMs * 1.5),
796
- this.cfg.maxPollIntervalMs
797
- );
798
- this.logger.warn(
799
- "[GitHubAuthProvider] Token endpoint returned non-OK status, backing off",
800
- {
801
- pollCount,
802
- status: resp.status,
803
- previousPollIntervalMs: pollIntervalMs,
804
- nextPollIntervalMs
805
- }
806
- );
807
- pollIntervalMs = nextPollIntervalMs;
808
- continue;
809
- }
810
- const data = await resp.json();
811
- if (data.access_token) {
812
- this.logger.info("[GitHubAuthProvider] Authorization granted, fetching user profile", {
813
- pollCount,
814
- elapsedMs: Date.now() - start
815
- });
816
- const rawUser = await this.fetchGitHubUser(data.access_token);
817
- const email = await this.resolveEmail(data.access_token, rawUser);
818
- this.logger.info("[GitHubAuthProvider] Device-flow login completed", {
819
- pollCount,
820
- elapsedMs: Date.now() - start,
821
- login: rawUser.login
822
- });
823
- return {
824
- token: data.access_token,
825
- refreshToken: data.refresh_token,
826
- expiresIn: data.expires_in,
827
- user: {
828
- id: String(rawUser.id),
829
- login: rawUser.login,
830
- name: rawUser.name ?? void 0,
831
- email: email ?? null,
832
- avatarUrl: rawUser.avatar_url ?? void 0
833
- }
834
- };
835
- }
836
- if (data.error === "authorization_pending") {
837
- this.logger.debug("[GitHubAuthProvider] Authorization still pending", {
838
- pollCount,
839
- elapsedMs: Date.now() - start
840
- });
841
- continue;
842
- }
843
- if (data.error === "slow_down") {
844
- consecutiveSlowDown++;
845
- const nextPollIntervalMs = Math.min(pollIntervalMs + 5e3, this.cfg.maxPollIntervalMs);
846
- this.logger.warn("[GitHubAuthProvider] GitHub requested slower polling (slow_down)", {
847
- pollCount,
848
- consecutiveSlowDown,
849
- previousPollIntervalMs: pollIntervalMs,
850
- nextPollIntervalMs
851
- });
852
- pollIntervalMs = nextPollIntervalMs;
853
- continue;
854
- }
855
- if (data.error === "access_denied") {
856
- this.logger.warn("[GitHubAuthProvider] User denied authorization", {
857
- pollCount
858
- });
859
- throw new Error("User denied authorization");
860
- }
861
- if (data.error === "expired_token") {
862
- this.logger.warn(
863
- "[GitHubAuthProvider] Device code expired before authorization completed",
864
- { pollCount, elapsedMs: Date.now() - start }
865
- );
866
- throw new Error("GitHub device code expired \u2014 restart the flow");
867
- }
868
- this.logger.error(
869
- "[GitHubAuthProvider] Unexpected device-flow error from token endpoint",
870
- data.error ?? "unknown",
871
- { pollCount }
872
- );
873
- throw new Error(`GitHub device flow error: ${data.error ?? "unknown"}`);
874
- }
875
- }
876
- async refreshAccessToken(refreshToken) {
877
- if (!refreshToken) {
878
- throw new Error("refreshAccessToken requires a non-empty refresh token");
879
- }
880
- const resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {
881
- method: "POST",
882
- headers: {
883
- Accept: "application/json",
884
- "Content-Type": "application/json",
885
- "User-Agent": "serviceme-core"
886
- },
887
- body: JSON.stringify({
888
- client_id: this.cfg.clientId,
889
- grant_type: "refresh_token",
890
- refresh_token: refreshToken
891
- })
892
- });
893
- if (!resp.ok) {
894
- throw new Error(`GitHub refresh failed: HTTP ${resp.status}`);
895
- }
896
- const data = await resp.json();
897
- if (!data.access_token) {
898
- throw new Error(`GitHub refresh failed: ${data.error ?? "no_access_token"}`);
899
- }
900
- const rawUser = await this.fetchGitHubUser(data.access_token);
901
- const email = await this.resolveEmail(data.access_token, rawUser);
902
- return {
903
- token: data.access_token,
904
- refreshToken: data.refresh_token ?? refreshToken,
905
- expiresIn: data.expires_in,
906
- user: {
907
- id: String(rawUser.id),
908
- login: rawUser.login,
909
- name: rawUser.name ?? void 0,
910
- email: email ?? null,
911
- avatarUrl: rawUser.avatar_url ?? void 0
912
- }
913
- };
914
- }
915
- async validateToken(token) {
916
- try {
917
- const resp = await this.cfg.fetchImpl(this.cfg.userUrl, {
918
- headers: {
919
- Accept: "application/vnd.github+json",
920
- Authorization: `Bearer ${token}`,
921
- "User-Agent": "serviceme-core"
922
- }
923
- });
924
- if (resp.status === 401) return false;
925
- if (!resp.ok) {
926
- throw new Error(`GitHub /user returned HTTP ${resp.status}`);
927
- }
928
- return true;
929
- } catch (err) {
930
- if (err instanceof Error && err.message.includes("401")) return false;
931
- throw err;
932
- }
933
- }
934
- async fetchAccountMeta(token) {
935
- const user = await this.fetchGitHubUser(token);
936
- const email = await this.resolveEmail(token, user);
937
- return {
938
- id: String(user.id),
939
- provider: this.providerId,
940
- displayName: user.name ?? user.login,
941
- login: user.login,
942
- email: email ?? buildGitHubLocalEmail(user.login),
943
- avatarUrl: user.avatar_url ?? void 0,
944
- expiresAt: null
945
- };
946
- }
947
- async fetchGitHubUser(token) {
948
- const resp = await this.cfg.fetchImpl(this.cfg.userUrl, {
949
- headers: {
950
- Accept: "application/vnd.github+json",
951
- Authorization: `Bearer ${token}`,
952
- "User-Agent": "serviceme-core"
953
- }
954
- });
955
- if (!resp.ok) {
956
- throw new Error(`GitHub /user failed: HTTP ${resp.status}`);
957
- }
958
- return await resp.json();
959
- }
960
- /**
961
- * GitHub's `/user` endpoint returns `null` when the user kept their
962
- * email private. The `/user/emails` endpoint reveals verified emails;
963
- * we pick the primary one, falling back to the synthetic
964
- * `<login>@github.local` form so the account is never email-less.
965
- */
966
- async resolveEmail(token, user) {
967
- if (user.email) return user.email;
968
- const emailsResp = await this.cfg.fetchImpl("https://api.github.com/user/emails", {
969
- headers: {
970
- Accept: "application/vnd.github+json",
971
- Authorization: `Bearer ${token}`,
972
- "User-Agent": "serviceme-core"
973
- }
974
- });
975
- if (!emailsResp.ok) {
976
- return null;
977
- }
978
- const emails = await emailsResp.json();
979
- const primary = emails.find((e) => e.primary && e.verified);
980
- return primary?.email ?? null;
981
- }
982
- };
983
- function sleep(ms) {
984
- return new Promise((resolve) => setTimeout(resolve, ms));
985
- }
986
-
987
- // src/auth/providers/MicrosoftAuthProvider.ts
988
- var MicrosoftProviderNotHostedError = class extends Error {
989
- constructor(message = "Microsoft auth requires the VSCode host; CLI does not yet implement MSA device flow") {
990
- super(message);
991
- this.name = "MicrosoftProviderNotHostedError";
992
- }
993
- };
994
- var MicrosoftAuthProvider = class {
995
- constructor() {
996
- this.providerId = "microsoft";
997
- }
998
- async requestDeviceFlow() {
999
- throw new MicrosoftProviderNotHostedError();
1000
- }
1001
- async completeDeviceFlow(_deviceCode, _shouldContinue) {
1002
- throw new MicrosoftProviderNotHostedError();
1003
- }
1004
- async refreshAccessToken(_refreshToken) {
1005
- throw new MicrosoftProviderNotHostedError();
1006
- }
1007
- async validateToken(_token) {
1008
- throw new MicrosoftProviderNotHostedError();
1009
- }
1010
- async fetchAccountMeta(_token) {
1011
- throw new MicrosoftProviderNotHostedError();
1012
- }
1013
- };
1014
- // Annotate the CommonJS export names for ESM import in node:
1015
- 0 && (module.exports = {
1016
- AccessControl,
1017
- AuthCore,
1018
- AuthStateManager,
1019
- GitHubAuthProvider,
1020
- InMemoryKeychainAuthTokenStore,
1021
- KeychainUnavailableError,
1022
- MicrosoftAuthProvider,
1023
- MicrosoftProviderNotHostedError,
1024
- ProviderRegistry,
1025
- buildGitHubLocalEmail,
1026
- isGitHubLocalEmail,
1027
- resolvePrimaryEmail
1028
- });
1029
- //# sourceMappingURL=auth.js.map
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_auth = require("./auth-DXam3c4S.js");
3
+ exports.AccessControl = require_auth.AccessControl;
4
+ exports.AuthCore = require_auth.AuthCore;
5
+ exports.AuthStateManager = require_auth.AuthStateManager;
6
+ exports.GitHubAuthProvider = require_auth.GitHubAuthProvider;
7
+ exports.InMemoryKeychainAuthTokenStore = require_auth.InMemoryKeychainAuthTokenStore;
8
+ exports.KeychainUnavailableError = require_auth.KeychainUnavailableError;
9
+ exports.MicrosoftAuthProvider = require_auth.MicrosoftAuthProvider;
10
+ exports.MicrosoftProviderNotHostedError = require_auth.MicrosoftProviderNotHostedError;
11
+ exports.ProviderRegistry = require_auth.ProviderRegistry;
12
+ exports.buildGitHubLocalEmail = require_auth.buildGitHubLocalEmail;
13
+ exports.isGitHubLocalEmail = require_auth.isGitHubLocalEmail;
14
+ exports.resolvePrimaryEmail = require_auth.resolvePrimaryEmail;