@learning-with-court/cli 0.0.2 → 0.1.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 +36 -8
- package/dist/auth/local-server.d.ts +27 -0
- package/dist/auth/local-server.js +85 -0
- package/dist/auth/local-server.js.map +1 -0
- package/dist/auth/login.d.ts +8 -0
- package/dist/auth/login.js +84 -0
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/pkce.d.ts +6 -0
- package/dist/auth/pkce.js +13 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/auth/resolve-token.js +7 -22
- package/dist/auth/resolve-token.js.map +1 -1
- package/dist/auth/token-cache.d.ts +12 -1
- package/dist/auth/token-cache.js +32 -1
- package/dist/auth/token-cache.js.map +1 -1
- package/dist/cli.js +4 -3
- package/dist/cli.js.map +1 -1
- package/dist/commands/auth.js +15 -7
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/setup.js +57 -1
- package/dist/commands/setup.js.map +1 -1
- package/dist/config.d.ts +3 -1
- package/dist/config.js +18 -2
- package/dist/config.js.map +1 -1
- package/package.json +8 -3
- package/dist/auth/oauth-login.d.ts +0 -27
- package/dist/auth/oauth-login.js +0 -164
- package/dist/auth/oauth-login.js.map +0 -1
- package/dist/commands/refresh.d.ts +0 -8
- package/dist/commands/refresh.js +0 -69
- package/dist/commands/refresh.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,27 +4,55 @@ CLI for the learning-with-court platform. Two modes:
|
|
|
4
4
|
|
|
5
5
|
1. **Stdio MCP proxy** (default): forwards tool calls from a local agent to
|
|
6
6
|
the hosted MCP server at `mcp.workshop.institute`. Auth resolves via env
|
|
7
|
-
var (`LWC_TOKEN`) → cached `~/.lwc/token.json` → browser
|
|
7
|
+
var (`LWC_TOKEN`) → cached `~/.lwc/token.json` → browser sign-in.
|
|
8
8
|
2. **Subcommands** for one-shot operations:
|
|
9
|
-
- `setup <workshop-id
|
|
10
|
-
|
|
9
|
+
- `setup <workshop-id> [--dir <path>]` — clone a workshop project
|
|
10
|
+
repo (default: `~/learning-with-court/<workshop-id>/`). Pass `--dir`
|
|
11
|
+
to install somewhere else; the CLI auto-creates parent directories.
|
|
11
12
|
- `refresh <workshop-id>` — refresh the clone token and `git pull`.
|
|
12
13
|
- `auth login` / `auth logout` — manual control of the cached token.
|
|
13
14
|
|
|
14
|
-
Modeled on `mixcraft-app/packages/mcp-proxy`.
|
|
15
|
-
|
|
16
15
|
## Usage
|
|
17
16
|
|
|
18
17
|
```sh
|
|
19
|
-
# Bootstrap a workshop project (
|
|
18
|
+
# Bootstrap a workshop project (default: ~/learning-with-court/mcp-workshop/)
|
|
20
19
|
npx -y @learning-with-court/cli@latest setup mcp-workshop
|
|
21
20
|
|
|
21
|
+
# Install into a folder of your choice instead
|
|
22
|
+
npx -y @learning-with-court/cli@latest setup mcp-workshop --dir ~/workshops/mcp-workshop
|
|
23
|
+
|
|
22
24
|
# Run as MCP proxy (configured by an agent's .mcp.json)
|
|
23
25
|
npx -y @learning-with-court/cli@latest
|
|
24
26
|
```
|
|
25
27
|
|
|
28
|
+
## Auth flow
|
|
29
|
+
|
|
30
|
+
`lwc auth login` (and any subcommand that needs a token) runs an
|
|
31
|
+
authorization-code + PKCE flow against `workshop.institute`:
|
|
32
|
+
|
|
33
|
+
1. The CLI starts a loopback server on an ephemeral localhost port and
|
|
34
|
+
opens `https://workshop.institute/cli-auth?...` with a PKCE challenge
|
|
35
|
+
and random state.
|
|
36
|
+
2. You sign in (if needed) and click **Authorize**.
|
|
37
|
+
3. The browser redirects to `http://localhost:<port>/callback?code=...`,
|
|
38
|
+
the CLI exchanges the code at `mcp.workshop.institute/cli/token`,
|
|
39
|
+
and caches the resulting JWT.
|
|
40
|
+
|
|
41
|
+
Tokens are RS256 JWTs signed by an AWS KMS key; the public key is
|
|
42
|
+
published at `https://mcp.workshop.institute/.well-known/jwks.json`.
|
|
43
|
+
Lifetime: 90 days. After expiry, re-run `lwc auth login`.
|
|
44
|
+
|
|
45
|
+
### Environment overrides
|
|
46
|
+
|
|
47
|
+
- `LWC_TOKEN` — bypass the cache and use this token directly.
|
|
48
|
+
- `LWC_API_URL` — point at a different MCP API origin (default
|
|
49
|
+
`https://mcp.workshop.institute`).
|
|
50
|
+
- `LWC_LANDING_URL` — override the browser-side landing origin (default
|
|
51
|
+
is derived from `LWC_API_URL`: `mcp.` and `mcp-` prefixes are
|
|
52
|
+
stripped, so `mcp-dev.workshop.institute` → `dev.workshop.institute`).
|
|
53
|
+
|
|
26
54
|
## Token storage
|
|
27
55
|
|
|
28
|
-
`~/.lwc/token.json`, mode `0600`. Contains the
|
|
29
|
-
|
|
56
|
+
`~/.lwc/token.json`, mode `0600`. Contains the JWT, expiry, and the
|
|
57
|
+
opaque user identifier. Delete the file or run `lwc auth logout` to
|
|
30
58
|
revoke locally.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface CallbackResult {
|
|
2
|
+
type: 'callback';
|
|
3
|
+
code: string;
|
|
4
|
+
state: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CancelResult {
|
|
7
|
+
type: 'cancel';
|
|
8
|
+
state: string;
|
|
9
|
+
}
|
|
10
|
+
export type AwaitedResult = CallbackResult | CancelResult;
|
|
11
|
+
export interface LocalServerHandle {
|
|
12
|
+
port: number;
|
|
13
|
+
done: Promise<AwaitedResult>;
|
|
14
|
+
close: () => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Start a local HTTP server on an OS-assigned ephemeral port. Resolves
|
|
18
|
+
* `done` when the browser hits `/callback?code=...&state=...` or
|
|
19
|
+
* `/cancel?state=...`. The caller is responsible for validating that the
|
|
20
|
+
* returned `state` matches what they generated.
|
|
21
|
+
*
|
|
22
|
+
* The server auto-closes after the first matching request OR after
|
|
23
|
+
* `timeoutMs` (default 5 minutes), at which point `done` rejects.
|
|
24
|
+
*/
|
|
25
|
+
export declare function startLocalCallbackServer(opts?: {
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
}): Promise<LocalServerHandle>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
const SUCCESS_HTML = `<!doctype html>
|
|
3
|
+
<html lang="en"><head><meta charset="utf-8"><title>Signed in</title>
|
|
4
|
+
<style>body{font-family:ui-sans-serif,system-ui,sans-serif;background:#f5f1e8;color:#1a1a1a;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0}main{max-width:32rem;padding:2rem;text-align:center}h1{color:#c8102e;margin:0 0 1rem}p{margin:0;line-height:1.6}</style>
|
|
5
|
+
</head><body><main><h1>Signed in</h1><p>You can close this tab and return to your terminal.</p></main></body></html>`;
|
|
6
|
+
const CANCEL_HTML = `<!doctype html>
|
|
7
|
+
<html lang="en"><head><meta charset="utf-8"><title>Cancelled</title>
|
|
8
|
+
<style>body{font-family:ui-sans-serif,system-ui,sans-serif;background:#f5f1e8;color:#1a1a1a;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0}main{max-width:32rem;padding:2rem;text-align:center}h1{margin:0 0 1rem}p{margin:0;line-height:1.6}</style>
|
|
9
|
+
</head><body><main><h1>Sign-in cancelled</h1><p>You can close this tab and return to your terminal.</p></main></body></html>`;
|
|
10
|
+
const NOT_FOUND_HTML = '<h1>Not found</h1>';
|
|
11
|
+
/**
|
|
12
|
+
* Start a local HTTP server on an OS-assigned ephemeral port. Resolves
|
|
13
|
+
* `done` when the browser hits `/callback?code=...&state=...` or
|
|
14
|
+
* `/cancel?state=...`. The caller is responsible for validating that the
|
|
15
|
+
* returned `state` matches what they generated.
|
|
16
|
+
*
|
|
17
|
+
* The server auto-closes after the first matching request OR after
|
|
18
|
+
* `timeoutMs` (default 5 minutes), at which point `done` rejects.
|
|
19
|
+
*/
|
|
20
|
+
export function startLocalCallbackServer(opts = {}) {
|
|
21
|
+
const timeoutMs = opts.timeoutMs ?? 5 * 60_000;
|
|
22
|
+
return new Promise((resolveStart, rejectStart) => {
|
|
23
|
+
let resolveDone;
|
|
24
|
+
let rejectDone;
|
|
25
|
+
const done = new Promise((res, rej) => {
|
|
26
|
+
resolveDone = res;
|
|
27
|
+
rejectDone = rej;
|
|
28
|
+
});
|
|
29
|
+
const server = createServer((req, res) => {
|
|
30
|
+
const host = req.headers.host ?? 'localhost';
|
|
31
|
+
const reqUrl = new URL(req.url ?? '/', `http://${host}`);
|
|
32
|
+
if (reqUrl.pathname === '/callback') {
|
|
33
|
+
const code = reqUrl.searchParams.get('code');
|
|
34
|
+
const state = reqUrl.searchParams.get('state');
|
|
35
|
+
if (!code || !state) {
|
|
36
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
37
|
+
res.end('<h1>Missing code or state</h1>');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
41
|
+
res.end(SUCCESS_HTML);
|
|
42
|
+
resolveDone({ type: 'callback', code, state });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (reqUrl.pathname === '/cancel') {
|
|
46
|
+
const state = reqUrl.searchParams.get('state') ?? '';
|
|
47
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
48
|
+
res.end(CANCEL_HTML);
|
|
49
|
+
resolveDone({ type: 'cancel', state });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
53
|
+
res.end(NOT_FOUND_HTML);
|
|
54
|
+
});
|
|
55
|
+
const timer = setTimeout(() => {
|
|
56
|
+
server.close();
|
|
57
|
+
rejectDone(new Error('Sign-in timed out. Run `lwc auth login` to retry.'));
|
|
58
|
+
}, timeoutMs);
|
|
59
|
+
timer.unref();
|
|
60
|
+
server.on('error', (err) => {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
rejectStart(err);
|
|
63
|
+
});
|
|
64
|
+
// Bind to an OS-assigned port (port 0). loopback only.
|
|
65
|
+
server.listen(0, '127.0.0.1', () => {
|
|
66
|
+
const addr = server.address();
|
|
67
|
+
if (!addr || typeof addr === 'string') {
|
|
68
|
+
rejectStart(new Error('Failed to determine local server port'));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
resolveStart({
|
|
72
|
+
port: addr.port,
|
|
73
|
+
done: done.finally(() => {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
server.close();
|
|
76
|
+
}),
|
|
77
|
+
close: () => {
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
server.close();
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=local-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-server.js","sourceRoot":"","sources":["../../src/auth/local-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type CachedToken } from './token-cache.js';
|
|
2
|
+
/**
|
|
3
|
+
* Drive the full browser-based login flow. Opens the user's browser to
|
|
4
|
+
* `<landing>/cli-auth?...`, waits for them to complete the authorize
|
|
5
|
+
* dance, exchanges the resulting code for a JWT, and returns it. Does
|
|
6
|
+
* NOT cache the token — caller is responsible for that.
|
|
7
|
+
*/
|
|
8
|
+
export declare function loginViaBrowser(): Promise<CachedToken>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { cliAuthPageUrl, tokenExchangeUrl } from '../config.js';
|
|
3
|
+
import { generatePkcePair, generateState } from './pkce.js';
|
|
4
|
+
import { startLocalCallbackServer } from './local-server.js';
|
|
5
|
+
import { decodeJwtPayload } from './token-cache.js';
|
|
6
|
+
function openBrowser(url) {
|
|
7
|
+
const cmd = process.platform === 'darwin'
|
|
8
|
+
? 'open'
|
|
9
|
+
: process.platform === 'win32'
|
|
10
|
+
? 'start'
|
|
11
|
+
: 'xdg-open';
|
|
12
|
+
const args = process.platform === 'win32' ? ['', url] : [url];
|
|
13
|
+
const child = spawn(cmd, args, { stdio: 'ignore', detached: true, shell: process.platform === 'win32' });
|
|
14
|
+
child.unref();
|
|
15
|
+
}
|
|
16
|
+
function buildAuthorizeUrl(params) {
|
|
17
|
+
const url = new URL(cliAuthPageUrl());
|
|
18
|
+
url.searchParams.set('state', params.state);
|
|
19
|
+
url.searchParams.set('code_challenge', params.codeChallenge);
|
|
20
|
+
url.searchParams.set('code_challenge_method', 'S256');
|
|
21
|
+
url.searchParams.set('port', String(params.port));
|
|
22
|
+
return url.toString();
|
|
23
|
+
}
|
|
24
|
+
async function exchangeCodeForToken(params) {
|
|
25
|
+
const response = await fetch(tokenExchangeUrl(), {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: { 'Content-Type': 'application/json' },
|
|
28
|
+
body: JSON.stringify({ code: params.code, code_verifier: params.codeVerifier }),
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
const text = await response.text().catch(() => '');
|
|
32
|
+
throw new Error(`Token exchange failed (${response.status}): ${text}`);
|
|
33
|
+
}
|
|
34
|
+
const data = (await response.json());
|
|
35
|
+
const claims = decodeJwtPayload(data.access_token);
|
|
36
|
+
const sub = typeof claims?.sub === 'string' ? claims.sub : '';
|
|
37
|
+
const expFromJwt = typeof claims?.exp === 'number' ? claims.exp * 1000 : null;
|
|
38
|
+
const expFromExpiresIn = Date.now() + data.expires_in * 1000;
|
|
39
|
+
return {
|
|
40
|
+
accessToken: data.access_token,
|
|
41
|
+
tokenType: data.token_type ?? 'Bearer',
|
|
42
|
+
expiresAt: expFromJwt ?? expFromExpiresIn,
|
|
43
|
+
scope: data.scope ?? '',
|
|
44
|
+
sub,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Drive the full browser-based login flow. Opens the user's browser to
|
|
49
|
+
* `<landing>/cli-auth?...`, waits for them to complete the authorize
|
|
50
|
+
* dance, exchanges the resulting code for a JWT, and returns it. Does
|
|
51
|
+
* NOT cache the token — caller is responsible for that.
|
|
52
|
+
*/
|
|
53
|
+
export async function loginViaBrowser() {
|
|
54
|
+
const state = generateState();
|
|
55
|
+
const { codeVerifier, codeChallenge } = generatePkcePair();
|
|
56
|
+
const server = await startLocalCallbackServer();
|
|
57
|
+
const authorizeUrl = buildAuthorizeUrl({
|
|
58
|
+
state,
|
|
59
|
+
codeChallenge,
|
|
60
|
+
port: server.port,
|
|
61
|
+
});
|
|
62
|
+
console.error('Opening browser to sign in...');
|
|
63
|
+
console.error(`If your browser doesn't open, visit:\n ${authorizeUrl}`);
|
|
64
|
+
openBrowser(authorizeUrl);
|
|
65
|
+
let result;
|
|
66
|
+
try {
|
|
67
|
+
result = await server.done;
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
server.close();
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
if (result.state !== state) {
|
|
74
|
+
throw new Error('State mismatch on auth callback (possible CSRF). Aborting.');
|
|
75
|
+
}
|
|
76
|
+
if (result.type === 'cancel') {
|
|
77
|
+
throw new Error('Sign-in cancelled. Run `lwc auth login` to retry.');
|
|
78
|
+
}
|
|
79
|
+
return exchangeCodeForToken({
|
|
80
|
+
code: result.code,
|
|
81
|
+
codeVerifier,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/auth/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
// 32 random bytes → 43-char base64url string. Matches the inputs the
|
|
3
|
+
// platform's /cli/authorize and /cli/token endpoints validate against
|
|
4
|
+
// (see docs/features/own-cli-auth-flow/plan.md, Phase 1).
|
|
5
|
+
export function generateState() {
|
|
6
|
+
return randomBytes(32).toString('base64url');
|
|
7
|
+
}
|
|
8
|
+
export function generatePkcePair() {
|
|
9
|
+
const codeVerifier = randomBytes(32).toString('base64url');
|
|
10
|
+
const codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url');
|
|
11
|
+
return { codeVerifier, codeChallenge };
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/auth/pkce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtD,qEAAqE;AACrE,sEAAsE;AACtE,0DAA0D;AAE1D,MAAM,UAAU,aAAa;IAC3B,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC/C,CAAC;AAOD,MAAM,UAAU,gBAAgB;IAC9B,MAAM,YAAY,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC3D,MAAM,aAAa,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACpF,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC;AACzC,CAAC"}
|
|
@@ -1,32 +1,17 @@
|
|
|
1
1
|
import { loadCachedToken, saveCachedToken, isTokenExpired, } from './token-cache.js';
|
|
2
|
-
import {
|
|
2
|
+
import { loginViaBrowser } from './login.js';
|
|
3
3
|
export async function resolveBearerToken() {
|
|
4
4
|
const envToken = process.env.LWC_TOKEN;
|
|
5
5
|
if (envToken)
|
|
6
6
|
return envToken;
|
|
7
|
-
const config = await discoverOAuthConfig();
|
|
8
|
-
return resolveOAuthToken(config);
|
|
9
|
-
}
|
|
10
|
-
async function resolveOAuthToken(config) {
|
|
11
7
|
const cached = loadCachedToken();
|
|
12
|
-
if (cached) {
|
|
13
|
-
|
|
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
|
-
}
|
|
8
|
+
if (cached && !isTokenExpired(cached)) {
|
|
9
|
+
return cached.accessToken;
|
|
28
10
|
}
|
|
29
|
-
|
|
11
|
+
// No cached token, or it expired. Drive the browser flow. The new CLI
|
|
12
|
+
// tokens are 90-day JWTs with no refresh token — re-auth is the
|
|
13
|
+
// recovery path on expiry.
|
|
14
|
+
const fresh = await loginViaBrowser();
|
|
30
15
|
saveCachedToken(fresh);
|
|
31
16
|
return fresh.accessToken;
|
|
32
17
|
}
|
|
@@ -1 +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,
|
|
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,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,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,eAAe,EAAE,CAAC;IACjC,IAAI,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,OAAO,MAAM,CAAC,WAAW,CAAC;IAC5B,CAAC;IAED,sEAAsE;IACtE,gEAAgE;IAChE,2BAA2B;IAC3B,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAC;IACtC,eAAe,CAAC,KAAK,CAAC,CAAC;IACvB,OAAO,KAAK,CAAC,WAAW,CAAC;AAC3B,CAAC"}
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
export interface CachedToken {
|
|
2
2
|
accessToken: string;
|
|
3
|
-
|
|
3
|
+
tokenType: string;
|
|
4
|
+
/** Unix-millis when the JWT's `exp` claim hits. */
|
|
4
5
|
expiresAt: number;
|
|
6
|
+
/** Space-delimited scope string from the token endpoint. */
|
|
7
|
+
scope: string;
|
|
8
|
+
/** JWT `sub` claim — the Clerk user_id, decoded for fast display. */
|
|
9
|
+
sub: string;
|
|
5
10
|
}
|
|
6
11
|
export declare function tokenPath(): string;
|
|
7
12
|
export declare function loadCachedToken(): CachedToken | null;
|
|
8
13
|
export declare function saveCachedToken(token: CachedToken): void;
|
|
9
14
|
export declare function clearCachedToken(): void;
|
|
10
15
|
export declare function isTokenExpired(token: CachedToken): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Decode a JWT's payload without verifying the signature. Used for
|
|
18
|
+
* extracting `sub` and `exp` for display — verification is the server's
|
|
19
|
+
* job, not the CLI's.
|
|
20
|
+
*/
|
|
21
|
+
export declare function decodeJwtPayload(jwt: string): Record<string, unknown> | null;
|
package/dist/auth/token-cache.js
CHANGED
|
@@ -12,7 +12,18 @@ export function loadCachedToken() {
|
|
|
12
12
|
}
|
|
13
13
|
try {
|
|
14
14
|
const raw = fs.readFileSync(TOKEN_PATH, 'utf-8');
|
|
15
|
-
|
|
15
|
+
const parsed = JSON.parse(raw);
|
|
16
|
+
if (typeof parsed.accessToken !== 'string' ||
|
|
17
|
+
typeof parsed.expiresAt !== 'number') {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
accessToken: parsed.accessToken,
|
|
22
|
+
tokenType: parsed.tokenType ?? 'Bearer',
|
|
23
|
+
expiresAt: parsed.expiresAt,
|
|
24
|
+
scope: parsed.scope ?? '',
|
|
25
|
+
sub: parsed.sub ?? '',
|
|
26
|
+
};
|
|
16
27
|
}
|
|
17
28
|
catch {
|
|
18
29
|
return null;
|
|
@@ -30,4 +41,24 @@ export function clearCachedToken() {
|
|
|
30
41
|
export function isTokenExpired(token) {
|
|
31
42
|
return Date.now() >= token.expiresAt - 60_000;
|
|
32
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Decode a JWT's payload without verifying the signature. Used for
|
|
46
|
+
* extracting `sub` and `exp` for display — verification is the server's
|
|
47
|
+
* job, not the CLI's.
|
|
48
|
+
*/
|
|
49
|
+
export function decodeJwtPayload(jwt) {
|
|
50
|
+
const parts = jwt.split('.');
|
|
51
|
+
if (parts.length !== 3)
|
|
52
|
+
return null;
|
|
53
|
+
const payload = parts[1];
|
|
54
|
+
if (!payload)
|
|
55
|
+
return null;
|
|
56
|
+
try {
|
|
57
|
+
const json = Buffer.from(payload, 'base64url').toString('utf-8');
|
|
58
|
+
return JSON.parse(json);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
33
64
|
//# sourceMappingURL=token-cache.js.map
|
|
@@ -1 +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;
|
|
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;AAa9B,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,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC;QACvD,IACE,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ;YACtC,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,EACpC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO;YACL,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,QAAQ;YACvC,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE;YACzB,GAAG,EAAE,MAAM,CAAC,GAAG,IAAI,EAAE;SACtB,CAAC;IACJ,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;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACjE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -24,14 +24,15 @@ Usage:
|
|
|
24
24
|
lwc remove <workshop-id> [--delete-files]
|
|
25
25
|
Drop from registry; --delete-files also
|
|
26
26
|
removes the on-disk clone.
|
|
27
|
-
lwc auth login | logout | status Manage cached
|
|
27
|
+
lwc auth login | logout | status Manage cached sign-in token.
|
|
28
28
|
lwc help Show this help.
|
|
29
29
|
|
|
30
30
|
Environment:
|
|
31
|
-
LWC_TOKEN Bypass cached/
|
|
31
|
+
LWC_TOKEN Bypass cached/browser flow with a Bearer token (CI use).
|
|
32
32
|
LWC_API_URL Override the platform base URL
|
|
33
33
|
(default https://mcp.workshop.institute).
|
|
34
|
-
|
|
34
|
+
LWC_LANDING_URL Override the landing-site URL (default derived from
|
|
35
|
+
LWC_API_URL by stripping the mcp. or mcp- prefix).
|
|
35
36
|
`;
|
|
36
37
|
function flagValue(args, flag) {
|
|
37
38
|
const idx = args.indexOf(flag);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEvE,MAAM,IAAI,GAAG
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { clearCachedToken, loadCachedToken, saveCachedToken, tokenPath, } from '../auth/token-cache.js';
|
|
2
|
-
import {
|
|
2
|
+
import { loginViaBrowser } from '../auth/login.js';
|
|
3
3
|
export async function authLogin() {
|
|
4
|
-
const
|
|
5
|
-
const token = await loginViaBrowser(config);
|
|
4
|
+
const token = await loginViaBrowser();
|
|
6
5
|
saveCachedToken(token);
|
|
7
|
-
console.error(`Signed in
|
|
6
|
+
console.error(`Signed in as ${token.sub || '(unknown)'}.`);
|
|
7
|
+
console.error(`Token cached at ${tokenPath()}.`);
|
|
8
8
|
}
|
|
9
9
|
export function authLogout() {
|
|
10
10
|
const existed = loadCachedToken() !== null;
|
|
@@ -16,11 +16,19 @@ export function authLogout() {
|
|
|
16
16
|
export function authStatus() {
|
|
17
17
|
const cached = loadCachedToken();
|
|
18
18
|
if (!cached) {
|
|
19
|
-
console.error('Not signed in.');
|
|
19
|
+
console.error('Not signed in. Run `lwc auth login` to sign in.');
|
|
20
20
|
process.exit(1);
|
|
21
21
|
}
|
|
22
22
|
const remainingMs = cached.expiresAt - Date.now();
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
if (remainingMs <= 0) {
|
|
24
|
+
console.error(`Signed in as ${cached.sub || '(unknown)'}, but token has expired.`);
|
|
25
|
+
console.error('Run `lwc auth login` to sign in again.');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const days = Math.floor(remainingMs / (24 * 60 * 60 * 1000));
|
|
29
|
+
const hours = Math.floor((remainingMs % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000));
|
|
30
|
+
const expiresIn = days > 0 ? `${days} day${days === 1 ? '' : 's'}` : `${hours} hour${hours === 1 ? '' : 's'}`;
|
|
31
|
+
console.error(`Signed in as ${cached.sub || '(unknown)'}.`);
|
|
32
|
+
console.error(`Token expires in ~${expiresIn} (${tokenPath()}).`);
|
|
25
33
|
}
|
|
26
34
|
//# sourceMappingURL=auth.js.map
|
|
@@ -1 +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,
|
|
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,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAC;IACtC,eAAe,CAAC,KAAK,CAAC,CAAC;IACvB,OAAO,CAAC,KAAK,CAAC,gBAAgB,KAAK,CAAC,GAAG,IAAI,WAAW,GAAG,CAAC,CAAC;IAC3D,OAAO,CAAC,KAAK,CAAC,mBAAmB,SAAS,EAAE,GAAG,CAAC,CAAC;AACnD,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,iDAAiD,CAAC,CAAC;QACjE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAClD,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,KAAK,CAAC,gBAAgB,MAAM,CAAC,GAAG,IAAI,WAAW,0BAA0B,CAAC,CAAC;QACnF,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;QACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IACnF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IAC9G,OAAO,CAAC,KAAK,CAAC,gBAAgB,MAAM,CAAC,GAAG,IAAI,WAAW,GAAG,CAAC,CAAC;IAC5D,OAAO,CAAC,KAAK,CAAC,qBAAqB,SAAS,KAAK,SAAS,EAAE,IAAI,CAAC,CAAC;AACpE,CAAC"}
|
package/dist/commands/setup.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
3
4
|
import { resolveBearerToken } from '../auth/resolve-token.js';
|
|
4
5
|
import { callRemoteTool, parseStructured } from '../proxy/call-tool.js';
|
|
5
6
|
import { ensureGitInstalled, git, cleanRemoteUrl } from '../git.js';
|
|
@@ -14,6 +15,10 @@ export async function runSetup(opts) {
|
|
|
14
15
|
}
|
|
15
16
|
console.error(`Setting up workshop "${opts.workshopId}"...`);
|
|
16
17
|
const bearerToken = await resolveBearerToken();
|
|
18
|
+
// Fill the post-auth silent gap with per-phase progress so learners
|
|
19
|
+
// see continuous activity instead of a multi-second blank terminal.
|
|
20
|
+
console.error('\nJust a moment while we set things up...');
|
|
21
|
+
step('Provisioning workshop access');
|
|
17
22
|
const result = await callRemoteTool({
|
|
18
23
|
bearerToken,
|
|
19
24
|
name: 'provision_workshop_repo',
|
|
@@ -28,11 +33,12 @@ export async function runSetup(opts) {
|
|
|
28
33
|
}
|
|
29
34
|
// Make sure the parent (e.g. ~/learning-with-court/) exists.
|
|
30
35
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
31
|
-
|
|
36
|
+
step('Cloning workshop repo');
|
|
32
37
|
const clone = git(['clone', data.clone_url, dest]);
|
|
33
38
|
if (clone.status !== 0) {
|
|
34
39
|
throw new Error(`git clone failed: ${clone.stderr.trim()}`);
|
|
35
40
|
}
|
|
41
|
+
step('Setting up local config');
|
|
36
42
|
const cleanUrl = cleanRemoteUrl(data.clone_url);
|
|
37
43
|
const setUrl = git(['remote', 'set-url', 'origin', cleanUrl], { cwd: dest });
|
|
38
44
|
if (setUrl.status !== 0) {
|
|
@@ -43,11 +49,61 @@ export async function runSetup(opts) {
|
|
|
43
49
|
path: dest,
|
|
44
50
|
installedAt: new Date().toISOString(),
|
|
45
51
|
});
|
|
52
|
+
installDeps(dest);
|
|
46
53
|
console.error('');
|
|
47
54
|
console.error(`Done. Open the workshop:`);
|
|
48
55
|
console.error(` cd ${dest}`);
|
|
49
56
|
console.error(`Then start your coding agent (Claude Code, Cursor, Codex, etc.).`);
|
|
50
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Run the workshop's package install if a package.json is present, so the
|
|
60
|
+
* learner can run verify scripts immediately without a "node_modules is
|
|
61
|
+
* missing" prompt on first session start. Picks pnpm/npm/yarn from the
|
|
62
|
+
* lockfile present at the dest. If no lockfile, defaults to pnpm. If the
|
|
63
|
+
* required package manager isn't installed, surfaces a hint and continues
|
|
64
|
+
* — clone is still useful, the learner can install themselves.
|
|
65
|
+
*/
|
|
66
|
+
function installDeps(dest) {
|
|
67
|
+
if (!fs.existsSync(path.join(dest, 'package.json'))) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const has = (f) => fs.existsSync(path.join(dest, f));
|
|
71
|
+
const tool = has('pnpm-lock.yaml')
|
|
72
|
+
? 'pnpm'
|
|
73
|
+
: has('yarn.lock')
|
|
74
|
+
? 'yarn'
|
|
75
|
+
: has('package-lock.json')
|
|
76
|
+
? 'npm'
|
|
77
|
+
: 'pnpm';
|
|
78
|
+
step(`Installing dependencies (${tool} install)`);
|
|
79
|
+
const proc = spawnSync(tool, ['install'], {
|
|
80
|
+
cwd: dest,
|
|
81
|
+
stdio: 'inherit',
|
|
82
|
+
shell: process.platform === 'win32',
|
|
83
|
+
});
|
|
84
|
+
if (proc.error) {
|
|
85
|
+
if (proc.error.code === 'ENOENT') {
|
|
86
|
+
console.error(` Note: ${tool} is not installed; skipped dependency install. ` +
|
|
87
|
+
`Run '${tool} install' in ${dest} before starting the workshop.`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
console.error(` Note: ${tool} install errored — ${proc.error.message}`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (proc.status !== 0) {
|
|
94
|
+
console.error(` Note: ${tool} install exited ${proc.status}. ` +
|
|
95
|
+
`Re-run it manually in ${dest} if needed.`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Per-phase progress line. Plain ASCII so it renders identically in TTY,
|
|
100
|
+
* piped output, log files, and CI capture — no spinner, no Unicode, no
|
|
101
|
+
* TTY detection. Goes to stderr to keep stdout reserved for any future
|
|
102
|
+
* structured-output mode.
|
|
103
|
+
*/
|
|
104
|
+
function step(msg) {
|
|
105
|
+
console.error(` -> ${msg}`);
|
|
106
|
+
}
|
|
51
107
|
function formatToolError(result) {
|
|
52
108
|
return result.content
|
|
53
109
|
.map((c) => c.text ?? '')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAiBnE,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAkB;IAC/C,kBAAkB,EAAE,CAAC;IAErB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAC5E,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,gDAAgD,IAAI,IAAI;YACtD,oCAAoC;YACpC,kCAAkC,IAAI,CAAC,UAAU,EAAE,CACtD,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,wBAAwB,IAAI,CAAC,UAAU,MAAM,CAAC,CAAC;IAE7D,MAAM,WAAW,GAAG,MAAM,kBAAkB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare const DEFAULT_API_URL = "https://mcp.workshop.institute";
|
|
2
2
|
export declare function apiUrl(): string;
|
|
3
3
|
export declare function mcpEndpoint(): string;
|
|
4
|
-
export declare function
|
|
4
|
+
export declare function landingUrl(): string;
|
|
5
|
+
export declare function tokenExchangeUrl(): string;
|
|
6
|
+
export declare function cliAuthPageUrl(): string;
|
package/dist/config.js
CHANGED
|
@@ -5,7 +5,23 @@ export function apiUrl() {
|
|
|
5
5
|
export function mcpEndpoint() {
|
|
6
6
|
return `${apiUrl()}/mcp`;
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// Derive the landing site URL from the API URL by stripping the leading
|
|
9
|
+
// `mcp.` or `mcp-` host prefix. Both prod (mcp.workshop.institute →
|
|
10
|
+
// workshop.institute) and dev (mcp-dev.workshop.institute →
|
|
11
|
+
// dev.workshop.institute) follow this convention. Override with
|
|
12
|
+
// LWC_LANDING_URL if the convention ever stops holding.
|
|
13
|
+
export function landingUrl() {
|
|
14
|
+
const override = process.env.LWC_LANDING_URL;
|
|
15
|
+
if (override)
|
|
16
|
+
return override;
|
|
17
|
+
const api = new URL(apiUrl());
|
|
18
|
+
const host = api.host.replace(/^mcp[.-]/, '');
|
|
19
|
+
return `${api.protocol}//${host}`;
|
|
20
|
+
}
|
|
21
|
+
export function tokenExchangeUrl() {
|
|
22
|
+
return `${apiUrl()}/cli/token`;
|
|
23
|
+
}
|
|
24
|
+
export function cliAuthPageUrl() {
|
|
25
|
+
return `${landingUrl()}/cli-auth`;
|
|
10
26
|
}
|
|
11
27
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,gCAAgC,CAAC;AAEhE,MAAM,UAAU,MAAM;IACpB,OAAO,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,eAAe,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,GAAG,MAAM,EAAE,MAAM,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,gCAAgC,CAAC;AAEhE,MAAM,UAAU,MAAM;IACpB,OAAO,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,eAAe,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,GAAG,MAAM,EAAE,MAAM,CAAC;AAC3B,CAAC;AAED,wEAAwE;AACxE,oEAAoE;AACpE,4DAA4D;AAC5D,gEAAgE;AAChE,wDAAwD;AACxD,MAAM,UAAU,UAAU;IACxB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAC7C,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9B,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC9C,OAAO,GAAG,GAAG,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,GAAG,MAAM,EAAE,YAAY,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,GAAG,UAAU,EAAE,WAAW,CAAC;AACpC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@learning-with-court/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "CLI for learning-with-court — proxies MCP and bootstraps workshop projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli.js",
|
|
@@ -14,13 +14,18 @@
|
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsc",
|
|
17
|
-
"typecheck": "tsc --noEmit"
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"prepublishOnly": "pnpm build"
|
|
18
19
|
},
|
|
19
20
|
"repository": {
|
|
20
21
|
"type": "git",
|
|
21
|
-
"url": "https://github.com/learning-with-court/
|
|
22
|
+
"url": "https://github.com/learning-with-court/platform"
|
|
22
23
|
},
|
|
23
24
|
"license": "MIT",
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public",
|
|
27
|
+
"registry": "https://registry.npmjs.org/"
|
|
28
|
+
},
|
|
24
29
|
"engines": {
|
|
25
30
|
"node": ">=20.0.0"
|
|
26
31
|
},
|
|
@@ -1,27 +0,0 @@
|
|
|
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>;
|
package/dist/auth/oauth-login.js
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-login.js","sourceRoot":"","sources":["../../src/auth/oauth-login.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +0,0 @@
|
|
|
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>;
|
package/dist/commands/refresh.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/commands/refresh.ts"],"names":[],"mappings":"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"}
|