@learning-with-court/cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +30 -0
  2. package/dist/auth/oauth-login.d.ts +27 -0
  3. package/dist/auth/oauth-login.js +164 -0
  4. package/dist/auth/oauth-login.js.map +1 -0
  5. package/dist/auth/resolve-token.d.ts +1 -0
  6. package/dist/auth/resolve-token.js +33 -0
  7. package/dist/auth/resolve-token.js.map +1 -0
  8. package/dist/auth/token-cache.d.ts +10 -0
  9. package/dist/auth/token-cache.js +33 -0
  10. package/dist/auth/token-cache.js.map +1 -0
  11. package/dist/cli.d.ts +2 -0
  12. package/dist/cli.js +117 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/commands/auth.d.ts +3 -0
  15. package/dist/commands/auth.js +26 -0
  16. package/dist/commands/auth.js.map +1 -0
  17. package/dist/commands/list.d.ts +1 -0
  18. package/dist/commands/list.js +22 -0
  19. package/dist/commands/list.js.map +1 -0
  20. package/dist/commands/refresh.d.ts +8 -0
  21. package/dist/commands/refresh.js +69 -0
  22. package/dist/commands/refresh.js.map +1 -0
  23. package/dist/commands/remove.d.ts +5 -0
  24. package/dist/commands/remove.js +19 -0
  25. package/dist/commands/remove.js.map +1 -0
  26. package/dist/commands/setup.d.ts +9 -0
  27. package/dist/commands/setup.js +58 -0
  28. package/dist/commands/setup.js.map +1 -0
  29. package/dist/commands/switch.d.ts +7 -0
  30. package/dist/commands/switch.js +28 -0
  31. package/dist/commands/switch.js.map +1 -0
  32. package/dist/commands/update.d.ts +10 -0
  33. package/dist/commands/update.js +80 -0
  34. package/dist/commands/update.js.map +1 -0
  35. package/dist/config.d.ts +4 -0
  36. package/dist/config.js +11 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/git.d.ts +11 -0
  39. package/dist/git.js +28 -0
  40. package/dist/git.js.map +1 -0
  41. package/dist/proxy/call-tool.d.ts +20 -0
  42. package/dist/proxy/call-tool.js +55 -0
  43. package/dist/proxy/call-tool.js.map +1 -0
  44. package/dist/proxy/schema.d.ts +19 -0
  45. package/dist/proxy/schema.js +47 -0
  46. package/dist/proxy/schema.js.map +1 -0
  47. package/dist/proxy/stdio-server.d.ts +1 -0
  48. package/dist/proxy/stdio-server.js +41 -0
  49. package/dist/proxy/stdio-server.js.map +1 -0
  50. package/dist/registry.d.ts +20 -0
  51. package/dist/registry.js +62 -0
  52. package/dist/registry.js.map +1 -0
  53. package/package.json +35 -0
