@0xmaxma/claude-gateway 1.8.7 → 1.8.8

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 (72) hide show
  1. package/README.md +96 -0
  2. package/dist/agent/runner.d.ts +47 -1
  3. package/dist/agent/runner.d.ts.map +1 -1
  4. package/dist/agent/runner.js +116 -17
  5. package/dist/agent/runner.js.map +1 -1
  6. package/dist/api/connectors-router.d.ts +50 -0
  7. package/dist/api/connectors-router.d.ts.map +1 -0
  8. package/dist/api/connectors-router.js +650 -0
  9. package/dist/api/connectors-router.js.map +1 -0
  10. package/dist/api/gateway-router.d.ts +4 -0
  11. package/dist/api/gateway-router.d.ts.map +1 -1
  12. package/dist/api/gateway-router.js +26 -0
  13. package/dist/api/gateway-router.js.map +1 -1
  14. package/dist/api/oauth-connectors-router.d.ts +43 -0
  15. package/dist/api/oauth-connectors-router.d.ts.map +1 -0
  16. package/dist/api/oauth-connectors-router.js +384 -0
  17. package/dist/api/oauth-connectors-router.js.map +1 -0
  18. package/dist/api/router.d.ts.map +1 -1
  19. package/dist/api/router.js +101 -13
  20. package/dist/api/router.js.map +1 -1
  21. package/dist/apps/agent-manager.d.ts +6 -2
  22. package/dist/apps/agent-manager.d.ts.map +1 -1
  23. package/dist/apps/agent-manager.js +10 -18
  24. package/dist/apps/agent-manager.js.map +1 -1
  25. package/dist/config/config-write-lock.d.ts +45 -0
  26. package/dist/config/config-write-lock.d.ts.map +1 -0
  27. package/dist/config/config-write-lock.js +164 -0
  28. package/dist/config/config-write-lock.js.map +1 -0
  29. package/dist/config/watcher.d.ts.map +1 -1
  30. package/dist/config/watcher.js +29 -0
  31. package/dist/config/watcher.js.map +1 -1
  32. package/dist/connectors/custom-connectors-store.d.ts +54 -0
  33. package/dist/connectors/custom-connectors-store.d.ts.map +1 -0
  34. package/dist/connectors/custom-connectors-store.js +204 -0
  35. package/dist/connectors/custom-connectors-store.js.map +1 -0
  36. package/dist/connectors/custom.d.ts +69 -0
  37. package/dist/connectors/custom.d.ts.map +1 -0
  38. package/dist/connectors/custom.js +158 -0
  39. package/dist/connectors/custom.js.map +1 -0
  40. package/dist/connectors/mcp-oauth.d.ts +152 -0
  41. package/dist/connectors/mcp-oauth.d.ts.map +1 -0
  42. package/dist/connectors/mcp-oauth.js +522 -0
  43. package/dist/connectors/mcp-oauth.js.map +1 -0
  44. package/dist/connectors/oauth-refresh-sweep.d.ts +71 -0
  45. package/dist/connectors/oauth-refresh-sweep.d.ts.map +1 -0
  46. package/dist/connectors/oauth-refresh-sweep.js +337 -0
  47. package/dist/connectors/oauth-refresh-sweep.js.map +1 -0
  48. package/dist/connectors/pending-oauth-store.d.ts +39 -0
  49. package/dist/connectors/pending-oauth-store.d.ts.map +1 -0
  50. package/dist/connectors/pending-oauth-store.js +50 -0
  51. package/dist/connectors/pending-oauth-store.js.map +1 -0
  52. package/dist/connectors/resolve.d.ts +52 -0
  53. package/dist/connectors/resolve.d.ts.map +1 -0
  54. package/dist/connectors/resolve.js +176 -0
  55. package/dist/connectors/resolve.js.map +1 -0
  56. package/dist/connectors/token-env.d.ts +93 -0
  57. package/dist/connectors/token-env.d.ts.map +1 -0
  58. package/dist/connectors/token-env.js +323 -0
  59. package/dist/connectors/token-env.js.map +1 -0
  60. package/dist/connectors/types.d.ts +126 -0
  61. package/dist/connectors/types.d.ts.map +1 -0
  62. package/dist/connectors/types.js +13 -0
  63. package/dist/connectors/types.js.map +1 -0
  64. package/dist/index.js +32 -1
  65. package/dist/index.js.map +1 -1
  66. package/dist/session/process.d.ts +23 -0
  67. package/dist/session/process.d.ts.map +1 -1
  68. package/dist/session/process.js +78 -2
  69. package/dist/session/process.js.map +1 -1
  70. package/dist/types.d.ts +46 -1
  71. package/dist/types.d.ts.map +1 -1
  72. package/package.json +1 -1
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ /**
3
+ * Connector resolution — the single source of truth shared by the session spawner
4
+ * and the HTTP API.
5
+ *
6
+ * resolveEnabledConnectors(): for the injection point in SessionProcess.writeMcpConfig.
7
+ * listConnectorStatus(): for GET /v1/connectors.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.resolveEnabledConnectors = resolveEnabledConnectors;
11
+ exports.refreshStatusOf = refreshStatusOf;
12
+ exports.listConnectorStatus = listConnectorStatus;
13
+ const token_env_1 = require("./token-env");
14
+ const custom_1 = require("./custom");
15
+ const oauth_refresh_sweep_1 = require("./oauth-refresh-sweep");
16
+ /**
17
+ * Build the mcpServers entries for every connector that is (a) in customConnectors,
18
+ * (b) enabled for this agent, and (c) connected (every `{placeholder}` secret it
19
+ * declares is present). Returns a map keyed by connector id, ready to merge into
20
+ * mcp-config.json.
21
+ *
22
+ * Enablement defaults to opt-OUT: a globally-connected connector is available to
23
+ * every agent the moment it's connected — an agent only misses it if explicitly
24
+ * disabled (`{enabled: false}`). Connecting a connector at all is the security gate;
25
+ * per-agent is a refinement, not a second required step.
26
+ *
27
+ * `defaultEnabled: false` (gateway.connectorsDefaultEnabled in config.json) flips
28
+ * that to opt-IN. The default suits the common single-operator install, but a
29
+ * gateway hosting agents for several different people is a different situation:
30
+ * there, connecting one connector hands its credential to every agent on the box,
31
+ * including agents whose chat users are not the person who connected it. Opting in
32
+ * per agent is the safer posture for that deployment, so it is available — as a
33
+ * deliberate choice, not a silent change of meaning for existing installs.
34
+ */
35
+ function resolveEnabledConnectors(agentConfig, customConnectors = {}, defaultEnabled = true) {
36
+ const enabled = agentConfig.connectors ?? {};
37
+ const tokenEnv = (0, token_env_1.readTokenEnv)();
38
+ // Null-prototype for the reason the `secrets` map below is one, one level up:
39
+ // `restartSessionsUsingConnector` asks this map what a single id resolves to
40
+ // (`resolved[connectorId]`), and a connector id may legitimately be `constructor`
41
+ // or `valueOf`. On a plain object that lookup answers with an inherited Function
42
+ // for an id that resolves to nothing — a "changed" fingerprint that restarts every
43
+ // session on the box, forever, since it never matches what a spawn recorded.
44
+ const out = Object.create(null);
45
+ const isEnabled = (id) => enabled[id]?.enabled ?? defaultEnabled;
46
+ for (const [id, entry] of Object.entries(customConnectors)) {
47
+ if (!isEnabled(id))
48
+ continue; // opted out (or not opted in)
49
+ try {
50
+ // Null-prototype for the same reason token-env.ts's parse() uses one: the
51
+ // names come from a pasted config, and PLACEHOLDER_RE happily matches
52
+ // `{constructor}` or `{toString}`.
53
+ const secrets = Object.create(null);
54
+ let allPresent = true;
55
+ for (const name of entry.secretNames) {
56
+ const value = tokenEnv[(0, custom_1.customSecretKey)(id, name)];
57
+ if (!value) {
58
+ allPresent = false;
59
+ break;
60
+ }
61
+ secrets[name] = value;
62
+ }
63
+ if (!allPresent)
64
+ continue; // enabled but not fully connected — skip
65
+ out[id] = (0, custom_1.substitutePlaceholders)(entry.config, secrets);
66
+ }
67
+ catch (err) {
68
+ // A malformed pasted config (see CustomConnectorEntry's doc comment:
69
+ // admin-trusted, not code-reviewed) must not take down every other
70
+ // connector's resolution — or worse, the whole session spawn — for
71
+ // every user of this agent. Skip just this one.
72
+ console.error(`resolveEnabledConnectors: connector=${id} resolve failed: ${err.message}`);
73
+ }
74
+ }
75
+ return out;
76
+ }
77
+ /**
78
+ * The `refresh` block of ConnectorStatus, or undefined when the connector's
79
+ * background refresh is healthy (or was never failing). Reads the sweep's own
80
+ * bookkeeping out of an already-loaded token env rather than re-reading the file
81
+ * per connector.
82
+ *
83
+ * Both failure streaks are reported, not just the transient one. A connector two
84
+ * refusals into the three-strike permanent count is one tick away from having its
85
+ * credentials deleted — the most actionable state this block can describe, and the
86
+ * one a caller most needs to see before it happens.
87
+ *
88
+ * `unrefreshable` covers the cases with no streak at all. A provider that issues no
89
+ * refresh_token (an AS advertising scopes that don't include `offline_access` — see
90
+ * the scope fallback in mcp-oauth.ts's `resolveScope`) leaves the sweep nothing to
91
+ * refresh with, so it skips the connector on every tick and never records a failure.
92
+ * Without this flag that connector keeps a green checkmark forever over an
93
+ * access_token that expired an hour in, which is the exact state the sweep's module
94
+ * comment says it exists to prevent.
95
+ */
96
+ function refreshStatusOf(id, tokenEnv) {
97
+ // A hand-edited or truncated mcp-token.env can hold anything, and the sweep floors
98
+ // the very same values before deciding whether to delete credentials — `parseCounter`
99
+ // is that floor, shared, so display and decision cannot drift apart.
100
+ const consecutiveFailures = (0, token_env_1.parseCounter)(tokenEnv[(0, oauth_refresh_sweep_1.refreshTransientCountSecretKey)(id)]);
101
+ const permanentFailures = (0, token_env_1.parseCounter)(tokenEnv[(0, oauth_refresh_sweep_1.refreshFailCountSecretKey)(id)]);
102
+ // Derived, not stored: the sweep never writes a marker for this, and shouldn't —
103
+ // it is a fact about what is already in the file, and computing it here means it
104
+ // cannot drift out of sync with the tokens it describes.
105
+ const expiresAt = (0, token_env_1.parseCounter)(tokenEnv[(0, oauth_refresh_sweep_1.expiresAtSecretKey)(id)]);
106
+ // Three states, one flag: there is a token here, nothing can renew it, and it is
107
+ // not currently known-good.
108
+ //
109
+ // `expiresAt === 0` is the third one, and it is not the "not signed in yet" case —
110
+ // that has no access_token at all, and the row already reports `connected: false`
111
+ // without help from here. A 'gateway' connector that HAS an access_token and no
112
+ // recorded expiry is one whose token this gateway never minted: every path that
113
+ // writes one writes an expiry alongside it, defaulting to an hour when the AS
114
+ // omits `expires_in` (see the callback and the sweep). The way to get here is to
115
+ // paste an access_token into an `oauth: true` add — now rejected at that route,
116
+ // but rows already in this state predate the check and are exactly the ones that
117
+ // need saying out loud, since they will simply stop working at a moment nothing
118
+ // recorded.
119
+ const unrefreshable = !!tokenEnv[(0, custom_1.customSecretKey)(id, 'access_token')] &&
120
+ !tokenEnv[(0, oauth_refresh_sweep_1.refreshTokenSecretKey)(id)] &&
121
+ (expiresAt === 0 || expiresAt <= Date.now());
122
+ if (!consecutiveFailures && !permanentFailures && !unrefreshable)
123
+ return undefined;
124
+ return {
125
+ consecutiveFailures,
126
+ permanentFailures,
127
+ nextAttemptAt: (0, token_env_1.parseCounter)(tokenEnv[(0, oauth_refresh_sweep_1.refreshBackoffUntilSecretKey)(id)]),
128
+ ...(unrefreshable ? { unrefreshable: true } : {}),
129
+ };
130
+ }
131
+ /** Connector list + connected state for the API. `connected` reflects secret presence. */
132
+ function listConnectorStatus(customConnectors = {}) {
133
+ const tokenEnv = (0, token_env_1.readTokenEnv)();
134
+ return Object.entries(customConnectors).map(([id, entry]) => {
135
+ try {
136
+ const connected = entry.secretNames.every((name) => !!tokenEnv[(0, custom_1.customSecretKey)(id, name)]);
137
+ // Omitted rather than set to undefined when refresh is healthy: the single-status
138
+ // route omits it too, and a caller doing `'refresh' in status` should get the same
139
+ // answer from both.
140
+ //
141
+ // 'gateway' only. An 'external' entry is refreshed by the control plane that
142
+ // pushed it — this gateway holds no refresh_token for it, so the sweep skips it
143
+ // and every counter here would read a constant 0.
144
+ const refresh = entry.credentialOwner === 'gateway' ? refreshStatusOf(id, tokenEnv) : undefined;
145
+ return {
146
+ id,
147
+ label: entry.label,
148
+ description: entry.description,
149
+ credentialOwner: entry.credentialOwner,
150
+ connected,
151
+ repoUrl: entry.sourceUrl,
152
+ ...(refresh ? { refresh } : {}),
153
+ };
154
+ }
155
+ catch (err) {
156
+ // Same per-entry isolation resolveEnabledConnectors uses above, and for a
157
+ // sharper reason: `entry.secretNames` is typed but never validated at read
158
+ // time, so one hand-edited (or older-build) config.json entry missing it
159
+ // throws here — inside an `async` Express 4 handler, which does not catch
160
+ // rejections. The request then never gets a response at all and the whole
161
+ // connector panel hangs, rather than one row degrading.
162
+ console.error(`listConnectorStatus: connector=${id} status failed: ${err.message}`);
163
+ return {
164
+ id,
165
+ label: typeof entry?.label === 'string' ? entry.label : id,
166
+ description: 'This connector’s configuration could not be read.',
167
+ // Not the entry's own value, even if it has a readable one: this row
168
+ // describes a connector nothing can be done with, and every other owner
169
+ // would invite a caller to offer a connect action that cannot work.
170
+ credentialOwner: 'none',
171
+ connected: false,
172
+ };
173
+ }
174
+ });
175
+ }
176
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/connectors/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Connector secret storage — ~/.claude-gateway/mcp-token.env
3
+ *
4
+ * A plain dotenv file (mode 0600) of `KEY=value` lines, one per connector secret
5
+ * (e.g. GITHUB_TOKEN=ghp_...). This mirrors the gateway's existing secret posture
6
+ * (~/.claude-gateway/.env holds bot tokens in plaintext); config.json only ever holds
7
+ * the env-var NAME, never the value.
8
+ *
9
+ * The file is parsed fresh on every read so a web "connect" takes effect on the next
10
+ * session spawn with no daemon restart. Reads fail soft (an unreadable file reads as
11
+ * "nothing is connected"); writes do not, because a write that fails soft would erase
12
+ * what it could not read — see `readTokenEnvForUpdate` vs `readTokenEnv`.
13
+ *
14
+ * Nothing outside this module parses the file — it is never handed to a shell or
15
+ * `--env-file` — so the quoting rules are ours and both sides of the round-trip live
16
+ * here. Secret VALUES are not ours, though: a provider's token endpoint decides what
17
+ * `access_token` looks like, and a value containing a newline would otherwise be
18
+ * written as a second `KEY=value` line and read back as a *different* connector's
19
+ * secret. See `encodeValue`/`decodeValue`.
20
+ */
21
+ export declare function isValidSecretKey(key: string): boolean;
22
+ /**
23
+ * Read all connector secrets for a caller that only intends to LOOK at them.
24
+ * Returns {} when the file does not exist — or cannot be read.
25
+ *
26
+ * Degrading here is the whole point: every caller asks "is this connected / when does
27
+ * it expire", and the honest answer for an unreadable file is "as far as we can tell,
28
+ * no". The throw it replaced propagated out of `GET /v1/connectors` — an `async`
29
+ * handler on Express 4, which does not catch rejections — into index.ts's
30
+ * `unhandledRejection` hook, which shuts the gateway down. A root-owned mcp-token.env
31
+ * (one `sudo`, or a restored volume) therefore killed every agent and channel on the
32
+ * box, and the panel's next poll after restart killed it again. Failures are still
33
+ * logged, because silence is its own harm.
34
+ */
35
+ export declare function readTokenEnv(): Record<string, string>;
36
+ /**
37
+ * `getSecret` against an ALREADY-loaded snapshot, for a caller reading several keys
38
+ * at once (`refreshOne`'s due-check, `refreshStatusOf`). Same "an empty value means
39
+ * absent" rule, in one place: it is the difference between "connected" and "not" on
40
+ * every status surface, and it was previously re-stated at each call site.
41
+ */
42
+ export declare function getSecretFrom(map: Record<string, string>, envName: string): string | null;
43
+ /** Get a single secret value, or null if absent (or unreadable — see `readTokenEnv`). */
44
+ export declare function getSecret(envName: string): string | null;
45
+ /**
46
+ * Coerce a raw value from this file into a non-negative, finite counter/timestamp.
47
+ *
48
+ * Every numeric thing stored here — the refresh failure streaks, the backoff deadline,
49
+ * the token expiry — is hand-editable text, and a NaN reached from it poisons whatever
50
+ * reads it: `NaN >= MAX_CONSECUTIVE_FAILURES` is false forever, so the sweep's give-up
51
+ * branch becomes unreachable and a NaN backoff never blocks its skip guard, while the
52
+ * same value serialized into a status response arrives as `null` and makes a caller's
53
+ * `failures >= 3` test quietly false. One floor, used by both the code that decides
54
+ * whether to delete credentials and the code that displays the decision, so the two
55
+ * cannot disagree about what the file says.
56
+ */
57
+ export declare function parseCounter(raw: string | null | undefined): number;
58
+ /** True when the secret is present and non-empty. */
59
+ export declare function hasSecret(envName: string): boolean;
60
+ /**
61
+ * Upsert a secret line (creates the file if needed).
62
+ *
63
+ * Throws on a malformed key — unlike the read path, this one cannot fail soft:
64
+ * silently dropping a write would leave the caller believing a secret was stored.
65
+ */
66
+ export declare function setSecret(envName: string, value: string): void;
67
+ /**
68
+ * Upsert several secrets in ONE read-modify-write.
69
+ *
70
+ * Not a race fix — each `setSecret` is already atomic against other JS (read, mutate
71
+ * and rename are all synchronous). It fixes *partial* application: storing an OAuth
72
+ * result used to be five separate rewrites, and a crash between them left an
73
+ * access_token with no expiry, which the sweep then treats as "due now" forever. One
74
+ * rename means the whole group lands or none of it does; every key is validated before
75
+ * anything is written, so a bad key aborts the batch rather than half-committing.
76
+ */
77
+ export declare function setSecrets(entries: Record<string, string>): void;
78
+ /**
79
+ * Upsert some secrets and remove others in ONE read-modify-write.
80
+ *
81
+ * `setSecrets` then `deleteSecrets` is two renames, reopening the partial-application
82
+ * hole `setSecrets` closes. The sweep needs this: recording one kind of failure clears
83
+ * the other kind's counter, and a crash between the writes leaves both set — which
84
+ * status reports as a backoff that is not in effect.
85
+ *
86
+ * Removals are applied before upserts, so a key appearing in both is written, not
87
+ * dropped. Validation covers every upserted key before anything is written; removals
88
+ * need none, since removing a key that could never have been written is a no-op.
89
+ */
90
+ export declare function updateSecrets(entries: Record<string, string>, removals?: readonly string[]): void;
91
+ /** Remove several secret lines in one rewrite. No-op for names that aren't present. */
92
+ export declare function deleteSecrets(envNames: readonly string[]): void;
93
+ //# sourceMappingURL=token-env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-env.d.ts","sourceRoot":"","sources":["../../src/connectors/token-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAqCH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAErD;AA0ED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAcrD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGzF;AAED,yFAAyF;AACzF,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAExD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGnE;AAED,qDAAqD;AACrD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAElD;AA+BD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAE9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAEhE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,QAAQ,GAAE,SAAS,MAAM,EAAO,GAC/B,IAAI,CAUN;AAED,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAS/D"}
@@ -0,0 +1,323 @@
1
+ "use strict";
2
+ /**
3
+ * Connector secret storage — ~/.claude-gateway/mcp-token.env
4
+ *
5
+ * A plain dotenv file (mode 0600) of `KEY=value` lines, one per connector secret
6
+ * (e.g. GITHUB_TOKEN=ghp_...). This mirrors the gateway's existing secret posture
7
+ * (~/.claude-gateway/.env holds bot tokens in plaintext); config.json only ever holds
8
+ * the env-var NAME, never the value.
9
+ *
10
+ * The file is parsed fresh on every read so a web "connect" takes effect on the next
11
+ * session spawn with no daemon restart. Reads fail soft (an unreadable file reads as
12
+ * "nothing is connected"); writes do not, because a write that fails soft would erase
13
+ * what it could not read — see `readTokenEnvForUpdate` vs `readTokenEnv`.
14
+ *
15
+ * Nothing outside this module parses the file — it is never handed to a shell or
16
+ * `--env-file` — so the quoting rules are ours and both sides of the round-trip live
17
+ * here. Secret VALUES are not ours, though: a provider's token endpoint decides what
18
+ * `access_token` looks like, and a value containing a newline would otherwise be
19
+ * written as a second `KEY=value` line and read back as a *different* connector's
20
+ * secret. See `encodeValue`/`decodeValue`.
21
+ */
22
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ var desc = Object.getOwnPropertyDescriptor(m, k);
25
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
26
+ desc = { enumerable: true, get: function() { return m[k]; } };
27
+ }
28
+ Object.defineProperty(o, k2, desc);
29
+ }) : (function(o, m, k, k2) {
30
+ if (k2 === undefined) k2 = k;
31
+ o[k2] = m[k];
32
+ }));
33
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
34
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
35
+ }) : function(o, v) {
36
+ o["default"] = v;
37
+ });
38
+ var __importStar = (this && this.__importStar) || (function () {
39
+ var ownKeys = function(o) {
40
+ ownKeys = Object.getOwnPropertyNames || function (o) {
41
+ var ar = [];
42
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
+ return ar;
44
+ };
45
+ return ownKeys(o);
46
+ };
47
+ return function (mod) {
48
+ if (mod && mod.__esModule) return mod;
49
+ var result = {};
50
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
+ __setModuleDefault(result, mod);
52
+ return result;
53
+ };
54
+ })();
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.isValidSecretKey = isValidSecretKey;
57
+ exports.readTokenEnv = readTokenEnv;
58
+ exports.getSecretFrom = getSecretFrom;
59
+ exports.getSecret = getSecret;
60
+ exports.parseCounter = parseCounter;
61
+ exports.hasSecret = hasSecret;
62
+ exports.setSecret = setSecret;
63
+ exports.setSecrets = setSecrets;
64
+ exports.updateSecrets = updateSecrets;
65
+ exports.deleteSecrets = deleteSecrets;
66
+ const fs = __importStar(require("fs"));
67
+ const os = __importStar(require("os"));
68
+ const path = __importStar(require("path"));
69
+ const paths_1 = require("../utils/paths");
70
+ /**
71
+ * Resolve the mcp-token.env path (override via GATEWAY_MCP_TOKEN_ENV_PATH, used by
72
+ * tests and by operators who keep secrets off the default volume).
73
+ *
74
+ * Tilde-expanded, because .env.example ships the override commented out as
75
+ * `~/.claude-gateway/mcp-token.env` and index.ts does the same for its sibling
76
+ * GATEWAY_CONFIG. A shell does not expand `~` inside a .env file, so without this
77
+ * an operator who uncomments the documented line gets a literal `~` directory
78
+ * created under the gateway's cwd by ensureDir() below — every existing connector
79
+ * then reads as disconnected and freshly minted OAuth tokens are written to the
80
+ * wrong file, with nothing logged to say so.
81
+ */
82
+ function tokenEnvPath() {
83
+ const override = process.env.GATEWAY_MCP_TOKEN_ENV_PATH;
84
+ return override
85
+ ? (0, paths_1.expandHome)(override)
86
+ : path.join(os.homedir(), '.claude-gateway', 'mcp-token.env');
87
+ }
88
+ /**
89
+ * Keys are env-var names, and the only structural delimiter this format has. Anything
90
+ * outside this shape (a newline, `=`, a space, a quote) could forge extra entries, so
91
+ * it is rejected rather than escaped — every key the gateway writes is
92
+ * `customSecretKey()`/`internalSecretKey()` output, so a rejection means a caller
93
+ * passed unvalidated user input. `-` is allowed because connector ids are slugs
94
+ * (`CUSTOM__google-calendar__…`); these are map keys here, never real env vars.
95
+ */
96
+ const KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
97
+ function isValidSecretKey(key) {
98
+ return KEY_RE.test(key);
99
+ }
100
+ /**
101
+ * Encode a value for one `KEY=value` line. Values that survive a raw round-trip (the
102
+ * common case — tokens are base64url/JWT) are written bare so the file stays greppable;
103
+ * anything else is double-quoted and backslash-escaped, which `decodeValue` reverses.
104
+ */
105
+ function encodeValue(value) {
106
+ const needsQuoting = value !== value.trim() || /["\\\n\r]/.test(value) || value.startsWith('#');
107
+ if (!needsQuoting)
108
+ return value;
109
+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r')}"`;
110
+ }
111
+ /** Reverse of `encodeValue`. Bare values are returned trimmed, as they always were. */
112
+ function decodeValue(raw) {
113
+ const v = raw.trim();
114
+ if (v.length < 2 || !v.startsWith('"') || !v.endsWith('"'))
115
+ return v;
116
+ const inner = v.slice(1, -1);
117
+ let out = '';
118
+ for (let i = 0; i < inner.length; i++) {
119
+ if (inner[i] !== '\\') {
120
+ out += inner[i];
121
+ continue;
122
+ }
123
+ const next = inner[++i];
124
+ out += next === 'n' ? '\n' : next === 'r' ? '\r' : (next ?? '');
125
+ }
126
+ return out;
127
+ }
128
+ /** Parse a dotenv file body into a flat map. Ignores blank lines and `#` comments. */
129
+ function parse(body) {
130
+ // Null-prototype so a hostile `__proto__` key can never reach Object.prototype.
131
+ const out = Object.create(null);
132
+ for (const rawLine of body.split('\n')) {
133
+ const line = rawLine.trim();
134
+ if (!line || line.startsWith('#'))
135
+ continue;
136
+ const eq = line.indexOf('=');
137
+ if (eq <= 0)
138
+ continue;
139
+ const key = line.slice(0, eq).trim();
140
+ if (!isValidSecretKey(key))
141
+ continue;
142
+ out[key] = decodeValue(line.slice(eq + 1));
143
+ }
144
+ return out;
145
+ }
146
+ /**
147
+ * The read half of a read-modify-write. Only ENOENT means "empty" — every other
148
+ * errno is rethrown, on purpose.
149
+ *
150
+ * `setSecrets`, `updateSecrets` and `deleteSecrets` rewrite the file whole from
151
+ * whatever this returns. Swallowing an EMFILE or a momentary EACCES here turned that
152
+ * into "write only the new entries", silently destroying every other connector's token
153
+ * with no error anywhere. Failing the write is strictly better — the file stays intact.
154
+ *
155
+ * Deliberately NOT exported: the strictness is right only when about to overwrite the
156
+ * file, and wrong for a read-only caller (see `readTokenEnv`).
157
+ */
158
+ function readTokenEnvForUpdate() {
159
+ try {
160
+ return parse(fs.readFileSync(tokenEnvPath(), 'utf-8'));
161
+ }
162
+ catch (err) {
163
+ if (err.code === 'ENOENT')
164
+ return {};
165
+ throw err;
166
+ }
167
+ }
168
+ // An unreadable file stays unreadable, and status pollers hit this every couple of
169
+ // seconds. Logging every failure buries the log; logging none is how an EACCES goes
170
+ // unnoticed for a week. One line per minute keeps the signal.
171
+ const READ_FAILURE_LOG_INTERVAL_MS = 60 * 1000;
172
+ let lastReadFailureLog = 0;
173
+ /**
174
+ * Read all connector secrets for a caller that only intends to LOOK at them.
175
+ * Returns {} when the file does not exist — or cannot be read.
176
+ *
177
+ * Degrading here is the whole point: every caller asks "is this connected / when does
178
+ * it expire", and the honest answer for an unreadable file is "as far as we can tell,
179
+ * no". The throw it replaced propagated out of `GET /v1/connectors` — an `async`
180
+ * handler on Express 4, which does not catch rejections — into index.ts's
181
+ * `unhandledRejection` hook, which shuts the gateway down. A root-owned mcp-token.env
182
+ * (one `sudo`, or a restored volume) therefore killed every agent and channel on the
183
+ * box, and the panel's next poll after restart killed it again. Failures are still
184
+ * logged, because silence is its own harm.
185
+ */
186
+ function readTokenEnv() {
187
+ try {
188
+ return readTokenEnvForUpdate();
189
+ }
190
+ catch (err) {
191
+ const now = Date.now();
192
+ if (now - lastReadFailureLog >= READ_FAILURE_LOG_INTERVAL_MS) {
193
+ lastReadFailureLog = now;
194
+ console.error(`token-env: cannot read ${tokenEnvPath()} (${err.code ?? 'unknown'})` +
195
+ ` — treating every connector as not connected until it is readable: ${err.message}`);
196
+ }
197
+ return {};
198
+ }
199
+ }
200
+ /**
201
+ * `getSecret` against an ALREADY-loaded snapshot, for a caller reading several keys
202
+ * at once (`refreshOne`'s due-check, `refreshStatusOf`). Same "an empty value means
203
+ * absent" rule, in one place: it is the difference between "connected" and "not" on
204
+ * every status surface, and it was previously re-stated at each call site.
205
+ */
206
+ function getSecretFrom(map, envName) {
207
+ const v = map[envName];
208
+ return v === undefined || v === '' ? null : v;
209
+ }
210
+ /** Get a single secret value, or null if absent (or unreadable — see `readTokenEnv`). */
211
+ function getSecret(envName) {
212
+ return getSecretFrom(readTokenEnv(), envName);
213
+ }
214
+ /**
215
+ * Coerce a raw value from this file into a non-negative, finite counter/timestamp.
216
+ *
217
+ * Every numeric thing stored here — the refresh failure streaks, the backoff deadline,
218
+ * the token expiry — is hand-editable text, and a NaN reached from it poisons whatever
219
+ * reads it: `NaN >= MAX_CONSECUTIVE_FAILURES` is false forever, so the sweep's give-up
220
+ * branch becomes unreachable and a NaN backoff never blocks its skip guard, while the
221
+ * same value serialized into a status response arrives as `null` and makes a caller's
222
+ * `failures >= 3` test quietly false. One floor, used by both the code that decides
223
+ * whether to delete credentials and the code that displays the decision, so the two
224
+ * cannot disagree about what the file says.
225
+ */
226
+ function parseCounter(raw) {
227
+ const n = Number(raw ?? '0');
228
+ return Number.isFinite(n) && n > 0 ? n : 0;
229
+ }
230
+ /** True when the secret is present and non-empty. */
231
+ function hasSecret(envName) {
232
+ return getSecret(envName) !== null;
233
+ }
234
+ /** Atomically rewrite the file (mode 0600) from a map. */
235
+ function writeAll(map) {
236
+ const file = tokenEnvPath();
237
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
238
+ const body = Object.entries(map)
239
+ .map(([k, v]) => `${k}=${encodeValue(v)}`)
240
+ .join('\n') + '\n';
241
+ const tmp = `${file}.tmp.${process.pid}.${Date.now()}`;
242
+ fs.writeFileSync(tmp, body, { mode: 0o600 });
243
+ try {
244
+ fs.renameSync(tmp, file);
245
+ }
246
+ catch (err) {
247
+ // `tmp` holds every secret this process knows. A rename that fails — a full
248
+ // disk, the directory replaced underneath us, EACCES after a permissions
249
+ // change — leaves that complete copy sitting next to the real file under a
250
+ // name nothing will ever clean up, and no later write reuses the path
251
+ // (pid+timestamp), so it accumulates one leaked credential dump per failure.
252
+ // Best-effort: the original error is what the caller needs to see, so a
253
+ // failed unlink must not replace it.
254
+ try {
255
+ fs.unlinkSync(tmp);
256
+ }
257
+ catch {
258
+ /* nothing better to do — surface the rename failure below */
259
+ }
260
+ throw err;
261
+ }
262
+ }
263
+ /**
264
+ * Upsert a secret line (creates the file if needed).
265
+ *
266
+ * Throws on a malformed key — unlike the read path, this one cannot fail soft:
267
+ * silently dropping a write would leave the caller believing a secret was stored.
268
+ */
269
+ function setSecret(envName, value) {
270
+ setSecrets({ [envName]: value });
271
+ }
272
+ /**
273
+ * Upsert several secrets in ONE read-modify-write.
274
+ *
275
+ * Not a race fix — each `setSecret` is already atomic against other JS (read, mutate
276
+ * and rename are all synchronous). It fixes *partial* application: storing an OAuth
277
+ * result used to be five separate rewrites, and a crash between them left an
278
+ * access_token with no expiry, which the sweep then treats as "due now" forever. One
279
+ * rename means the whole group lands or none of it does; every key is validated before
280
+ * anything is written, so a bad key aborts the batch rather than half-committing.
281
+ */
282
+ function setSecrets(entries) {
283
+ updateSecrets(entries, []);
284
+ }
285
+ /**
286
+ * Upsert some secrets and remove others in ONE read-modify-write.
287
+ *
288
+ * `setSecrets` then `deleteSecrets` is two renames, reopening the partial-application
289
+ * hole `setSecrets` closes. The sweep needs this: recording one kind of failure clears
290
+ * the other kind's counter, and a crash between the writes leaves both set — which
291
+ * status reports as a backoff that is not in effect.
292
+ *
293
+ * Removals are applied before upserts, so a key appearing in both is written, not
294
+ * dropped. Validation covers every upserted key before anything is written; removals
295
+ * need none, since removing a key that could never have been written is a no-op.
296
+ */
297
+ function updateSecrets(entries, removals = []) {
298
+ for (const key of Object.keys(entries)) {
299
+ if (!isValidSecretKey(key)) {
300
+ throw new Error(`Invalid secret key '${key}' — expected /${KEY_RE.source}/`);
301
+ }
302
+ }
303
+ const map = readTokenEnvForUpdate();
304
+ for (const name of removals)
305
+ delete map[name];
306
+ for (const [key, value] of Object.entries(entries))
307
+ map[key] = value;
308
+ writeAll(map);
309
+ }
310
+ /** Remove several secret lines in one rewrite. No-op for names that aren't present. */
311
+ function deleteSecrets(envNames) {
312
+ const map = readTokenEnvForUpdate();
313
+ let changed = false;
314
+ for (const name of envNames) {
315
+ if (!(name in map))
316
+ continue;
317
+ delete map[name];
318
+ changed = true;
319
+ }
320
+ if (changed)
321
+ writeAll(map);
322
+ }
323
+ //# sourceMappingURL=token-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-env.js","sourceRoot":"","sources":["../../src/connectors/token-env.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCH,4CAEC;AAuFD,oCAcC;AAQD,sCAGC;AAGD,8BAEC;AAcD,oCAGC;AAGD,8BAEC;AAqCD,8BAEC;AAYD,gCAEC;AAcD,sCAaC;AAGD,sCASC;AA5QD,uCAAyB;AACzB,uCAAyB;AACzB,2CAA6B;AAE7B,0CAA4C;AAE5C;;;;;;;;;;;GAWG;AACH,SAAS,YAAY;IACnB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC;IACxD,OAAO,QAAQ;QACb,CAAC,CAAC,IAAA,kBAAU,EAAC,QAAQ,CAAC;QACtB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,iBAAiB,EAAE,eAAe,CAAC,CAAC;AAClE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,GAAG,2BAA2B,CAAC;AAE3C,SAAgB,gBAAgB,CAAC,GAAW;IAC1C,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,KAAa;IAChC,MAAM,YAAY,GAChB,KAAK,KAAK,KAAK,CAAC,IAAI,EAAE,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAC7E,IAAI,CAAC,YAAY;QAAE,OAAO,KAAK,CAAC;IAChC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC;AAC9G,CAAC;AAED,uFAAuF;AACvF,SAAS,WAAW,CAAC,GAAW;IAC9B,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACrB,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACrE,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACtB,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;YAChB,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;QACxB,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,sFAAsF;AACtF,SAAS,KAAK,CAAC,IAAY;IACzB,gFAAgF;IAChF,MAAM,GAAG,GAA2B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACxD,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,EAAE,IAAI,CAAC;YAAE,SAAS;QACtB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACrC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC;YAAE,SAAS;QACrC,GAAG,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,qBAAqB;IAC5B,IAAI,CAAC;QACH,OAAO,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;IACzD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QAChE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,oFAAoF;AACpF,8DAA8D;AAC9D,MAAM,4BAA4B,GAAG,EAAE,GAAG,IAAI,CAAC;AAC/C,IAAI,kBAAkB,GAAG,CAAC,CAAC;AAE3B;;;;;;;;;;;;GAYG;AACH,SAAgB,YAAY;IAC1B,IAAI,CAAC;QACH,OAAO,qBAAqB,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,GAAG,GAAG,kBAAkB,IAAI,4BAA4B,EAAE,CAAC;YAC7D,kBAAkB,GAAG,GAAG,CAAC;YACzB,OAAO,CAAC,KAAK,CACX,0BAA0B,YAAY,EAAE,KAAM,GAA6B,CAAC,IAAI,IAAI,SAAS,GAAG;gBAC9F,sEAAuE,GAAa,CAAC,OAAO,EAAE,CACjG,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,GAA2B,EAAE,OAAe;IACxE,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;IACvB,OAAO,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,yFAAyF;AACzF,SAAgB,SAAS,CAAC,OAAe;IACvC,OAAO,aAAa,CAAC,YAAY,EAAE,EAAE,OAAO,CAAC,CAAC;AAChD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,YAAY,CAAC,GAA8B;IACzD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;IAC7B,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,qDAAqD;AACrD,SAAgB,SAAS,CAAC,OAAe;IACvC,OAAO,SAAS,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;AACrC,CAAC;AAED,0DAA0D;AAC1D,SAAS,QAAQ,CAAC,GAA2B;IAC3C,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnE,MAAM,IAAI,GACR,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;SAChB,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;SACzC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvB,MAAM,GAAG,GAAG,GAAG,IAAI,QAAQ,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IACvD,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7C,IAAI,CAAC;QACH,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,4EAA4E;QAC5E,yEAAyE;QACzE,2EAA2E;QAC3E,sEAAsE;QACtE,6EAA6E;QAC7E,wEAAwE;QACxE,qCAAqC;QACrC,IAAI,CAAC;YACH,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC;YACP,6DAA6D;QAC/D,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAgB,SAAS,CAAC,OAAe,EAAE,KAAa;IACtD,UAAU,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,UAAU,CAAC,OAA+B;IACxD,aAAa,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,aAAa,CAC3B,OAA+B,EAC/B,WAA8B,EAAE;IAEhC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,uBAAuB,GAAG,iBAAiB,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IACD,MAAM,GAAG,GAAG,qBAAqB,EAAE,CAAC;IACpC,KAAK,MAAM,IAAI,IAAI,QAAQ;QAAE,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACrE,QAAQ,CAAC,GAAG,CAAC,CAAC;AAChB,CAAC;AAED,uFAAuF;AACvF,SAAgB,aAAa,CAAC,QAA2B;IACvD,MAAM,GAAG,GAAG,qBAAqB,EAAE,CAAC;IACpC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC;YAAE,SAAS;QAC7B,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;QACjB,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;IACD,IAAI,OAAO;QAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;AAC7B,CAAC"}