@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
@@ -0,0 +1,212 @@
1
+ import * as path from "node:path";
2
+ import * as os from "node:os";
3
+ //#region src/paths/userHome.ts
4
+ /**
5
+ * SERVICEME user home directory helpers.
6
+ *
7
+ * All paths resolve under {@link getServicemeHome}, which is either the
8
+ * `SERVICEME_HOME` environment variable (when set and non-empty) or
9
+ * `$HOME/.serviceme` on POSIX / `%USERPROFILE%\.serviceme` on Windows.
10
+ *
11
+ * Tests inject `homeDir` and `servicemeHomeEnv` overrides via
12
+ * {@link setUserHomeOverrides} / {@link resetUserHomeOverrides} so they can
13
+ * exercise the path logic without touching the real user environment.
14
+ */
15
+ /** Layout constants — kept in one place so other modules can reuse them. */
16
+ const SERVICEME_DIR_NAME = ".serviceme";
17
+ const REPOS_SUBDIR = "repos";
18
+ const CACHE_SUBDIR = "cache";
19
+ const DRAFTS_SUBDIR = "drafts";
20
+ const SKILL_DRAFTS_SUBDIR = "skills";
21
+ const AGENT_DRAFTS_SUBDIR = "agents";
22
+ const REPOS_CONFIG_FILENAME = "repos.json";
23
+ const WORKSPACES_SUBDIR = "workspaces";
24
+ const WORKSPACE_CONTENT_STATE_FILENAME = "copilot-content-state.json";
25
+ /** rev.20 — r7 BYOM Server Proxy toggle self-state (read by ext + CLI + server). */
26
+ const SERVER_PROXY_GLOBAL_FILENAME = "server-proxy.json";
27
+ /**
28
+ * Repo id regex — used to validate any `repoId` argument before it is joined
29
+ * into a filesystem path. Keeps path traversal attempts out and gives us a
30
+ * predictable on-disk shape.
31
+ */
32
+ const SAFE_REPO_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
33
+ /** Environment variable that overrides the user-home root directory. */
34
+ const SERVICEME_HOME_ENV = "SERVICEME_HOME";
35
+ let activeOverrides = {};
36
+ function setUserHomeOverrides(overrides) {
37
+ activeOverrides = { ...overrides };
38
+ }
39
+ function resetUserHomeOverrides() {
40
+ activeOverrides = {};
41
+ }
42
+ function resolveHomeDir() {
43
+ const injected = activeOverrides.homeDir;
44
+ if (injected !== void 0) return injected;
45
+ return os.homedir();
46
+ }
47
+ function resolveServicemeHomeEnv() {
48
+ const injected = activeOverrides.servicemeHomeEnv;
49
+ if (injected !== void 0) return injected.length > 0 ? injected : void 0;
50
+ const envValue = process.env[SERVICEME_HOME_ENV];
51
+ return envValue && envValue.length > 0 ? envValue : void 0;
52
+ }
53
+ function resolvePlatform() {
54
+ return activeOverrides.platform ?? process.platform;
55
+ }
56
+ function assertSafeRepoId(repoId) {
57
+ if (typeof repoId !== "string" || repoId.length === 0 || !SAFE_REPO_ID_PATTERN.test(repoId)) throw new Error(`Invalid repo id: ${JSON.stringify(repoId)}. Must match ${SAFE_REPO_ID_PATTERN} (alphanumeric start, then alphanumerics / underscores / hyphens, ≤ 64 chars).`);
58
+ return repoId;
59
+ }
60
+ /**
61
+ * The raw OS home directory (`os.homedir()`), honoring test overrides
62
+ * ({@link setUserHomeOverrides}). Exported for callers that need a
63
+ * home-relative path *outside* of `~/.serviceme` — e.g. the
64
+ * `~/.agents/{skills,agents}` convention used by `SkillLinker` for
65
+ * user-scope links.
66
+ */
67
+ function getHomeDir() {
68
+ return resolveHomeDir();
69
+ }
70
+ /**
71
+ * Root directory for all SERVICEME user-level state. Resolves to
72
+ * `${SERVICEME_HOME}` when that env var is set, otherwise `${HOME}/.serviceme`
73
+ * (POSIX) or `%USERPROFILE%\.serviceme` (Windows via `os.homedir`).
74
+ */
75
+ function getServicemeHome() {
76
+ const override = resolveServicemeHomeEnv();
77
+ if (override !== void 0) return path.resolve(override);
78
+ return path.join(resolveHomeDir(), SERVICEME_DIR_NAME);
79
+ }
80
+ /** `$HOME/.serviceme/repos` (or `${SERVICEME_HOME}/repos`). */
81
+ function getReposDir() {
82
+ return path.join(getServicemeHome(), REPOS_SUBDIR);
83
+ }
84
+ /** `$HOME/.serviceme/repos/<repoId>` — validates `repoId` first. */
85
+ function getRepoDir(repoId) {
86
+ return path.join(getReposDir(), assertSafeRepoId(repoId));
87
+ }
88
+ /** `$HOME/.serviceme/cache` — generic per-user cache. */
89
+ function getCacheDir() {
90
+ return path.join(getServicemeHome(), CACHE_SUBDIR);
91
+ }
92
+ /** `$HOME/.serviceme/cache/<repoId>` — per-repo sync state lives here. */
93
+ function getRepoCacheDir(repoId) {
94
+ return path.join(getCacheDir(), assertSafeRepoId(repoId));
95
+ }
96
+ /** `$HOME/.serviceme/drafts` — local edits not yet pushed upstream. */
97
+ function getDraftsDir() {
98
+ return path.join(getServicemeHome(), DRAFTS_SUBDIR);
99
+ }
100
+ /** `$HOME/.serviceme/drafts/skills` — local skill drafts. */
101
+ function getSkillDraftsDir() {
102
+ return path.join(getDraftsDir(), SKILL_DRAFTS_SUBDIR);
103
+ }
104
+ /** `$HOME/.serviceme/drafts/agents` — local agent drafts. */
105
+ function getAgentDraftsDir() {
106
+ return path.join(getDraftsDir(), AGENT_DRAFTS_SUBDIR);
107
+ }
108
+ /**
109
+ * `$HOME/.serviceme/repos.json` — the single config entry point for repo
110
+ * metadata. Always under the resolved home root (i.e. follows
111
+ * `SERVICEME_HOME` overrides too).
112
+ */
113
+ function getReposConfigPath() {
114
+ return path.join(getServicemeHome(), REPOS_CONFIG_FILENAME);
115
+ }
116
+ /** `~/.serviceme/workspaces` — machine-local per-workspace state root. */
117
+ function getWorkspacesDir() {
118
+ return path.join(getServicemeHome(), WORKSPACES_SUBDIR);
119
+ }
120
+ /**
121
+ * Convenience helper for callers that need to switch behaviour on platform
122
+ * (e.g. `SkillLinker` chooses symlink vs junction). Exported mostly so tests
123
+ * can pin the value without touching `process.platform` directly.
124
+ */
125
+ function getHomePlatform() {
126
+ return resolvePlatform();
127
+ }
128
+ /** Layout constants for scheduled-tasks files. Kept here so other modules
129
+ * can reuse them and the names stay in sync with the design doc. */
130
+ const SCHEDULED_TASKS_CONFIG_FILENAME = "scheduled-tasks.json";
131
+ const SCHEDULED_TASKS_LOG_FILENAME = "scheduled-tasks-log.json";
132
+ const SCHEDULER_PID_FILENAME = "scheduler.pid";
133
+ const SCHEDULER_LOCK_FILENAME = "scheduler.lock";
134
+ const SCHEDULER_LOG_FILENAME = "scheduler.log";
135
+ const MIGRATION_FAILURES_FILENAME = "migration-failures.json";
136
+ const KNOWN_WORKSPACES_FILENAME = "known-workspaces.json";
137
+ /** `~/.serviceme/scheduled-tasks.json` — the global v2 task list. */
138
+ function getScheduledTasksConfigPath() {
139
+ return path.join(getServicemeHome(), SCHEDULED_TASKS_CONFIG_FILENAME);
140
+ }
141
+ /** `~/.serviceme/scheduled-tasks-log.json` — the global execution log (200 LRU). */
142
+ function getScheduledTasksLogPath() {
143
+ return path.join(getServicemeHome(), SCHEDULED_TASKS_LOG_FILENAME);
144
+ }
145
+ /** `~/.serviceme/scheduler.pid` — global daemon PID file. */
146
+ function getSchedulerPidPath() {
147
+ return path.join(getServicemeHome(), SCHEDULER_PID_FILENAME);
148
+ }
149
+ /** `~/.serviceme/scheduler.lock` — global daemon startup flock. */
150
+ function getSchedulerLockPath() {
151
+ return path.join(getServicemeHome(), SCHEDULER_LOCK_FILENAME);
152
+ }
153
+ /** `~/.serviceme/scheduler.log` — global daemon log (daemon lifecycle, not task execution). */
154
+ function getSchedulerLogPath() {
155
+ return path.join(getServicemeHome(), SCHEDULER_LOG_FILENAME);
156
+ }
157
+ /** `~/.serviceme/migration-failures.json` — diagnostics for failed v1→v2 imports. */
158
+ function getMigrationFailuresPath() {
159
+ return path.join(getServicemeHome(), MIGRATION_FAILURES_FILENAME);
160
+ }
161
+ /** `~/.serviceme/known-workspaces.json` — workspace list the extension has seen. */
162
+ function getKnownWorkspacesPath() {
163
+ return path.join(getServicemeHome(), KNOWN_WORKSPACES_FILENAME);
164
+ }
165
+ /**
166
+ * `~/.serviceme/server-proxy.json` — r7 BYOM Server Proxy toggle
167
+ * self-state. Written by the extension's `ProxyConfigService`, readable
168
+ * by the CLI / Server so they can decide whether to route traffic
169
+ * through the corporate proxy without booting VS Code.
170
+ *
171
+ * Lives under user-level `~/.serviceme/` (not under a workspace) because
172
+ * the toggle is a *user preference* — the same human enabling it on
173
+ * machine A should be able to rely on it on machine B after sync, not
174
+ * have it disappear when they switch repos.
175
+ */
176
+ function getServerProxyGlobalPath() {
177
+ return path.join(getServicemeHome(), SERVER_PROXY_GLOBAL_FILENAME);
178
+ }
179
+ /** Layout constants for the Phase 5 client-side state files. The server
180
+ * already has the corresponding routes (`/api/v1/auth/...`,
181
+ * `/api/v1/device/...`, etc.) — the client just needs the on-disk
182
+ * file paths + a first-run bootstrap so the auth/device/toolbox
183
+ * services can open + read them without a per-call existence check. */
184
+ const CREDENTIALS_CONFIG_FILENAME = "credentials.json";
185
+ const DEVICE_JSON_FILENAME = "device.json";
186
+ const TOOLBOX_JSON_FILENAME = "toolbox.json";
187
+ const MACHINE_ID_FILENAME = "machine-id";
188
+ const PROFILES_JSON_FILENAME = "profiles.json";
189
+ /** `~/.serviceme/credentials.json` — better-auth session tokens + refresh state (client-side cache). */
190
+ function getCredentialsConfigPath() {
191
+ return path.join(getServicemeHome(), CREDENTIALS_CONFIG_FILENAME);
192
+ }
193
+ /** `~/.serviceme/device.json` — device enrollment payload (id, public key fingerprint, claimed-by). */
194
+ function getDeviceJsonPath() {
195
+ return path.join(getServicemeHome(), DEVICE_JSON_FILENAME);
196
+ }
197
+ /** `~/.serviceme/toolbox.json` — local toolbox state (installed tool refs, per-user prefs). */
198
+ function getToolboxJsonPath() {
199
+ return path.join(getServicemeHome(), TOOLBOX_JSON_FILENAME);
200
+ }
201
+ /** `~/.serviceme/machine-id` — opaque stable per-install id (uuid v4 string, no JSON wrapper). */
202
+ function getMachineIdPath() {
203
+ return path.join(getServicemeHome(), MACHINE_ID_FILENAME);
204
+ }
205
+ /** `~/.serviceme/profiles.json` — cached projection of server-side profile rows; refresh on demand. */
206
+ function getProfilesJsonPath() {
207
+ return path.join(getServicemeHome(), PROFILES_JSON_FILENAME);
208
+ }
209
+ //#endregion
210
+ export { getDraftsDir as A, getReposDir as B, WORKSPACES_SUBDIR as C, getCacheDir as D, getAgentDraftsDir as E, getMigrationFailuresPath as F, getSchedulerPidPath as G, getScheduledTasksLogPath as H, getProfilesJsonPath as I, getSkillDraftsDir as J, getServerProxyGlobalPath as K, getRepoCacheDir as L, getHomePlatform as M, getKnownWorkspacesPath as N, getCredentialsConfigPath as O, getMachineIdPath as P, setUserHomeOverrides as Q, getRepoDir as R, TOOLBOX_JSON_FILENAME as S, assertSafeRepoId as T, getSchedulerLockPath as U, getScheduledTasksConfigPath as V, getSchedulerLogPath as W, getWorkspacesDir as X, getToolboxJsonPath as Y, resetUserHomeOverrides as Z, SCHEDULER_PID_FILENAME as _, DRAFTS_SUBDIR as a, SERVICEME_HOME_ENV as b, MIGRATION_FAILURES_FILENAME as c, REPOS_SUBDIR as d, SAFE_REPO_ID_PATTERN as f, SCHEDULER_LOG_FILENAME as g, SCHEDULER_LOCK_FILENAME as h, DEVICE_JSON_FILENAME as i, getHomeDir as j, getDeviceJsonPath as k, PROFILES_JSON_FILENAME as l, SCHEDULED_TASKS_LOG_FILENAME as m, CACHE_SUBDIR as n, KNOWN_WORKSPACES_FILENAME as o, SCHEDULED_TASKS_CONFIG_FILENAME as p, getServicemeHome as q, CREDENTIALS_CONFIG_FILENAME as r, MACHINE_ID_FILENAME as s, AGENT_DRAFTS_SUBDIR as t, REPOS_CONFIG_FILENAME as u, SERVER_PROXY_GLOBAL_FILENAME as v, WORKSPACE_CONTENT_STATE_FILENAME as w, SKILL_DRAFTS_SUBDIR as x, SERVICEME_DIR_NAME as y, getReposConfigPath as z };
211
+
212
+ //# sourceMappingURL=userHome-CcnAQPG1.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"userHome-CcnAQPG1.mjs","names":[],"sources":["../src/paths/userHome.ts"],"sourcesContent":["import * as os from \"node:os\";\nimport * as path from \"node:path\";\n\n/**\n * SERVICEME user home directory helpers.\n *\n * All paths resolve under {@link getServicemeHome}, which is either the\n * `SERVICEME_HOME` environment variable (when set and non-empty) or\n * `$HOME/.serviceme` on POSIX / `%USERPROFILE%\\.serviceme` on Windows.\n *\n * Tests inject `homeDir` and `servicemeHomeEnv` overrides via\n * {@link setUserHomeOverrides} / {@link resetUserHomeOverrides} so they can\n * exercise the path logic without touching the real user environment.\n */\n\n/** Layout constants — kept in one place so other modules can reuse them. */\nexport const SERVICEME_DIR_NAME = \".serviceme\";\nexport const REPOS_SUBDIR = \"repos\";\nexport const CACHE_SUBDIR = \"cache\";\nexport const DRAFTS_SUBDIR = \"drafts\";\nexport const SKILL_DRAFTS_SUBDIR = \"skills\";\nexport const AGENT_DRAFTS_SUBDIR = \"agents\";\nexport const REPOS_CONFIG_FILENAME = \"repos.json\";\nexport const WORKSPACES_SUBDIR = \"workspaces\";\nexport const WORKSPACE_CONTENT_STATE_FILENAME = \"copilot-content-state.json\";\n\n/** rev.20 — r7 BYOM Server Proxy toggle self-state (read by ext + CLI + server). */\nexport const SERVER_PROXY_GLOBAL_FILENAME = \"server-proxy.json\";\n\n/**\n * Repo id regex — used to validate any `repoId` argument before it is joined\n * into a filesystem path. Keeps path traversal attempts out and gives us a\n * predictable on-disk shape.\n */\nexport const SAFE_REPO_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;\n\n/** Environment variable that overrides the user-home root directory. */\nexport const SERVICEME_HOME_ENV = \"SERVICEME_HOME\";\n\n/**\n * Test seam: lets unit tests inject deterministic values for `os.homedir()`\n * and the `SERVICEME_HOME` env override without actually mutating\n * `process.env` (which would leak into other tests).\n */\ninterface UserHomeOverrides {\n\thomeDir?: string | undefined;\n\tservicemeHomeEnv?: string | undefined;\n\tplatform?: NodeJS.Platform | undefined;\n}\n\nlet activeOverrides: UserHomeOverrides = {};\n\nexport function setUserHomeOverrides(overrides: UserHomeOverrides): void {\n\tactiveOverrides = { ...overrides };\n}\n\nexport function resetUserHomeOverrides(): void {\n\tactiveOverrides = {};\n}\n\nfunction resolveHomeDir(): string {\n\tconst injected = activeOverrides.homeDir;\n\tif (injected !== undefined) {\n\t\treturn injected;\n\t}\n\treturn os.homedir();\n}\n\nfunction resolveServicemeHomeEnv(): string | undefined {\n\tconst injected = activeOverrides.servicemeHomeEnv;\n\tif (injected !== undefined) {\n\t\t// Treat empty string as \"not set\" — `process.env` always returns a string\n\t\t// but tests may deliberately pass \"\" to opt out.\n\t\treturn injected.length > 0 ? injected : undefined;\n\t}\n\tconst envValue = process.env[SERVICEME_HOME_ENV];\n\treturn envValue && envValue.length > 0 ? envValue : undefined;\n}\n\nfunction resolvePlatform(): NodeJS.Platform {\n\treturn activeOverrides.platform ?? process.platform;\n}\n\nexport function assertSafeRepoId(repoId: string): string {\n\tif (typeof repoId !== \"string\" || repoId.length === 0 || !SAFE_REPO_ID_PATTERN.test(repoId)) {\n\t\tthrow new Error(\n\t\t\t`Invalid repo id: ${JSON.stringify(repoId)}. ` +\n\t\t\t\t`Must match ${SAFE_REPO_ID_PATTERN} (alphanumeric start, then ` +\n\t\t\t\t`alphanumerics / underscores / hyphens, ≤ 64 chars).`\n\t\t);\n\t}\n\treturn repoId;\n}\n\n/**\n * The raw OS home directory (`os.homedir()`), honoring test overrides\n * ({@link setUserHomeOverrides}). Exported for callers that need a\n * home-relative path *outside* of `~/.serviceme` — e.g. the\n * `~/.agents/{skills,agents}` convention used by `SkillLinker` for\n * user-scope links.\n */\nexport function getHomeDir(): string {\n\treturn resolveHomeDir();\n}\n\n/**\n * Root directory for all SERVICEME user-level state. Resolves to\n * `${SERVICEME_HOME}` when that env var is set, otherwise `${HOME}/.serviceme`\n * (POSIX) or `%USERPROFILE%\\.serviceme` (Windows via `os.homedir`).\n */\nexport function getServicemeHome(): string {\n\tconst override = resolveServicemeHomeEnv();\n\tif (override !== undefined) {\n\t\treturn path.resolve(override);\n\t}\n\treturn path.join(resolveHomeDir(), SERVICEME_DIR_NAME);\n}\n\n/** `$HOME/.serviceme/repos` (or `${SERVICEME_HOME}/repos`). */\nexport function getReposDir(): string {\n\treturn path.join(getServicemeHome(), REPOS_SUBDIR);\n}\n\n/** `$HOME/.serviceme/repos/<repoId>` — validates `repoId` first. */\nexport function getRepoDir(repoId: string): string {\n\treturn path.join(getReposDir(), assertSafeRepoId(repoId));\n}\n\n/** `$HOME/.serviceme/cache` — generic per-user cache. */\nexport function getCacheDir(): string {\n\treturn path.join(getServicemeHome(), CACHE_SUBDIR);\n}\n\n/** `$HOME/.serviceme/cache/<repoId>` — per-repo sync state lives here. */\nexport function getRepoCacheDir(repoId: string): string {\n\treturn path.join(getCacheDir(), assertSafeRepoId(repoId));\n}\n\n/** `$HOME/.serviceme/drafts` — local edits not yet pushed upstream. */\nexport function getDraftsDir(): string {\n\treturn path.join(getServicemeHome(), DRAFTS_SUBDIR);\n}\n\n/** `$HOME/.serviceme/drafts/skills` — local skill drafts. */\nexport function getSkillDraftsDir(): string {\n\treturn path.join(getDraftsDir(), SKILL_DRAFTS_SUBDIR);\n}\n\n/** `$HOME/.serviceme/drafts/agents` — local agent drafts. */\nexport function getAgentDraftsDir(): string {\n\treturn path.join(getDraftsDir(), AGENT_DRAFTS_SUBDIR);\n}\n\n/**\n * `$HOME/.serviceme/repos.json` — the single config entry point for repo\n * metadata. Always under the resolved home root (i.e. follows\n * `SERVICEME_HOME` overrides too).\n */\nexport function getReposConfigPath(): string {\n\treturn path.join(getServicemeHome(), REPOS_CONFIG_FILENAME);\n}\n\n/** `~/.serviceme/workspaces` — machine-local per-workspace state root. */\nexport function getWorkspacesDir(): string {\n\treturn path.join(getServicemeHome(), WORKSPACES_SUBDIR);\n}\n\n/**\n * Convenience helper for callers that need to switch behaviour on platform\n * (e.g. `SkillLinker` chooses symlink vs junction). Exported mostly so tests\n * can pin the value without touching `process.platform` directly.\n */\nexport function getHomePlatform(): NodeJS.Platform {\n\treturn resolvePlatform();\n}\n\n// ─── Scheduled Tasks paths (added by M1.5.3) ───────────────────────────────\n\n/** Layout constants for scheduled-tasks files. Kept here so other modules\n * can reuse them and the names stay in sync with the design doc. */\nexport const SCHEDULED_TASKS_CONFIG_FILENAME = \"scheduled-tasks.json\";\nexport const SCHEDULED_TASKS_LOG_FILENAME = \"scheduled-tasks-log.json\";\nexport const SCHEDULER_PID_FILENAME = \"scheduler.pid\";\nexport const SCHEDULER_LOCK_FILENAME = \"scheduler.lock\";\nexport const SCHEDULER_LOG_FILENAME = \"scheduler.log\";\nexport const MIGRATION_FAILURES_FILENAME = \"migration-failures.json\";\nexport const KNOWN_WORKSPACES_FILENAME = \"known-workspaces.json\";\n\n/** `~/.serviceme/scheduled-tasks.json` — the global v2 task list. */\nexport function getScheduledTasksConfigPath(): string {\n\treturn path.join(getServicemeHome(), SCHEDULED_TASKS_CONFIG_FILENAME);\n}\n\n/** `~/.serviceme/scheduled-tasks-log.json` — the global execution log (200 LRU). */\nexport function getScheduledTasksLogPath(): string {\n\treturn path.join(getServicemeHome(), SCHEDULED_TASKS_LOG_FILENAME);\n}\n\n/** `~/.serviceme/scheduler.pid` — global daemon PID file. */\nexport function getSchedulerPidPath(): string {\n\treturn path.join(getServicemeHome(), SCHEDULER_PID_FILENAME);\n}\n\n/** `~/.serviceme/scheduler.lock` — global daemon startup flock. */\nexport function getSchedulerLockPath(): string {\n\treturn path.join(getServicemeHome(), SCHEDULER_LOCK_FILENAME);\n}\n\n/** `~/.serviceme/scheduler.log` — global daemon log (daemon lifecycle, not task execution). */\nexport function getSchedulerLogPath(): string {\n\treturn path.join(getServicemeHome(), SCHEDULER_LOG_FILENAME);\n}\n\n/** `~/.serviceme/migration-failures.json` — diagnostics for failed v1→v2 imports. */\nexport function getMigrationFailuresPath(): string {\n\treturn path.join(getServicemeHome(), MIGRATION_FAILURES_FILENAME);\n}\n\n/** `~/.serviceme/known-workspaces.json` — workspace list the extension has seen. */\nexport function getKnownWorkspacesPath(): string {\n\treturn path.join(getServicemeHome(), KNOWN_WORKSPACES_FILENAME);\n}\n\n// ─── r7 BYOM Server Proxy (rev.20) ─────────────────────────────────────────\n\n/**\n * `~/.serviceme/server-proxy.json` — r7 BYOM Server Proxy toggle\n * self-state. Written by the extension's `ProxyConfigService`, readable\n * by the CLI / Server so they can decide whether to route traffic\n * through the corporate proxy without booting VS Code.\n *\n * Lives under user-level `~/.serviceme/` (not under a workspace) because\n * the toggle is a *user preference* — the same human enabling it on\n * machine A should be able to rely on it on machine B after sync, not\n * have it disappear when they switch repos.\n */\nexport function getServerProxyGlobalPath(): string {\n\treturn path.join(getServicemeHome(), SERVER_PROXY_GLOBAL_FILENAME);\n}\n\n// ─── Phase 5 (auth + device + toolbox) placeholders (Spec §11.12) ────────\n\n/** Layout constants for the Phase 5 client-side state files. The server\n * already has the corresponding routes (`/api/v1/auth/...`,\n * `/api/v1/device/...`, etc.) — the client just needs the on-disk\n * file paths + a first-run bootstrap so the auth/device/toolbox\n * services can open + read them without a per-call existence check. */\nexport const CREDENTIALS_CONFIG_FILENAME = \"credentials.json\";\nexport const DEVICE_JSON_FILENAME = \"device.json\";\nexport const TOOLBOX_JSON_FILENAME = \"toolbox.json\";\nexport const MACHINE_ID_FILENAME = \"machine-id\";\nexport const PROFILES_JSON_FILENAME = \"profiles.json\";\n\n/** `~/.serviceme/credentials.json` — better-auth session tokens + refresh state (client-side cache). */\nexport function getCredentialsConfigPath(): string {\n\treturn path.join(getServicemeHome(), CREDENTIALS_CONFIG_FILENAME);\n}\n\n/** `~/.serviceme/device.json` — device enrollment payload (id, public key fingerprint, claimed-by). */\nexport function getDeviceJsonPath(): string {\n\treturn path.join(getServicemeHome(), DEVICE_JSON_FILENAME);\n}\n\n/** `~/.serviceme/toolbox.json` — local toolbox state (installed tool refs, per-user prefs). */\nexport function getToolboxJsonPath(): string {\n\treturn path.join(getServicemeHome(), TOOLBOX_JSON_FILENAME);\n}\n\n/** `~/.serviceme/machine-id` — opaque stable per-install id (uuid v4 string, no JSON wrapper). */\nexport function getMachineIdPath(): string {\n\treturn path.join(getServicemeHome(), MACHINE_ID_FILENAME);\n}\n\n/** `~/.serviceme/profiles.json` — cached projection of server-side profile rows; refresh on demand. */\nexport function getProfilesJsonPath(): string {\n\treturn path.join(getServicemeHome(), PROFILES_JSON_FILENAME);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serviceme/devtools-core",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Reusable Node.js core capabilities powering the SERVICEME CLI and integrations.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "repository": {
@@ -77,24 +77,24 @@
77
77
  "registry": "https://registry.npmjs.org/"
