@learning-with-court/cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/auth/oauth-login.d.ts +27 -0
- package/dist/auth/oauth-login.js +164 -0
- package/dist/auth/oauth-login.js.map +1 -0
- package/dist/auth/resolve-token.d.ts +1 -0
- package/dist/auth/resolve-token.js +33 -0
- package/dist/auth/resolve-token.js.map +1 -0
- package/dist/auth/token-cache.d.ts +10 -0
- package/dist/auth/token-cache.js +33 -0
- package/dist/auth/token-cache.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +117 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.js +26 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +22 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/refresh.d.ts +8 -0
- package/dist/commands/refresh.js +69 -0
- package/dist/commands/refresh.js.map +1 -0
- package/dist/commands/remove.d.ts +5 -0
- package/dist/commands/remove.js +19 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/setup.d.ts +9 -0
- package/dist/commands/setup.js +58 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/switch.d.ts +7 -0
- package/dist/commands/switch.js +28 -0
- package/dist/commands/switch.js.map +1 -0
- package/dist/commands/update.d.ts +10 -0
- package/dist/commands/update.js +80 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +11 -0
- package/dist/config.js.map +1 -0
- package/dist/git.d.ts +11 -0
- package/dist/git.js +28 -0
- package/dist/git.js.map +1 -0
- package/dist/proxy/call-tool.d.ts +20 -0
- package/dist/proxy/call-tool.js +55 -0
- package/dist/proxy/call-tool.js.map +1 -0
- package/dist/proxy/schema.d.ts +19 -0
- package/dist/proxy/schema.js +47 -0
- package/dist/proxy/schema.js.map +1 -0
- package/dist/proxy/stdio-server.d.ts +1 -0
- package/dist/proxy/stdio-server.js +41 -0
- package/dist/proxy/stdio-server.js.map +1 -0
- package/dist/registry.d.ts +20 -0
- package/dist/registry.js +62 -0
- package/dist/registry.js.map +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# @learning-with-court/cli
|
|
2
|
+
|
|
3
|
+
CLI for the learning-with-court platform. Two modes:
|
|
4
|
+
|
|
5
|
+
1. **Stdio MCP proxy** (default): forwards tool calls from a local agent to
|
|
6
|
+
the hosted MCP server at `mcp.workshop.institute`. Auth resolves via env
|
|
7
|
+
var (`LWC_TOKEN`) → cached `~/.lwc/token.json` → browser OAuth (PKCE).
|
|
8
|
+
2. **Subcommands** for one-shot operations:
|
|
9
|
+
- `setup <workshop-id>` — clone a workshop project repo into the
|
|
10
|
+
current directory after Clerk sign-in.
|
|
11
|
+
- `refresh <workshop-id>` — refresh the clone token and `git pull`.
|
|
12
|
+
- `auth login` / `auth logout` — manual control of the cached token.
|
|
13
|
+
|
|
14
|
+
Modeled on `mixcraft-app/packages/mcp-proxy`.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
# Bootstrap a workshop project (any agent, any OS)
|
|
20
|
+
npx -y @learning-with-court/cli@latest setup mcp-workshop
|
|
21
|
+
|
|
22
|
+
# Run as MCP proxy (configured by an agent's .mcp.json)
|
|
23
|
+
npx -y @learning-with-court/cli@latest
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Token storage
|
|
27
|
+
|
|
28
|
+
`~/.lwc/token.json`, mode `0600`. Contains the Clerk OAuth access token,
|
|
29
|
+
refresh token, and expiry. Delete the file or run `lwc auth logout` to
|
|
30
|
+
revoke locally.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CachedToken } from './token-cache.js';
|
|
2
|
+
export interface OAuthConfig {
|
|
3
|
+
authorizeUrl: string;
|
|
4
|
+
tokenUrl: string;
|
|
5
|
+
clientId: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function discoverOAuthConfig(): Promise<OAuthConfig>;
|
|
8
|
+
export declare function generateCodeVerifier(): string;
|
|
9
|
+
export declare function buildAuthorizationUrl(params: {
|
|
10
|
+
authorizeUrl: string;
|
|
11
|
+
clientId: string;
|
|
12
|
+
redirectUri: string;
|
|
13
|
+
codeVerifier: string;
|
|
14
|
+
}): string;
|
|
15
|
+
export declare function exchangeCodeForToken(params: {
|
|
16
|
+
tokenUrl: string;
|
|
17
|
+
clientId: string;
|
|
18
|
+
code: string;
|
|
19
|
+
redirectUri: string;
|
|
20
|
+
codeVerifier: string;
|
|
21
|
+
}): Promise<CachedToken>;
|
|
22
|
+
export declare function refreshAccessToken(params: {
|
|
23
|
+
tokenUrl: string;
|
|
24
|
+
clientId: string;
|
|
25
|
+
refreshToken: string;
|
|
26
|
+
}): Promise<CachedToken>;
|
|
27
|
+
export declare function loginViaBrowser(params: OAuthConfig): Promise<CachedToken>;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { metadataUrl, apiUrl } from '../config.js';
|
|
5
|
+
// Matches the callbackPort already registered with the workshop's Clerk
|
|
6
|
+
// OAuth clients (prod and dev). Don't change without updating Clerk first.
|
|
7
|
+
const REDIRECT_PORT = 8080;
|
|
8
|
+
const REDIRECT_URI = `http://localhost:${REDIRECT_PORT}/callback`;
|
|
9
|
+
export async function discoverOAuthConfig() {
|
|
10
|
+
// Fetch in parallel: RFC 8414 metadata for the endpoints, RFC 7591
|
|
11
|
+
// registration shim for the client_id. Honors LWC_OAUTH_CLIENT_ID for
|
|
12
|
+
// CI/local override.
|
|
13
|
+
const [metaResp, regResp] = await Promise.all([
|
|
14
|
+
fetch(metadataUrl()),
|
|
15
|
+
fetch(`${apiUrl()}/register`, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
body: '{}',
|
|
19
|
+
}),
|
|
20
|
+
]);
|
|
21
|
+
if (!metaResp.ok) {
|
|
22
|
+
throw new Error(`Failed to fetch OAuth metadata (${metaResp.status})`);
|
|
23
|
+
}
|
|
24
|
+
if (!regResp.ok) {
|
|
25
|
+
throw new Error(`Failed to fetch OAuth client registration (${regResp.status})`);
|
|
26
|
+
}
|
|
27
|
+
const metadata = (await metaResp.json());
|
|
28
|
+
const registration = (await regResp.json());
|
|
29
|
+
const clientId = process.env.LWC_OAUTH_CLIENT_ID ?? registration.client_id;
|
|
30
|
+
if (!clientId) {
|
|
31
|
+
throw new Error('No OAuth client_id available from /register endpoint.');
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
authorizeUrl: metadata.authorization_endpoint,
|
|
35
|
+
tokenUrl: metadata.token_endpoint,
|
|
36
|
+
clientId,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function generateCodeVerifier() {
|
|
40
|
+
return randomBytes(32).toString('base64url');
|
|
41
|
+
}
|
|
42
|
+
function computeCodeChallenge(verifier) {
|
|
43
|
+
return createHash('sha256').update(verifier).digest('base64url');
|
|
44
|
+
}
|
|
45
|
+
export function buildAuthorizationUrl(params) {
|
|
46
|
+
const challenge = computeCodeChallenge(params.codeVerifier);
|
|
47
|
+
const url = new URL(params.authorizeUrl);
|
|
48
|
+
url.searchParams.set('client_id', params.clientId);
|
|
49
|
+
url.searchParams.set('redirect_uri', params.redirectUri);
|
|
50
|
+
url.searchParams.set('response_type', 'code');
|
|
51
|
+
url.searchParams.set('code_challenge', challenge);
|
|
52
|
+
url.searchParams.set('code_challenge_method', 'S256');
|
|
53
|
+
url.searchParams.set('scope', 'openid profile offline_access');
|
|
54
|
+
return url.toString();
|
|
55
|
+
}
|
|
56
|
+
export async function exchangeCodeForToken(params) {
|
|
57
|
+
const body = new URLSearchParams({
|
|
58
|
+
grant_type: 'authorization_code',
|
|
59
|
+
client_id: params.clientId,
|
|
60
|
+
code: params.code,
|
|
61
|
+
redirect_uri: params.redirectUri,
|
|
62
|
+
code_verifier: params.codeVerifier,
|
|
63
|
+
});
|
|
64
|
+
const response = await fetch(params.tokenUrl, {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
67
|
+
body: body.toString(),
|
|
68
|
+
});
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
throw new Error(`Token exchange failed (${response.status}): ${await response.text()}`);
|
|
71
|
+
}
|
|
72
|
+
const data = (await response.json());
|
|
73
|
+
return {
|
|
74
|
+
accessToken: data.access_token,
|
|
75
|
+
refreshToken: data.refresh_token,
|
|
76
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export async function refreshAccessToken(params) {
|
|
80
|
+
const body = new URLSearchParams({
|
|
81
|
+
grant_type: 'refresh_token',
|
|
82
|
+
client_id: params.clientId,
|
|
83
|
+
refresh_token: params.refreshToken,
|
|
84
|
+
});
|
|
85
|
+
const response = await fetch(params.tokenUrl, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
88
|
+
body: body.toString(),
|
|
89
|
+
});
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new Error(`Token refresh failed (${response.status}): ${await response.text()}`);
|
|
92
|
+
}
|
|
93
|
+
const data = (await response.json());
|
|
94
|
+
return {
|
|
95
|
+
accessToken: data.access_token,
|
|
96
|
+
refreshToken: data.refresh_token,
|
|
97
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function openBrowser(url) {
|
|
101
|
+
const cmd = process.platform === 'darwin'
|
|
102
|
+
? 'open'
|
|
103
|
+
: process.platform === 'win32'
|
|
104
|
+
? 'start'
|
|
105
|
+
: 'xdg-open';
|
|
106
|
+
const child = spawn(cmd, [url], { stdio: 'ignore', detached: true });
|
|
107
|
+
child.unref();
|
|
108
|
+
}
|
|
109
|
+
export async function loginViaBrowser(params) {
|
|
110
|
+
const codeVerifier = generateCodeVerifier();
|
|
111
|
+
const url = buildAuthorizationUrl({
|
|
112
|
+
authorizeUrl: params.authorizeUrl,
|
|
113
|
+
clientId: params.clientId,
|
|
114
|
+
redirectUri: REDIRECT_URI,
|
|
115
|
+
codeVerifier,
|
|
116
|
+
});
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
const server = createServer(async (req, res) => {
|
|
119
|
+
const reqUrl = new URL(req.url ?? '/', `http://localhost:${REDIRECT_PORT}`);
|
|
120
|
+
if (reqUrl.pathname !== '/callback') {
|
|
121
|
+
res.writeHead(404);
|
|
122
|
+
res.end('Not found');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const code = reqUrl.searchParams.get('code');
|
|
126
|
+
const error = reqUrl.searchParams.get('error');
|
|
127
|
+
if (error || !code) {
|
|
128
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
129
|
+
res.end('<h1>Sign-in failed</h1><p>You can close this tab.</p>');
|
|
130
|
+
server.close();
|
|
131
|
+
reject(new Error(`OAuth error: ${error ?? 'no code received'}`));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const token = await exchangeCodeForToken({
|
|
136
|
+
tokenUrl: params.tokenUrl,
|
|
137
|
+
clientId: params.clientId,
|
|
138
|
+
code,
|
|
139
|
+
redirectUri: REDIRECT_URI,
|
|
140
|
+
codeVerifier,
|
|
141
|
+
});
|
|
142
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
143
|
+
res.end('<h1>Signed in</h1><p>You can close this tab and return to your terminal.</p>');
|
|
144
|
+
server.close();
|
|
145
|
+
resolve(token);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
149
|
+
res.end('<h1>Token exchange failed</h1><p>Please try again.</p>');
|
|
150
|
+
server.close();
|
|
151
|
+
reject(err);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
server.listen(REDIRECT_PORT, () => {
|
|
155
|
+
console.error('Opening browser to sign in...');
|
|
156
|
+
openBrowser(url);
|
|
157
|
+
});
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
server.close();
|
|
160
|
+
reject(new Error('Sign-in timed out. Please try again.'));
|
|
161
|
+
}, 120_000);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=oauth-login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-login.js","sourceRoot":"","sources":["../../src/auth/oauth-login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveBearerToken(): Promise<string>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { loadCachedToken, saveCachedToken, isTokenExpired, } from './token-cache.js';
|
|
2
|
+
import { discoverOAuthConfig, loginViaBrowser, refreshAccessToken, } from './oauth-login.js';
|
|
3
|
+
export async function resolveBearerToken() {
|
|
4
|
+
const envToken = process.env.LWC_TOKEN;
|
|
5
|
+
if (envToken)
|
|
6
|
+
return envToken;
|
|
7
|
+
const config = await discoverOAuthConfig();
|
|
8
|
+
return resolveOAuthToken(config);
|
|
9
|
+
}
|
|
10
|
+
async function resolveOAuthToken(config) {
|
|
11
|
+
const cached = loadCachedToken();
|
|
12
|
+
if (cached) {
|
|
13
|
+
if (!isTokenExpired(cached))
|
|
14
|
+
return cached.accessToken;
|
|
15
|
+
try {
|
|
16
|
+
console.error('Refreshing access token...');
|
|
17
|
+
const refreshed = await refreshAccessToken({
|
|
18
|
+
tokenUrl: config.tokenUrl,
|
|
19
|
+
clientId: config.clientId,
|
|
20
|
+
refreshToken: cached.refreshToken,
|
|
21
|
+
});
|
|
22
|
+
saveCachedToken(refreshed);
|
|
23
|
+
return refreshed.accessToken;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
console.error('Token refresh failed. Re-authenticating...');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const fresh = await loginViaBrowser(config);
|
|
30
|
+
saveCachedToken(fresh);
|
|
31
|
+
return fresh.accessToken;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=resolve-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-token.js","sourceRoot":"","sources":["../../src/auth/resolve-token.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,eAAe,EACf,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,kBAAkB,GAEnB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;IACvC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9B,MAAM,MAAM,GAAG,MAAM,mBAAmB,EAAE,CAAC;IAC3C,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAC;AACnC,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,MAAmB;IAClD,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC;YAAE,OAAO,MAAM,CAAC,WAAW,CAAC;QACvD,IAAI,CAAC;YACH,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;YAC5C,MAAM,SAAS,GAAG,MAAM,kBAAkB,CAAC;gBACzC,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,YAAY,EAAE,MAAM,CAAC,YAAY;aAClC,CAAC,CAAC;YACH,eAAe,CAAC,SAAS,CAAC,CAAC;YAC3B,OAAO,SAAS,CAAC,WAAW,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,CAAC;IAC5C,eAAe,CAAC,KAAK,CAAC,CAAC;IACvB,OAAO,KAAK,CAAC,WAAW,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface CachedToken {
|
|
2
|
+
accessToken: string;
|
|
3
|
+
refreshToken: string;
|
|
4
|
+
expiresAt: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function tokenPath(): string;
|
|
7
|
+
export declare function loadCachedToken(): CachedToken | null;
|
|
8
|
+
export declare function saveCachedToken(token: CachedToken): void;
|
|
9
|
+
export declare function clearCachedToken(): void;
|
|
10
|
+
export declare function isTokenExpired(token: CachedToken): boolean;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
const TOKEN_DIR = path.join(os.homedir(), '.lwc');
|
|
5
|
+
const TOKEN_PATH = path.join(TOKEN_DIR, 'token.json');
|
|
6
|
+
export function tokenPath() {
|
|
7
|
+
return TOKEN_PATH;
|
|
8
|
+
}
|
|
9
|
+
export function loadCachedToken() {
|
|
10
|
+
if (!fs.existsSync(TOKEN_PATH)) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const raw = fs.readFileSync(TOKEN_PATH, 'utf-8');
|
|
15
|
+
return JSON.parse(raw);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function saveCachedToken(token) {
|
|
22
|
+
fs.mkdirSync(TOKEN_DIR, { recursive: true, mode: 0o700 });
|
|
23
|
+
fs.writeFileSync(TOKEN_PATH, JSON.stringify(token, null, 2), { mode: 0o600 });
|
|
24
|
+
}
|
|
25
|
+
export function clearCachedToken() {
|
|
26
|
+
if (fs.existsSync(TOKEN_PATH)) {
|
|
27
|
+
fs.unlinkSync(TOKEN_PATH);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function isTokenExpired(token) {
|
|
31
|
+
return Date.now() >= token.expiresAt - 60_000;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=token-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-cache.js","sourceRoot":"","sources":["../../src/auth/token-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAQ9B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;AAClD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;AAEtD,MAAM,UAAU,SAAS;IACvB,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAgB,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAkB;IAChD,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1D,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAChF,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC9B,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAkB;IAC/C,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC;AAChD,CAAC"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { runStdioProxy } from './proxy/stdio-server.js';
|
|
5
|
+
import { runSetup } from './commands/setup.js';
|
|
6
|
+
import { runUpdate } from './commands/update.js';
|
|
7
|
+
import { runList } from './commands/list.js';
|
|
8
|
+
import { runSwitch } from './commands/switch.js';
|
|
9
|
+
import { runRemove } from './commands/remove.js';
|
|
10
|
+
import { authLogin, authLogout, authStatus } from './commands/auth.js';
|
|
11
|
+
const HELP = `learning-with-court CLI
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
lwc Run as stdio MCP proxy (used by agents).
|
|
15
|
+
lwc setup <workshop-id> [--dir <path>]
|
|
16
|
+
Install a workshop (default
|
|
17
|
+
~/learning-with-court/<workshop-id>).
|
|
18
|
+
lwc list Show installed workshops.
|
|
19
|
+
lwc switch <workshop-id> Print 'cd <path>' for the workshop.
|
|
20
|
+
(eval \\\`lwc switch <id>\\\` to cd in shell.)
|
|
21
|
+
lwc update [<workshop-id>] [--token-only]
|
|
22
|
+
Refresh + git pull. Updates all installed
|
|
23
|
+
workshops if no id is given.
|
|
24
|
+
lwc remove <workshop-id> [--delete-files]
|
|
25
|
+
Drop from registry; --delete-files also
|
|
26
|
+
removes the on-disk clone.
|
|
27
|
+
lwc auth login | logout | status Manage cached Clerk token.
|
|
28
|
+
lwc help Show this help.
|
|
29
|
+
|
|
30
|
+
Environment:
|
|
31
|
+
LWC_TOKEN Bypass cached/OAuth flow with a Bearer token (CI use).
|
|
32
|
+
LWC_API_URL Override the platform base URL
|
|
33
|
+
(default https://mcp.workshop.institute).
|
|
34
|
+
LWC_OAUTH_CLIENT_ID Override the OAuth client_id discovered from /register.
|
|
35
|
+
`;
|
|
36
|
+
function flagValue(args, flag) {
|
|
37
|
+
const idx = args.indexOf(flag);
|
|
38
|
+
return idx >= 0 ? args[idx + 1] : undefined;
|
|
39
|
+
}
|
|
40
|
+
async function main(argv) {
|
|
41
|
+
const [cmd, ...rest] = argv;
|
|
42
|
+
if (!cmd) {
|
|
43
|
+
await runStdioProxy();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
switch (cmd) {
|
|
47
|
+
case 'setup': {
|
|
48
|
+
const workshopId = rest[0];
|
|
49
|
+
if (!workshopId)
|
|
50
|
+
throw new Error('Usage: lwc setup <workshop-id> [--dir <path>]');
|
|
51
|
+
// Backward-compat: positional second arg as dest.
|
|
52
|
+
const dirFlag = flagValue(rest, '--dir');
|
|
53
|
+
const positional = rest[1] && !rest[1].startsWith('--') ? rest[1] : undefined;
|
|
54
|
+
await runSetup({ workshopId, dest: dirFlag ?? positional });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
case 'update':
|
|
58
|
+
case 'refresh': {
|
|
59
|
+
const tokenOnly = rest.includes('--token-only') || rest.includes('--no-pull');
|
|
60
|
+
const workshopId = rest.find((a) => !a.startsWith('--'));
|
|
61
|
+
await runUpdate({ workshopId, tokenOnly });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
case 'list':
|
|
65
|
+
case 'ls':
|
|
66
|
+
runList();
|
|
67
|
+
return;
|
|
68
|
+
case 'switch': {
|
|
69
|
+
const workshopId = rest[0];
|
|
70
|
+
if (!workshopId)
|
|
71
|
+
throw new Error('Usage: lwc switch <workshop-id>');
|
|
72
|
+
runSwitch(workshopId);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
case 'remove':
|
|
76
|
+
case 'rm': {
|
|
77
|
+
const workshopId = rest[0];
|
|
78
|
+
if (!workshopId)
|
|
79
|
+
throw new Error('Usage: lwc remove <workshop-id> [--delete-files]');
|
|
80
|
+
const deleteFiles = rest.includes('--delete-files');
|
|
81
|
+
runRemove({ workshopId, deleteFiles });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
case 'auth': {
|
|
85
|
+
const sub = rest[0];
|
|
86
|
+
if (sub === 'login')
|
|
87
|
+
return authLogin();
|
|
88
|
+
if (sub === 'logout')
|
|
89
|
+
return authLogout();
|
|
90
|
+
if (sub === 'status')
|
|
91
|
+
return authStatus();
|
|
92
|
+
throw new Error('Usage: lwc auth <login|logout|status>');
|
|
93
|
+
}
|
|
94
|
+
case 'help':
|
|
95
|
+
case '--help':
|
|
96
|
+
case '-h':
|
|
97
|
+
console.error(HELP);
|
|
98
|
+
return;
|
|
99
|
+
default:
|
|
100
|
+
throw new Error(`Unknown command: ${cmd}\n\n${HELP}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const isMain = process.argv[1] != null && (() => {
|
|
104
|
+
try {
|
|
105
|
+
return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
})();
|
|
111
|
+
if (isMain) {
|
|
112
|
+
main(process.argv.slice(2)).catch((err) => {
|
|
113
|
+
console.error('Error:', err instanceof Error ? err.message : String(err));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { clearCachedToken, loadCachedToken, saveCachedToken, tokenPath, } from '../auth/token-cache.js';
|
|
2
|
+
import { discoverOAuthConfig, loginViaBrowser, } from '../auth/oauth-login.js';
|
|
3
|
+
export async function authLogin() {
|
|
4
|
+
const config = await discoverOAuthConfig();
|
|
5
|
+
const token = await loginViaBrowser(config);
|
|
6
|
+
saveCachedToken(token);
|
|
7
|
+
console.error(`Signed in. Token cached at ${tokenPath()}.`);
|
|
8
|
+
}
|
|
9
|
+
export function authLogout() {
|
|
10
|
+
const existed = loadCachedToken() !== null;
|
|
11
|
+
clearCachedToken();
|
|
12
|
+
console.error(existed
|
|
13
|
+
? `Cleared cached token at ${tokenPath()}.`
|
|
14
|
+
: 'No cached token to clear.');
|
|
15
|
+
}
|
|
16
|
+
export function authStatus() {
|
|
17
|
+
const cached = loadCachedToken();
|
|
18
|
+
if (!cached) {
|
|
19
|
+
console.error('Not signed in.');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const remainingMs = cached.expiresAt - Date.now();
|
|
23
|
+
const minutes = Math.max(0, Math.round(remainingMs / 60_000));
|
|
24
|
+
console.error(`Signed in. Token expires in ~${minutes} min (${tokenPath()}).`);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,SAAS,GACV,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,mBAAmB,EACnB,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,MAAM,MAAM,GAAG,MAAM,mBAAmB,EAAE,CAAC;IAC3C,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,CAAC;IAC5C,eAAe,CAAC,KAAK,CAAC,CAAC;IACvB,OAAO,CAAC,KAAK,CAAC,8BAA8B,SAAS,EAAE,GAAG,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,OAAO,GAAG,eAAe,EAAE,KAAK,IAAI,CAAC;IAC3C,gBAAgB,EAAE,CAAC;IACnB,OAAO,CAAC,KAAK,CACX,OAAO;QACL,CAAC,CAAC,2BAA2B,SAAS,EAAE,GAAG;QAC3C,CAAC,CAAC,2BAA2B,CAChC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;QAChC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAClD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC;IAC9D,OAAO,CAAC,KAAK,CAAC,gCAAgC,OAAO,SAAS,SAAS,EAAE,IAAI,CAAC,CAAC;AACjF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runList(): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { listInstalls, registryPath } from '../registry.js';
|
|
3
|
+
export function runList() {
|
|
4
|
+
const installs = listInstalls();
|
|
5
|
+
if (installs.length === 0) {
|
|
6
|
+
console.error('No workshops installed.');
|
|
7
|
+
console.error('');
|
|
8
|
+
console.error('Run: lwc setup <workshop-id>');
|
|
9
|
+
console.error(`(Registry: ${registryPath()})`);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
for (const e of installs) {
|
|
13
|
+
const exists = fs.existsSync(e.path);
|
|
14
|
+
const status = exists ? 'ok' : 'MISSING (path no longer exists)';
|
|
15
|
+
const installed = e.installedAt.split('T')[0];
|
|
16
|
+
const refreshed = e.lastRefreshedAt
|
|
17
|
+
? `last refresh ${e.lastRefreshedAt.split('T')[0]}`
|
|
18
|
+
: 'never refreshed';
|
|
19
|
+
console.log(`${e.workshopId}\t${e.path}\t[${status}, ${refreshed}, installed ${installed}]`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE5D,MAAM,UAAU,OAAO;IACrB,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACzC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAC9C,OAAO,CAAC,KAAK,CAAC,cAAc,YAAY,EAAE,GAAG,CAAC,CAAC;QAC/C,OAAO;IACT,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,iCAAiC,CAAC;QACjE,MAAM,SAAS,GAAG,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9C,MAAM,SAAS,GAAG,CAAC,CAAC,eAAe;YACjC,CAAC,CAAC,gBAAgB,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE;YACnD,CAAC,CAAC,iBAAiB,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,IAAI,MAAM,MAAM,KAAK,SAAS,eAAe,SAAS,GAAG,CAAC,CAAC;IAC/F,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface RefreshOptions {
|
|
2
|
+
workshopId: string;
|
|
3
|
+
/** Working directory of the cloned workshop. Defaults to cwd. */
|
|
4
|
+
cwd?: string;
|
|
5
|
+
/** Run `git pull` after refreshing the remote URL. */
|
|
6
|
+
pull?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function runRefresh(opts: RefreshOptions): Promise<void>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { resolveBearerToken } from '../auth/resolve-token.js';
|
|
2
|
+
import { callRemoteTool, parseStructured } from '../proxy/call-tool.js';
|
|
3
|
+
import { ensureGitInstalled, git, cleanRemoteUrl } from '../git.js';
|
|
4
|
+
export async function runRefresh(opts) {
|
|
5
|
+
ensureGitInstalled();
|
|
6
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
7
|
+
// Safety: only operate on a working tree that is a clone of the workshop's
|
|
8
|
+
// repo. Mismatched origin URLs (or non-git dirs) are almost always the
|
|
9
|
+
// user being in the wrong directory. Require an explicit --cwd if the
|
|
10
|
+
// caller knows what they're doing.
|
|
11
|
+
ensureWorkshopRepo(cwd, opts.workshopId);
|
|
12
|
+
const bearerToken = await resolveBearerToken();
|
|
13
|
+
const result = await callRemoteTool({
|
|
14
|
+
bearerToken,
|
|
15
|
+
name: 'refresh_workshop_repo',
|
|
16
|
+
args: { workshop_id: opts.workshopId },
|
|
17
|
+
});
|
|
18
|
+
if (result.isError) {
|
|
19
|
+
throw new Error(`Refresh failed: ${formatToolError(result)}`);
|
|
20
|
+
}
|
|
21
|
+
const data = parseStructured(result);
|
|
22
|
+
if (!data?.clone_url) {
|
|
23
|
+
throw new Error('Refresh returned no clone URL.');
|
|
24
|
+
}
|
|
25
|
+
const cleanUrl = cleanRemoteUrl(data.clone_url);
|
|
26
|
+
const setUrl = git(['remote', 'set-url', 'origin', data.clone_url], { cwd });
|
|
27
|
+
if (setUrl.status !== 0) {
|
|
28
|
+
throw new Error(`git remote set-url failed: ${setUrl.stderr.trim()}`);
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
if (opts.pull) {
|
|
32
|
+
const pull = git(['pull', '--ff-only'], { cwd });
|
|
33
|
+
if (pull.status !== 0) {
|
|
34
|
+
throw new Error(`git pull failed: ${pull.stderr.trim()}`);
|
|
35
|
+
}
|
|
36
|
+
console.error(pull.stdout.trim());
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
git(['remote', 'set-url', 'origin', cleanUrl], { cwd });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function ensureWorkshopRepo(cwd, workshopId) {
|
|
44
|
+
const inGit = git(['rev-parse', '--is-inside-work-tree'], { cwd });
|
|
45
|
+
if (inGit.status !== 0 || inGit.stdout.trim() !== 'true') {
|
|
46
|
+
throw new Error(`Not inside a git working tree: ${cwd}`);
|
|
47
|
+
}
|
|
48
|
+
const remote = git(['remote', 'get-url', 'origin'], { cwd });
|
|
49
|
+
if (remote.status !== 0) {
|
|
50
|
+
throw new Error(`No origin remote in ${cwd}.`);
|
|
51
|
+
}
|
|
52
|
+
const url = remote.stdout.trim();
|
|
53
|
+
// Match either schuettc/<repo>.git or <org>/<repo>.git, with or without
|
|
54
|
+
// an embedded x-access-token. Any URL containing the workshop's slug
|
|
55
|
+
// suffix is acceptable — we don't pin the org so users can move repos.
|
|
56
|
+
const expectedSlugSuffix = `learning-with-court-${workshopId}`;
|
|
57
|
+
if (!url.includes(expectedSlugSuffix)) {
|
|
58
|
+
throw new Error(`Refusing to refresh: cwd's origin (${url}) is not a clone of workshop "${workshopId}".\n` +
|
|
59
|
+
`Run from inside the cloned workshop dir, or pass --cwd <path>.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function formatToolError(result) {
|
|
63
|
+
return result.content
|
|
64
|
+
.map((c) => c.text ?? '')
|
|
65
|
+
.filter(Boolean)
|
|
66
|
+
.join(' ')
|
|
67
|
+
.trim() || 'unknown error';
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=refresh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/commands/refresh.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { getInstall, removeFromRegistry } from '../registry.js';
|
|
3
|
+
export function runRemove(opts) {
|
|
4
|
+
const entry = getInstall(opts.workshopId);
|
|
5
|
+
if (!entry) {
|
|
6
|
+
console.error(`No workshop "${opts.workshopId}" registered.`);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
if (opts.deleteFiles && fs.existsSync(entry.path)) {
|
|
10
|
+
console.error(`Deleting ${entry.path}...`);
|
|
11
|
+
fs.rmSync(entry.path, { recursive: true, force: true });
|
|
12
|
+
}
|
|
13
|
+
else if (fs.existsSync(entry.path)) {
|
|
14
|
+
console.error(`Note: ${entry.path} still exists on disk (pass --delete-files to remove it too).`);
|
|
15
|
+
}
|
|
16
|
+
removeFromRegistry(opts.workshopId);
|
|
17
|
+
console.error(`Removed "${opts.workshopId}" from the registry.`);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=remove.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove.js","sourceRoot":"","sources":["../../src/commands/remove.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAOhE,MAAM,UAAU,SAAS,CAAC,IAAmB;IAC3C,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,gBAAgB,IAAI,CAAC,UAAU,eAAe,CAAC,CAAC;QAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAClD,OAAO,CAAC,KAAK,CAAC,YAAY,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC;QAC3C,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,CAAC;SAAM,IAAI,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,OAAO,CAAC,KAAK,CACX,SAAS,KAAK,CAAC,IAAI,+DAA+D,CACnF,CAAC;IACJ,CAAC;IAED,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpC,OAAO,CAAC,KAAK,CAAC,YAAY,IAAI,CAAC,UAAU,sBAAsB,CAAC,CAAC;AACnE,CAAC"}
|