@learning-with-court/cli 0.0.1 → 0.1.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 CHANGED
@@ -6,8 +6,10 @@ CLI for the learning-with-court platform. Two modes:
6
6
  the hosted MCP server at `mcp.workshop.institute`. Auth resolves via env
7
7
  var (`LWC_TOKEN`) → cached `~/.lwc/token.json` → browser OAuth (PKCE).
8
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.
9
+ - `setup <workshop-id> [--dir <path>]` — clone a workshop project
10
+ repo (default: `~/learning-with-court/<workshop-id>/`) after Clerk
11
+ sign-in. Pass `--dir` to install somewhere else; the CLI auto-creates
12
+ parent directories.
11
13
  - `refresh <workshop-id>` — refresh the clone token and `git pull`.
12
14
  - `auth login` / `auth logout` — manual control of the cached token.
13
15
 
@@ -16,9 +18,12 @@ Modeled on `mixcraft-app/packages/mcp-proxy`.
16
18
  ## Usage
17
19
 
18
20
  ```sh
19
- # Bootstrap a workshop project (any agent, any OS)
21
+ # Bootstrap a workshop project (default: ~/learning-with-court/mcp-workshop/)
20
22
  npx -y @learning-with-court/cli@latest setup mcp-workshop
21
23
 
24
+ # Install into a folder of your choice instead
25
+ npx -y @learning-with-court/cli@latest setup mcp-workshop --dir ~/workshops/mcp-workshop
26
+
22
27
  # Run as MCP proxy (configured by an agent's .mcp.json)
23
28
  npx -y @learning-with-court/cli@latest
24
29
  ```
@@ -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,6 @@
1
+ export declare function generateState(): string;
2
+ export interface PkcePair {
3
+ codeVerifier: string;
4
+ codeChallenge: string;
5
+ }
6
+ export declare function generatePkcePair(): PkcePair;
@@ -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 { discoverOAuthConfig, loginViaBrowser, refreshAccessToken, } from './oauth-login.js';
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
- 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
- }
8
+ if (cached && !isTokenExpired(cached)) {
9
+ return cached.accessToken;
28
10
  }
29
- const fresh = await loginViaBrowser(config);
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,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"}
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
- refreshToken: string;
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;
@@ -12,7 +12,18 @@ export function loadCachedToken() {
12
12
  }