78
78
  },
79
79
  "dependencies": {
80
- "comment-json": "^4.5.1",
80
+ "comment-json": "^5.0.0",
81
81
  "json5": "^2.2.3",
82
82
  "yauzl": "^3.2.0",
83
- "zod": "^4.3.6",
84
- "@serviceme/devtools-protocol": "2.0.0"
83
+ "zod": "^4.5.4",
84
+ "@serviceme/devtools-protocol": "2.0.2"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@types/node": "^24",
88
- "@types/yauzl": "^2.10.3",
89
- "tsup": "^8.5.1",
90
- "tsx": "^4.22.0",
91
- "typescript": "^5.9.3"
88
+ "@types/yauzl": "^3.4.0",
89
+ "tsdown": "^0.23.0",
90
+ "tsx": "^4.23.13",
91
+ "typescript": "^7.0.2"
92
92
  },
93
93
  "scripts": {
94
- "watch": "tsup --watch",
94
+ "watch": "tsdown --watch",
95
95
  "lint": "biome check src test",
96
96
  "format": "biome check --write src test",
97
- "build": "tsup",
97
+ "build": "tsdown",
98
98
  "typecheck": "tsc --noEmit",
99
99
  "test": "pnpm run build && node --test test/**/*.test.js && node --import tsx --test --test-force-exit src/**/__tests__/**/*.test.ts",
100
100
  "release:check": "pnpm run build && npm pack --dry-run",
package/dist/auth.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/auth/index.ts","../src/auth/AccessControl.ts","../src/logger.ts","../src/auth/AuthStateManager.ts","../src/auth/ProviderRegistry.ts","../src/auth/AuthCore.ts","../src/auth/KeychainAuthTokenStore.ts","../src/auth/utils/githubUserEmail.ts","../src/auth/providers/GitHubAuthProvider.ts","../src/auth/providers/MicrosoftAuthProvider.ts"],"sourcesContent":["/**\n * AuthCore barrel — Phase 5.2 public surface.\n *\n * Consumers (CLI / Extension / Bridge) should import from\n * `@serviceme/devtools-core/auth` (the subpath export added in\n * `package.json` `exports` map) rather than reaching into\n * individual files.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `auth/index.ts`\n */\n\nexport {\n\ttype AccessCheckResult,\n\tAccessControl,\n\ttype AccessControlOptions,\n\ttype KeyValueStore,\n\ttype OrgMembershipFetcher,\n} from \"./AccessControl\";\nexport {\n\tAuthCore,\n\ttype AuthCoreOptions,\n\ttype CancellationCheck,\n\ttype DeviceFlowUiCallback,\n} from \"./AuthCore\";\nexport { AuthStateManager, type AuthStateManagerOptions } from \"./AuthStateManager\";\nexport {\n\tInMemoryKeychainAuthTokenStore,\n\ttype KeychainAccountKey,\n\ttype KeychainAuthTokenStore,\n\ttype KeychainTokenEnvelope,\n\ttype KeychainTokenMetadata,\n\tKeychainUnavailableError,\n} from \"./KeychainAuthTokenStore\";\nexport { ProviderRegistry } from \"./ProviderRegistry\";\nexport {\n\tGitHubAuthProvider,\n\ttype GitHubAuthProviderConfig,\n} from \"./providers/GitHubAuthProvider\";\nexport type {\n\tIAuthProvider,\n\tIAuthProviderSession,\n\tIAuthProviderUserInfo,\n} from \"./providers/IAuthProvider\";\nexport {\n\tMicrosoftAuthProvider,\n\tMicrosoftProviderNotHostedError,\n} from \"./providers/MicrosoftAuthProvider\";\nexport {\n\tbuildGitHubLocalEmail,\n\tisGitHubLocalEmail,\n\tresolvePrimaryEmail,\n} from \"./utils/githubUserEmail\";\n","/**\n * AccessControl — Pure logic for org-membership gating.\n *\n * Ported from `apps/extension/src/services/auth/AccessControlService.ts`\n * but stripped of all VSCode dependencies:\n * - No `vscode.window.showWarningMessage` — callers render the notice.\n * - No `vscode.context.globalState` — callers inject a `KeyValueStore`.\n * - No `vscode.Event` — uses a plain `onDidChange` callback.\n *\n * The class is generic over the membership-fetcher function so it can\n * run identically with the CLI's `@serviceme/devtools-shared` `getGitHubOrgMembership`\n * or with the Extension's bundled copy. ADL-003 forbids Core from\n * importing `@serviceme/devtools-shared` directly, so the org membership\n * result type is defined locally and the runtime adapter wraps the\n * shared helper at the boundary (Phase 5.3+).\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `AccessControl.ts 从 AccessControlService 改写`\n * - ADL-003 — `@serviceme/devtools-shared` boundary (Core MUST NOT import shared)\n */\n\nimport type { AuthAccountMeta, AuthProvider } from \"@serviceme/devtools-protocol\";\n\n/** Core-local mirror of `GitHubOrgMembershipCheckResult` (defined in `@serviceme/devtools-shared`). */\nexport interface CoreOrgMembershipResult {\n\tstatus: \"active\" | \"pending\" | \"not_member\" | \"unknown\";\n\thttpStatus?: number;\n\trole?: string;\n\tdirectMembership?: boolean;\n}\n\n/**\n * Upstream org-membership fetcher. CLI / Extension adapters wrap\n * `getGitHubOrgMembership` (or the local extension copy) to match\n * this signature.\n */\nexport type OrgMembershipFetcher = (token: string, org: string) => Promise<CoreOrgMembershipResult>;\n\n/** Pluggable key/value store. Extension injects `globalState`-backed impl; CLI injects a file-backed impl. */\nexport interface KeyValueStore {\n\tget<T>(key: string): T | undefined;\n\tupdate<T>(key: string, value: T): Promise<void>;\n}\n\nexport interface AccessCheckResult {\n\tallowed: boolean;\n\treason?: \"not_authenticated\" | \"not_member\";\n\tusername?: string;\n}\n\nexport interface AccessControlOptions {\n\torg: string;\n\t/** Domains that auto-qualify Microsoft users without a GitHub link. */\n\tmicrosoftEmailDomains?: readonly string[];\n\t/** TTL for in-memory membership cache (ms). */\n\tcacheTtlMs?: number;\n\t/** TTL for persisted membership cache (ms). */\n\tpersistentCacheTtlMs?: number;\n\t/** Tokens whose bearer can be supplied to `OrgMembershipFetcher`. */\n\ttokenFetcher: (provider: AuthProvider) => Promise<string | null>;\n\t/** Upstream org-membership fetcher (typically `getGitHubOrgMembership` from `@serviceme/devtools-shared`). */\n\torgFetcher: OrgMembershipFetcher;\n\t/** Clock seam for tests. */\n\tnow?: () => number;\n}\n\nconst DEFAULT_CACHE_TTL_MS = 10 * 60 * 1000;\nconst DEFAULT_PERSISTENT_CACHE_TTL_MS = 24 * 60 * 60 * 1000;\nconst KEY_GRANTED_USERS = \"msDevTools.accessControl.grantedUsers\";\nconst KEY_MEMBERSHIP_CACHE = \"msDevTools.accessControl.membershipCache\";\nconst KEY_SERVER_IN_ORG = \"msDevTools.accessControl.serverInOrg\";\n\ninterface CacheEntry {\n\tisMember: boolean;\n\tat: number;\n}\n\ninterface PersistentCache {\n\t[username: string]: CacheEntry;\n}\n\nexport class AccessControl {\n\tprivate readonly memCache = new Map<string, CacheEntry>();\n\tprivate readonly serverInOrgMem = new Map<string, boolean>();\n\tprivate readonly listeners = new Set<() => void>();\n\tprivate readonly opts: Required<\n\t\tOmit<AccessControlOptions, \"now\" | \"tokenFetcher\" | \"microsoftEmailDomains\" | \"orgFetcher\">\n\t> & {\n\t\ttokenFetcher: AccessControlOptions[\"tokenFetcher\"];\n\t\torgFetcher: OrgMembershipFetcher;\n\t\tmicrosoftEmailDomains: readonly string[];\n\t\tnow: () => number;\n\t};\n\n\tconstructor(\n\t\tprivate readonly kv: KeyValueStore,\n\t\toptions: AccessControlOptions\n\t) {\n\t\tthis.opts = {\n\t\t\torg: options.org,\n\t\t\ttokenFetcher: options.tokenFetcher,\n\t\t\torgFetcher: options.orgFetcher,\n\t\t\tmicrosoftEmailDomains: options.microsoftEmailDomains ?? [],\n\t\t\tcacheTtlMs: options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS,\n\t\t\tpersistentCacheTtlMs: options.persistentCacheTtlMs ?? DEFAULT_PERSISTENT_CACHE_TTL_MS,\n\t\t\tnow: options.now ?? (() => Date.now()),\n\t\t};\n\t\t// Hydrate `serverInOrg` from persistent storage so the gate can run instantly on restart.\n\t\tconst persistedServerInOrg = this.kv.get<Record<string, boolean>>(KEY_SERVER_IN_ORG);\n\t\tif (persistedServerInOrg) {\n\t\t\tfor (const [key, value] of Object.entries(persistedServerInOrg)) {\n\t\t\t\tthis.serverInOrgMem.set(key, value);\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Subscribe to inOrg changes (used by extension to refresh access-gated UI). */\n\tonDidChange(listener: () => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\t/** Full check: requires authentication + org membership / admin grant. */\n\tasync checkAccess(user: AuthAccountMeta | null): Promise<AccessCheckResult> {\n\t\tif (!user) {\n\t\t\treturn { allowed: false, reason: \"not_authenticated\" };\n\t\t}\n\n\t\t// Microsoft users: server-confirmed inOrg takes priority over GitHub link.\n\t\tif (user.provider === \"microsoft\" && user.login) {\n\t\t\tconst inOrg = this.serverInOrgMem.get(user.login.toLowerCase());\n\t\t\tif (inOrg === true) return { allowed: true };\n\t\t\tif (inOrg === false) {\n\t\t\t\treturn { allowed: false, reason: \"not_member\", username: user.login };\n\t\t\t}\n\t\t}\n\n\t\t// Find a GitHub account if available (linked for Microsoft users).\n\t\tconst githubAccount = user.provider === \"github\" ? user : null;\n\t\tif (!githubAccount && user.provider === \"microsoft\" && user.login) {\n\t\t\tconst [, domain = \"\"] = user.login.split(\"@\");\n\t\t\tconst isDomainMember =\n\t\t\t\tdomain.length > 0 && this.opts.microsoftEmailDomains.includes(domain.toLowerCase());\n\t\t\tif (isDomainMember) return { allowed: true };\n\t\t\t// No GitHub link + unknown domain — deny until the next server-confirmed claim.\n\t\t\treturn { allowed: false, reason: \"not_member\", username: user.login };\n\t\t}\n\t\tif (!githubAccount) {\n\t\t\t// No identity at all — treat as authenticated for now (caller may have other gating).\n\t\t\treturn { allowed: true };\n\t\t}\n\n\t\tconst accessUsername = githubAccount.login ?? user.login ?? \"\";\n\t\tif (accessUsername.length === 0) {\n\t\t\treturn { allowed: false, reason: \"not_member\", username: \"\" };\n\t\t}\n\n\t\t// Admin-granted users bypass the org check.\n\t\tconst granted = this.getGrantedUsers();\n\t\tif (granted.includes(accessUsername.toLowerCase())) {\n\t\t\treturn { allowed: true };\n\t\t}\n\n\t\tconst isMember = await this.checkOrgMembership(accessUsername);\n\t\treturn isMember\n\t\t\t? { allowed: true }\n\t\t\t: { allowed: false, reason: \"not_member\", username: accessUsername };\n\t}\n\n\t/** Admin: grant access to a GitHub username (bypasses org check). */\n\tasync grantAccess(username: string): Promise<void> {\n\t\tconst list = this.getGrantedUsers();\n\t\tconst key = username.toLowerCase();\n\t\tif (!list.includes(key)) {\n\t\t\tlist.push(key);\n\t\t\tawait this.kv.update(KEY_GRANTED_USERS, list);\n\t\t}\n\t}\n\n\t/** Admin: revoke a previously granted access. */\n\tasync revokeAccess(username: string): Promise<void> {\n\t\tconst updated = this.getGrantedUsers().filter((u) => u !== username.toLowerCase());\n\t\tawait this.kv.update(KEY_GRANTED_USERS, updated);\n\t}\n\n\t/** Snapshot of all admin-granted usernames (lowercased). */\n\tgetGrantedUsers(): string[] {\n\t\treturn this.kv.get<string[]>(KEY_GRANTED_USERS) ?? [];\n\t}\n\n\t/** Cached membership lookup with both in-memory + persistent layers. */\n\tasync checkOrgMembership(username: string): Promise<boolean> {\n\t\tconst now = this.opts.now();\n\n\t\t// 1. In-memory cache.\n\t\tconst inMem = this.memCache.get(username);\n\t\tif (inMem && now - inMem.at < this.opts.cacheTtlMs) {\n\t\t\treturn inMem.isMember;\n\t\t}\n\n\t\t// 2. Persistent cache.\n\t\tconst persistent = this.kv.get<PersistentCache>(KEY_MEMBERSHIP_CACHE) ?? {};\n\t\tconst persistentEntry = persistent[username];\n\t\tif (persistentEntry && now - persistentEntry.at < this.opts.persistentCacheTtlMs) {\n\t\t\tthis.memCache.set(username, persistentEntry);\n\t\t\treturn persistentEntry.isMember;\n\t\t}\n\n\t\t// 3. Hit upstream GitHub API via the injected fetcher.\n\t\tconst token = await this.opts.tokenFetcher(\"github\");\n\t\tif (!token) return false;\n\n\t\ttry {\n\t\t\tconst result = await this.opts.orgFetcher(token, this.opts.org);\n\t\t\tconst decision = this.resolveDecision(username, result);\n\t\t\tif (typeof decision === \"boolean\") {\n\t\t\t\tthis.updateCache(username, decision);\n\t\t\t\treturn decision;\n\t\t\t}\n\t\t\t// Indeterminate — fail-open.\n\t\t\treturn true;\n\t\t} catch {\n\t\t\t// Fail-open on transient network errors.\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t/** Server-confirmed inOrg status (set by device claim flow). */\n\tsetServerInOrg(username: string, inOrg: boolean): void {\n\t\tconst key = username.toLowerCase();\n\t\tthis.serverInOrgMem.set(key, inOrg);\n\t\tconst persisted = this.kv.get<Record<string, boolean>>(KEY_SERVER_IN_ORG) ?? {};\n\t\tpersisted[key] = inOrg;\n\t\tvoid this.kv.update(KEY_SERVER_IN_ORG, persisted);\n\t\tthis.notifyListeners();\n\t}\n\n\t/** Inspect server-confirmed inOrg for a username; returns undefined when unknown. */\n\tgetServerInOrg(username: string): boolean | undefined {\n\t\treturn this.serverInOrgMem.get(username.toLowerCase());\n\t}\n\n\tprivate resolveDecision(\n\t\t_username: string,\n\t\tmembership: CoreOrgMembershipResult\n\t): boolean | undefined {\n\t\tif (membership.status === \"active\" || membership.status === \"pending\") return true;\n\t\tif (membership.status === \"not_member\") return false;\n\t\treturn undefined;\n\t}\n\n\tprivate updateCache(username: string, isMember: boolean): void {\n\t\tconst at = this.opts.now();\n\t\tthis.memCache.set(username, { isMember, at });\n\t\tconst persistent = this.kv.get<PersistentCache>(KEY_MEMBERSHIP_CACHE) ?? {};\n\t\tpersistent[username] = { isMember, at };\n\t\tvoid this.kv.update(KEY_MEMBERSHIP_CACHE, persistent);\n\t}\n\n\tprivate notifyListeners(): void {\n\t\tfor (const listener of this.listeners) {\n\t\t\ttry {\n\t\t\t\tlistener();\n\t\t\t} catch {\n\t\t\t\t// listener errors must not propagate; AccessControl is best-effort.\n\t\t\t}\n\t\t}\n\t}\n}\n","export interface ServiceMeLogger {\n\tdebug(message: string, ...args: unknown[]): void;\n\tinfo(message: string, ...args: unknown[]): void;\n\twarn(message: string, ...args: unknown[]): void;\n\terror(message: string, ...args: unknown[]): void;\n}\n\nexport const noopLogger: ServiceMeLogger = {\n\tdebug() {},\n\tinfo() {},\n\twarn() {},\n\terror() {},\n};\n\nfunction formatArgs(args: unknown[]): string {\n\treturn args\n\t\t.map((arg) => {\n\t\t\tif (typeof arg === \"string\") {\n\t\t\t\treturn arg;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\treturn JSON.stringify(arg);\n\t\t\t} catch {\n\t\t\t\treturn String(arg);\n\t\t\t}\n\t\t})\n\t\t.join(\" \");\n}\n\nexport function createConsoleLogger(prefix = \"serviceme\"): ServiceMeLogger {\n\treturn {\n\t\tdebug(message: string, ...args: unknown[]) {\n\t\t\tprocess.stderr.write(`[${prefix}] DEBUG ${message} ${formatArgs(args)}\\n`);\n\t\t},\n\t\tinfo(message: string, ...args: unknown[]) {\n\t\t\tprocess.stderr.write(`[${prefix}] INFO ${message} ${formatArgs(args)}\\n`);\n\t\t},\n\t\twarn(message: string, ...args: unknown[]) {\n\t\t\tprocess.stderr.write(`[${prefix}] WARN ${message} ${formatArgs(args)}\\n`);\n\t\t},\n\t\terror(message: string, ...args: unknown[]) {\n\t\t\tprocess.stderr.write(`[${prefix}] ERROR ${message} ${formatArgs(args)}\\n`);\n\t\t},\n\t};\n}\n","/**\n * AuthStateManager — In-memory mirror of the persisted auth state.\n *\n * Holds the multi-account map (provider + accountId keyed) plus the\n * \"active\" provider/account pair. Emits change events via Node's\n * built-in `events.EventEmitter` so listeners stay decoupled.\n *\n * Important: this class does NOT touch `vscode.SecretStorage` or any\n * file — it only keeps the metadata (`AuthAccountMeta`) and the\n * `activeProvider` selection. Token bytes are looked up via the\n * `KeychainAuthTokenStore` on demand. This separation is what lets\n * `AuthCore` run identically in CLI + Extension.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `AuthStateManager.ts events.EventEmitter, NO VSCode dep`\n * - ADL-004 — token bytes never enter Core state\n */\n\nimport { EventEmitter } from \"node:events\";\n\nimport type { AuthAccountMeta, AuthProvider, AuthStatus } from \"@serviceme/devtools-protocol\";\n\nexport interface AuthStateManagerEvents {\n\tchange: () => void;\n}\n\nexport interface AuthStateManagerOptions {\n\t/** EventEmitter listener cap; defaults to 32 (Node default) but raised for hot test paths. */\n\tmaxListeners?: number;\n}\n\nexport class AuthStateManager {\n\tprivate readonly emitter = new EventEmitter();\n\tprivate accounts: AuthAccountMeta[] = [];\n\tprivate activeProvider: AuthProvider | null = null;\n\tprivate activeAccountId: string | null = null;\n\tprivate lastError: string | null = null;\n\n\tconstructor(opts: AuthStateManagerOptions = {}) {\n\t\tif (opts.maxListeners !== undefined) {\n\t\t\tthis.emitter.setMaxListeners(opts.maxListeners);\n\t\t}\n\t}\n\n\t/** Subscribe to state-change events. Returns a disposer. */\n\tonDidChange(listener: () => void): () => void {\n\t\tthis.emitter.on(\"change\", listener);\n\t\treturn () => this.emitter.off(\"change\", listener);\n\t}\n\n\t/** Snapshot of all known accounts (immutable copy). */\n\tlistAccounts(): AuthAccountMeta[] {\n\t\treturn [...this.accounts];\n\t}\n\n\t/** Find an account by `(provider, accountId)` tuple; returns `null` when absent. */\n\tfindAccount(provider: AuthProvider, accountId: string): AuthAccountMeta | null {\n\t\treturn this.accounts.find((a) => a.provider === provider && a.id === accountId) ?? null;\n\t}\n\n\t/** First account for the requested provider — used for \"switch to GitHub\" UX. */\n\tfindFirstForProvider(provider: AuthProvider): AuthAccountMeta | null {\n\t\treturn this.accounts.find((a) => a.provider === provider) ?? null;\n\t}\n\n\t/** Insert or update an account entry. New accounts land at the head of the list. */\n\tupsertAccount(meta: AuthAccountMeta): void {\n\t\tconst idx = this.accounts.findIndex((a) => a.provider === meta.provider && a.id === meta.id);\n\t\tif (idx >= 0) {\n\t\t\tthis.accounts[idx] = meta;\n\t\t} else {\n\t\t\tthis.accounts.unshift(meta);\n\t\t}\n\t\tthis.fire();\n\t}\n\n\t/** Remove an account entry. Returns `true` when an entry was removed. */\n\tremoveAccount(provider: AuthProvider, accountId: string): boolean {\n\t\tconst before = this.accounts.length;\n\t\tthis.accounts = this.accounts.filter((a) => !(a.provider === provider && a.id === accountId));\n\t\tconst removed = this.accounts.length !== before;\n\t\t// If we removed the active provider's account, clear the active marker.\n\t\tif (removed && this.activeProvider === provider && this.activeAccountId === accountId) {\n\t\t\tconst stillHasSameProvider = this.accounts.some((a) => a.provider === provider);\n\t\t\tif (!stillHasSameProvider) {\n\t\t\t\tthis.activeProvider = null;\n\t\t\t\tthis.activeAccountId = null;\n\t\t\t} else {\n\t\t\t\tconst next = this.accounts.find((a) => a.provider === provider);\n\t\t\t\tif (next) {\n\t\t\t\t\tthis.activeAccountId = next.id;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (removed) this.fire();\n\t\treturn removed;\n\t}\n\n\t/** Set the active provider. When `accountId` is omitted, picks the first account for that provider. */\n\tsetActive(provider: AuthProvider, accountId?: string): boolean {\n\t\tconst match =\n\t\t\taccountId !== undefined\n\t\t\t\t? this.accounts.find((a) => a.provider === provider && a.id === accountId)\n\t\t\t\t: this.accounts.find((a) => a.provider === provider);\n\t\tif (!match) return false;\n\t\tthis.activeProvider = provider;\n\t\tthis.activeAccountId = match.id;\n\t\tthis.fire();\n\t\treturn true;\n\t}\n\n\t/** Currently active provider — `null` when no session is active. */\n\tgetActiveProvider(): AuthProvider | null {\n\t\treturn this.activeProvider;\n\t}\n\n\t/** Currently active account metadata — `null` when none. */\n\tgetActiveAccount(): AuthAccountMeta | null {\n\t\tif (!this.activeProvider || !this.activeAccountId) return null;\n\t\treturn (\n\t\t\tthis.accounts.find(\n\t\t\t\t(a) => a.provider === this.activeProvider && a.id === this.activeAccountId\n\t\t\t) ?? null\n\t\t);\n\t}\n\n\t/** True when at least one provider has an account entry. */\n\thasAnySession(): boolean {\n\t\treturn this.accounts.length > 0 && this.activeProvider !== null;\n\t}\n\n\t/** Snapshot of the state for `auth.status` and bridge serialization. */\n\tgetStatus(): AuthStatus {\n\t\tconst grouped: Partial<Record<AuthProvider, AuthAccountMeta>> = {};\n\t\tfor (const account of this.accounts) {\n\t\t\tgrouped[account.provider] = account;\n\t\t}\n\t\treturn {\n\t\t\tactiveProvider: this.activeProvider,\n\t\t\taccounts: grouped,\n\t\t\thasAnySession: this.hasAnySession(),\n\t\t\tlastError: this.lastError ?? undefined,\n\t\t};\n\t}\n\n\t/** Record a non-fatal error from the last login/refresh attempt. */\n\trecordError(message: string): void {\n\t\tthis.lastError = message;\n\t\tthis.fire();\n\t}\n\n\t/** Clear the recorded error (e.g. after a successful login). */\n\tclearError(): void {\n\t\tif (this.lastError === null) return;\n\t\tthis.lastError = null;\n\t\tthis.fire();\n\t}\n\n\t/** Drop every account — used by `auth.logout` with no provider. */\n\tclearAll(): void {\n\t\tthis.accounts = [];\n\t\tthis.activeProvider = null;\n\t\tthis.activeAccountId = null;\n\t\tthis.lastError = null;\n\t\tthis.fire();\n\t}\n\n\tprivate fire(): void {\n\t\tthis.emitter.emit(\"change\");\n\t}\n}\n","/**\n * ProviderRegistry — Maps `AuthProvider` ids to `IAuthProvider` instances.\n *\n * `AuthCore` looks up providers by `AuthProvider` enum (string union\n * per the protocol). The registry is mutable so that callers can\n * replace a provider (e.g. swap a real keyring-backed implementation\n * in tests), but providers must be registered before `AuthCore.login()`\n * is called.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `ProviderRegistry.ts`\n */\n\nimport type { AuthProvider } from \"@serviceme/devtools-protocol\";\n\nimport type { IAuthProvider } from \"./providers/IAuthProvider\";\n\nexport class ProviderRegistry {\n\tprivate readonly providers = new Map<AuthProvider, IAuthProvider>();\n\n\t/** Register or replace a provider implementation. */\n\tregister(provider: IAuthProvider): void {\n\t\tthis.providers.set(provider.providerId, provider);\n\t}\n\n\t/** Look up a registered provider by id; throws when missing. */\n\tget(providerId: AuthProvider): IAuthProvider {\n\t\tconst p = this.providers.get(providerId);\n\t\tif (!p) {\n\t\t\tthrow new Error(`Auth provider not registered: ${providerId}`);\n\t\t}\n\t\treturn p;\n\t}\n\n\t/** Non-throwing lookup; returns `undefined` when the provider is unknown. */\n\ttryGet(providerId: AuthProvider): IAuthProvider | undefined {\n\t\treturn this.providers.get(providerId);\n\t}\n\n\t/** Return all registered provider ids — used by `auth.status` and bridge capability hints. */\n\tlist(): AuthProvider[] {\n\t\treturn [...this.providers.keys()];\n\t}\n\n\t/** True when a provider has been registered for this id. */\n\thas(providerId: AuthProvider): boolean {\n\t\treturn this.providers.has(providerId);\n\t}\n}\n","/**\n * AuthCore — Main entry for the auth domain.\n *\n * Owns the `ProviderRegistry` + `AuthStateManager` + `KeychainAuthTokenStore`.\n * Provides a small, side-effectful surface that CLI / Extension / Bridge\n * handlers can call:\n *\n * - `login(provider)` → drives Device Flow, persists token via store.\n * - `logout(provider?)` → clears the active session (and token bytes).\n * - `status()` → snapshot for `auth.status`.\n * - `whoami()` → minimal identity for `auth.whoami`.\n * - `switchProvider(p)` → flip the active provider (multi-account).\n *\n * Core NEVER holds token bytes past the `KeychainAuthTokenStore.set()`\n * boundary — callers pass the token to the store immediately, then\n * drop the local reference. Per ADL-004 the token bytes only live in\n * (a) SecretStorage / (b) keyring / (c) HTTP Authorization header.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `AuthCore.ts 主入口`\n * - ADL-002 — Device Flow OAuth\n * - ADL-004 — Auth token storage\n */\n\nimport type {\n\tAuthAccountMeta,\n\tAuthLoginResult,\n\tAuthProvider,\n\tAuthStatus,\n\tAuthSwitchResult,\n\tAuthWhoamiResult,\n} from \"@serviceme/devtools-protocol\";\nimport { noopLogger, type ServiceMeLogger } from \"../logger\";\nimport type { AccessCheckResult, AccessControl } from \"./AccessControl\";\nimport { AuthStateManager } from \"./AuthStateManager\";\nimport type { KeychainAuthTokenStore } from \"./KeychainAuthTokenStore\";\nimport { ProviderRegistry } from \"./ProviderRegistry\";\nimport type { IAuthProvider, IAuthProviderSession } from \"./providers/IAuthProvider\";\n\nexport interface AuthCoreOptions {\n\tproviders: IAuthProvider[];\n\ttokenStore: KeychainAuthTokenStore;\n\taccessControl?: AccessControl;\n\tstateManager?: AuthStateManager;\n\t/** Diagnostic logger for login/logout lifecycle events (never receives token bytes). */\n\tlogger?: ServiceMeLogger;\n}\n\n/**\n * Callback invoked once the user finishes the device-flow handshake but\n * BEFORE the token is written to the keychain. Lets the caller display\n * the `userCode` + `verificationUrl` to the user (Phase 5.3 CLI prints to\n * stdout; Phase 5.5 Extension pops a webview notification).\n */\nexport type DeviceFlowUiCallback = (result: AuthLoginResult) => void | Promise<void>;\n\n/**\n * Optional cancellation handle — when `shouldContinue()` returns false,\n * the polling loop exits with a clear error so callers can render\n * \"Login cancelled\" UX.\n */\nexport type CancellationCheck = () => boolean;\n\nexport class AuthCore {\n\tprivate readonly registry: ProviderRegistry;\n\tprivate readonly state: AuthStateManager;\n\tprivate readonly tokenStore: KeychainAuthTokenStore;\n\tprivate readonly accessControl?: AccessControl;\n\tprivate readonly logger: ServiceMeLogger;\n\n\tconstructor(opts: AuthCoreOptions) {\n\t\tthis.registry = new ProviderRegistry();\n\t\tfor (const provider of opts.providers) {\n\t\t\tthis.registry.register(provider);\n\t\t}\n\t\tthis.state = opts.stateManager ?? new AuthStateManager();\n\t\tthis.tokenStore = opts.tokenStore;\n\t\tthis.accessControl = opts.accessControl;\n\t\tthis.logger = opts.logger ?? noopLogger;\n\t}\n\n\t/** Snapshot of every account, the active provider, and the last error. */\n\tstatus(): AuthStatus {\n\t\treturn this.state.getStatus();\n\t}\n\n\t/** List accounts (immutable copy). */\n\tlistAccounts(): AuthAccountMeta[] {\n\t\treturn this.state.listAccounts();\n\t}\n\n\t/**\n\t * Drive the device-flow login for `provider`.\n\t *\n\t * Sequence:\n\t * 1. Ask the provider for the user code + verification URL.\n\t * 2. Surface the code to the user (via `ui`).\n\t * 3. Poll until the user authorizes (or `shouldContinue` aborts).\n\t * 4. Persist the token via `KeychainAuthTokenStore.set()`.\n\t * 5. Insert the resulting `AuthAccountMeta` into state and mark active.\n\t */\n\tasync login(\n\t\tprovider: AuthProvider,\n\t\tui: DeviceFlowUiCallback,\n\t\tshouldContinue?: CancellationCheck\n\t): Promise<AuthLoginResult> {\n\t\tconst providerImpl = this.registry.get(provider);\n\t\tthis.logger.info(\"[AuthCore] Starting device-flow login\", { provider });\n\t\ttry {\n\t\t\tconst initial = await providerImpl.requestDeviceFlow();\n\t\t\tawait ui(initial);\n\t\t\tif (!initial.deviceCode) {\n\t\t\t\tthis.logger.error(\"[AuthCore] Provider did not return deviceCode\", provider);\n\t\t\t\tthrow new Error(\"Auth provider did not return deviceCode for device flow completion\");\n\t\t\t}\n\t\t\tconst session = await providerImpl.completeDeviceFlow(\n\t\t\t\tinitial.deviceCode,\n\t\t\t\tshouldContinue,\n\t\t\t\tinitial.pollIntervalMs\n\t\t\t);\n\t\t\tconst result = await this.persistSession(providerImpl, session);\n\t\t\tthis.logger.info(\"[AuthCore] Device-flow login completed\", { provider });\n\t\t\treturn result;\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tthis.logger.error(\"[AuthCore] Device-flow login failed\", message, {\n\t\t\t\tprovider,\n\t\t\t});\n\t\t\tthis.state.recordError(message);\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\t/**\n\t * Complete the device-flow handshake given a pre-fetched user code.\n\t * Useful when the caller (Phase 5.3 CLI) wants to fetch the code,\n\t * print the URL, and then poll on a subsequent invocation.\n\t */\n\tasync completeLogin(\n\t\tprovider: AuthProvider,\n\t\tdeviceCode: string,\n\t\tshouldContinue?: CancellationCheck\n\t): Promise<AuthLoginResult> {\n\t\tconst providerImpl = this.registry.get(provider);\n\t\ttry {\n\t\t\tconst session = await providerImpl.completeDeviceFlow(deviceCode, shouldContinue);\n\t\t\treturn await this.persistSession(providerImpl, session);\n\t\t} catch (err) {\n\t\t\tthis.state.recordError(err instanceof Error ? err.message : String(err));\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\t/** Resolve the provider + token for the active session; returns null when no session is active. */\n\tasync resolveActiveToken(): Promise<{\n\t\tprovider: AuthProvider;\n\t\taccount: AuthAccountMeta;\n\t\ttoken: string;\n\t} | null> {\n\t\tconst provider = this.state.getActiveProvider();\n\t\tif (!provider) return null;\n\t\tconst account = this.state.getActiveAccount();\n\t\tif (!account) return null;\n\t\tconst envelope = await this.tokenStore.get({\n\t\t\tprovider,\n\t\t\taccountId: account.id,\n\t\t});\n\t\tif (!envelope) return null;\n\t\treturn { provider, account, token: envelope.token };\n\t}\n\n\t/**\n\t * Logout: removes the token bytes from the keychain + drops the\n\t * account entry from state. When `provider` is omitted, clears\n\t * every account.\n\t */\n\tasync logout(\n\t\tprovider?: AuthProvider\n\t): Promise<{ provider: AuthProvider | null; success: boolean }> {\n\t\tthis.logger.info(\"[AuthCore] Logout requested\", {\n\t\t\tprovider: provider ?? \"all\",\n\t\t});\n\t\tif (!provider) {\n\t\t\t// Clear every account's token bytes + metadata.\n\t\t\tfor (const account of this.state.listAccounts()) {\n\t\t\t\tawait this.tokenStore.delete({\n\t\t\t\t\tprovider: account.provider,\n\t\t\t\t\taccountId: account.id,\n\t\t\t\t});\n\t\t\t\tthis.state.removeAccount(account.provider, account.id);\n\t\t\t}\n\t\t\tthis.state.clearAll();\n\t\t\treturn { provider: null, success: true };\n\t\t}\n\n\t\tconst accountsForProvider = this.state.listAccounts().filter((a) => a.provider === provider);\n\t\tlet removed = false;\n\t\tfor (const account of accountsForProvider) {\n\t\t\tawait this.tokenStore.delete({ provider, accountId: account.id });\n\t\t\tconst didRemove = this.state.removeAccount(provider, account.id);\n\t\t\tremoved = removed || didRemove;\n\t\t}\n\t\tif (this.state.getActiveProvider() === provider) {\n\t\t\t// Reset active if we just removed the active provider's account.\n\t\t\tconst firstRemaining = this.state.listAccounts()[0];\n\t\t\tif (firstRemaining) {\n\t\t\t\tthis.state.setActive(firstRemaining.provider);\n\t\t\t}\n\t\t}\n\t\treturn { provider, success: removed };\n\t}\n\n\t/** Minimal identity for `auth.whoami`. */\n\tasync whoami(): Promise<AuthWhoamiResult> {\n\t\tconst provider = this.state.getActiveProvider();\n\t\tconst account = this.state.getActiveAccount();\n\t\tif (!provider || !account) {\n\t\t\treturn { provider: null };\n\t\t}\n\t\treturn {\n\t\t\tprovider,\n\t\t\tlogin: account.login,\n\t\t\tname: account.displayName,\n\t\t\tavatarUrl: account.avatarUrl,\n\t\t\temail: account.email,\n\t\t};\n\t}\n\n\t/** Switch the active provider. Returns the resulting active account. */\n\tswitchProvider(provider: AuthProvider, accountId?: string): AuthSwitchResult {\n\t\tconst ok = this.state.setActive(provider, accountId);\n\t\tconst account = this.state.getActiveAccount();\n\t\treturn {\n\t\t\tactiveProvider: ok ? provider : this.state.getActiveProvider(),\n\t\t\taccount,\n\t\t};\n\t}\n\n\t/** Run the access-control check against the active account (optional, requires AccessControl). */\n\tasync checkAccess(): Promise<AccessCheckResult | null> {\n\t\tif (!this.accessControl) return null;\n\t\tconst account = this.state.getActiveAccount();\n\t\treturn this.accessControl.checkAccess(account);\n\t}\n\n\t/** Expose the state manager for test inspection (not for mutation). */\n\tgetStateManager(): AuthStateManager {\n\t\treturn this.state;\n\t}\n\n\t/** Expose the provider registry for test inspection. */\n\tgetProviderRegistry(): ProviderRegistry {\n\t\treturn this.registry;\n\t}\n\n\t/** Expose the access control (or undefined when not configured). */\n\tgetAccessControl(): AccessControl | undefined {\n\t\treturn this.accessControl;\n\t}\n\n\tprivate async persistSession(\n\t\tproviderImpl: IAuthProvider,\n\t\tsession: IAuthProviderSession\n\t): Promise<AuthLoginResult> {\n\t\tconst expiresAt = session.expiresIn ? Date.now() + session.expiresIn * 1000 : null;\n\t\tconst meta = await providerImpl.fetchAccountMeta(session.token);\n\t\tconst accountMeta: AuthAccountMeta = {\n\t\t\tid: meta.id,\n\t\t\tprovider: meta.provider,\n\t\t\tdisplayName: meta.displayName,\n\t\t\tlogin: meta.login,\n\t\t\temail: meta.email,\n\t\t\tavatarUrl: meta.avatarUrl,\n\t\t\texpiresAt: meta.expiresAt ?? expiresAt,\n\t\t};\n\t\tthis.logger.debug(\"[AuthCore] Persisting session\", {\n\t\t\tprovider: meta.provider,\n\t\t\taccountId: meta.id,\n\t\t});\n\n\t\tawait this.tokenStore.set({ provider: meta.provider, accountId: meta.id }, session.token, {\n\t\t\texpiresAt,\n\t\t});\n\t\tthis.state.upsertAccount(accountMeta);\n\t\tthis.state.setActive(meta.provider, meta.id);\n\t\tthis.state.clearError();\n\t\treturn {\n\t\t\tprovider: meta.provider,\n\t\t\tmessage: `Logged in as ${meta.login ?? meta.id}`,\n\t\t};\n\t}\n}\n","/**\n * KeychainAuthTokenStore — abstract adapter for token byte persistence.\n *\n * Per ADL-004 the OAuth token bytes live in one of three places:\n * (a) VSCode `SecretStorage` (Extension process)\n * (b) `@napi-rs/keyring` Entry (CLI process)\n * (c) HTTP request `Authorization` header\n *\n * `AuthCore` MUST NOT call any of these directly. Instead it receives\n * a `KeychainAuthTokenStore` via DI; CLI provides a `@napi-rs/keyring`\n * adapter, Extension provides a `SecretStorage` adapter. This keeps\n * Core free of native-binding concerns and lets the same business\n * logic power both runtimes.\n *\n * Implementations:\n * - CLI: `apps/serviceme-cli/src/.../KeyringTokenStore.ts` (Phase 5.3)\n * - Extension: `apps/extension/src/.../SecretStorageTokenStore.ts` (Phase 5.5)\n *\n * Failure semantics — `get()` returns `null` when no token is stored\n * (cold-start), throws when the underlying keychain is unavailable\n * (rare on Linux without libsecret). Callers should surface the throw\n * as `AUTH_KEYRING_UNAVAILABLE` rather than silently falling back to\n * in-memory storage (per ADL-004 §不变量).\n *\n * Refs:\n * - ADL-004 — CLI 端 Auth Token 存储选型\n * - 4.功能规划.md §2.1 — \"Core MUST NOT directly depend on `@napi-rs/keyring`\"\n */\n\n/** Opaque account identifier (provider + upstream user id). */\nexport interface KeychainAccountKey {\n\tprovider: string;\n\taccountId: string;\n}\n\n/** Non-secret metadata returned alongside a `get()` so callers can audit. */\nexport interface KeychainTokenMetadata {\n\tprovider: string;\n\taccountId: string;\n\texpiresAt?: number | null;\n\tstoredAt?: number;\n}\n\n/**\n * Result envelope — callers receive the token bytes (for immediate use\n * in an HTTP `Authorization` header) plus optional non-secret metadata.\n *\n * IMPORTANT: the `token` field is plain `string` here, not the\n * `SecretToken` brand. The provider -> store -> HTTP-header pipeline\n * runs inside a single trust boundary; crossing that boundary requires\n * `KeychainTokenEnvelope<SecretToken>` and the `auth.tokenRead`\n * capability gate (see ADL-004 §不变量).\n */\nexport interface KeychainTokenEnvelope {\n\ttoken: string;\n\tmetadata: KeychainTokenMetadata;\n}\n\n/**\n * Abstract token-storage adapter. All methods are async because the\n * keyring / SecretStorage backends are async-by-nature.\n *\n * Thread-safety: implementations MUST be safe for concurrent calls;\n * `AuthCore` will multiplex over multiple providers on the same\n * runtime.\n */\nexport interface KeychainAuthTokenStore {\n\t/** Persist token bytes for the given provider + account pair. */\n\tset(key: KeychainAccountKey, token: string, opts?: { expiresAt?: number | null }): Promise<void>;\n\n\t/** Fetch the token bytes; returns `null` when none is stored. */\n\tget(key: KeychainAccountKey): Promise<KeychainTokenEnvelope | null>;\n\n\t/** Remove the token entry. Idempotent — removing a missing entry is not an error. */\n\tdelete(key: KeychainAccountKey): Promise<void>;\n\n\t/**\n\t * List all stored token keys (metadata only — never the token bytes).\n\t * Useful for multi-account enumeration and bridge `auth.status`.\n\t */\n\tlist(): Promise<KeychainAccountKey[]>;\n\n\t/**\n\t * Health probe — returns `false` when the keychain is unreachable\n\t * (e.g. Linux without libsecret). Callers SHOULD probe on first\n\t * use and surface `AUTH_KEYRING_UNAVAILABLE` rather than degrade.\n\t */\n\tisAvailable(): Promise<boolean>;\n}\n\n/**\n * Sentinel error thrown by `KeychainAuthTokenStore.get()` / `.set()`\n * when the underlying keychain is unavailable. Callers map this to\n * `AUTH_KEYRING_UNAVAILABLE` (Phase 5.3 error code).\n */\nexport class KeychainUnavailableError extends Error {\n\tconstructor(message: string, cause?: unknown) {\n\t\t// Use the native ES2022 `Error` two-arg form instead of redeclaring\n\t\t// `cause` as a class field — a field redeclaration shadows the\n\t\t// inherited member and requires an `override` modifier under\n\t\t// `noImplicitOverride` (downstream consumers like the Extension),\n\t\t// which in turn fails THIS package's own build if its `lib` doesn't\n\t\t// also include `cause` on `Error`. Relying on the built-in\n\t\t// constructor option avoids the mismatch entirely (this package's\n\t\t// `tsconfig.json` targets `lib: [\"ES2022\", \"DOM\"]`, so `cause` is\n\t\t// recognized here too).\n\t\tsuper(message, cause !== undefined ? { cause } : undefined);\n\t\tthis.name = \"KeychainUnavailableError\";\n\t}\n}\n\n/**\n * In-memory `KeychainAuthTokenStore` — test/dev fallback. NEVER use\n * in production: tokens live in process memory and disappear on exit.\n * Production wiring is `KeyringTokenStore` (CLI) / `SecretStorageTokenStore`\n * (Extension).\n */\nexport class InMemoryKeychainAuthTokenStore implements KeychainAuthTokenStore {\n\tprivate readonly entries = new Map<\n\t\tstring,\n\t\t{ token: string; expiresAt: number | null; storedAt: number }\n\t>();\n\n\tprivate compositeKey(key: KeychainAccountKey): string {\n\t\treturn `${key.provider}:${key.accountId}`;\n\t}\n\n\tasync set(\n\t\tkey: KeychainAccountKey,\n\t\ttoken: string,\n\t\topts?: { expiresAt?: number | null }\n\t): Promise<void> {\n\t\tthis.entries.set(this.compositeKey(key), {\n\t\t\ttoken,\n\t\t\texpiresAt: opts?.expiresAt ?? null,\n\t\t\tstoredAt: Date.now(),\n\t\t});\n\t}\n\n\tasync get(key: KeychainAccountKey): Promise<KeychainTokenEnvelope | null> {\n\t\tconst entry = this.entries.get(this.compositeKey(key));\n\t\tif (!entry) return null;\n\t\treturn {\n\t\t\ttoken: entry.token,\n\t\t\tmetadata: {\n\t\t\t\tprovider: key.provider,\n\t\t\t\taccountId: key.accountId,\n\t\t\t\texpiresAt: entry.expiresAt,\n\t\t\t\tstoredAt: entry.storedAt,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync delete(key: KeychainAccountKey): Promise<void> {\n\t\tthis.entries.delete(this.compositeKey(key));\n\t}\n\n\tasync list(): Promise<KeychainAccountKey[]> {\n\t\tconst out: KeychainAccountKey[] = [];\n\t\tfor (const composite of this.entries.keys()) {\n\t\t\tconst sepIdx = composite.indexOf(\":\");\n\t\t\tif (sepIdx <= 0) continue;\n\t\t\tout.push({\n\t\t\t\tprovider: composite.slice(0, sepIdx),\n\t\t\t\taccountId: composite.slice(sepIdx + 1),\n\t\t\t});\n\t\t}\n\t\treturn out;\n\t}\n\n\tasync isAvailable(): Promise<boolean> {\n\t\treturn true;\n\t}\n}\n","/**\n * AuthCore — Pure local utilities for GitHub local email handling.\n *\n * Copied verbatim from `@serviceme/devtools-shared/src/github-user-email.ts` (15 LOC)\n * because ADL-003 forbids Core from depending on `@serviceme/devtools-shared`. These\n * helpers are pure functions with zero side effects, so the duplication is\n * trivial to keep in sync.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — \"utils/githubUserEmail.ts 纯函数,直接搬\"\n * - ADL-003 — `@serviceme/devtools-shared` boundary decision\n */\n\n/**\n * Returns true when the supplied address is a synthetic GitHub \"local\" email\n * (suffixed with `@github.local`). Real GitHub OAuth clients often return\n * `null` for `email` (user kept it private) and the application substitutes\n * `<login>@github.local` to keep the field non-null.\n */\nexport function isGitHubLocalEmail(email: string | null | undefined): boolean {\n\treturn typeof email === \"string\" && email.trim().toLowerCase().endsWith(\"@github.local\");\n}\n\n/** Build a synthetic `<login>@github.local` address. */\nexport function buildGitHubLocalEmail(login: string): string {\n\treturn `${login}@github.local`;\n}\n\n/**\n * Resolve the user's primary email address — falling back to the synthetic\n * `<login>@github.local` form when the upstream email is missing or blank.\n */\nexport function resolvePrimaryEmail(login: string, email: string | null | undefined): string {\n\tconst normalizedEmail = email?.trim();\n\treturn normalizedEmail || buildGitHubLocalEmail(login);\n}\n","/**\n * GitHubAuthProvider — Device Flow OAuth implementation.\n *\n * Implements the GitHub OAuth Device Flow per the official spec\n * (https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow).\n *\n * Per ADL-002 the device flow is the locked authentication strategy for\n * SERVICEME — no localhost callback server, no PAT. This class is a\n * pure HTTP client (uses native `fetch`, no vscode dependency) that\n * returns the token bytes for the caller's `KeychainAuthTokenStore` to\n * persist immediately.\n *\n * Token bytes NEVER enter logs / errors / debug output. The provider\n * exposes only the user-visible code + verification URL during the\n * device-flow handshake.\n *\n * Refs:\n * - ADL-002 — Device Flow OAuth (locked)\n * - 4.功能规划.md §2.1 — `providers/GitHubAuthProvider.ts`\n */\n\nimport type { AuthAccountMeta, AuthLoginResult, AuthProvider } from \"@serviceme/devtools-protocol\";\nimport { noopLogger, type ServiceMeLogger } from \"../../logger\";\nimport { buildGitHubLocalEmail } from \"../utils/githubUserEmail\";\nimport type { IAuthProvider, IAuthProviderSession } from \"./IAuthProvider\";\n\nconst DEFAULT_DEVICE_CODE_URL = \"https://github.com/login/device/code\";\nconst DEFAULT_TOKEN_URL = \"https://github.com/login/oauth/access_token\";\nconst DEFAULT_USER_URL = \"https://api.github.com/user\";\nconst DEFAULT_SCOPE = \"read:user user:email\";\n\n/** Attempts for the one-shot device-code request before surfacing a network error. */\nconst DEVICE_CODE_MAX_ATTEMPTS = 3;\nconst DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS = 500;\n\n/**\n * `fetch` (both browser and Node's undici) rejects with a `TypeError`\n * (e.g. \"fetch failed\") when the underlying connection never completes —\n * DNS failure, TLS handshake reset, ECONNRESET, etc. These are transient\n * network hiccups, distinct from a well-formed non-2xx HTTP response, and\n * should be retried rather than immediately failing the whole login.\n */\nfunction isTransientNetworkError(error: unknown): boolean {\n\treturn error instanceof TypeError;\n}\n\n/** Tunable provider config — exposed for tests + forks. */\nexport interface GitHubAuthProviderConfig {\n\tclientId: string;\n\tdeviceCodeUrl?: string;\n\ttokenUrl?: string;\n\tuserUrl?: string;\n\tscope?: string;\n\t/** Polling interval (ms) floor. The device-flow response's `interval` wins when larger. */\n\tminPollIntervalMs?: number;\n\t/**\n\t * Maximum poll interval after back-off. Must stay generous — GitHub's\n\t * `slow_down` response requires adding a fixed step to the interval each\n\t * time it occurs (see `completeDeviceFlow`), and capping this too low\n\t * prevents the interval from ever satisfying GitHub's actual requirement,\n\t * causing a permanent `slow_down` loop that never clears even after the\n\t * user authorizes.\n\t */\n\tmaxPollIntervalMs?: number;\n\t/** Fetch override (for tests + DI). */\n\tfetchImpl?: typeof fetch;\n\t/** Poll-delay override (for tests + DI). Defaults to a real timer-based sleep. */\n\tsleepImpl?: (ms: number) => Promise<void>;\n\t/** Maximum wall-clock time to wait for the user before giving up (ms). Defaults to `expires_in * 1000`. */\n\tmaxWaitMs?: number;\n\t/** Base delay (ms) before retrying the device-code request after a transient network error. */\n\tdeviceCodeRetryBaseDelayMs?: number;\n\t/**\n\t * Diagnostic logger — never receives token bytes or the raw `device_code`,\n\t * only user-facing codes, HTTP statuses, and poll-loop timing so callers\n\t * can troubleshoot a stuck / slow device-flow login.\n\t */\n\tlogger?: ServiceMeLogger;\n}\n\ninterface DeviceCodeResponse {\n\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\texpires_in: number;\n\tinterval: number;\n}\n\ninterface TokenPollResponse {\n\taccess_token?: string;\n\trefresh_token?: string;\n\texpires_in?: number;\n\ttoken_type?: string;\n\tscope?: string;\n\terror?: string;\n\terror_description?: string;\n\terror_uri?: string;\n}\n\ninterface GitHubUserResponse {\n\tid: number;\n\tlogin: string;\n\tname?: string | null;\n\temail?: string | null;\n\tavatar_url?: string | null;\n}\n\nexport class GitHubAuthProvider implements IAuthProvider {\n\treadonly providerId: AuthProvider = \"github\";\n\tprivate readonly cfg: Required<\n\t\tOmit<GitHubAuthProviderConfig, \"maxWaitMs\" | \"fetchImpl\" | \"logger\" | \"sleepImpl\">\n\t> & {\n\t\tmaxWaitMs?: number;\n\t\tfetchImpl: typeof fetch;\n\t};\n\tprivate readonly logger: ServiceMeLogger;\n\tprivate readonly sleepImpl: (ms: number) => Promise<void>;\n\n\tconstructor(config: GitHubAuthProviderConfig) {\n\t\tif (!config.clientId || config.clientId.length === 0) {\n\t\t\tthrow new Error(\"GitHubAuthProvider requires a non-empty `clientId`\");\n\t\t}\n\t\tthis.cfg = {\n\t\t\tclientId: config.clientId,\n\t\t\tdeviceCodeUrl: config.deviceCodeUrl ?? DEFAULT_DEVICE_CODE_URL,\n\t\t\ttokenUrl: config.tokenUrl ?? DEFAULT_TOKEN_URL,\n\t\t\tuserUrl: config.userUrl ?? DEFAULT_USER_URL,\n\t\t\tscope: config.scope ?? DEFAULT_SCOPE,\n\t\t\tminPollIntervalMs: config.minPollIntervalMs ?? 1000,\n\t\t\tmaxPollIntervalMs: config.maxPollIntervalMs ?? 60000,\n\t\t\tmaxWaitMs: config.maxWaitMs,\n\t\t\tfetchImpl: config.fetchImpl ?? fetch,\n\t\t\tdeviceCodeRetryBaseDelayMs:\n\t\t\t\tconfig.deviceCodeRetryBaseDelayMs ?? DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS,\n\t\t};\n\t\tthis.logger = config.logger ?? noopLogger;\n\t\tthis.sleepImpl = config.sleepImpl ?? sleep;\n\t}\n\n\tasync requestDeviceFlow(opts?: { scope?: string }): Promise<AuthLoginResult> {\n\t\tconst body = JSON.stringify({\n\t\t\tclient_id: this.cfg.clientId,\n\t\t\tscope: opts?.scope ?? this.cfg.scope,\n\t\t});\n\n\t\tlet lastNetworkError: unknown;\n\t\tfor (let attempt = 1; attempt <= DEVICE_CODE_MAX_ATTEMPTS; attempt++) {\n\t\t\tthis.logger.debug(\"[GitHubAuthProvider] Requesting device code\", {\n\t\t\t\tattempt,\n\t\t\t\tmaxAttempts: DEVICE_CODE_MAX_ATTEMPTS,\n\t\t\t});\n\t\t\tlet resp: Response;\n\t\t\ttry {\n\t\t\t\tresp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t\t\t},\n\t\t\t\t\tbody,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tif (!isTransientNetworkError(error) || attempt === DEVICE_CODE_MAX_ATTEMPTS) {\n\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\"[GitHubAuthProvider] Device-code request failed (non-retryable)\",\n\t\t\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t{ attempt }\n\t\t\t\t\t);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tconst delayMs = this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1);\n\t\t\t\tthis.logger.warn(\n\t\t\t\t\t\"[GitHubAuthProvider] Transient network error requesting device code, retrying\",\n\t\t\t\t\t{\n\t\t\t\t\t\tattempt,\n\t\t\t\t\t\tdelayMs,\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t\tlastNetworkError = error;\n\t\t\t\tawait this.sleepImpl(delayMs);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!resp.ok) {\n\t\t\t\tthis.logger.error(\n\t\t\t\t\t\"[GitHubAuthProvider] Device-code request rejected by GitHub\",\n\t\t\t\t\t`HTTP ${resp.status}`\n\t\t\t\t);\n\t\t\t\tthrow new Error(`GitHub device-code request failed: HTTP ${resp.status}`);\n\t\t\t}\n\t\t\tconst data = (await resp.json()) as DeviceCodeResponse;\n\t\t\tif (!data.device_code || !data.user_code || !data.verification_uri) {\n\t\t\t\tthis.logger.error(\n\t\t\t\t\t\"[GitHubAuthProvider] Device-code response missing required fields\",\n\t\t\t\t\tJSON.stringify(Object.keys(data))\n\t\t\t\t);\n\t\t\t\tthrow new Error(\"GitHub device-code response missing required fields\");\n\t\t\t}\n\t\t\tthis.logger.info(\"[GitHubAuthProvider] Device code obtained\", {\n\t\t\t\tuserCode: data.user_code,\n\t\t\t\tverificationUri: data.verification_uri,\n\t\t\t\texpiresInSec: data.expires_in,\n\t\t\t\tpollIntervalSec: data.interval,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tprovider: this.providerId,\n\t\t\t\tdeviceCode: data.device_code,\n\t\t\t\tuserCode: data.user_code,\n\t\t\t\tverificationUrl: data.verification_uri,\n\t\t\t\texpiresAt: Date.now() + data.expires_in * 1000,\n\t\t\t\tpollIntervalMs: data.interval * 1000,\n\t\t\t\tmessage: `Open ${data.verification_uri} and enter ${data.user_code}`,\n\t\t\t};\n\t\t}\n\t\t// Unreachable in practice — the loop always returns or throws — but keeps\n\t\t// the type checker happy and surfaces the last transient error if hit.\n\t\tthrow lastNetworkError ?? new Error(\"GitHub device-code request failed\");\n\t}\n\n\tasync completeDeviceFlow(\n\t\tdeviceCode: string,\n\t\tshouldContinue?: () => boolean,\n\t\tinitialPollIntervalMs?: number\n\t): Promise<IAuthProviderSession> {\n\t\tconst start = Date.now();\n\t\t// Honor GitHub's declared `interval` (from the matching `requestDeviceFlow()`\n\t\t// response) as the starting rate — polling faster than this can put the\n\t\t// device code into a `slow_down` state that never clears (see class docs).\n\t\tlet pollIntervalMs = Math.max(this.cfg.minPollIntervalMs, initialPollIntervalMs ?? 0);\n\t\tlet consecutiveSlowDown = 0;\n\t\tlet pollCount = 0;\n\t\tthis.logger.info(\"[GitHubAuthProvider] Starting device-flow polling\", {\n\t\t\tinitialPollIntervalMs: pollIntervalMs,\n\t\t\tmaxWaitMs: this.cfg.maxWaitMs,\n\t\t});\n\n\t\twhile (true) {\n\t\t\tif (shouldContinue && !shouldContinue()) {\n\t\t\t\tthis.logger.info(\"[GitHubAuthProvider] Device flow cancelled by caller\", {\n\t\t\t\t\telapsedMs: Date.now() - start,\n\t\t\t\t\tpollCount,\n\t\t\t\t});\n\t\t\t\tthrow new Error(\"GitHub device flow cancelled by caller\");\n\t\t\t}\n\t\t\tconst elapsed = Date.now() - start;\n\t\t\tif (this.cfg.maxWaitMs !== undefined && elapsed > this.cfg.maxWaitMs) {\n\t\t\t\tthis.logger.warn(\"[GitHubAuthProvider] Device flow exceeded max wait time\", {\n\t\t\t\t\telapsedMs: elapsed,\n\t\t\t\t\tmaxWaitMs: this.cfg.maxWaitMs,\n\t\t\t\t\tpollCount,\n\t\t\t\t});\n\t\t\t\tthrow new Error(\"GitHub device flow exceeded max wait time\");\n\t\t\t}\n\t\t\tawait this.sleepImpl(pollIntervalMs);\n\t\t\tif (shouldContinue && !shouldContinue()) {\n\t\t\t\tthis.logger.info(\"[GitHubAuthProvider] Device flow cancelled by caller\", {\n\t\t\t\t\telapsedMs: Date.now() - start,\n\t\t\t\t\tpollCount,\n\t\t\t\t});\n\t\t\t\tthrow new Error(\"GitHub device flow cancelled by caller\");\n\t\t\t}\n\n\t\t\tpollCount++;\n\t\t\tthis.logger.debug(\"[GitHubAuthProvider] Polling for authorization\", {\n\t\t\t\tpollCount,\n\t\t\t\telapsedMs: Date.now() - start,\n\t\t\t\tpollIntervalMs,\n\t\t\t});\n\t\t\tlet resp: Response;\n\t\t\ttry {\n\t\t\t\tresp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\tclient_id: this.cfg.clientId,\n\t\t\t\t\t\tdevice_code: deviceCode,\n\t\t\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tif (!isTransientNetworkError(error)) {\n\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\"[GitHubAuthProvider] Non-retryable error while polling token endpoint\",\n\t\t\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t{ pollCount }\n\t\t\t\t\t);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\t// Transient network failure (DNS/TLS/reset) — back off and retry\n\t\t\t\t// polling until the user acts, same as a non-2xx HTTP response.\n\t\t\t\tconst nextPollIntervalMs = Math.min(\n\t\t\t\t\tMath.round(pollIntervalMs * 1.5),\n\t\t\t\t\tthis.cfg.maxPollIntervalMs\n\t\t\t\t);\n\t\t\t\tthis.logger.warn(\n\t\t\t\t\t\"[GitHubAuthProvider] Transient network error while polling, backing off\",\n\t\t\t\t\t{\n\t\t\t\t\t\tpollCount,\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\tpreviousPollIntervalMs: pollIntervalMs,\n\t\t\t\t\t\tnextPollIntervalMs,\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t\tpollIntervalMs = nextPollIntervalMs;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!resp.ok) {\n\t\t\t\t// Transient HTTP failure — back off and retry until the user acts.\n\t\t\t\tconst nextPollIntervalMs = Math.min(\n\t\t\t\t\tMath.round(pollIntervalMs * 1.5),\n\t\t\t\t\tthis.cfg.maxPollIntervalMs\n\t\t\t\t);\n\t\t\t\tthis.logger.warn(\n\t\t\t\t\t\"[GitHubAuthProvider] Token endpoint returned non-OK status, backing off\",\n\t\t\t\t\t{\n\t\t\t\t\t\tpollCount,\n\t\t\t\t\t\tstatus: resp.status,\n\t\t\t\t\t\tpreviousPollIntervalMs: pollIntervalMs,\n\t\t\t\t\t\tnextPollIntervalMs,\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t\tpollIntervalMs = nextPollIntervalMs;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst data = (await resp.json()) as TokenPollResponse;\n\t\t\tif (data.access_token) {\n\t\t\t\tthis.logger.info(\"[GitHubAuthProvider] Authorization granted, fetching user profile\", {\n\t\t\t\t\tpollCount,\n\t\t\t\t\telapsedMs: Date.now() - start,\n\t\t\t\t});\n\t\t\t\tconst rawUser = await this.fetchGitHubUser(data.access_token);\n\t\t\t\tconst email = await this.resolveEmail(data.access_token, rawUser);\n\t\t\t\tthis.logger.info(\"[GitHubAuthProvider] Device-flow login completed\", {\n\t\t\t\t\tpollCount,\n\t\t\t\t\telapsedMs: Date.now() - start,\n\t\t\t\t\tlogin: rawUser.login,\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\ttoken: data.access_token,\n\t\t\t\t\trefreshToken: data.refresh_token,\n\t\t\t\t\texpiresIn: data.expires_in,\n\t\t\t\t\tuser: {\n\t\t\t\t\t\tid: String(rawUser.id),\n\t\t\t\t\t\tlogin: rawUser.login,\n\t\t\t\t\t\tname: rawUser.name ?? undefined,\n\t\t\t\t\t\temail: email ?? null,\n\t\t\t\t\t\tavatarUrl: rawUser.avatar_url ?? undefined,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (data.error === \"authorization_pending\") {\n\t\t\t\t// User hasn't acted yet — keep polling without back-off inflation.\n\t\t\t\tthis.logger.debug(\"[GitHubAuthProvider] Authorization still pending\", {\n\t\t\t\t\tpollCount,\n\t\t\t\t\telapsedMs: Date.now() - start,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (data.error === \"slow_down\") {\n\t\t\t\tconsecutiveSlowDown++;\n\t\t\t\t// Per the OAuth Device Flow spec: on `slow_down`, add a fixed 5\n\t\t\t\t// seconds to the polling interval (not a multiplicative back-off).\n\t\t\t\t// Repeatedly under-shooting this requirement is what causes a\n\t\t\t\t// device code to get stuck returning `slow_down` forever.\n\t\t\t\tconst nextPollIntervalMs = Math.min(pollIntervalMs + 5000, this.cfg.maxPollIntervalMs);\n\t\t\t\tthis.logger.warn(\"[GitHubAuthProvider] GitHub requested slower polling (slow_down)\", {\n\t\t\t\t\tpollCount,\n\t\t\t\t\tconsecutiveSlowDown,\n\t\t\t\t\tpreviousPollIntervalMs: pollIntervalMs,\n\t\t\t\t\tnextPollIntervalMs,\n\t\t\t\t});\n\t\t\t\tpollIntervalMs = nextPollIntervalMs;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (data.error === \"access_denied\") {\n\t\t\t\tthis.logger.warn(\"[GitHubAuthProvider] User denied authorization\", {\n\t\t\t\t\tpollCount,\n\t\t\t\t});\n\t\t\t\tthrow new Error(\"User denied authorization\");\n\t\t\t}\n\t\t\tif (data.error === \"expired_token\") {\n\t\t\t\tthis.logger.warn(\n\t\t\t\t\t\"[GitHubAuthProvider] Device code expired before authorization completed\",\n\t\t\t\t\t{ pollCount, elapsedMs: Date.now() - start }\n\t\t\t\t);\n\t\t\t\tthrow new Error(\"GitHub device code expired — restart the flow\");\n\t\t\t}\n\t\t\tthis.logger.error(\n\t\t\t\t\"[GitHubAuthProvider] Unexpected device-flow error from token endpoint\",\n\t\t\t\tdata.error ?? \"unknown\",\n\t\t\t\t{ pollCount }\n\t\t\t);\n\t\t\tthrow new Error(`GitHub device flow error: ${data.error ?? \"unknown\"}`);\n\t\t}\n\t}\n\n\tasync refreshAccessToken(refreshToken: string): Promise<IAuthProviderSession> {\n\t\tif (!refreshToken) {\n\t\t\tthrow new Error(\"refreshAccessToken requires a non-empty refresh token\");\n\t\t}\n\t\tconst resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tclient_id: this.cfg.clientId,\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t}),\n\t\t});\n\t\tif (!resp.ok) {\n\t\t\tthrow new Error(`GitHub refresh failed: HTTP ${resp.status}`);\n\t\t}\n\t\tconst data = (await resp.json()) as TokenPollResponse;\n\t\tif (!data.access_token) {\n\t\t\tthrow new Error(`GitHub refresh failed: ${data.error ?? \"no_access_token\"}`);\n\t\t}\n\t\tconst rawUser = await this.fetchGitHubUser(data.access_token);\n\t\tconst email = await this.resolveEmail(data.access_token, rawUser);\n\t\treturn {\n\t\t\ttoken: data.access_token,\n\t\t\trefreshToken: data.refresh_token ?? refreshToken,\n\t\t\texpiresIn: data.expires_in,\n\t\t\tuser: {\n\t\t\t\tid: String(rawUser.id),\n\t\t\t\tlogin: rawUser.login,\n\t\t\t\tname: rawUser.name ?? undefined,\n\t\t\t\temail: email ?? null,\n\t\t\t\tavatarUrl: rawUser.avatar_url ?? undefined,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync validateToken(token: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst resp = await this.cfg.fetchImpl(this.cfg.userUrl, {\n\t\t\t\theaders: {\n\t\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t\t},\n\t\t\t});\n\t\t\tif (resp.status === 401) return false;\n\t\t\tif (!resp.ok) {\n\t\t\t\tthrow new Error(`GitHub /user returned HTTP ${resp.status}`);\n\t\t\t}\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\t// Network / parse errors are surfaced; only 401 means invalid.\n\t\t\tif (err instanceof Error && err.message.includes(\"401\")) return false;\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync fetchAccountMeta(token: string): Promise<AuthAccountMeta> {\n\t\tconst user = await this.fetchGitHubUser(token);\n\t\tconst email = await this.resolveEmail(token, user);\n\t\treturn {\n\t\t\tid: String(user.id),\n\t\t\tprovider: this.providerId,\n\t\t\tdisplayName: user.name ?? user.login,\n\t\t\tlogin: user.login,\n\t\t\temail: email ?? buildGitHubLocalEmail(user.login),\n\t\t\tavatarUrl: user.avatar_url ?? undefined,\n\t\t\texpiresAt: null,\n\t\t};\n\t}\n\n\tprivate async fetchGitHubUser(token: string): Promise<GitHubUserResponse> {\n\t\tconst resp = await this.cfg.fetchImpl(this.cfg.userUrl, {\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t});\n\t\tif (!resp.ok) {\n\t\t\tthrow new Error(`GitHub /user failed: HTTP ${resp.status}`);\n\t\t}\n\t\treturn (await resp.json()) as GitHubUserResponse;\n\t}\n\n\t/**\n\t * GitHub's `/user` endpoint returns `null` when the user kept their\n\t * email private. The `/user/emails` endpoint reveals verified emails;\n\t * we pick the primary one, falling back to the synthetic\n\t * `<login>@github.local` form so the account is never email-less.\n\t */\n\tprivate async resolveEmail(token: string, user: GitHubUserResponse): Promise<string | null> {\n\t\tif (user.email) return user.email;\n\t\tconst emailsResp = await this.cfg.fetchImpl(\"https://api.github.com/user/emails\", {\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t});\n\t\tif (!emailsResp.ok) {\n\t\t\treturn null;\n\t\t}\n\t\tconst emails = (await emailsResp.json()) as Array<{\n\t\t\temail: string;\n\t\t\tprimary: boolean;\n\t\t\tvisibility: string | null;\n\t\t\tverified: boolean;\n\t\t}>;\n\t\tconst primary = emails.find((e) => e.primary && e.verified);\n\t\treturn primary?.email ?? null;\n\t}\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * MicrosoftAuthProvider — Microsoft Account (MSA) OAuth Device Flow stub.\n *\n * The Extension's existing `MicrosoftAuthProvider` (`apps/extension/src/services/auth/providers/MicrosoftAuthProvider.ts`)\n * delegates to VSCode's built-in `vscode.authentication.getSession()` API\n * — there's no direct Microsoft device-flow endpoint exposed for\n * first-party apps. To keep Core usable from the CLI without VSCode,\n * we expose a minimal stub that throws \"not implemented in Core\" so\n * callers can detect and route back to the Extension adapter.\n *\n * The reason this lives in Core at all (instead of being purely\n * Extension-side) is the cross-runtime registry: `AuthCore` needs to\n * know which providers it MIGHT support, even if only the Extension\n * process can actually fulfil the Microsoft login.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `providers/MicrosoftAuthProvider.ts 从 extension 整体迁移`\n * - ADL-002 — Device Flow OAuth (locked, GitHub-only)\n */\n\nimport type { AuthAccountMeta, AuthLoginResult, AuthProvider } from \"@serviceme/devtools-protocol\";\n\nimport type { IAuthProvider, IAuthProviderSession } from \"./IAuthProvider\";\n\n/**\n * Error thrown when callers invoke Microsoft auth from a non-Extension\n * runtime (CLI / Bridge). The CLI maps this to `AUTH_PROVIDER_REQUIRES_HOST`\n * so the user is prompted to retry inside VSCode.\n */\nexport class MicrosoftProviderNotHostedError extends Error {\n\tconstructor(\n\t\tmessage = \"Microsoft auth requires the VSCode host; CLI does not yet implement MSA device flow\"\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"MicrosoftProviderNotHostedError\";\n\t}\n}\n\nexport class MicrosoftAuthProvider implements IAuthProvider {\n\treadonly providerId: AuthProvider = \"microsoft\";\n\n\tasync requestDeviceFlow(): Promise<AuthLoginResult> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync completeDeviceFlow(\n\t\t_deviceCode: string,\n\t\t_shouldContinue?: () => boolean\n\t): Promise<IAuthProviderSession> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync refreshAccessToken(_refreshToken: string): Promise<IAuthProviderSession> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync validateToken(_token: string): Promise<boolean> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync fetchAccountMeta(_token: string): Promise<AuthAccountMeta> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACkEA,IAAM,uBAAuB,KAAK,KAAK;AACvC,IAAM,kCAAkC,KAAK,KAAK,KAAK;AACvD,IAAM,oBAAoB;AAC1B,IAAM,uBAAuB;AAC7B,IAAM,oBAAoB;AAWnB,IAAM,gBAAN,MAAoB;AAAA,EAa1B,YACkB,IACjB,SACC;AAFgB;AAblB,SAAiB,WAAW,oBAAI,IAAwB;AACxD,SAAiB,iBAAiB,oBAAI,IAAqB;AAC3D,SAAiB,YAAY,oBAAI,IAAgB;AAchD,SAAK,OAAO;AAAA,MACX,KAAK,QAAQ;AAAA,MACb,cAAc,QAAQ;AAAA,MACtB,YAAY,QAAQ;AAAA,MACpB,uBAAuB,QAAQ,yBAAyB,CAAC;AAAA,MACzD,YAAY,QAAQ,cAAc;AAAA,MAClC,sBAAsB,QAAQ,wBAAwB;AAAA,MACtD,KAAK,QAAQ,QAAQ,MAAM,KAAK,IAAI;AAAA,IACrC;AAEA,UAAM,uBAAuB,KAAK,GAAG,IAA6B,iBAAiB;AACnF,QAAI,sBAAsB;AACzB,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,oBAAoB,GAAG;AAChE,aAAK,eAAe,IAAI,KAAK,KAAK;AAAA,MACnC;AAAA,IACD;AAAA,EACD;AAAA;AAAA,EAGA,YAAY,UAAkC;AAC7C,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM,KAAK,UAAU,OAAO,QAAQ;AAAA,EAC5C;AAAA;AAAA,EAGA,MAAM,YAAY,MAA0D;AAC3E,QAAI,CAAC,MAAM;AACV,aAAO,EAAE,SAAS,OAAO,QAAQ,oBAAoB;AAAA,IACtD;AAGA,QAAI,KAAK,aAAa,eAAe,KAAK,OAAO;AAChD,YAAM,QAAQ,KAAK,eAAe,IAAI,KAAK,MAAM,YAAY,CAAC;AAC9D,UAAI,UAAU,KAAM,QAAO,EAAE,SAAS,KAAK;AAC3C,UAAI,UAAU,OAAO;AACpB,eAAO,EAAE,SAAS,OAAO,QAAQ,cAAc,UAAU,KAAK,MAAM;AAAA,MACrE;AAAA,IACD;AAGA,UAAM,gBAAgB,KAAK,aAAa,WAAW,OAAO;AAC1D,QAAI,CAAC,iBAAiB,KAAK,aAAa,eAAe,KAAK,OAAO;AAClE,YAAM,CAAC,EAAE,SAAS,EAAE,IAAI,KAAK,MAAM,MAAM,GAAG;AAC5C,YAAM,iBACL,OAAO,SAAS,KAAK,KAAK,KAAK,sBAAsB,SAAS,OAAO,YAAY,CAAC;AACnF,UAAI,eAAgB,QAAO,EAAE,SAAS,KAAK;AAE3C,aAAO,EAAE,SAAS,OAAO,QAAQ,cAAc,UAAU,KAAK,MAAM;AAAA,IACrE;AACA,QAAI,CAAC,eAAe;AAEnB,aAAO,EAAE,SAAS,KAAK;AAAA,IACxB;AAEA,UAAM,iBAAiB,cAAc,SAAS,KAAK,SAAS;AAC5D,QAAI,eAAe,WAAW,GAAG;AAChC,aAAO,EAAE,SAAS,OAAO,QAAQ,cAAc,UAAU,GAAG;AAAA,IAC7D;AAGA,UAAM,UAAU,KAAK,gBAAgB;AACrC,QAAI,QAAQ,SAAS,eAAe,YAAY,CAAC,GAAG;AACnD,aAAO,EAAE,SAAS,KAAK;AAAA,IACxB;AAEA,UAAM,WAAW,MAAM,KAAK,mBAAmB,cAAc;AAC7D,WAAO,WACJ,EAAE,SAAS,KAAK,IAChB,EAAE,SAAS,OAAO,QAAQ,cAAc,UAAU,eAAe;AAAA,EACrE;AAAA;AAAA,EAGA,MAAM,YAAY,UAAiC;AAClD,UAAM,OAAO,KAAK,gBAAgB;AAClC,UAAM,MAAM,SAAS,YAAY;AACjC,QAAI,CAAC,KAAK,SAAS,GAAG,GAAG;AACxB,WAAK,KAAK,GAAG;AACb,YAAM,KAAK,GAAG,OAAO,mBAAmB,IAAI;AAAA,IAC7C;AAAA,EACD;AAAA;AAAA,EAGA,MAAM,aAAa,UAAiC;AACnD,UAAM,UAAU,KAAK,gBAAgB,EAAE,OAAO,CAAC,MAAM,MAAM,SAAS,YAAY,CAAC;AACjF,UAAM,KAAK,GAAG,OAAO,mBAAmB,OAAO;AAAA,EAChD;AAAA;AAAA,EAGA,kBAA4B;AAC3B,WAAO,KAAK,GAAG,IAAc,iBAAiB,KAAK,CAAC;AAAA,EACrD;AAAA;AAAA,EAGA,MAAM,mBAAmB,UAAoC;AAC5D,UAAM,MAAM,KAAK,KAAK,IAAI;AAG1B,UAAM,QAAQ,KAAK,SAAS,IAAI,QAAQ;AACxC,QAAI,SAAS,MAAM,MAAM,KAAK,KAAK,KAAK,YAAY;AACnD,aAAO,MAAM;AAAA,IACd;AAGA,UAAM,aAAa,KAAK,GAAG,IAAqB,oBAAoB,KAAK,CAAC;AAC1E,UAAM,kBAAkB,WAAW,QAAQ;AAC3C,QAAI,mBAAmB,MAAM,gBAAgB,KAAK,KAAK,KAAK,sBAAsB;AACjF,WAAK,SAAS,IAAI,UAAU,eAAe;AAC3C,aAAO,gBAAgB;AAAA,IACxB;AAGA,UAAM,QAAQ,MAAM,KAAK,KAAK,aAAa,QAAQ;AACnD,QAAI,CAAC,MAAO,QAAO;AAEnB,QAAI;AACH,YAAM,SAAS,MAAM,KAAK,KAAK,WAAW,OAAO,KAAK,KAAK,GAAG;AAC9D,YAAM,WAAW,KAAK,gBAAgB,UAAU,MAAM;AACtD,UAAI,OAAO,aAAa,WAAW;AAClC,aAAK,YAAY,UAAU,QAAQ;AACnC,eAAO;AAAA,MACR;AAEA,aAAO;AAAA,IACR,QAAQ;AAEP,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA,EAGA,eAAe,UAAkB,OAAsB;AACtD,UAAM,MAAM,SAAS,YAAY;AACjC,SAAK,eAAe,IAAI,KAAK,KAAK;AAClC,UAAM,YAAY,KAAK,GAAG,IAA6B,iBAAiB,KAAK,CAAC;AAC9E,cAAU,GAAG,IAAI;AACjB,SAAK,KAAK,GAAG,OAAO,mBAAmB,SAAS;AAChD,SAAK,gBAAgB;AAAA,EACtB;AAAA;AAAA,EAGA,eAAe,UAAuC;AACrD,WAAO,KAAK,eAAe,IAAI,SAAS,YAAY,CAAC;AAAA,EACtD;AAAA,EAEQ,gBACP,WACA,YACsB;AACtB,QAAI,WAAW,WAAW,YAAY,WAAW,WAAW,UAAW,QAAO;AAC9E,QAAI,WAAW,WAAW,aAAc,QAAO;AAC/C,WAAO;AAAA,EACR;AAAA,EAEQ,YAAY,UAAkB,UAAyB;AAC9D,UAAM,KAAK,KAAK,KAAK,IAAI;AACzB,SAAK,SAAS,IAAI,UAAU,EAAE,UAAU,GAAG,CAAC;AAC5C,UAAM,aAAa,KAAK,GAAG,IAAqB,oBAAoB,KAAK,CAAC;AAC1E,eAAW,QAAQ,IAAI,EAAE,UAAU,GAAG;AACtC,SAAK,KAAK,GAAG,OAAO,sBAAsB,UAAU;AAAA,EACrD;AAAA,EAEQ,kBAAwB;AAC/B,eAAW,YAAY,KAAK,WAAW;AACtC,UAAI;AACH,iBAAS;AAAA,MACV,QAAQ;AAAA,MAER;AAAA,IACD;AAAA,EACD;AACD;;;ACrQO,IAAM,aAA8B;AAAA,EAC1C,QAAQ;AAAA,EAAC;AAAA,EACT,OAAO;AAAA,EAAC;AAAA,EACR,OAAO;AAAA,EAAC;AAAA,EACR,QAAQ;AAAA,EAAC;AACV;;;ACMA,yBAA6B;AAatB,IAAM,mBAAN,MAAuB;AAAA,EAO7B,YAAY,OAAgC,CAAC,GAAG;AANhD,SAAiB,UAAU,IAAI,gCAAa;AAC5C,SAAQ,WAA8B,CAAC;AACvC,SAAQ,iBAAsC;AAC9C,SAAQ,kBAAiC;AACzC,SAAQ,YAA2B;AAGlC,QAAI,KAAK,iBAAiB,QAAW;AACpC,WAAK,QAAQ,gBAAgB,KAAK,YAAY;AAAA,IAC/C;AAAA,EACD;AAAA;AAAA,EAGA,YAAY,UAAkC;AAC7C,SAAK,QAAQ,GAAG,UAAU,QAAQ;AAClC,WAAO,MAAM,KAAK,QAAQ,IAAI,UAAU,QAAQ;AAAA,EACjD;AAAA;AAAA,EAGA,eAAkC;AACjC,WAAO,CAAC,GAAG,KAAK,QAAQ;AAAA,EACzB;AAAA;AAAA,EAGA,YAAY,UAAwB,WAA2C;AAC9E,WAAO,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,YAAY,EAAE,OAAO,SAAS,KAAK;AAAA,EACpF;AAAA;AAAA,EAGA,qBAAqB,UAAgD;AACpE,WAAO,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ,KAAK;AAAA,EAC9D;AAAA;AAAA,EAGA,cAAc,MAA6B;AAC1C,UAAM,MAAM,KAAK,SAAS,UAAU,CAAC,MAAM,EAAE,aAAa,KAAK,YAAY,EAAE,OAAO,KAAK,EAAE;AAC3F,QAAI,OAAO,GAAG;AACb,WAAK,SAAS,GAAG,IAAI;AAAA,IACtB,OAAO;AACN,WAAK,SAAS,QAAQ,IAAI;AAAA,IAC3B;AACA,SAAK,KAAK;AAAA,EACX;AAAA;AAAA,EAGA,cAAc,UAAwB,WAA4B;AACjE,UAAM,SAAS,KAAK,SAAS;AAC7B,SAAK,WAAW,KAAK,SAAS,OAAO,CAAC,MAAM,EAAE,EAAE,aAAa,YAAY,EAAE,OAAO,UAAU;AAC5F,UAAM,UAAU,KAAK,SAAS,WAAW;AAEzC,QAAI,WAAW,KAAK,mBAAmB,YAAY,KAAK,oBAAoB,WAAW;AACtF,YAAM,uBAAuB,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ;AAC9E,UAAI,CAAC,sBAAsB;AAC1B,aAAK,iBAAiB;AACtB,aAAK,kBAAkB;AAAA,MACxB,OAAO;AACN,cAAM,OAAO,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ;AAC9D,YAAI,MAAM;AACT,eAAK,kBAAkB,KAAK;AAAA,QAC7B;AAAA,MACD;AAAA,IACD;AACA,QAAI,QAAS,MAAK,KAAK;AACvB,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,UAAU,UAAwB,WAA6B;AAC9D,UAAM,QACL,cAAc,SACX,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,YAAY,EAAE,OAAO,SAAS,IACvE,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ;AACrD,QAAI,CAAC,MAAO,QAAO;AACnB,SAAK,iBAAiB;AACtB,SAAK,kBAAkB,MAAM;AAC7B,SAAK,KAAK;AACV,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,oBAAyC;AACxC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,mBAA2C;AAC1C,QAAI,CAAC,KAAK,kBAAkB,CAAC,KAAK,gBAAiB,QAAO;AAC1D,WACC,KAAK,SAAS;AAAA,MACb,CAAC,MAAM,EAAE,aAAa,KAAK,kBAAkB,EAAE,OAAO,KAAK;AAAA,IAC5D,KAAK;AAAA,EAEP;AAAA;AAAA,EAGA,gBAAyB;AACxB,WAAO,KAAK,SAAS,SAAS,KAAK,KAAK,mBAAmB;AAAA,EAC5D;AAAA;AAAA,EAGA,YAAwB;AACvB,UAAM,UAA0D,CAAC;AACjE,eAAW,WAAW,KAAK,UAAU;AACpC,cAAQ,QAAQ,QAAQ,IAAI;AAAA,IAC7B;AACA,WAAO;AAAA,MACN,gBAAgB,KAAK;AAAA,MACrB,UAAU;AAAA,MACV,eAAe,KAAK,cAAc;AAAA,MAClC,WAAW,KAAK,aAAa;AAAA,IAC9B;AAAA,EACD;AAAA;AAAA,EAGA,YAAY,SAAuB;AAClC,SAAK,YAAY;AACjB,SAAK,KAAK;AAAA,EACX;AAAA;AAAA,EAGA,aAAmB;AAClB,QAAI,KAAK,cAAc,KAAM;AAC7B,SAAK,YAAY;AACjB,SAAK,KAAK;AAAA,EACX;AAAA;AAAA,EAGA,WAAiB;AAChB,SAAK,WAAW,CAAC;AACjB,SAAK,iBAAiB;AACtB,SAAK,kBAAkB;AACvB,SAAK,YAAY;AACjB,SAAK,KAAK;AAAA,EACX;AAAA,EAEQ,OAAa;AACpB,SAAK,QAAQ,KAAK,QAAQ;AAAA,EAC3B;AACD;;;ACzJO,IAAM,mBAAN,MAAuB;AAAA,EAAvB;AACN,SAAiB,YAAY,oBAAI,IAAiC;AAAA;AAAA;AAAA,EAGlE,SAAS,UAA+B;AACvC,SAAK,UAAU,IAAI,SAAS,YAAY,QAAQ;AAAA,EACjD;AAAA;AAAA,EAGA,IAAI,YAAyC;AAC5C,UAAM,IAAI,KAAK,UAAU,IAAI,UAAU;AACvC,QAAI,CAAC,GAAG;AACP,YAAM,IAAI,MAAM,iCAAiC,UAAU,EAAE;AAAA,IAC9D;AACA,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,OAAO,YAAqD;AAC3D,WAAO,KAAK,UAAU,IAAI,UAAU;AAAA,EACrC;AAAA;AAAA,EAGA,OAAuB;AACtB,WAAO,CAAC,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,EACjC;AAAA;AAAA,EAGA,IAAI,YAAmC;AACtC,WAAO,KAAK,UAAU,IAAI,UAAU;AAAA,EACrC;AACD;;;ACeO,IAAM,WAAN,MAAe;AAAA,EAOrB,YAAY,MAAuB;AAClC,SAAK,WAAW,IAAI,iBAAiB;AACrC,eAAW,YAAY,KAAK,WAAW;AACtC,WAAK,SAAS,SAAS,QAAQ;AAAA,IAChC;AACA,SAAK,QAAQ,KAAK,gBAAgB,IAAI,iBAAiB;AACvD,SAAK,aAAa,KAAK;AACvB,SAAK,gBAAgB,KAAK;AAC1B,SAAK,SAAS,KAAK,UAAU;AAAA,EAC9B;AAAA;AAAA,EAGA,SAAqB;AACpB,WAAO,KAAK,MAAM,UAAU;AAAA,EAC7B;AAAA;AAAA,EAGA,eAAkC;AACjC,WAAO,KAAK,MAAM,aAAa;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,MACL,UACA,IACA,gBAC2B;AAC3B,UAAM,eAAe,KAAK,SAAS,IAAI,QAAQ;AAC/C,SAAK,OAAO,KAAK,yCAAyC,EAAE,SAAS,CAAC;AACtE,QAAI;AACH,YAAM,UAAU,MAAM,aAAa,kBAAkB;AACrD,YAAM,GAAG,OAAO;AAChB,UAAI,CAAC,QAAQ,YAAY;AACxB,aAAK,OAAO,MAAM,iDAAiD,QAAQ;AAC3E,cAAM,IAAI,MAAM,oEAAoE;AAAA,MACrF;AACA,YAAM,UAAU,MAAM,aAAa;AAAA,QAClC,QAAQ;AAAA,QACR;AAAA,QACA,QAAQ;AAAA,MACT;AACA,YAAM,SAAS,MAAM,KAAK,eAAe,cAAc,OAAO;AAC9D,WAAK,OAAO,KAAK,0CAA0C,EAAE,SAAS,CAAC;AACvE,aAAO;AAAA,IACR,SAAS,KAAK;AACb,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,WAAK,OAAO,MAAM,uCAAuC,SAAS;AAAA,QACjE;AAAA,MACD,CAAC;AACD,WAAK,MAAM,YAAY,OAAO;AAC9B,YAAM;AAAA,IACP;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,cACL,UACA,YACA,gBAC2B;AAC3B,UAAM,eAAe,KAAK,SAAS,IAAI,QAAQ;AAC/C,QAAI;AACH,YAAM,UAAU,MAAM,aAAa,mBAAmB,YAAY,cAAc;AAChF,aAAO,MAAM,KAAK,eAAe,cAAc,OAAO;AAAA,IACvD,SAAS,KAAK;AACb,WAAK,MAAM,YAAY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AACvE,YAAM;AAAA,IACP;AAAA,EACD;AAAA;AAAA,EAGA,MAAM,qBAII;AACT,UAAM,WAAW,KAAK,MAAM,kBAAkB;AAC9C,QAAI,CAAC,SAAU,QAAO;AACtB,UAAM,UAAU,KAAK,MAAM,iBAAiB;AAC5C,QAAI,CAAC,QAAS,QAAO;AACrB,UAAM,WAAW,MAAM,KAAK,WAAW,IAAI;AAAA,MAC1C;AAAA,MACA,WAAW,QAAQ;AAAA,IACpB,CAAC;AACD,QAAI,CAAC,SAAU,QAAO;AACtB,WAAO,EAAE,UAAU,SAAS,OAAO,SAAS,MAAM;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OACL,UAC+D;AAC/D,SAAK,OAAO,KAAK,+BAA+B;AAAA,MAC/C,UAAU,YAAY;AAAA,IACvB,CAAC;AACD,QAAI,CAAC,UAAU;AAEd,iBAAW,WAAW,KAAK,MAAM,aAAa,GAAG;AAChD,cAAM,KAAK,WAAW,OAAO;AAAA,UAC5B,UAAU,QAAQ;AAAA,UAClB,WAAW,QAAQ;AAAA,QACpB,CAAC;AACD,aAAK,MAAM,cAAc,QAAQ,UAAU,QAAQ,EAAE;AAAA,MACtD;AACA,WAAK,MAAM,SAAS;AACpB,aAAO,EAAE,UAAU,MAAM,SAAS,KAAK;AAAA,IACxC;AAEA,UAAM,sBAAsB,KAAK,MAAM,aAAa,EAAE,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ;AAC3F,QAAI,UAAU;AACd,eAAW,WAAW,qBAAqB;AAC1C,YAAM,KAAK,WAAW,OAAO,EAAE,UAAU,WAAW,QAAQ,GAAG,CAAC;AAChE,YAAM,YAAY,KAAK,MAAM,cAAc,UAAU,QAAQ,EAAE;AAC/D,gBAAU,WAAW;AAAA,IACtB;AACA,QAAI,KAAK,MAAM,kBAAkB,MAAM,UAAU;AAEhD,YAAM,iBAAiB,KAAK,MAAM,aAAa,EAAE,CAAC;AAClD,UAAI,gBAAgB;AACnB,aAAK,MAAM,UAAU,eAAe,QAAQ;AAAA,MAC7C;AAAA,IACD;AACA,WAAO,EAAE,UAAU,SAAS,QAAQ;AAAA,EACrC;AAAA;AAAA,EAGA,MAAM,SAAoC;AACzC,UAAM,WAAW,KAAK,MAAM,kBAAkB;AAC9C,UAAM,UAAU,KAAK,MAAM,iBAAiB;AAC5C,QAAI,CAAC,YAAY,CAAC,SAAS;AAC1B,aAAO,EAAE,UAAU,KAAK;AAAA,IACzB;AACA,WAAO;AAAA,MACN;AAAA,MACA,OAAO,QAAQ;AAAA,MACf,MAAM,QAAQ;AAAA,MACd,WAAW,QAAQ;AAAA,MACnB,OAAO,QAAQ;AAAA,IAChB;AAAA,EACD;AAAA;AAAA,EAGA,eAAe,UAAwB,WAAsC;AAC5E,UAAM,KAAK,KAAK,MAAM,UAAU,UAAU,SAAS;AACnD,UAAM,UAAU,KAAK,MAAM,iBAAiB;AAC5C,WAAO;AAAA,MACN,gBAAgB,KAAK,WAAW,KAAK,MAAM,kBAAkB;AAAA,MAC7D;AAAA,IACD;AAAA,EACD;AAAA;AAAA,EAGA,MAAM,cAAiD;AACtD,QAAI,CAAC,KAAK,cAAe,QAAO;AAChC,UAAM,UAAU,KAAK,MAAM,iBAAiB;AAC5C,WAAO,KAAK,cAAc,YAAY,OAAO;AAAA,EAC9C;AAAA;AAAA,EAGA,kBAAoC;AACnC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,sBAAwC;AACvC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,mBAA8C;AAC7C,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,MAAc,eACb,cACA,SAC2B;AAC3B,UAAM,YAAY,QAAQ,YAAY,KAAK,IAAI,IAAI,QAAQ,YAAY,MAAO;AAC9E,UAAM,OAAO,MAAM,aAAa,iBAAiB,QAAQ,KAAK;AAC9D,UAAM,cAA+B;AAAA,MACpC,IAAI,KAAK;AAAA,MACT,UAAU,KAAK;AAAA,MACf,aAAa,KAAK;AAAA,MAClB,OAAO,KAAK;AAAA,MACZ,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,WAAW,KAAK,aAAa;AAAA,IAC9B;AACA,SAAK,OAAO,MAAM,iCAAiC;AAAA,MAClD,UAAU,KAAK;AAAA,MACf,WAAW,KAAK;AAAA,IACjB,CAAC;AAED,UAAM,KAAK,WAAW,IAAI,EAAE,UAAU,KAAK,UAAU,WAAW,KAAK,GAAG,GAAG,QAAQ,OAAO;AAAA,MACzF;AAAA,IACD,CAAC;AACD,SAAK,MAAM,cAAc,WAAW;AACpC,SAAK,MAAM,UAAU,KAAK,UAAU,KAAK,EAAE;AAC3C,SAAK,MAAM,WAAW;AACtB,WAAO;AAAA,MACN,UAAU,KAAK;AAAA,MACf,SAAS,gBAAgB,KAAK,SAAS,KAAK,EAAE;AAAA,IAC/C;AAAA,EACD;AACD;;;ACpMO,IAAM,2BAAN,cAAuC,MAAM;AAAA,EACnD,YAAY,SAAiB,OAAiB;AAU7C,UAAM,SAAS,UAAU,SAAY,EAAE,MAAM,IAAI,MAAS;AAC1D,SAAK,OAAO;AAAA,EACb;AACD;AAQO,IAAM,iCAAN,MAAuE;AAAA,EAAvE;AACN,SAAiB,UAAU,oBAAI,IAG7B;AAAA;AAAA,EAEM,aAAa,KAAiC;AACrD,WAAO,GAAG,IAAI,QAAQ,IAAI,IAAI,SAAS;AAAA,EACxC;AAAA,EAEA,MAAM,IACL,KACA,OACA,MACgB;AAChB,SAAK,QAAQ,IAAI,KAAK,aAAa,GAAG,GAAG;AAAA,MACxC;AAAA,MACA,WAAW,MAAM,aAAa;AAAA,MAC9B,UAAU,KAAK,IAAI;AAAA,IACpB,CAAC;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,KAAgE;AACzE,UAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,aAAa,GAAG,CAAC;AACrD,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO;AAAA,MACN,OAAO,MAAM;AAAA,MACb,UAAU;AAAA,QACT,UAAU,IAAI;AAAA,QACd,WAAW,IAAI;AAAA,QACf,WAAW,MAAM;AAAA,QACjB,UAAU,MAAM;AAAA,MACjB;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,OAAO,KAAwC;AACpD,SAAK,QAAQ,OAAO,KAAK,aAAa,GAAG,CAAC;AAAA,EAC3C;AAAA,EAEA,MAAM,OAAsC;AAC3C,UAAM,MAA4B,CAAC;AACnC,eAAW,aAAa,KAAK,QAAQ,KAAK,GAAG;AAC5C,YAAM,SAAS,UAAU,QAAQ,GAAG;AACpC,UAAI,UAAU,EAAG;AACjB,UAAI,KAAK;AAAA,QACR,UAAU,UAAU,MAAM,GAAG,MAAM;AAAA,QACnC,WAAW,UAAU,MAAM,SAAS,CAAC;AAAA,MACtC,CAAC;AAAA,IACF;AACA,WAAO;AAAA,EACR;AAAA,EAEA,MAAM,cAAgC;AACrC,WAAO;AAAA,EACR;AACD;;;AC1JO,SAAS,mBAAmB,OAA2C;AAC7E,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,YAAY,EAAE,SAAS,eAAe;AACxF;AAGO,SAAS,sBAAsB,OAAuB;AAC5D,SAAO,GAAG,KAAK;AAChB;AAMO,SAAS,oBAAoB,OAAe,OAA0C;AAC5F,QAAM,kBAAkB,OAAO,KAAK;AACpC,SAAO,mBAAmB,sBAAsB,KAAK;AACtD;;;ACTA,IAAM,0BAA0B;AAChC,IAAM,oBAAoB;AAC1B,IAAM,mBAAmB;AACzB,IAAM,gBAAgB;AAGtB,IAAM,2BAA2B;AACjC,IAAM,0CAA0C;AAShD,SAAS,wBAAwB,OAAyB;AACzD,SAAO,iBAAiB;AACzB;AA+DO,IAAM,qBAAN,MAAkD;AAAA,EAWxD,YAAY,QAAkC;AAV9C,SAAS,aAA2B;AAWnC,QAAI,CAAC,OAAO,YAAY,OAAO,SAAS,WAAW,GAAG;AACrD,YAAM,IAAI,MAAM,oDAAoD;AAAA,IACrE;AACA,SAAK,MAAM;AAAA,MACV,UAAU,OAAO;AAAA,MACjB,eAAe,OAAO,iBAAiB;AAAA,MACvC,UAAU,OAAO,YAAY;AAAA,MAC7B,SAAS,OAAO,WAAW;AAAA,MAC3B,OAAO,OAAO,SAAS;AAAA,MACvB,mBAAmB,OAAO,qBAAqB;AAAA,MAC/C,mBAAmB,OAAO,qBAAqB;AAAA,MAC/C,WAAW,OAAO;AAAA,MAClB,WAAW,OAAO,aAAa;AAAA,MAC/B,4BACC,OAAO,8BAA8B;AAAA,IACvC;AACA,SAAK,SAAS,OAAO,UAAU;AAC/B,SAAK,YAAY,OAAO,aAAa;AAAA,EACtC;AAAA,EAEA,MAAM,kBAAkB,MAAqD;AAC5E,UAAM,OAAO,KAAK,UAAU;AAAA,MAC3B,WAAW,KAAK,IAAI;AAAA,MACpB,OAAO,MAAM,SAAS,KAAK,IAAI;AAAA,IAChC,CAAC;AAED,QAAI;AACJ,aAAS,UAAU,GAAG,WAAW,0BAA0B,WAAW;AACrE,WAAK,OAAO,MAAM,+CAA+C;AAAA,QAChE;AAAA,QACA,aAAa;AAAA,MACd,CAAC;AACD,UAAI;AACJ,UAAI;AACH,eAAO,MAAM,KAAK,IAAI,UAAU,KAAK,IAAI,eAAe;AAAA,UACvD,QAAQ;AAAA,UACR,SAAS;AAAA,YACR,QAAQ;AAAA,YACR,gBAAgB;AAAA,YAChB,cAAc;AAAA,UACf;AAAA,UACA;AAAA,QACD,CAAC;AAAA,MACF,SAAS,OAAO;AACf,YAAI,CAAC,wBAAwB,KAAK,KAAK,YAAY,0BAA0B;AAC5E,eAAK,OAAO;AAAA,YACX;AAAA,YACA,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,YACrD,EAAE,QAAQ;AAAA,UACX;AACA,gBAAM;AAAA,QACP;AACA,cAAM,UAAU,KAAK,IAAI,6BAA6B,MAAM,UAAU;AACtE,aAAK,OAAO;AAAA,UACX;AAAA,UACA;AAAA,YACC;AAAA,YACA;AAAA,YACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UAC7D;AAAA,QACD;AACA,2BAAmB;AACnB,cAAM,KAAK,UAAU,OAAO;AAC5B;AAAA,MACD;AACA,UAAI,CAAC,KAAK,IAAI;AACb,aAAK,OAAO;AAAA,UACX;AAAA,UACA,QAAQ,KAAK,MAAM;AAAA,QACpB;AACA,cAAM,IAAI,MAAM,2CAA2C,KAAK,MAAM,EAAE;AAAA,MACzE;AACA,YAAM,OAAQ,MAAM,KAAK,KAAK;AAC9B,UAAI,CAAC,KAAK,eAAe,CAAC,KAAK,aAAa,CAAC,KAAK,kBAAkB;AACnE,aAAK,OAAO;AAAA,UACX;AAAA,UACA,KAAK,UAAU,OAAO,KAAK,IAAI,CAAC;AAAA,QACjC;AACA,cAAM,IAAI,MAAM,qDAAqD;AAAA,MACtE;AACA,WAAK,OAAO,KAAK,6CAA6C;AAAA,QAC7D,UAAU,KAAK;AAAA,QACf,iBAAiB,KAAK;AAAA,QACtB,cAAc,KAAK;AAAA,QACnB,iBAAiB,KAAK;AAAA,MACvB,CAAC;AACD,aAAO;AAAA,QACN,UAAU,KAAK;AAAA,QACf,YAAY,KAAK;AAAA,QACjB,UAAU,KAAK;AAAA,QACf,iBAAiB,KAAK;AAAA,QACtB,WAAW,KAAK,IAAI,IAAI,KAAK,aAAa;AAAA,QAC1C,gBAAgB,KAAK,WAAW;AAAA,QAChC,SAAS,QAAQ,KAAK,gBAAgB,cAAc,KAAK,SAAS;AAAA,MACnE;AAAA,IACD;AAGA,UAAM,oBAAoB,IAAI,MAAM,mCAAmC;AAAA,EACxE;AAAA,EAEA,MAAM,mBACL,YACA,gBACA,uBACgC;AAChC,UAAM,QAAQ,KAAK,IAAI;AAIvB,QAAI,iBAAiB,KAAK,IAAI,KAAK,IAAI,mBAAmB,yBAAyB,CAAC;AACpF,QAAI,sBAAsB;AAC1B,QAAI,YAAY;AAChB,SAAK,OAAO,KAAK,qDAAqD;AAAA,MACrE,uBAAuB;AAAA,MACvB,WAAW,KAAK,IAAI;AAAA,IACrB,CAAC;AAED,WAAO,MAAM;AACZ,UAAI,kBAAkB,CAAC,eAAe,GAAG;AACxC,aAAK,OAAO,KAAK,wDAAwD;AAAA,UACxE,WAAW,KAAK,IAAI,IAAI;AAAA,UACxB;AAAA,QACD,CAAC;AACD,cAAM,IAAI,MAAM,wCAAwC;AAAA,MACzD;AACA,YAAM,UAAU,KAAK,IAAI,IAAI;AAC7B,UAAI,KAAK,IAAI,cAAc,UAAa,UAAU,KAAK,IAAI,WAAW;AACrE,aAAK,OAAO,KAAK,2DAA2D;AAAA,UAC3E,WAAW;AAAA,UACX,WAAW,KAAK,IAAI;AAAA,UACpB;AAAA,QACD,CAAC;AACD,cAAM,IAAI,MAAM,2CAA2C;AAAA,MAC5D;AACA,YAAM,KAAK,UAAU,cAAc;AACnC,UAAI,kBAAkB,CAAC,eAAe,GAAG;AACxC,aAAK,OAAO,KAAK,wDAAwD;AAAA,UACxE,WAAW,KAAK,IAAI,IAAI;AAAA,UACxB;AAAA,QACD,CAAC;AACD,cAAM,IAAI,MAAM,wCAAwC;AAAA,MACzD;AAEA;AACA,WAAK,OAAO,MAAM,kDAAkD;AAAA,QACnE;AAAA,QACA,WAAW,KAAK,IAAI,IAAI;AAAA,QACxB;AAAA,MACD,CAAC;AACD,UAAI;AACJ,UAAI;AACH,eAAO,MAAM,KAAK,IAAI,UAAU,KAAK,IAAI,UAAU;AAAA,UAClD,QAAQ;AAAA,UACR,SAAS;AAAA,YACR,QAAQ;AAAA,YACR,gBAAgB;AAAA,YAChB,cAAc;AAAA,UACf;AAAA,UACA,MAAM,KAAK,UAAU;AAAA,YACpB,WAAW,KAAK,IAAI;AAAA,YACpB,aAAa;AAAA,YACb,YAAY;AAAA,UACb,CAAC;AAAA,QACF,CAAC;AAAA,MACF,SAAS,OAAO;AACf,YAAI,CAAC,wBAAwB,KAAK,GAAG;AACpC,eAAK,OAAO;AAAA,YACX;AAAA,YACA,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,YACrD,EAAE,UAAU;AAAA,UACb;AACA,gBAAM;AAAA,QACP;AAGA,cAAM,qBAAqB,KAAK;AAAA,UAC/B,KAAK,MAAM,iBAAiB,GAAG;AAAA,UAC/B,KAAK,IAAI;AAAA,QACV;AACA,aAAK,OAAO;AAAA,UACX;AAAA,UACA;AAAA,YACC;AAAA,YACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,YAC5D,wBAAwB;AAAA,YACxB;AAAA,UACD;AAAA,QACD;AACA,yBAAiB;AACjB;AAAA,MACD;AACA,UAAI,CAAC,KAAK,IAAI;AAEb,cAAM,qBAAqB,KAAK;AAAA,UAC/B,KAAK,MAAM,iBAAiB,GAAG;AAAA,UAC/B,KAAK,IAAI;AAAA,QACV;AACA,aAAK,OAAO;AAAA,UACX;AAAA,UACA;AAAA,YACC;AAAA,YACA,QAAQ,KAAK;AAAA,YACb,wBAAwB;AAAA,YACxB;AAAA,UACD;AAAA,QACD;AACA,yBAAiB;AACjB;AAAA,MACD;AACA,YAAM,OAAQ,MAAM,KAAK,KAAK;AAC9B,UAAI,KAAK,cAAc;AACtB,aAAK,OAAO,KAAK,qEAAqE;AAAA,UACrF;AAAA,UACA,WAAW,KAAK,IAAI,IAAI;AAAA,QACzB,CAAC;AACD,cAAM,UAAU,MAAM,KAAK,gBAAgB,KAAK,YAAY;AAC5D,cAAM,QAAQ,MAAM,KAAK,aAAa,KAAK,cAAc,OAAO;AAChE,aAAK,OAAO,KAAK,oDAAoD;AAAA,UACpE;AAAA,UACA,WAAW,KAAK,IAAI,IAAI;AAAA,UACxB,OAAO,QAAQ;AAAA,QAChB,CAAC;AACD,eAAO;AAAA,UACN,OAAO,KAAK;AAAA,UACZ,cAAc,KAAK;AAAA,UACnB,WAAW,KAAK;AAAA,UAChB,MAAM;AAAA,YACL,IAAI,OAAO,QAAQ,EAAE;AAAA,YACrB,OAAO,QAAQ;AAAA,YACf,MAAM,QAAQ,QAAQ;AAAA,YACtB,OAAO,SAAS;AAAA,YAChB,WAAW,QAAQ,cAAc;AAAA,UAClC;AAAA,QACD;AAAA,MACD;AACA,UAAI,KAAK,UAAU,yBAAyB;AAE3C,aAAK,OAAO,MAAM,oDAAoD;AAAA,UACrE;AAAA,UACA,WAAW,KAAK,IAAI,IAAI;AAAA,QACzB,CAAC;AACD;AAAA,MACD;AACA,UAAI,KAAK,UAAU,aAAa;AAC/B;AAKA,cAAM,qBAAqB,KAAK,IAAI,iBAAiB,KAAM,KAAK,IAAI,iBAAiB;AACrF,aAAK,OAAO,KAAK,oEAAoE;AAAA,UACpF;AAAA,UACA;AAAA,UACA,wBAAwB;AAAA,UACxB;AAAA,QACD,CAAC;AACD,yBAAiB;AACjB;AAAA,MACD;AACA,UAAI,KAAK,UAAU,iBAAiB;AACnC,aAAK,OAAO,KAAK,kDAAkD;AAAA,UAClE;AAAA,QACD,CAAC;AACD,cAAM,IAAI,MAAM,2BAA2B;AAAA,MAC5C;AACA,UAAI,KAAK,UAAU,iBAAiB;AACnC,aAAK,OAAO;AAAA,UACX;AAAA,UACA,EAAE,WAAW,WAAW,KAAK,IAAI,IAAI,MAAM;AAAA,QAC5C;AACA,cAAM,IAAI,MAAM,oDAA+C;AAAA,MAChE;AACA,WAAK,OAAO;AAAA,QACX;AAAA,QACA,KAAK,SAAS;AAAA,QACd,EAAE,UAAU;AAAA,MACb;AACA,YAAM,IAAI,MAAM,6BAA6B,KAAK,SAAS,SAAS,EAAE;AAAA,IACvE;AAAA,EACD;AAAA,EAEA,MAAM,mBAAmB,cAAqD;AAC7E,QAAI,CAAC,cAAc;AAClB,YAAM,IAAI,MAAM,uDAAuD;AAAA,IACxE;AACA,UAAM,OAAO,MAAM,KAAK,IAAI,UAAU,KAAK,IAAI,UAAU;AAAA,MACxD,QAAQ;AAAA,MACR,SAAS;AAAA,QACR,QAAQ;AAAA,QACR,gBAAgB;AAAA,QAChB,cAAc;AAAA,MACf;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACpB,WAAW,KAAK,IAAI;AAAA,QACpB,YAAY;AAAA,QACZ,eAAe;AAAA,MAChB,CAAC;AAAA,IACF,CAAC;AACD,QAAI,CAAC,KAAK,IAAI;AACb,YAAM,IAAI,MAAM,+BAA+B,KAAK,MAAM,EAAE;AAAA,IAC7D;AACA,UAAM,OAAQ,MAAM,KAAK,KAAK;AAC9B,QAAI,CAAC,KAAK,cAAc;AACvB,YAAM,IAAI,MAAM,0BAA0B,KAAK,SAAS,iBAAiB,EAAE;AAAA,IAC5E;AACA,UAAM,UAAU,MAAM,KAAK,gBAAgB,KAAK,YAAY;AAC5D,UAAM,QAAQ,MAAM,KAAK,aAAa,KAAK,cAAc,OAAO;AAChE,WAAO;AAAA,MACN,OAAO,KAAK;AAAA,MACZ,cAAc,KAAK,iBAAiB;AAAA,MACpC,WAAW,KAAK;AAAA,MAChB,MAAM;AAAA,QACL,IAAI,OAAO,QAAQ,EAAE;AAAA,QACrB,OAAO,QAAQ;AAAA,QACf,MAAM,QAAQ,QAAQ;AAAA,QACtB,OAAO,SAAS;AAAA,QAChB,WAAW,QAAQ,cAAc;AAAA,MAClC;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,cAAc,OAAiC;AACpD,QAAI;AACH,YAAM,OAAO,MAAM,KAAK,IAAI,UAAU,KAAK,IAAI,SAAS;AAAA,QACvD,SAAS;AAAA,UACR,QAAQ;AAAA,UACR,eAAe,UAAU,KAAK;AAAA,UAC9B,cAAc;AAAA,QACf;AAAA,MACD,CAAC;AACD,UAAI,KAAK,WAAW,IAAK,QAAO;AAChC,UAAI,CAAC,KAAK,IAAI;AACb,cAAM,IAAI,MAAM,8BAA8B,KAAK,MAAM,EAAE;AAAA,MAC5D;AACA,aAAO;AAAA,IACR,SAAS,KAAK;AAEb,UAAI,eAAe,SAAS,IAAI,QAAQ,SAAS,KAAK,EAAG,QAAO;AAChE,YAAM;AAAA,IACP;AAAA,EACD;AAAA,EAEA,MAAM,iBAAiB,OAAyC;AAC/D,UAAM,OAAO,MAAM,KAAK,gBAAgB,KAAK;AAC7C,UAAM,QAAQ,MAAM,KAAK,aAAa,OAAO,IAAI;AACjD,WAAO;AAAA,MACN,IAAI,OAAO,KAAK,EAAE;AAAA,MAClB,UAAU,KAAK;AAAA,MACf,aAAa,KAAK,QAAQ,KAAK;AAAA,MAC/B,OAAO,KAAK;AAAA,MACZ,OAAO,SAAS,sBAAsB,KAAK,KAAK;AAAA,MAChD,WAAW,KAAK,cAAc;AAAA,MAC9B,WAAW;AAAA,IACZ;AAAA,EACD;AAAA,EAEA,MAAc,gBAAgB,OAA4C;AACzE,UAAM,OAAO,MAAM,KAAK,IAAI,UAAU,KAAK,IAAI,SAAS;AAAA,MACvD,SAAS;AAAA,QACR,QAAQ;AAAA,QACR,eAAe,UAAU,KAAK;AAAA,QAC9B,cAAc;AAAA,MACf;AAAA,IACD,CAAC;AACD,QAAI,CAAC,KAAK,IAAI;AACb,YAAM,IAAI,MAAM,6BAA6B,KAAK,MAAM,EAAE;AAAA,IAC3D;AACA,WAAQ,MAAM,KAAK,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,aAAa,OAAe,MAAkD;AAC3F,QAAI,KAAK,MAAO,QAAO,KAAK;AAC5B,UAAM,aAAa,MAAM,KAAK,IAAI,UAAU,sCAAsC;AAAA,MACjF,SAAS;AAAA,QACR,QAAQ;AAAA,QACR,eAAe,UAAU,KAAK;AAAA,QAC9B,cAAc;AAAA,MACf;AAAA,IACD,CAAC;AACD,QAAI,CAAC,WAAW,IAAI;AACnB,aAAO;AAAA,IACR;AACA,UAAM,SAAU,MAAM,WAAW,KAAK;AAMtC,UAAM,UAAU,OAAO,KAAK,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ;AAC1D,WAAO,SAAS,SAAS;AAAA,EAC1B;AACD;AAEA,SAAS,MAAM,IAA2B;AACzC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACxD;;;AC5eO,IAAM,kCAAN,cAA8C,MAAM;AAAA,EAC1D,YACC,UAAU,uFACT;AACD,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACb;AACD;AAEO,IAAM,wBAAN,MAAqD;AAAA,EAArD;AACN,SAAS,aAA2B;AAAA;AAAA,EAEpC,MAAM,oBAA8C;AACnD,UAAM,IAAI,gCAAgC;AAAA,EAC3C;AAAA,EAEA,MAAM,mBACL,aACA,iBACgC;AAChC,UAAM,IAAI,gCAAgC;AAAA,EAC3C;AAAA,EAEA,MAAM,mBAAmB,eAAsD;AAC9E,UAAM,IAAI,gCAAgC;AAAA,EAC3C;AAAA,EAEA,MAAM,cAAc,QAAkC;AACrD,UAAM,IAAI,gCAAgC;AAAA,EAC3C;AAAA,EAEA,MAAM,iBAAiB,QAA0C;AAChE,UAAM,IAAI,gCAAgC;AAAA,EAC3C;AACD;","names":[]}