ctxr-cli 0.30.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -3
- package/dist/commands/land.d.ts.map +1 -1
- package/dist/commands/land.js +3 -18
- package/dist/commands/land.js.map +1 -1
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +10 -2
- package/dist/commands/serve.js.map +1 -1
- package/dist/config/schema.d.ts +18 -1
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +41 -9
- package/dist/config/schema.js.map +1 -1
- package/dist/core/browse/approve.d.ts +3 -0
- package/dist/core/browse/approve.d.ts.map +1 -1
- package/dist/core/browse/approve.js.map +1 -1
- package/dist/core/browse/page-url.d.ts +15 -0
- package/dist/core/browse/page-url.d.ts.map +1 -1
- package/dist/core/browse/page-url.js +8 -1
- package/dist/core/browse/page-url.js.map +1 -1
- package/dist/core/forge/credentials.d.ts +12 -1
- package/dist/core/forge/credentials.d.ts.map +1 -1
- package/dist/core/forge/credentials.js +25 -1
- package/dist/core/forge/credentials.js.map +1 -1
- package/dist/core/forge/github.d.ts +9 -0
- package/dist/core/forge/github.d.ts.map +1 -1
- package/dist/core/forge/github.js +6 -0
- package/dist/core/forge/github.js.map +1 -1
- package/dist/core/mcp/drafts.d.ts +30 -0
- package/dist/core/mcp/drafts.d.ts.map +1 -0
- package/dist/core/mcp/drafts.js +52 -0
- package/dist/core/mcp/drafts.js.map +1 -0
- package/dist/core/mcp/http.d.ts +40 -0
- package/dist/core/mcp/http.d.ts.map +1 -0
- package/dist/core/mcp/http.js +90 -0
- package/dist/core/mcp/http.js.map +1 -0
- package/dist/core/mcp/jsonrpc.d.ts +73 -0
- package/dist/core/mcp/jsonrpc.d.ts.map +1 -0
- package/dist/core/mcp/jsonrpc.js +149 -0
- package/dist/core/mcp/jsonrpc.js.map +1 -0
- package/dist/core/mcp/members.d.ts +11 -0
- package/dist/core/mcp/members.d.ts.map +1 -0
- package/dist/core/mcp/members.js +21 -0
- package/dist/core/mcp/members.js.map +1 -0
- package/dist/core/mcp/oauth.d.ts +99 -0
- package/dist/core/mcp/oauth.d.ts.map +1 -0
- package/dist/core/mcp/oauth.js +454 -0
- package/dist/core/mcp/oauth.js.map +1 -0
- package/dist/core/mcp/pages.d.ts +3 -0
- package/dist/core/mcp/pages.d.ts.map +1 -0
- package/dist/core/mcp/pages.js +41 -0
- package/dist/core/mcp/pages.js.map +1 -0
- package/dist/core/mcp/ripgrep.d.ts +48 -0
- package/dist/core/mcp/ripgrep.d.ts.map +1 -0
- package/dist/core/mcp/ripgrep.js +98 -0
- package/dist/core/mcp/ripgrep.js.map +1 -0
- package/dist/core/mcp/rwlock.d.ts +16 -0
- package/dist/core/mcp/rwlock.d.ts.map +1 -0
- package/dist/core/mcp/rwlock.js +59 -0
- package/dist/core/mcp/rwlock.js.map +1 -0
- package/dist/core/mcp/sessions.d.ts +84 -0
- package/dist/core/mcp/sessions.d.ts.map +1 -0
- package/dist/core/mcp/sessions.js +154 -0
- package/dist/core/mcp/sessions.js.map +1 -0
- package/dist/core/mcp/surface.d.ts +42 -0
- package/dist/core/mcp/surface.d.ts.map +1 -0
- package/dist/core/mcp/surface.js +141 -0
- package/dist/core/mcp/surface.js.map +1 -0
- package/dist/core/mcp/sync.d.ts +91 -0
- package/dist/core/mcp/sync.d.ts.map +1 -0
- package/dist/core/mcp/sync.js +218 -0
- package/dist/core/mcp/sync.js.map +1 -0
- package/dist/core/mcp/tools/capture.d.ts +64 -0
- package/dist/core/mcp/tools/capture.d.ts.map +1 -0
- package/dist/core/mcp/tools/capture.js +182 -0
- package/dist/core/mcp/tools/capture.js.map +1 -0
- package/dist/core/mcp/tools/edit.d.ts +79 -0
- package/dist/core/mcp/tools/edit.d.ts.map +1 -0
- package/dist/core/mcp/tools/edit.js +212 -0
- package/dist/core/mcp/tools/edit.js.map +1 -0
- package/dist/core/mcp/tools/files.d.ts +163 -0
- package/dist/core/mcp/tools/files.d.ts.map +1 -0
- package/dist/core/mcp/tools/files.js +265 -0
- package/dist/core/mcp/tools/files.js.map +1 -0
- package/dist/core/mcp/tools/find.d.ts +82 -0
- package/dist/core/mcp/tools/find.d.ts.map +1 -0
- package/dist/core/mcp/tools/find.js +136 -0
- package/dist/core/mcp/tools/find.js.map +1 -0
- package/dist/core/mcp/tools/gather.d.ts +88 -0
- package/dist/core/mcp/tools/gather.d.ts.map +1 -0
- package/dist/core/mcp/tools/gather.js +123 -0
- package/dist/core/mcp/tools/gather.js.map +1 -0
- package/dist/core/mcp/tools/index.d.ts +21 -0
- package/dist/core/mcp/tools/index.d.ts.map +1 -0
- package/dist/core/mcp/tools/index.js +129 -0
- package/dist/core/mcp/tools/index.js.map +1 -0
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +19 -14
- package/dist/core/skills.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/templates/mcp/Edit.md +7 -0
- package/templates/mcp/Glob.md +5 -0
- package/templates/mcp/Grep.md +10 -0
- package/templates/mcp/Read.md +7 -0
- package/templates/mcp/Write.md +7 -0
- package/templates/mcp/capture.md +9 -0
- package/templates/mcp/consent.html +40 -0
- package/templates/mcp/find.md +9 -0
- package/templates/mcp/gather.md +5 -0
- package/templates/mcp/server-instructions.md +17 -0
- package/templates/skills/{ctxr-circleback → ctxr-capture-circleback}/sweep.mjs +6 -5
- package/templates/skills/ctxr-capture-circleback.md +67 -0
- package/templates/skills/ctxr-capture.md +10 -4
- package/templates/skills/ctxr-circleback.md +0 -70
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { BodyTooLargeError, readBody, sendJson, sendText } from './http.js';
|
|
3
|
+
import { MCP_SCOPES, scopesFor } from './members.js';
|
|
4
|
+
/**
|
|
5
|
+
* serve-the-store-over-mcp design.md D4–D5: `ctxr serve` is the OAuth 2.1
|
|
6
|
+
* authorisation server for its own `/mcp`, and it stores nothing it issues.
|
|
7
|
+
* Client ids, authorisation codes and access tokens are HMAC-signed payloads.
|
|
8
|
+
* Who may sign in is decided by the access proxy's verified identity — the
|
|
9
|
+
* assertion `change_approval.identity` configures, checked on every
|
|
10
|
+
* `/authorize` request exactly as the approval routes check it — and by the
|
|
11
|
+
* `mcp` block the server last loaded, consulted again on every `/mcp`
|
|
12
|
+
* request, so removing someone there refuses their next call.
|
|
13
|
+
*/
|
|
14
|
+
export const ACCESS_TOKEN_TTL_SECONDS = 30 * 24 * 60 * 60;
|
|
15
|
+
export const AUTHORIZATION_CODE_TTL_SECONDS = 10 * 60;
|
|
16
|
+
const MAX_FORM_BYTES = 64 * 1024;
|
|
17
|
+
const MAX_REDIRECT_URIS = 10;
|
|
18
|
+
const MAX_REDIRECT_URI_LENGTH = 2000;
|
|
19
|
+
export const PROTECTED_RESOURCE_METADATA_PATH = '/.well-known/oauth-protected-resource';
|
|
20
|
+
export const AUTHORIZATION_SERVER_METADATA_PATH = '/.well-known/oauth-authorization-server';
|
|
21
|
+
export const OPENID_CONFIGURATION_PATH = '/.well-known/openid-configuration';
|
|
22
|
+
export const REGISTER_PATH = '/register';
|
|
23
|
+
export const AUTHORIZE_PATH = '/authorize';
|
|
24
|
+
export const TOKEN_PATH = '/token';
|
|
25
|
+
/** Every path the OAuth side of the MCP surface answers. */
|
|
26
|
+
export const OAUTH_PATHS = [
|
|
27
|
+
PROTECTED_RESOURCE_METADATA_PATH,
|
|
28
|
+
`${PROTECTED_RESOURCE_METADATA_PATH}/mcp`,
|
|
29
|
+
AUTHORIZATION_SERVER_METADATA_PATH,
|
|
30
|
+
OPENID_CONFIGURATION_PATH,
|
|
31
|
+
REGISTER_PATH,
|
|
32
|
+
AUTHORIZE_PATH,
|
|
33
|
+
TOKEN_PATH,
|
|
34
|
+
];
|
|
35
|
+
function b64url(buffer) {
|
|
36
|
+
return buffer.toString('base64url');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* `kind.payload.mac`, the kind inside the MAC so a client id can never be
|
|
40
|
+
* replayed as a token. Authorisation codes are signed under a key that mixes
|
|
41
|
+
* in a per-process nonce: a restart invalidates outstanding codes instead of
|
|
42
|
+
* forgetting which were spent (design.md D5).
|
|
43
|
+
*/
|
|
44
|
+
export class Signer {
|
|
45
|
+
key;
|
|
46
|
+
codeKey;
|
|
47
|
+
constructor(key) {
|
|
48
|
+
this.key = key;
|
|
49
|
+
this.codeKey = createHmac('sha256', key).update(`authorization-code:${randomBytes(16).toString('hex')}`).digest();
|
|
50
|
+
}
|
|
51
|
+
keyFor(kind) {
|
|
52
|
+
return kind === 'code' ? this.codeKey : this.key;
|
|
53
|
+
}
|
|
54
|
+
sign(kind, payload) {
|
|
55
|
+
const body = b64url(Buffer.from(JSON.stringify(payload), 'utf8'));
|
|
56
|
+
const mac = createHmac('sha256', this.keyFor(kind)).update(`${kind}.${body}`).digest();
|
|
57
|
+
return `${kind}.${body}.${b64url(mac)}`;
|
|
58
|
+
}
|
|
59
|
+
verify(kind, token) {
|
|
60
|
+
const parts = token.split('.');
|
|
61
|
+
if (parts.length !== 3 || parts[0] !== kind)
|
|
62
|
+
return undefined;
|
|
63
|
+
const expected = createHmac('sha256', this.keyFor(kind)).update(`${kind}.${parts[1]}`).digest();
|
|
64
|
+
const actual = Buffer.from(parts[2], 'base64url');
|
|
65
|
+
if (actual.length !== expected.length || !timingSafeEqual(actual, expected))
|
|
66
|
+
return undefined;
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** The OAuth endpoints of the MCP surface. Built per request from the loaded configuration; holds no state of its own. */
|
|
76
|
+
export class AuthorizationServer {
|
|
77
|
+
options;
|
|
78
|
+
constructor(options) {
|
|
79
|
+
this.options = options;
|
|
80
|
+
}
|
|
81
|
+
nowSeconds() {
|
|
82
|
+
return Math.floor(this.options.now().getTime() / 1000);
|
|
83
|
+
}
|
|
84
|
+
get protectedResourceMetadataUrl() {
|
|
85
|
+
return `${this.options.issuer}${PROTECTED_RESOURCE_METADATA_PATH}/mcp`;
|
|
86
|
+
}
|
|
87
|
+
protectedResourceMetadata() {
|
|
88
|
+
return {
|
|
89
|
+
resource: this.options.resource,
|
|
90
|
+
authorization_servers: [this.options.issuer],
|
|
91
|
+
scopes_supported: [...MCP_SCOPES],
|
|
92
|
+
bearer_methods_supported: ['header'],
|
|
93
|
+
resource_name: 'contexture store',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
authorizationServerMetadata() {
|
|
97
|
+
const issuer = this.options.issuer;
|
|
98
|
+
return {
|
|
99
|
+
issuer,
|
|
100
|
+
authorization_endpoint: `${issuer}${AUTHORIZE_PATH}`,
|
|
101
|
+
token_endpoint: `${issuer}${TOKEN_PATH}`,
|
|
102
|
+
registration_endpoint: `${issuer}${REGISTER_PATH}`,
|
|
103
|
+
response_types_supported: ['code'],
|
|
104
|
+
grant_types_supported: ['authorization_code'],
|
|
105
|
+
code_challenge_methods_supported: ['S256'],
|
|
106
|
+
token_endpoint_auth_methods_supported: ['none'],
|
|
107
|
+
scopes_supported: [...MCP_SCOPES],
|
|
108
|
+
authorization_response_iss_parameter_supported: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** Handles every OAuth path; returns false for anything else. */
|
|
112
|
+
async handle(req, res, pathname) {
|
|
113
|
+
if (!OAUTH_PATHS.includes(pathname))
|
|
114
|
+
return false;
|
|
115
|
+
const method = req.method ?? 'GET';
|
|
116
|
+
// Browser-based clients (the MCP inspector) read metadata and call registration and the token
|
|
117
|
+
// endpoint cross-origin; none of them use cookies, so a wildcard origin grants nothing a direct request lacks.
|
|
118
|
+
const cors = pathname === AUTHORIZE_PATH ? {} : { 'Access-Control-Allow-Origin': '*' };
|
|
119
|
+
if (method === 'OPTIONS' && pathname !== AUTHORIZE_PATH) {
|
|
120
|
+
res.writeHead(204, {
|
|
121
|
+
...cors,
|
|
122
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
123
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, MCP-Protocol-Version',
|
|
124
|
+
'Access-Control-Max-Age': '86400',
|
|
125
|
+
});
|
|
126
|
+
res.end();
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
if (pathname.startsWith('/.well-known/')) {
|
|
131
|
+
if (method !== 'GET' && method !== 'HEAD')
|
|
132
|
+
return methodNotAllowed(res, 'GET');
|
|
133
|
+
const body = pathname.startsWith(PROTECTED_RESOURCE_METADATA_PATH) ? this.protectedResourceMetadata() : this.authorizationServerMetadata();
|
|
134
|
+
sendJson(res, 200, body, cors);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
if (pathname === REGISTER_PATH) {
|
|
138
|
+
if (method !== 'POST')
|
|
139
|
+
return methodNotAllowed(res, 'POST');
|
|
140
|
+
await this.register(req, res, cors);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
if (pathname === AUTHORIZE_PATH) {
|
|
144
|
+
if (method === 'GET' || method === 'HEAD')
|
|
145
|
+
await this.authorizeForm(req, res);
|
|
146
|
+
else if (method === 'POST')
|
|
147
|
+
await this.authorizeConfirm(req, res);
|
|
148
|
+
else
|
|
149
|
+
return methodNotAllowed(res, 'GET, POST');
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
if (method !== 'POST')
|
|
153
|
+
return methodNotAllowed(res, 'POST');
|
|
154
|
+
await this.token(req, res, cors);
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
if (err instanceof BodyTooLargeError) {
|
|
159
|
+
sendText(res, 413, `${err.message}\n`);
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// ---- registration (RFC 7591), stateless --------------------------------
|
|
166
|
+
async register(req, res, cors) {
|
|
167
|
+
let body;
|
|
168
|
+
try {
|
|
169
|
+
body = JSON.parse(await readBody(req, MAX_FORM_BYTES));
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
if (err instanceof BodyTooLargeError)
|
|
173
|
+
throw err;
|
|
174
|
+
sendJson(res, 400, { error: 'invalid_client_metadata', error_description: 'The registration body is not JSON.' }, cors);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const metadata = (typeof body === 'object' && body !== null ? body : {});
|
|
178
|
+
const uris = metadata.redirect_uris;
|
|
179
|
+
const hosts = Array.isArray(uris) ? uris.map((u) => { try {
|
|
180
|
+
return new URL(String(u)).host || String(u);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return String(u).slice(0, 60);
|
|
184
|
+
} }) : [];
|
|
185
|
+
this.options.log?.(`mcp register "${String(metadata.client_name ?? '').slice(0, 60)}" auth=${String(metadata.token_endpoint_auth_method ?? '-')} ` +
|
|
186
|
+
`grants=${Array.isArray(metadata.grant_types) ? metadata.grant_types.join(',') : '-'} redirects=${hosts.join(',') || '-'}`);
|
|
187
|
+
if (!Array.isArray(uris) || uris.length === 0 || uris.length > MAX_REDIRECT_URIS || !uris.every((u) => typeof u === 'string' && acceptableRedirectUri(u))) {
|
|
188
|
+
sendJson(res, 400, { error: 'invalid_redirect_uri', error_description: `Register 1–${MAX_REDIRECT_URIS} absolute redirect URIs: https, loopback http, or an application scheme.` }, cors);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const grantTypes = metadata.grant_types;
|
|
192
|
+
if (Array.isArray(grantTypes) && !grantTypes.includes('authorization_code')) {
|
|
193
|
+
sendJson(res, 400, { error: 'invalid_client_metadata', error_description: 'This server grants authorization_code only.' }, cors);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const name = typeof metadata.client_name === 'string' ? metadata.client_name.slice(0, 100) : undefined;
|
|
197
|
+
const payload = { r: uris, ...(name ? { n: name } : {}) };
|
|
198
|
+
sendJson(res, 201, {
|
|
199
|
+
client_id: this.options.signer.sign('client', payload),
|
|
200
|
+
client_id_issued_at: this.nowSeconds(),
|
|
201
|
+
redirect_uris: payload.r,
|
|
202
|
+
...(name ? { client_name: name } : {}),
|
|
203
|
+
token_endpoint_auth_method: 'none',
|
|
204
|
+
grant_types: ['authorization_code'],
|
|
205
|
+
response_types: ['code'],
|
|
206
|
+
}, cors);
|
|
207
|
+
}
|
|
208
|
+
// ---- authorization --------------------------------------------------------
|
|
209
|
+
/**
|
|
210
|
+
* Until the client and redirect URI are known good, an error is shown and
|
|
211
|
+
* never redirected — redirecting to an unverified URI is how an open
|
|
212
|
+
* redirector is built.
|
|
213
|
+
*/
|
|
214
|
+
validateAuthorizeRequest(params) {
|
|
215
|
+
const clientId = params.get('client_id') ?? '';
|
|
216
|
+
const client = this.options.signer.verify('client', clientId);
|
|
217
|
+
if (client === undefined || !Array.isArray(client.r)) {
|
|
218
|
+
return { kind: 'fatal', message: 'This sign-in link names a client this store never registered. Start connecting again from your app.' };
|
|
219
|
+
}
|
|
220
|
+
const requestedRedirect = params.get('redirect_uri');
|
|
221
|
+
const redirectUri = requestedRedirect ?? (client.r.length === 1 ? client.r[0] : '');
|
|
222
|
+
if (!client.r.includes(redirectUri))
|
|
223
|
+
return { kind: 'fatal', message: 'This sign-in link names a redirect address the client did not register.' };
|
|
224
|
+
const state = params.get('state');
|
|
225
|
+
const fail = (error, description) => ({ kind: 'redirect-error', redirectUri, state, error, description });
|
|
226
|
+
if (params.get('response_type') !== 'code')
|
|
227
|
+
return fail('unsupported_response_type', 'Only response_type=code is supported.');
|
|
228
|
+
const challenge = params.get('code_challenge') ?? '';
|
|
229
|
+
if (params.get('code_challenge_method') !== 'S256' || !/^[A-Za-z0-9_-]{43}$/.test(challenge)) {
|
|
230
|
+
return fail('invalid_request', 'PKCE with code_challenge_method=S256 is required.');
|
|
231
|
+
}
|
|
232
|
+
const resource = params.get('resource');
|
|
233
|
+
if (resource !== null && !sameResource(resource, this.options.resource))
|
|
234
|
+
return fail('invalid_target', `This server issues tokens only for ${this.options.resource}.`);
|
|
235
|
+
let requested = null;
|
|
236
|
+
const scope = params.get('scope');
|
|
237
|
+
if (scope !== null && scope.trim().length > 0) {
|
|
238
|
+
const words = scope.split(/\s+/);
|
|
239
|
+
const known = MCP_SCOPES.filter((s) => words.includes(s));
|
|
240
|
+
if (known.length === 0)
|
|
241
|
+
return fail('invalid_scope', `Request any of: ${MCP_SCOPES.join(' ')}.`);
|
|
242
|
+
requested = known;
|
|
243
|
+
}
|
|
244
|
+
return { kind: 'ok', request: { clientId, clientName: client.n ?? 'An app', redirectUri, state, challenge, scopes: requested } };
|
|
245
|
+
}
|
|
246
|
+
/** The verified member and the scopes this request would grant, or why there is none. */
|
|
247
|
+
async member(req, request) {
|
|
248
|
+
const verified = await this.options.verifier.verify(req.headers[this.options.identityHeader.toLowerCase()]);
|
|
249
|
+
if (!verified.ok)
|
|
250
|
+
return { kind: 'refused', status: 401, message: 'Open this page through the store’s sign-in (its access proxy), then try connecting again.' };
|
|
251
|
+
const held = scopesFor(verified.email, this.options.mcp());
|
|
252
|
+
if (held.length === 0)
|
|
253
|
+
return { kind: 'refused', status: 403, message: `${verified.email} may not connect apps to this store. Ask whoever runs it to add you.` };
|
|
254
|
+
const requested = request.scopes;
|
|
255
|
+
const scopes = requested === null ? held : held.filter((s) => requested.includes(s));
|
|
256
|
+
return scopes.length === 0 ? { kind: 'no-scope' } : { kind: 'member', email: verified.email, scopes };
|
|
257
|
+
}
|
|
258
|
+
hiddenFieldsFor(params) {
|
|
259
|
+
const fields = {};
|
|
260
|
+
for (const key of ['response_type', 'client_id', 'redirect_uri', 'code_challenge', 'code_challenge_method', 'state', 'scope', 'resource']) {
|
|
261
|
+
const value = params.get(key);
|
|
262
|
+
if (value !== null)
|
|
263
|
+
fields[key] = value;
|
|
264
|
+
}
|
|
265
|
+
return fields;
|
|
266
|
+
}
|
|
267
|
+
async authorizeForm(req, res) {
|
|
268
|
+
const params = new URL(req.url ?? '/', 'http://localhost').searchParams;
|
|
269
|
+
const verdict = this.validateAuthorizeRequest(params);
|
|
270
|
+
if (verdict.kind === 'fatal')
|
|
271
|
+
return sendText(res, 400, `${verdict.message}\n`);
|
|
272
|
+
if (verdict.kind === 'redirect-error')
|
|
273
|
+
return redirectWithError(res, verdict, this.options.issuer);
|
|
274
|
+
const who = await this.member(req, verdict.request);
|
|
275
|
+
if (who.kind === 'refused')
|
|
276
|
+
return sendText(res, who.status, `${who.message}\n`);
|
|
277
|
+
if (who.kind === 'no-scope')
|
|
278
|
+
return redirectWithError(res, { ...verdict.request, error: 'invalid_scope', description: 'You hold none of the requested scopes.' }, this.options.issuer);
|
|
279
|
+
const html = await this.options.renderConsentPage({
|
|
280
|
+
clientName: verdict.request.clientName,
|
|
281
|
+
redirectHost: new URL(verdict.request.redirectUri).host || verdict.request.redirectUri,
|
|
282
|
+
email: who.email,
|
|
283
|
+
scopes: who.scopes,
|
|
284
|
+
hiddenFields: this.hiddenFieldsFor(params),
|
|
285
|
+
});
|
|
286
|
+
sendPage(res, html);
|
|
287
|
+
}
|
|
288
|
+
/** The member's confirmation: only from a page this server rendered (`Origin`), only for a verified member. */
|
|
289
|
+
async authorizeConfirm(req, res) {
|
|
290
|
+
if (req.headers.origin !== this.options.issuer)
|
|
291
|
+
return sendText(res, 403, 'This confirmation did not come from this store’s own page.\n');
|
|
292
|
+
const params = new URLSearchParams(await readBody(req, MAX_FORM_BYTES));
|
|
293
|
+
const verdict = this.validateAuthorizeRequest(params);
|
|
294
|
+
if (verdict.kind === 'fatal')
|
|
295
|
+
return sendText(res, 400, `${verdict.message}\n`);
|
|
296
|
+
if (verdict.kind === 'redirect-error')
|
|
297
|
+
return redirectWithError(res, verdict, this.options.issuer);
|
|
298
|
+
const request = verdict.request;
|
|
299
|
+
const who = await this.member(req, request);
|
|
300
|
+
if (who.kind === 'refused')
|
|
301
|
+
return sendText(res, who.status, `${who.message}\n`);
|
|
302
|
+
if (who.kind === 'no-scope')
|
|
303
|
+
return redirectWithError(res, { ...request, error: 'invalid_scope', description: 'You hold none of the requested scopes.' }, this.options.issuer);
|
|
304
|
+
if (params.get('decision') !== 'allow') {
|
|
305
|
+
return redirectWithError(res, { ...request, error: 'access_denied', description: 'The member declined to connect this app.' }, this.options.issuer);
|
|
306
|
+
}
|
|
307
|
+
const code = this.options.signer.sign('code', {
|
|
308
|
+
c: digest(request.clientId),
|
|
309
|
+
r: request.redirectUri,
|
|
310
|
+
cc: request.challenge,
|
|
311
|
+
rs: this.options.resource,
|
|
312
|
+
e: who.email,
|
|
313
|
+
s: who.scopes,
|
|
314
|
+
x: this.nowSeconds() + AUTHORIZATION_CODE_TTL_SECONDS,
|
|
315
|
+
n: randomBytes(16).toString('hex'),
|
|
316
|
+
});
|
|
317
|
+
const target = new URL(request.redirectUri);
|
|
318
|
+
target.searchParams.set('code', code);
|
|
319
|
+
if (request.state !== null)
|
|
320
|
+
target.searchParams.set('state', request.state);
|
|
321
|
+
target.searchParams.set('iss', this.options.issuer);
|
|
322
|
+
res.writeHead(302, { Location: target.toString(), 'Cache-Control': 'no-store' });
|
|
323
|
+
res.end();
|
|
324
|
+
}
|
|
325
|
+
// ---- token endpoint -------------------------------------------------------
|
|
326
|
+
async token(req, res, cors) {
|
|
327
|
+
const text = await readBody(req, MAX_FORM_BYTES);
|
|
328
|
+
let params;
|
|
329
|
+
if ((req.headers['content-type'] ?? '').includes('application/json')) {
|
|
330
|
+
try {
|
|
331
|
+
params = new URLSearchParams(Object.entries(JSON.parse(text)).map(([k, v]) => [k, String(v)]));
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
return sendJson(res, 400, { error: 'invalid_request', error_description: 'The token request body is not JSON.' }, cors);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
params = new URLSearchParams(text);
|
|
339
|
+
}
|
|
340
|
+
const refuse = (error, description) => sendJson(res, 400, { error, error_description: description }, cors);
|
|
341
|
+
if (params.get('grant_type') !== 'authorization_code')
|
|
342
|
+
return refuse('unsupported_grant_type', 'Only grant_type=authorization_code is supported.');
|
|
343
|
+
const code = this.options.signer.verify('code', params.get('code') ?? '');
|
|
344
|
+
if (code === undefined || code.x <= this.nowSeconds())
|
|
345
|
+
return refuse('invalid_grant', 'The authorization code is invalid or has expired.');
|
|
346
|
+
// Spent before any other check and before any await: two redemptions racing for one code cannot both pass.
|
|
347
|
+
this.pruneSpent();
|
|
348
|
+
if (this.options.spentCodes.has(code.n))
|
|
349
|
+
return refuse('invalid_grant', 'The authorization code has already been used.');
|
|
350
|
+
this.options.spentCodes.set(code.n, code.x);
|
|
351
|
+
const clientId = params.get('client_id');
|
|
352
|
+
if (clientId === null || digest(clientId) !== code.c)
|
|
353
|
+
return refuse('invalid_grant', 'The authorization code was issued to another client.');
|
|
354
|
+
if (params.get('redirect_uri') !== null && params.get('redirect_uri') !== code.r)
|
|
355
|
+
return refuse('invalid_grant', 'redirect_uri does not match the authorization request.');
|
|
356
|
+
const verifier = params.get('code_verifier') ?? '';
|
|
357
|
+
if (!/^[A-Za-z0-9._~-]{43,128}$/.test(verifier) || b64url(createHash('sha256').update(verifier).digest()) !== code.cc) {
|
|
358
|
+
return refuse('invalid_grant', 'The PKCE code_verifier does not match the code_challenge.');
|
|
359
|
+
}
|
|
360
|
+
const resource = params.get('resource');
|
|
361
|
+
if (resource !== null && !sameResource(resource, code.rs))
|
|
362
|
+
return refuse('invalid_target', `This server issues tokens only for ${code.rs}.`);
|
|
363
|
+
const held = scopesFor(code.e, this.options.mcp());
|
|
364
|
+
const scopes = code.s.filter((s) => held.includes(s));
|
|
365
|
+
if (scopes.length === 0)
|
|
366
|
+
return refuse('invalid_grant', 'The member this code was issued to may no longer connect.');
|
|
367
|
+
const accessToken = this.options.signer.sign('token', { e: code.e, s: scopes, rs: code.rs, x: this.nowSeconds() + ACCESS_TOKEN_TTL_SECONDS });
|
|
368
|
+
sendJson(res, 200, { access_token: accessToken, token_type: 'Bearer', expires_in: ACCESS_TOKEN_TTL_SECONDS, scope: scopes.join(' ') }, { ...cors, Pragma: 'no-cache' });
|
|
369
|
+
}
|
|
370
|
+
pruneSpent() {
|
|
371
|
+
const now = this.nowSeconds();
|
|
372
|
+
for (const [nonce, expiry] of this.options.spentCodes)
|
|
373
|
+
if (expiry <= now)
|
|
374
|
+
this.options.spentCodes.delete(nonce);
|
|
375
|
+
}
|
|
376
|
+
// ---- bearer verification ----------------------------------------------------
|
|
377
|
+
/** remote-access spec: every `/mcp` request carries a token this server issued, narrowed to what its email holds now. */
|
|
378
|
+
async authenticate(req) {
|
|
379
|
+
const match = /^Bearer\s+(\S+)$/i.exec(req.headers.authorization ?? '');
|
|
380
|
+
const challenge = (error) => ({
|
|
381
|
+
'WWW-Authenticate': `Bearer resource_metadata="${this.protectedResourceMetadataUrl}"${error ? `, error="${error}"` : ''}`,
|
|
382
|
+
});
|
|
383
|
+
if (match === null) {
|
|
384
|
+
return { kind: 'refused', status: 401, headers: challenge(), body: { error: 'invalid_token', error_description: 'This store needs you to sign in.' } };
|
|
385
|
+
}
|
|
386
|
+
const principal = this.principalFor(match[1]);
|
|
387
|
+
if (principal === undefined) {
|
|
388
|
+
return { kind: 'refused', status: 401, headers: challenge('invalid_token'), body: { error: 'invalid_token', error_description: 'The token is invalid or expired, or its member may no longer connect.' } };
|
|
389
|
+
}
|
|
390
|
+
return { kind: 'admitted', principal };
|
|
391
|
+
}
|
|
392
|
+
principalFor(token) {
|
|
393
|
+
const payload = this.options.signer.verify('token', token);
|
|
394
|
+
if (payload === undefined || payload.x <= this.nowSeconds() || !sameResource(payload.rs, this.options.resource))
|
|
395
|
+
return undefined;
|
|
396
|
+
const held = scopesFor(payload.e, this.options.mcp());
|
|
397
|
+
const scopes = payload.s.filter((s) => held.includes(s));
|
|
398
|
+
return scopes.length === 0 ? undefined : { email: payload.e, scopes };
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
function digest(value) {
|
|
402
|
+
return createHash('sha256').update(value).digest('hex').slice(0, 32);
|
|
403
|
+
}
|
|
404
|
+
function sameResource(a, b) {
|
|
405
|
+
return a.replace(/\/+$/, '') === b.replace(/\/+$/, '');
|
|
406
|
+
}
|
|
407
|
+
const FORBIDDEN_SCHEMES = new Set(['javascript:', 'data:', 'file:', 'vbscript:', 'blob:']);
|
|
408
|
+
function acceptableRedirectUri(uri) {
|
|
409
|
+
if (uri.length > MAX_REDIRECT_URI_LENGTH)
|
|
410
|
+
return false;
|
|
411
|
+
let parsed;
|
|
412
|
+
try {
|
|
413
|
+
parsed = new URL(uri);
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
if (parsed.hash !== '' || FORBIDDEN_SCHEMES.has(parsed.protocol))
|
|
419
|
+
return false;
|
|
420
|
+
if (parsed.protocol === 'http:')
|
|
421
|
+
return ['localhost', '127.0.0.1', '[::1]'].includes(parsed.hostname);
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
function redirectWithError(res, verdict, issuer) {
|
|
425
|
+
const target = new URL(verdict.redirectUri);
|
|
426
|
+
target.searchParams.set('error', verdict.error);
|
|
427
|
+
target.searchParams.set('error_description', verdict.description);
|
|
428
|
+
if (verdict.state !== null)
|
|
429
|
+
target.searchParams.set('state', verdict.state);
|
|
430
|
+
target.searchParams.set('iss', issuer);
|
|
431
|
+
res.writeHead(302, { Location: target.toString(), 'Cache-Control': 'no-store' });
|
|
432
|
+
res.end();
|
|
433
|
+
}
|
|
434
|
+
/** The consent page: never framed, never scripted, never cached. */
|
|
435
|
+
function sendPage(res, html) {
|
|
436
|
+
const buffer = Buffer.from(html, 'utf8');
|
|
437
|
+
res.writeHead(200, {
|
|
438
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
439
|
+
'Content-Length': buffer.length,
|
|
440
|
+
'Cache-Control': 'no-store',
|
|
441
|
+
'X-Frame-Options': 'DENY',
|
|
442
|
+
// No form-action: browsers apply it to the redirect that follows the submit, which must reach the client's callback.
|
|
443
|
+
'Content-Security-Policy': "default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'",
|
|
444
|
+
// Not no-referrer: under it a browser submits the consent form with `Origin: null`, which the confirmation's
|
|
445
|
+
// origin check must refuse. same-origin sends the real origin to this server and nothing to the client's site.
|
|
446
|
+
'Referrer-Policy': 'same-origin',
|
|
447
|
+
});
|
|
448
|
+
res.end(buffer);
|
|
449
|
+
}
|
|
450
|
+
function methodNotAllowed(res, allow) {
|
|
451
|
+
sendText(res, 405, 'method not allowed\n', { Allow: allow });
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../src/core/mcp/oauth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/pages.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAqBlD,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBjG"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { escapeHtml } from '../browse/render.js';
|
|
5
|
+
/**
|
|
6
|
+
* The consent page is markup, not a markdown fragment, so it is read the way
|
|
7
|
+
* `browse/templates.ts` reads the serve shell rather than through
|
|
8
|
+
* `packagedTemplate()`. Every substituted value is escaped here; the template
|
|
9
|
+
* carries no script.
|
|
10
|
+
*/
|
|
11
|
+
function templatesDir() {
|
|
12
|
+
return fileURLToPath(new URL('../../../templates/mcp', import.meta.url));
|
|
13
|
+
}
|
|
14
|
+
let cached;
|
|
15
|
+
/** What each scope lets an app do, in the member's terms. */
|
|
16
|
+
const SCOPE_TEXT = {
|
|
17
|
+
read: 'search and read the store’s notes',
|
|
18
|
+
capture: 'save material into the store’s inbox, where it waits for approval before it lands',
|
|
19
|
+
edit: 'edit and create notes in a draft that lands only once someone approves it',
|
|
20
|
+
};
|
|
21
|
+
export async function renderConsentPage(storeName, view) {
|
|
22
|
+
cached ??= await readFile(path.join(templatesDir(), 'consent.html'), 'utf8');
|
|
23
|
+
const replacements = {
|
|
24
|
+
__STORE_NAME__: escapeHtml(storeName),
|
|
25
|
+
__CLIENT_NAME__: escapeHtml(view.clientName),
|
|
26
|
+
__EMAIL__: escapeHtml(view.email),
|
|
27
|
+
__REDIRECT_HOST__: escapeHtml(view.redirectHost),
|
|
28
|
+
__SCOPE_ITEMS__: view.scopes.map((s) => `<li>${escapeHtml(SCOPE_TEXT[s])}</li>`).join('\n'),
|
|
29
|
+
__HIDDEN_FIELDS__: Object.entries(view.hiddenFields)
|
|
30
|
+
.map(([name, value]) => `<input type="hidden" name="${escapeHtml(name)}" value="${escapeHtml(value)}">`)
|
|
31
|
+
.join('\n'),
|
|
32
|
+
};
|
|
33
|
+
// One pass, so a substituted value that happens to contain a token is never substituted again.
|
|
34
|
+
return cached.replace(/__[A-Z_]+__/g, (token) => {
|
|
35
|
+
const value = replacements[token];
|
|
36
|
+
if (value === undefined)
|
|
37
|
+
throw new Error(`templates/mcp/consent.html carries an unknown placeholder ${token}`);
|
|
38
|
+
return value;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=pages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pages.js","sourceRoot":"","sources":["../../../src/core/mcp/pages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAIjD;;;;;GAKG;AACH,SAAS,YAAY;IACnB,OAAO,aAAa,CAAC,IAAI,GAAG,CAAC,wBAAwB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,IAAI,MAA0B,CAAC;AAE/B,6DAA6D;AAC7D,MAAM,UAAU,GAAuC;IACrD,IAAI,EAAE,mCAAmC;IACzC,OAAO,EAAE,mFAAmF;IAC5F,IAAI,EAAE,2EAA2E;CAClF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,SAAiB,EAAE,IAAqB;IAC9E,MAAM,KAAK,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;IAC7E,MAAM,YAAY,GAA2B;QAC3C,cAAc,EAAE,UAAU,CAAC,SAAS,CAAC;QACrC,eAAe,EAAE,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC;QAC5C,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;QACjC,iBAAiB,EAAE,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC;QAChD,eAAe,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAC3F,iBAAiB,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC;aACjD,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,8BAA8B,UAAU,CAAC,IAAI,CAAC,YAAY,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC;aACvG,IAAI,CAAC,IAAI,CAAC;KACd,CAAC;IACF,+FAA+F;IAC/F,OAAO,MAAM,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,EAAE;QAC9C,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,KAAK,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,6DAA6D,KAAK,EAAE,CAAC,CAAC;QAC/G,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* serve-the-store-over-mcp design.md D12: the one module that spawns
|
|
3
|
+
* ripgrep, for the MCP surface's `Grep` and `Glob`. Claude Code's own Grep
|
|
4
|
+
* parameters map onto ripgrep's flags one for one, so they are passed through
|
|
5
|
+
* as an argument vector — never a shell — with `--` ahead of the pattern, so
|
|
6
|
+
* no pattern is ever read as a flag. Ripgrep's engine runs in time linear in
|
|
7
|
+
* its input, so no pattern can hang the server; the time and output limits
|
|
8
|
+
* here bound everything else.
|
|
9
|
+
*/
|
|
10
|
+
export declare const RIPGREP_TIMEOUT_MS = 5000;
|
|
11
|
+
export declare const RIPGREP_MAX_OUTPUT_BYTES: number;
|
|
12
|
+
export interface GrepParams {
|
|
13
|
+
pattern: string;
|
|
14
|
+
path?: string;
|
|
15
|
+
glob?: string;
|
|
16
|
+
type?: string;
|
|
17
|
+
output_mode?: 'content' | 'files_with_matches' | 'count';
|
|
18
|
+
'-i'?: boolean;
|
|
19
|
+
'-n'?: boolean;
|
|
20
|
+
'-A'?: number;
|
|
21
|
+
'-B'?: number;
|
|
22
|
+
'-C'?: number;
|
|
23
|
+
context?: number;
|
|
24
|
+
multiline?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* One output entry: the file it belongs to, and the rest of ripgrep's line after the path (null for a bare path).
|
|
28
|
+
* Read from `--null` output, where a NUL ends every path, so a path containing `:` or `-` is never split wrongly. A
|
|
29
|
+
* context-group separator (`--`) has no path.
|
|
30
|
+
*/
|
|
31
|
+
export interface RipgrepEntry {
|
|
32
|
+
path: string | null;
|
|
33
|
+
rest: string | null;
|
|
34
|
+
}
|
|
35
|
+
export type RipgrepOutcome = {
|
|
36
|
+
kind: 'ok';
|
|
37
|
+
entries: RipgrepEntry[];
|
|
38
|
+
} | {
|
|
39
|
+
kind: 'no-match';
|
|
40
|
+
} | {
|
|
41
|
+
kind: 'error';
|
|
42
|
+
message: string;
|
|
43
|
+
};
|
|
44
|
+
/** Claude Code's Grep, mapped flag for flag. `searchPath` is store-relative, already confined to the store. */
|
|
45
|
+
export declare function grep(cwd: string, params: GrepParams, searchPath: string | null, hidden: readonly string[]): Promise<RipgrepOutcome>;
|
|
46
|
+
/** Claude Code's Glob: files matching `pattern`, under `searchPath`, sorted by path. */
|
|
47
|
+
export declare function glob(cwd: string, pattern: string, searchPath: string | null, hidden: readonly string[]): Promise<RipgrepOutcome>;
|
|
48
|
+
//# sourceMappingURL=ripgrep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ripgrep.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/ripgrep.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,eAAO,MAAM,kBAAkB,OAAQ,CAAC;AACxC,eAAO,MAAM,wBAAwB,QAAkB,CAAC;AAExD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,SAAS,GAAG,oBAAoB,GAAG,OAAO,CAAC;IACzD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,YAAY,EAAE,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AA8CjI,+GAA+G;AAC/G,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAqBnI;AAED,wFAAwF;AACxF,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAKhI"}
|