@@ -0,0 +1,9 @@
1
+ export interface SetupOptions {
2
+ workshopId: string;
3
+ /**
4
+ * Override the install dir. When omitted, defaults to
5
+ * `~/learning-with-court/<workshopId>`.
6
+ */
7
+ dest?: string;
8
+ }
9
+ export declare function runSetup(opts: SetupOptions): Promise<void>;
@@ -0,0 +1,58 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { resolveBearerToken } from '../auth/resolve-token.js';
4
+ import { callRemoteTool, parseStructured } from '../proxy/call-tool.js';
5
+ import { ensureGitInstalled, git, cleanRemoteUrl } from '../git.js';
6
+ import { defaultInstallPath, recordInstall } from '../registry.js';
7
+ export async function runSetup(opts) {
8
+ ensureGitInstalled();
9
+ const dest = path.resolve(opts.dest ?? defaultInstallPath(opts.workshopId));
10
+ if (fs.existsSync(dest) && fs.readdirSync(dest).length > 0) {
11
+ throw new Error(`Destination already exists and is not empty: ${dest}\n` +
12
+ `Already installed? Try: lwc list\n` +
13
+ `Or remove it first: lwc remove ${opts.workshopId}`);
14
+ }
15
+ console.error(`Setting up workshop "${opts.workshopId}"...`);
16
+ const bearerToken = await resolveBearerToken();
17
+ const result = await callRemoteTool({
18
+ bearerToken,
19
+ name: 'provision_workshop_repo',
20
+ args: { workshop_id: opts.workshopId },
21
+ });
22
+ if (result.isError) {
23
+ throw new Error(`Provisioning failed: ${formatToolError(result)}`);
24
+ }
25
+ const data = parseStructured(result);
26
+ if (!data?.clone_url) {
27
+ throw new Error('Provisioning returned no clone URL.');
28
+ }
29
+ // Make sure the parent (e.g. ~/learning-with-court/) exists.
30
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
31
+ console.error(`Cloning into ${dest}...`);
32
+ const clone = git(['clone', data.clone_url, dest]);
33
+ if (clone.status !== 0) {
34
+ throw new Error(`git clone failed: ${clone.stderr.trim()}`);
35
+ }
36
+ const cleanUrl = cleanRemoteUrl(data.clone_url);
37
+ const setUrl = git(['remote', 'set-url', 'origin', cleanUrl], { cwd: dest });
38
+ if (setUrl.status !== 0) {
39
+ console.error(`Warning: failed to strip token from origin URL — clean it manually with:\n git -C ${dest} remote set-url origin ${cleanUrl}`);
40
+ }
41
+ recordInstall({
42
+ workshopId: opts.workshopId,
43
+ path: dest,
44
+ installedAt: new Date().toISOString(),
45
+ });
46
+ console.error('');
47
+ console.error(`Done. Open the workshop:`);
48
+ console.error(` cd ${dest}`);
49
+ console.error(`Then start your coding agent (Claude Code, Cursor, Codex, etc.).`);
50
+ }
51
+ function formatToolError(result) {
52
+ return result.content
53
+ .map((c) => c.text ?? '')
54
+ .filter(Boolean)
55
+ .join(' ')
56
+ .trim() || 'unknown error';
57
+ }
58
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Print the `cd` command for a workshop. We can't change the parent
3
+ * shell's cwd from a child process — the standard Unix-tool-of-tools
4
+ * convention is to print the command and let the user execute it (or
5
+ * eval it via `eval $(lwc switch foo)`).
6
+ */
7
+ export declare function runSwitch(workshopId: string): void;
@@ -0,0 +1,28 @@
1
+ import * as fs from 'node:fs';
2
+ import { getInstall } from '../registry.js';
3
+ /**
4
+ * Print the `cd` command for a workshop. We can't change the parent
5
+ * shell's cwd from a child process — the standard Unix-tool-of-tools
6
+ * convention is to print the command and let the user execute it (or
7
+ * eval it via `eval $(lwc switch foo)`).
8
+ */
9
+ export function runSwitch(workshopId) {
10
+ const entry = getInstall(workshopId);
11
+ if (!entry) {
12
+ console.error(`No workshop "${workshopId}" installed. Try: lwc list`);
13
+ process.exit(1);
14
+ }
15
+ if (!fs.existsSync(entry.path)) {
16
+ console.error(`Workshop "${workshopId}" registry entry points at ${entry.path}, but that path no longer exists.\n` +
17
+ `Either restore it or run: lwc remove ${workshopId}`);
18
+ process.exit(1);
19
+ }
20
+ // Print to STDOUT so it's eval-friendly. Status messages go to stderr.
21
+ console.log(`cd ${shellQuote(entry.path)}`);
22
+ }
23
+ function shellQuote(s) {
24
+ if (/^[A-Za-z0-9_./-]+$/.test(s))
25
+ return s;
26
+ return `'${s.replace(/'/g, `'\\''`)}'`;
27
+ }
28
+ //# sourceMappingURL=switch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"switch.js","sourceRoot":"","sources":["../../src/commands/switch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,UAAkB;IAC1C,MAAM,KAAK,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,gBAAgB,UAAU,4BAA4B,CAAC,CAAC;QACtE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,KAAK,CACX,aAAa,UAAU,8BAA8B,KAAK,CAAC,IAAI,qCAAqC;YAClG,wCAAwC,UAAU,EAAE,CACvD,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,uEAAuE;IACvE,OAAO,CAAC,GAAG,CAAC,MAAM,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,IAAI,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IAC3C,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AACzC,CAAC"}
@@ -0,0 +1,10 @@
1
+ export interface UpdateOptions {
2
+ /**
3
+ * Specific workshop to update. When omitted, updates every installed
4
+ * workshop in the registry.
5
+ */
6
+ workshopId?: string;
7
+ /** When true, only refresh the token (no `git pull`). */
8
+ tokenOnly?: boolean;
9
+ }
10
+ export declare function runUpdate(opts: UpdateOptions): Promise<void>;
@@ -0,0 +1,80 @@
1
+ import * as fs from 'node:fs';
2
+ import { resolveBearerToken } from '../auth/resolve-token.js';
3
+ import { callRemoteTool, parseStructured } from '../proxy/call-tool.js';
4
+ import { ensureGitInstalled, git, cleanRemoteUrl } from '../git.js';
5
+ import { getInstall, listInstalls, recordRefresh, } from '../registry.js';
6
+ export async function runUpdate(opts) {
7
+ ensureGitInstalled();
8
+ const targets = opts.workshopId
9
+ ? singleTarget(opts.workshopId)
10
+ : listInstalls();
11
+ if (targets.length === 0) {
12
+ console.error(opts.workshopId
13
+ ? `Workshop "${opts.workshopId}" is not registered. Try: lwc list`
14
+ : 'No workshops registered.');
15
+ process.exit(1);
16
+ }
17
+ const bearerToken = await resolveBearerToken();
18
+ let failures = 0;
19
+ for (const entry of targets) {
20
+ if (!fs.existsSync(entry.path)) {
21
+ console.error(`[${entry.workshopId}] SKIP — path missing: ${entry.path}`);
22
+ failures++;
23
+ continue;
24
+ }
25
+ try {
26
+ await updateOne(entry, bearerToken, opts.tokenOnly ?? false);
27
+ console.error(`[${entry.workshopId}] OK`);
28
+ }
29
+ catch (err) {
30
+ console.error(`[${entry.workshopId}] FAIL — ${err instanceof Error ? err.message : String(err)}`);
31
+ failures++;
32
+ }
33
+ }
34
+ if (failures > 0)
35
+ process.exit(1);
36
+ }
37
+ function singleTarget(workshopId) {
38
+ const e = getInstall(workshopId);
39
+ return e ? [e] : [];
40
+ }
41
+ async function updateOne(entry, bearerToken, tokenOnly) {
42
+ const result = await callRemoteTool({
43
+ bearerToken,
44
+ name: 'refresh_workshop_repo',
45
+ args: { workshop_id: entry.workshopId },
46
+ });
47
+ if (result.isError) {
48
+ throw new Error(formatToolError(result));
49
+ }
50
+ const data = parseStructured(result);
51
+ if (!data?.clone_url)
52
+ throw new Error('Refresh returned no clone URL.');
53
+ const cleanUrl = cleanRemoteUrl(data.clone_url);
54
+ const setUrl = git(['remote', 'set-url', 'origin', data.clone_url], {
55
+ cwd: entry.path,
56
+ });
57
+ if (setUrl.status !== 0) {
58
+ throw new Error(`git remote set-url failed: ${setUrl.stderr.trim()}`);
59
+ }
60
+ try {
61
+ if (!tokenOnly) {
62
+ const pull = git(['pull', '--ff-only'], { cwd: entry.path });
63
+ if (pull.status !== 0) {
64
+ throw new Error(`git pull failed: ${pull.stderr.trim()}`);
65
+ }
66
+ }
67
+ }
68
+ finally {
69
+ git(['remote', 'set-url', 'origin', cleanUrl], { cwd: entry.path });
70
+ }
71
+ recordRefresh(entry.workshopId);
72
+ }
73
+ function formatToolError(result) {
74
+ return result.content
75
+ .map((c) => c.text ?? '')
76
+ .filter(Boolean)
77
+ .join(' ')
78
+ .trim() || 'unknown error';
79
+ }
80
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare const DEFAULT_API_URL = "https://mcp.workshop.institute";
2
+ export declare function apiUrl(): string;
3
+ export declare function mcpEndpoint(): string;
4
+ export declare function metadataUrl(): string;
package/dist/config.js ADDED
@@ -0,0 +1,11 @@
1
+ export const DEFAULT_API_URL = 'https://mcp.workshop.institute';
2
+ export function apiUrl() {
3
+ return process.env.LWC_API_URL ?? DEFAULT_API_URL;
4
+ }
5
+ export function mcpEndpoint() {
6
+ return `${apiUrl()}/mcp`;
7
+ }
8
+ export function metadataUrl() {
9
+ return `${apiUrl()}/.well-known/oauth-authorization-server`;
10
+ }
11
+ //# sourceMappingURL=config.js.map
@@ -0,0 +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"}
package/dist/git.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export interface GitResult {
2
+ status: number;
3
+ stdout: string;
4
+ stderr: string;
5
+ }
6
+ export declare function git(args: string[], opts?: {
7
+ cwd?: string;
8
+ }): GitResult;
9
+ export declare function ensureGitInstalled(): void;
10
+ /** Strip embedded credentials from a tokenized clone URL. */
11
+ export declare function cleanRemoteUrl(tokenizedUrl: string): string;
package/dist/git.js ADDED
@@ -0,0 +1,28 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ export function git(args, opts = {}) {
3
+ const result = spawnSync('git', args, {
4
+ cwd: opts.cwd,
5
+ encoding: 'utf-8',
6
+ stdio: ['ignore', 'pipe', 'pipe'],
7
+ });
8
+ return {
9
+ status: result.status ?? 1,
10
+ stdout: result.stdout?.toString() ?? '',
11
+ stderr: result.stderr?.toString() ?? '',
12
+ };
13
+ }
14
+ export function ensureGitInstalled() {
15
+ const r = git(['--version']);
16
+ if (r.status !== 0) {
17
+ throw new Error('`git` is required but was not found on PATH.');
18
+ }
19
+ }
20
+ /** Strip embedded credentials from a tokenized clone URL. */
21
+ export function cleanRemoteUrl(tokenizedUrl) {
22
+ // x-access-token:<token>@github.com/... → https://github.com/...
23
+ const url = new URL(tokenizedUrl);
24
+ url.username = '';
25
+ url.password = '';
26
+ return url.toString();
27
+ }
28
+ //# sourceMappingURL=git.js.map
@@ -0,0 +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"}
@@ -0,0 +1,20 @@
1
+ export interface ToolResult {
2
+ content: Array<{
3
+ type: string;
4
+ text?: string;
5
+ }>;
6
+ isError?: boolean;
7
+ structuredContent?: unknown;
8
+ }
9
+ /**
10
+ * One-shot tool invocation against the hosted MCP server. Used by the
11
+ * `setup` and `refresh` subcommands, which don't need a long-lived
12
+ * stdio proxy — they just call a single tool and exit.
13
+ */
14
+ export declare function callRemoteTool(params: {
15
+ bearerToken: string;
16
+ name: string;
17
+ args: Record<string, unknown>;
18
+ }): Promise<ToolResult>;
19
+ export declare function extractText(result: ToolResult): string;
20
+ export declare function parseStructured<T>(result: ToolResult): T | null;
@@ -0,0 +1,55 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
3
+ import { mcpEndpoint } from '../config.js';
4
+ /**
5
+ * One-shot tool invocation against the hosted MCP server. Used by the
6
+ * `setup` and `refresh` subcommands, which don't need a long-lived
7
+ * stdio proxy — they just call a single tool and exit.
8
+ */
9
+ export async function callRemoteTool(params) {
10
+ const transport = new StreamableHTTPClientTransport(new URL(mcpEndpoint()), {
11
+ requestInit: {
12
+ headers: { Authorization: `Bearer ${params.bearerToken}` },
13
+ },
14
+ });
15
+ const client = new Client({
16
+ name: 'learning-with-court-cli',
17
+ version: '0.0.1',
18
+ });
19
+ try {
20
+ await client.connect(transport);
21
+ const result = await client.callTool({
22
+ name: params.name,
23
+ arguments: params.args,
24
+ });
25
+ return {
26
+ content: result.content,
27
+ isError: result.isError,
28
+ structuredContent: result.structuredContent,
29
+ };
30
+ }
31
+ finally {
32
+ await client.close().catch(() => { });
33
+ }
34
+ }
35
+ export function extractText(result) {
36
+ return result.content
37
+ .filter((c) => c.type === 'text' && typeof c.text === 'string')
38
+ .map((c) => c.text)
39
+ .join('\n');
40
+ }
41
+ export function parseStructured(result) {
42
+ if (result.structuredContent !== undefined && result.structuredContent !== null) {
43
+ return result.structuredContent;
44
+ }
45
+ const text = extractText(result);
46
+ if (!text)
47
+ return null;
48
+ try {
49
+ return JSON.parse(text);
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ //# sourceMappingURL=call-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"call-tool.js","sourceRoot":"","sources":["../../src/proxy/call-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAQ3C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAIpC;IACC,MAAM,SAAS,GAAG,IAAI,6BAA6B,CAAC,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC,EAAE;QAC1E,WAAW,EAAE;YACX,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,CAAC,WAAW,EAAE,EAAE;SAC3D;KACF,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;QACxB,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAChC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC;YACnC,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,SAAS,EAAE,MAAM,CAAC,IAAI;SACvB,CAAC,CAAC;QACH,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAgC;YAChD,OAAO,EAAE,MAAM,CAAC,OAA8B;YAC9C,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;SAC5C,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACvC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAkB;IAC5C,OAAO,MAAM,CAAC,OAAO;SAClB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;SAC9D,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAc,CAAC;SAC5B,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,eAAe,CAAI,MAAkB;IACnD,IAAI,MAAM,CAAC,iBAAiB,KAAK,SAAS,IAAI,MAAM,CAAC,iBAAiB,KAAK,IAAI,EAAE,CAAC;QAChF,OAAO,MAAM,CAAC,iBAAsB,CAAC;IACvC,CAAC;IACD,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+ export interface JsonSchemaProp {
3
+ type?: string;
4
+ description?: string;
5
+ default?: unknown;
6
+ minimum?: number;
7
+ maximum?: number;
8
+ items?: {
9
+ type?: string;
10
+ };
11
+ }
12
+ export declare function jsonSchemaToZod(prop: JsonSchemaProp, isRequired: boolean): z.ZodTypeAny;
13
+ export declare function buildZodShape(tool: {
14
+ inputSchema?: {
15
+ properties?: Record<string, JsonSchemaProp>;
16
+ required?: string[];
17
+ };
18
+ annotations?: Record<string, unknown>;
19
+ }): Record<string, z.ZodTypeAny>;
@@ -0,0 +1,47 @@
1
+ import { z } from 'zod';
2
+ export function jsonSchemaToZod(prop, isRequired) {
3
+ let schema;
4
+ switch (prop.type) {
5
+ case 'number':
6
+ case 'integer': {
7
+ let num = z.coerce.number();
8
+ if (prop.minimum !== undefined)
9
+ num = num.min(prop.minimum);
10
+ if (prop.maximum !== undefined)
11
+ num = num.max(prop.maximum);
12
+ schema = num;
13
+ break;
14
+ }
15
+ case 'boolean':
16
+ schema = z.boolean();
17
+ break;
18
+ case 'array':
19
+ schema = z.array(prop.items?.type === 'number' ? z.coerce.number() : z.string());
20
+ break;
21
+ default:
22
+ schema = z.string();
23
+ break;
24
+ }
25
+ if (prop.description)
26
+ schema = schema.describe(prop.description);
27
+ if (prop.default !== undefined)
28
+ schema = schema.optional().default(prop.default);
29
+ else if (!isRequired)
30
+ schema = schema.optional();
31
+ return schema;
32
+ }
33
+ export function buildZodShape(tool) {
34
+ const props = tool.inputSchema?.properties;
35
+ const source = props && Object.keys(props).length > 0
36
+ ? props
37
+ : tool.annotations ?? {};
38
+ const requiredFields = new Set(tool.inputSchema?.required ?? []);
39
+ const shape = {};
40
+ for (const [key, def] of Object.entries(source)) {
41
+ if (typeof def === 'object' && def !== null && 'type' in def) {
42
+ shape[key] = jsonSchemaToZod(def, requiredFields.has(key));
43
+ }
44
+ }
45
+ return shape;
46
+ }
47
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/proxy/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAWxB,MAAM,UAAU,eAAe,CAAC,IAAoB,EAAE,UAAmB;IACvE,IAAI,MAAoB,CAAC;IAEzB,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,QAAQ,CAAC;QACd,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,IAAI,GAAG,GAAiB,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAC1C,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;gBAAE,GAAG,GAAI,GAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC7E,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;gBAAE,GAAG,GAAI,GAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC7E,MAAM,GAAG,GAAG,CAAC;YACb,MAAM;QACR,CAAC;QACD,KAAK,SAAS;YACZ,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;YACrB,MAAM;QACR,KAAK,OAAO;YACV,MAAM,GAAG,CAAC,CAAC,KAAK,CACd,IAAI,CAAC,KAAK,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAC/D,CAAC;YACF,MAAM;QACR;YACE,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;YACpB,MAAM;IACV,CAAC;IAED,IAAI,IAAI,CAAC,WAAW;QAAE,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACjE,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;QAAE,MAAM,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;SAC5E,IAAI,CAAC,UAAU;QAAE,MAAM,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;IAEjD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAM7B;IACC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC;IAC3C,MAAM,MAAM,GAAG,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC;QACnD,CAAC,CAAC,KAAK;QACP,CAAC,CAAE,IAAI,CAAC,WAA0D,IAAI,EAAE,CAAC;IAC3E,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC;IAEjE,MAAM,KAAK,GAAiC,EAAE,CAAC;IAC/C,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAChD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;YAC7D,KAAK,CAAC,GAAG,CAAC,GAAG,eAAe,CAAC,GAAG,EAAE,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function runStdioProxy(): Promise<void>;
@@ -0,0 +1,41 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { mcpEndpoint } from '../config.js';
6
+ import { resolveBearerToken } from '../auth/resolve-token.js';
7
+ import { buildZodShape } from './schema.js';
8
+ export async function runStdioProxy() {
9
+ const bearerToken = await resolveBearerToken();
10
+ const remoteTransport = new StreamableHTTPClientTransport(new URL(mcpEndpoint()), {
11
+ requestInit: {
12
+ headers: { Authorization: `Bearer ${bearerToken}` },
13
+ },
14
+ });
15
+ const remoteClient = new Client({
16
+ name: 'learning-with-court-cli',
17
+ version: '0.0.1',
18
+ });
19
+ await remoteClient.connect(remoteTransport);
20
+ const { tools } = await remoteClient.listTools();
21
+ const localServer = new McpServer({
22
+ name: 'learning-with-court',
23
+ version: '0.0.1',
24
+ });
25
+ for (const tool of tools) {
26
+ const zodShape = buildZodShape(tool);
27
+ localServer.tool(tool.name, tool.description ?? '', zodShape, async (args) => {
28
+ const result = await remoteClient.callTool({
29
+ name: tool.name,
30
+ arguments: args,
31
+ });
32
+ return {
33
+ content: result.content,
34
+ isError: result.isError,
35
+ };
36
+ });
37
+ }
38
+ const stdioTransport = new StdioServerTransport();
39
+ await localServer.connect(stdioTransport);
40
+ }
41
+ //# sourceMappingURL=stdio-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio-server.js","sourceRoot":"","sources":["../../src/proxy/stdio-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,WAAW,GAAG,MAAM,kBAAkB,EAAE,CAAC;IAE/C,MAAM,eAAe,GAAG,IAAI,6BAA6B,CACvD,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC,EACtB;QACE,WAAW,EAAE;YACX,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,WAAW,EAAE,EAAE;SACpD;KACF,CACF,CAAC;IAEF,MAAM,YAAY,GAAG,IAAI,MAAM,CAAC;QAC9B,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,MAAM,YAAY,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAE5C,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,YAAY,CAAC,SAAS,EAAE,CAAC;IAEjD,MAAM,WAAW,GAAG,IAAI,SAAS,CAAC;QAChC,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,aAAa,CAAC,IAA2C,CAAC,CAAC;QAC5E,WAAW,CAAC,IAAI,CACd,IAAI,CAAC,IAAI,EACT,IAAI,CAAC,WAAW,IAAI,EAAE,EACtB,QAAQ,EACR,KAAK,EAAE,IAA6B,EAAE,EAAE;YACtC,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC;gBACzC,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,SAAS,EAAE,IAAI;aAChB,CAAC,CAAC;YACH,OAAO;gBACL,OAAO,EAAE,MAAM,CAAC,OAAgD;gBAChE,OAAO,EAAE,MAAM,CAAC,OAA8B;aAC/C,CAAC;QACJ,CAAC,CACF,CAAC;IACJ,CAAC;IAED,MAAM,cAAc,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAClD,MAAM,WAAW,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,20 @@
1
+ export interface RegistryEntry {
2
+ workshopId: string;
3
+ path: string;
4
+ installedAt: string;
5
+ lastRefreshedAt?: string;
6
+ }
7
+ interface RegistryFile {
8
+ version: 1;
9
+ workshops: Record<string, RegistryEntry>;
10
+ }
11
+ export declare function defaultInstallRoot(): string;
12
+ export declare function defaultInstallPath(workshopId: string): string;
13
+ export declare function registryPath(): string;
14
+ export declare function loadRegistry(): RegistryFile;
15
+ export declare function recordInstall(entry: RegistryEntry): void;
16
+ export declare function recordRefresh(workshopId: string): void;
17
+ export declare function removeFromRegistry(workshopId: string): boolean;
18
+ export declare function getInstall(workshopId: string): RegistryEntry | null;
19
+ export declare function listInstalls(): RegistryEntry[];
20
+ export {};
@@ -0,0 +1,62 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ const REG_DIR = path.join(os.homedir(), '.lwc');
5
+ const REG_PATH = path.join(REG_DIR, 'workshops.json');
6
+ const EMPTY = { version: 1, workshops: {} };
7
+ export function defaultInstallRoot() {
8
+ return path.join(os.homedir(), 'learning-with-court');
9
+ }
10
+ export function defaultInstallPath(workshopId) {
11
+ return path.join(defaultInstallRoot(), workshopId);
12
+ }
13
+ export function registryPath() {
14
+ return REG_PATH;
15
+ }
16
+ export function loadRegistry() {
17
+ if (!fs.existsSync(REG_PATH))
18
+ return { ...EMPTY };
19
+ try {
20
+ const raw = fs.readFileSync(REG_PATH, 'utf-8');
21
+ const parsed = JSON.parse(raw);
22
+ if (parsed.version !== 1 || typeof parsed.workshops !== 'object') {
23
+ return { ...EMPTY };
24
+ }
25
+ return parsed;
26
+ }
27
+ catch {
28
+ return { ...EMPTY };
29
+ }
30
+ }
31
+ function saveRegistry(reg) {
32
+ fs.mkdirSync(REG_DIR, { recursive: true, mode: 0o700 });
33
+ fs.writeFileSync(REG_PATH, JSON.stringify(reg, null, 2), { mode: 0o600 });
34
+ }
35
+ export function recordInstall(entry) {
36
+ const reg = loadRegistry();
37
+ reg.workshops[entry.workshopId] = entry;
38
+ saveRegistry(reg);
39
+ }
40
+ export function recordRefresh(workshopId) {
41
+ const reg = loadRegistry();
42
+ const existing = reg.workshops[workshopId];
43
+ if (!existing)
44
+ return;
45
+ existing.lastRefreshedAt = new Date().toISOString();
46
+ saveRegistry(reg);
47
+ }
48
+ export function removeFromRegistry(workshopId) {
49
+ const reg = loadRegistry();
50
+ if (!reg.workshops[workshopId])
51
+ return false;
52
+ delete reg.workshops[workshopId];
53
+ saveRegistry(reg);
54
+ return true;
55
+ }
56
+ export function getInstall(workshopId) {
57
+ return loadRegistry().workshops[workshopId] ?? null;
58
+ }
59
+ export function listInstalls() {
60
+ return Object.values(loadRegistry().workshops).sort((a, b) => a.workshopId.localeCompare(b.workshopId));
61
+ }
62
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@learning-with-court/cli",
3
+ "version": "0.0.1",
4
+ "description": "CLI for learning-with-court — proxies MCP and bootstraps workshop projects.",
5
+ "type": "module",
6
+ "main": "dist/cli.js",
7
+ "bin": {
8
+ "learning-with-court": "./dist/cli.js",
9
+ "lwc": "./dist/cli.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "typecheck": "tsc --noEmit"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/learning-with-court/learning-with-court-platform"
22
+ },
23
+ "license": "MIT",
24
+ "engines": {
25
+ "node": ">=20.0.0"
26
+ },
27
+ "dependencies": {
28
+ "@modelcontextprotocol/sdk": "^1.29.0",
29
+ "zod": "^3.25.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^22.10.0",
33
+ "typescript": "^5.7.2"
34
+ }
35
+ }