13
13
  try {
14
14
  const raw = fs.readFileSync(TOKEN_PATH, 'utf-8');
15
- return JSON.parse(raw);
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;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"}
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 Clerk token.
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/OAuth flow with a Bearer token (CI use).
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
- LWC_OAUTH_CLIENT_ID Override the OAuth client_id discovered from /register.
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":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,10 @@
1
1
  import { clearCachedToken, loadCachedToken, saveCachedToken, tokenPath, } from '../auth/token-cache.js';
2
- import { discoverOAuthConfig, loginViaBrowser, } from '../auth/oauth-login.js';
2
+ import { loginViaBrowser } from '../auth/login.js';
3
3
  export async function authLogin() {
4
- const config = await discoverOAuthConfig();
5
- const token = await loginViaBrowser(config);
4
+ const token = await loginViaBrowser();
6
5
  saveCachedToken(token);
7
- console.error(`Signed in. Token cached at ${tokenPath()}.`);
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
- const minutes = Math.max(0, Math.round(remainingMs / 60_000));
24
- console.error(`Signed in. Token expires in ~${minutes} min (${tokenPath()}).`);
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,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"}
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"}
@@ -14,6 +14,10 @@ export async function runSetup(opts) {
14
14
  }
15
15
  console.error(`Setting up workshop "${opts.workshopId}"...`);
16
16
  const bearerToken = await resolveBearerToken();
17
+ // Fill the post-auth silent gap with per-phase progress so learners
18
+ // see continuous activity instead of a multi-second blank terminal.
19
+ console.error('\nJust a moment while we set things up...');
20
+ step('Provisioning workshop access');
17
21
  const result = await callRemoteTool({
18
22
  bearerToken,
19
23
  name: 'provision_workshop_repo',
@@ -28,11 +32,12 @@ export async function runSetup(opts) {
28
32
  }
29
33
  // Make sure the parent (e.g. ~/learning-with-court/) exists.
30
34
  fs.mkdirSync(path.dirname(dest), { recursive: true });
31
- console.error(`Cloning into ${dest}...`);
35
+ step('Cloning workshop repo');
32
36
  const clone = git(['clone', data.clone_url, dest]);
33
37
  if (clone.status !== 0) {
34
38
  throw new Error(`git clone failed: ${clone.stderr.trim()}`);
35
39
  }
40
+ step('Setting up local config');
36
41
  const cleanUrl = cleanRemoteUrl(data.clone_url);
37
42
  const setUrl = git(['remote', 'set-url', 'origin', cleanUrl], { cwd: dest });
38
43
  if (setUrl.status !== 0) {
@@ -48,6 +53,15 @@ export async function runSetup(opts) {
48
53
  console.error(` cd ${dest}`);
49
54
  console.error(`Then start your coding agent (Claude Code, Cursor, Codex, etc.).`);
50
55
  }
56
+ /**
57
+ * Per-phase progress line. Plain ASCII so it renders identically in TTY,
58
+ * piped output, log files, and CI capture — no spinner, no Unicode, no
59
+ * TTY detection. Goes to stderr to keep stdout reserved for any future
60
+ * structured-output mode.
61
+ */
62
+ function step(msg) {
63
+ console.error(` -> ${msg}`);
64
+ }
51
65
  function formatToolError(result) {
52
66
  return result.content
53
67
  .map((c) => c.text ?? '')
@@ -1 +1 @@
1
- {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"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"}
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 metadataUrl(): string;
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
- export function metadataUrl() {
9
- return `${apiUrl()}/.well-known/oauth-authorization-server`;
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
@@ -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,WAAW;IACzB,OAAO,GAAG,MAAM,EAAE,yCAAyC,CAAC;AAC9D,CAAC"}
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/dist/git.js CHANGED
@@ -1,9 +1,17 @@
1
1
  import { spawnSync } from 'node:child_process';
2
+ /**
3
+ * Disable any user-configured credential helpers (e.g. the AWS CodeCommit
4
+ * helper, which hijacks GitHub auth on Macs with the AWS toolchain installed)
5
+ * for our operations. We pass our own short-lived token in the URL — git
6
+ * shouldn't ask any helper for credentials.
7
+ */
8
+ const NEUTRAL_GIT_FLAGS = ['-c', 'credential.helper='];
2
9
  export function git(args, opts = {}) {
3
- const result = spawnSync('git', args, {
10
+ const result = spawnSync('git', [...NEUTRAL_GIT_FLAGS, ...args], {
4
11
  cwd: opts.cwd,
5
12
  encoding: 'utf-8',
6
13
  stdio: ['ignore', 'pipe', 'pipe'],
14
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
7
15
  });
8
16
  return {
9
17
  status: result.status ?? 1,
package/dist/git.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAQ/C,MAAM,UAAU,GAAG,CAAC,IAAc,EAAE,OAAyB,EAAE;IAC7D,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE;QACpC,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,QAAQ,EAAE,OAAO;QACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;KAClC,CAAC,CAAC;IACH,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;QACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;KACxC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;IAC7B,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;AACH,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,cAAc,CAAC,YAAoB;IACjD,iEAAiE;IACjE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;IAClC,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC;IAClB,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC;IAClB,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC"}
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAQ/C;;;;;GAKG;AACH,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC;AAEvD,MAAM,UAAU,GAAG,CAAC,IAAc,EAAE,OAAyB,EAAE;IAC7D,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,GAAG,iBAAiB,EAAE,GAAG,IAAI,CAAC,EAAE;QAC/D,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,QAAQ,EAAE,OAAO;QACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;QACjC,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE,GAAG,EAAE;KAClD,CAAC,CAAC;IACH,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;QACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;KACxC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;IAC7B,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;AACH,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,cAAc,CAAC,YAAoB;IACjD,iEAAiE;IACjE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;IAClC,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC;IAClB,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC;IAClB,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@learning-with-court/cli",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
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/learning-with-court-platform"
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>;
@@ -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>;
@@ -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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"}