@donkeylabs/cli 0.4.0 → 0.4.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkeylabs/cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "description": "CLI for @donkeylabs/server - project scaffolding and code generation",
6
6
  "main": "./src/index.ts",
@@ -8,6 +8,7 @@ import { mkdir, writeFile, readFile, readdir, copyFile, stat } from "node:fs/pro
8
8
  import { join, resolve, dirname, basename } from "node:path";
9
9
  import { existsSync } from "node:fs";
10
10
  import { fileURLToPath } from "node:url";
11
+ import { spawn } from "node:child_process";
11
12
  import pc from "picocolors";
12
13
  import prompts from "prompts";
13
14
 
@@ -38,21 +39,45 @@ const RENAME_MAP: Record<string, string> = {
38
39
 
39
40
  export async function initCommand(args: string[]) {
40
41
  // Parse --type flag if provided
41
- let projectDir = ".";
42
+ let projectDir: string | null = null;
42
43
  let typeArg: string | null = null;
43
44
 
44
45
  for (let i = 0; i < args.length; i++) {
45
46
  if (args[i] === "--type" && args[i + 1]) {
46
47
  typeArg = args[i + 1];
47
48
  i++; // skip next arg
48
- } else if (!args[i].startsWith("-")) {
49
- projectDir = args[i];
49
+ } else if (!args[i]?.startsWith("-")) {
50
+ projectDir = args[i] ?? null;
50
51
  }
51
52
  }
52
53
 
53
- const targetDir = resolve(process.cwd(), projectDir);
54
+ console.log(pc.bold("\nšŸš€ Create a new @donkeylabs/server project\n"));
55
+
56
+ // If no project directory provided, prompt for it
57
+ if (!projectDir) {
58
+ const { name } = await prompts({
59
+ type: "text",
60
+ name: "name",
61
+ message: "Project name:",
62
+ initial: "my-donkeylabs-app",
63
+ validate: (value) => {
64
+ if (!value) return "Project name is required";
65
+ if (!/^[a-zA-Z0-9-_]+$/.test(value)) {
66
+ return "Project name can only contain letters, numbers, dashes, and underscores";
67
+ }
68
+ return true;
69
+ },
70
+ });
71
+
72
+ if (!name) {
73
+ console.log(pc.yellow("Cancelled."));
74
+ return;
75
+ }
54
76
 
55
- console.log(pc.bold("\nInitializing @donkeylabs/server project...\n"));
77
+ projectDir = name;
78
+ }
79
+
80
+ const targetDir = resolve(process.cwd(), projectDir!);
56
81
 
57
82
  let projectType: ProjectType;
58
83
 
@@ -130,45 +155,140 @@ export async function initCommand(args: string[]) {
130
155
  await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
131
156
  }
132
157
 
133
- // Print success message
134
- if (projectType === "server") {
135
- console.log(`
136
- ${pc.bold(pc.green("Success!"))} Server project initialized.
158
+ console.log(pc.green("\nāœ“ Project files created\n"));
137
159
 
138
- ${pc.bold("Next steps:")}
139
- 1. Install dependencies:
140
- ${pc.cyan("bun install")}
160
+ // Auto-install dependencies
161
+ console.log(pc.cyan("Installing dependencies...\n"));
162
+ const installSuccess = await runCommand("bun", ["install"], targetDir);
141
163
 
142
- 2. Start development:
143
- ${pc.cyan("bun run dev")}
144
-
145
- 3. Generate types after adding plugins:
146
- ${pc.cyan("bun run gen:types")}
147
- `);
164
+ if (!installSuccess) {
165
+ console.log(pc.yellow("\n⚠ Dependency installation failed."));
166
+ console.log(pc.dim(" Run 'bun install' manually to install dependencies.\n"));
148
167
  } else {
149
- console.log(`
150
- ${pc.bold(pc.green("Success!"))} SvelteKit project initialized.
168
+ console.log(pc.green("\nāœ“ Dependencies installed\n"));
169
+ }
151
170
 
152
- ${pc.bold("Next steps:")}
153
- 1. Install dependencies:
154
- ${pc.cyan("bun install")}
171
+ // Ask about MCP setup
172
+ const { setupMcp } = await prompts({
173
+ type: "confirm",
174
+ name: "setupMcp",
175
+ message: `Setup MCP for AI-assisted development? ${pc.dim("(Highly recommended)")}`,
176
+ initial: true,
177
+ });
178
+
179
+ if (setupMcp) {
180
+ // Ask which IDE
181
+ const { ide } = await prompts({
182
+ type: "select",
183
+ name: "ide",
184
+ message: "Which AI IDE are you using?",
185
+ choices: [
186
+ { title: "Claude Code", value: "claude", description: "Anthropic's Claude Code CLI" },
187
+ { title: "Cursor", value: "cursor", description: "Cursor AI IDE" },
188
+ { title: "Windsurf", value: "windsurf", description: "Codeium Windsurf" },
189
+ { title: "Other / Skip instructions", value: "skip" },
190
+ ],
191
+ });
155
192
 
156
- 2. Start development:
157
- ${pc.cyan("bun run dev")}
193
+ // Install @donkeylabs/mcp
194
+ console.log(pc.cyan("\nInstalling @donkeylabs/mcp...\n"));
195
+ const mcpInstallSuccess = await runCommand("bun", ["add", "-d", "@donkeylabs/mcp"], targetDir);
196
+
197
+ if (mcpInstallSuccess) {
198
+ console.log(pc.green("āœ“ Installed @donkeylabs/mcp\n"));
199
+ }
200
+
201
+ // Create .mcp.json
202
+ const mcpConfig = {
203
+ mcpServers: {
204
+ donkeylabs: {
205
+ command: "bunx",
206
+ args: ["@donkeylabs/mcp"],
207
+ cwd: "${workspaceFolder}",
208
+ },
209
+ },
210
+ };
211
+
212
+ await writeFile(join(targetDir, ".mcp.json"), JSON.stringify(mcpConfig, null, 2) + "\n");
213
+ console.log(pc.green("āœ“ Created .mcp.json\n"));
214
+
215
+ // Show IDE-specific instructions
216
+ if (ide === "claude") {
217
+ console.log(pc.cyan("Claude Code Setup:"));
218
+ console.log(pc.dim("─".repeat(40)));
219
+ console.log(`
220
+ The .mcp.json file has been created in your project.
221
+ Claude Code will automatically detect and use this configuration.
222
+
223
+ ${pc.bold("To verify:")}
224
+ 1. Open Claude Code in this project directory
225
+ 2. The MCP tools should be available automatically
226
+ 3. Try asking Claude to "list plugins" or "get project info"
227
+ `);
228
+ } else if (ide === "cursor") {
229
+ console.log(pc.cyan("Cursor Setup:"));
230
+ console.log(pc.dim("─".repeat(40)));
231
+ console.log(`
232
+ ${pc.bold("To configure Cursor:")}
233
+ 1. Open Cursor Settings (Cmd/Ctrl + ,)
234
+ 2. Search for "MCP" or "Model Context Protocol"
235
+ 3. Add the donkeylabs server from .mcp.json
236
+ 4. Restart Cursor to apply changes
237
+ `);
238
+ } else if (ide === "windsurf") {
239
+ console.log(pc.cyan("Windsurf Setup:"));
240
+ console.log(pc.dim("─".repeat(40)));
241
+ console.log(`
242
+ ${pc.bold("To configure Windsurf:")}
243
+ 1. Open Windsurf settings
244
+ 2. Navigate to AI / MCP configuration
245
+ 3. Add the donkeylabs server from .mcp.json
246
+ `);
247
+ }
248
+ }
158
249
 
159
- 3. Build for production:
160
- ${pc.cyan("bun run build")}
250
+ // Print final success message
251
+ console.log(pc.bold(pc.green("\nšŸŽ‰ Project ready!\n")));
161
252
 
162
- 4. Preview production build:
163
- ${pc.cyan("bun run preview")}
253
+ if (projectType === "server") {
254
+ console.log(`${pc.bold("Start development:")}
255
+ ${pc.cyan("cd " + (projectDir !== "." ? projectDir : ""))}
256
+ ${pc.cyan("bun run dev")}
257
+ `);
258
+ } else {
259
+ console.log(`${pc.bold("Start development:")}
260
+ ${projectDir !== "." ? pc.cyan("cd " + projectDir) + "\n " : ""}${pc.cyan("bun run dev")}
164
261
 
165
262
  ${pc.bold("Project structure:")}
166
- src/server/index.ts - Your @donkeylabs/server API
167
- src/lib/api.ts - Typed API client
168
- src/routes/ - SvelteKit pages
169
- src/hooks.server.ts - Server hooks for SSR
263
+ src/server/ - @donkeylabs/server API
264
+ src/lib/api.ts - Typed API client
265
+ src/routes/ - SvelteKit pages
170
266
  `);
171
267
  }
268
+
269
+ if (setupMcp) {
270
+ console.log(pc.dim("MCP is configured. Your AI assistant can now help you build!"));
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Run a command and return success status
276
+ */
277
+ async function runCommand(cmd: string, args: string[], cwd: string): Promise<boolean> {
278
+ return new Promise((resolve) => {
279
+ const child = spawn(cmd, args, {
280
+ stdio: "inherit",
281
+ cwd,
282
+ });
283
+
284
+ child.on("close", (code) => {
285
+ resolve(code === 0);
286
+ });
287
+
288
+ child.on("error", () => {
289
+ resolve(false);
290
+ });
291
+ });
172
292
  }
173
293
 
174
294
  /**
@@ -0,0 +1,305 @@
1
+ /**
2
+ * MCP (Model Context Protocol) setup command
3
+ *
4
+ * Sets up the @donkeylabs/mcp server for AI-assisted development
5
+ */
6
+
7
+ import { existsSync } from "node:fs";
8
+ import { readFile, writeFile } from "node:fs/promises";
9
+ import { join } from "node:path";
10
+ import { spawn } from "node:child_process";
11
+ import pc from "picocolors";
12
+ import prompts from "prompts";
13
+
14
+ interface McpConfig {
15
+ mcpServers?: Record<string, {
16
+ command: string;
17
+ args: string[];
18
+ cwd?: string;
19
+ env?: Record<string, string>;
20
+ }>;
21
+ }
22
+
23
+ async function detectPackageManager(): Promise<"bun" | "npm" | "pnpm" | "yarn"> {
24
+ if (existsSync("bun.lockb") || existsSync("bun.lock")) return "bun";
25
+ if (existsSync("pnpm-lock.yaml")) return "pnpm";
26
+ if (existsSync("yarn.lock")) return "yarn";
27
+ return "npm";
28
+ }
29
+
30
+ async function installPackage(pkg: string, dev: boolean = true): Promise<boolean> {
31
+ const pm = await detectPackageManager();
32
+
33
+ const args: string[] = [];
34
+ switch (pm) {
35
+ case "bun":
36
+ args.push("add", dev ? "-d" : "", pkg);
37
+ break;
38
+ case "pnpm":
39
+ args.push("add", dev ? "-D" : "", pkg);
40
+ break;
41
+ case "yarn":
42
+ args.push("add", dev ? "-D" : "", pkg);
43
+ break;
44
+ default:
45
+ args.push("install", dev ? "--save-dev" : "--save", pkg);
46
+ }
47
+
48
+ console.log(pc.dim(`$ ${pm} ${args.filter(Boolean).join(" ")}`));
49
+
50
+ return new Promise((resolve) => {
51
+ const child = spawn(pm, args.filter(Boolean), {
52
+ stdio: "inherit",
53
+ cwd: process.cwd(),
54
+ });
55
+
56
+ child.on("close", (code) => {
57
+ resolve(code === 0);
58
+ });
59
+
60
+ child.on("error", () => {
61
+ resolve(false);
62
+ });
63
+ });
64
+ }
65
+
66
+ async function readMcpConfig(): Promise<McpConfig> {
67
+ const configPath = join(process.cwd(), ".mcp.json");
68
+
69
+ if (!existsSync(configPath)) {
70
+ return {};
71
+ }
72
+
73
+ try {
74
+ const content = await readFile(configPath, "utf-8");
75
+ return JSON.parse(content);
76
+ } catch {
77
+ return {};
78
+ }
79
+ }
80
+
81
+ async function writeMcpConfig(config: McpConfig): Promise<void> {
82
+ const configPath = join(process.cwd(), ".mcp.json");
83
+ await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
84
+ }
85
+
86
+ async function setupClaudeCode(): Promise<void> {
87
+ console.log(pc.cyan("\nClaude Code Setup:"));
88
+ console.log(pc.dim("─".repeat(40)));
89
+ console.log(`
90
+ The .mcp.json file has been created in your project root.
91
+ Claude Code will automatically detect and use this configuration.
92
+
93
+ ${pc.bold("To verify:")}
94
+ 1. Open Claude Code in this project
95
+ 2. The MCP tools should be available automatically
96
+ 3. Try asking Claude to "list plugins" or "get project info"
97
+
98
+ ${pc.bold("Manual setup (if needed):")}
99
+ Add to your Claude Code settings:
100
+ ${pc.dim(JSON.stringify({
101
+ "mcpServers": {
102
+ "donkeylabs": {
103
+ "command": "bunx",
104
+ "args": ["@donkeylabs/mcp"],
105
+ "cwd": "${workspaceFolder}"
106
+ }
107
+ }
108
+ }, null, 2))}
109
+ `);
110
+ }
111
+
112
+ async function setupCursor(): Promise<void> {
113
+ console.log(pc.cyan("\nCursor Setup:"));
114
+ console.log(pc.dim("─".repeat(40)));
115
+ console.log(`
116
+ ${pc.bold("To configure Cursor:")}
117
+ 1. Open Cursor Settings (Cmd/Ctrl + ,)
118
+ 2. Search for "MCP" or "Model Context Protocol"
119
+ 3. Add the donkeylabs server configuration:
120
+
121
+ ${pc.dim(JSON.stringify({
122
+ "donkeylabs": {
123
+ "command": "bunx",
124
+ "args": ["@donkeylabs/mcp"],
125
+ "cwd": "${workspaceFolder}"
126
+ }
127
+ }, null, 2))}
128
+
129
+ 4. Restart Cursor to apply changes
130
+ `);
131
+ }
132
+
133
+ async function setupWindsurf(): Promise<void> {
134
+ console.log(pc.cyan("\nWindsurf Setup:"));
135
+ console.log(pc.dim("─".repeat(40)));
136
+ console.log(`
137
+ ${pc.bold("To configure Windsurf:")}
138
+ 1. Open Windsurf settings
139
+ 2. Navigate to AI / MCP configuration
140
+ 3. Add the donkeylabs server:
141
+
142
+ ${pc.dim(JSON.stringify({
143
+ "donkeylabs": {
144
+ "command": "bunx",
145
+ "args": ["@donkeylabs/mcp"],
146
+ "cwd": "${workspaceFolder}"
147
+ }
148
+ }, null, 2))}
149
+ `);
150
+ }
151
+
152
+ export async function mcpCommand(args: string[]): Promise<void> {
153
+ const subcommand = args[0];
154
+
155
+ if (!subcommand || subcommand === "setup") {
156
+ await setupMcp(args.slice(1));
157
+ } else if (subcommand === "help" || subcommand === "--help") {
158
+ printMcpHelp();
159
+ } else {
160
+ console.error(pc.red(`Unknown mcp subcommand: ${subcommand}`));
161
+ printMcpHelp();
162
+ process.exit(1);
163
+ }
164
+ }
165
+
166
+ function printMcpHelp(): void {
167
+ console.log(`
168
+ ${pc.bold("donkeylabs mcp")} - Setup MCP server for AI-assisted development
169
+
170
+ ${pc.bold("Usage:")}
171
+ donkeylabs mcp Interactive MCP setup
172
+ donkeylabs mcp setup Setup MCP (interactive)
173
+ donkeylabs mcp setup --claude Setup for Claude Code
174
+ donkeylabs mcp setup --cursor Setup for Cursor
175
+ donkeylabs mcp setup --all Setup for all IDEs
176
+
177
+ ${pc.bold("Options:")}
178
+ --claude Configure for Claude Code
179
+ --cursor Configure for Cursor
180
+ --windsurf Configure for Windsurf
181
+ --all Show setup for all IDEs
182
+ --skip-install Skip installing @donkeylabs/mcp package
183
+
184
+ ${pc.bold("What this does:")}
185
+ 1. Installs @donkeylabs/mcp as a dev dependency
186
+ 2. Creates/updates .mcp.json in your project
187
+ 3. Provides IDE-specific setup instructions
188
+
189
+ ${pc.bold("MCP Tools Available:")}
190
+ - get_project_info - View project structure and routes
191
+ - create_plugin - Create new plugins
192
+ - add_service_method - Add methods to plugin services
193
+ - add_migration - Create database migrations
194
+ - create_router - Create new routers
195
+ - add_route - Add routes to routers
196
+ - add_cron - Schedule cron jobs
197
+ - add_event - Register events
198
+ - add_async_job - Register background jobs
199
+ - generate_types - Regenerate types
200
+ - generate_client - Generate API client
201
+ `);
202
+ }
203
+
204
+ async function setupMcp(args: string[]): Promise<void> {
205
+ console.log(pc.bold("\nšŸ”§ Setting up @donkeylabs/mcp\n"));
206
+
207
+ // Check if we're in a donkeylabs project
208
+ const configPath = join(process.cwd(), "donkeylabs.config.ts");
209
+ const hasConfig = existsSync(configPath);
210
+
211
+ if (!hasConfig) {
212
+ console.log(pc.yellow("⚠ No donkeylabs.config.ts found in current directory."));
213
+ console.log(pc.dim(" The MCP server works best in a @donkeylabs/server project."));
214
+ console.log(pc.dim(" Run 'donkeylabs init' to create a new project first.\n"));
215
+
216
+ const { proceed } = await prompts({
217
+ type: "confirm",
218
+ name: "proceed",
219
+ message: "Continue anyway?",
220
+ initial: false,
221
+ });
222
+
223
+ if (!proceed) {
224
+ console.log(pc.dim("Aborted."));
225
+ return;
226
+ }
227
+ }
228
+
229
+ // Parse args for flags
230
+ const skipInstall = args.includes("--skip-install");
231
+ const forClaude = args.includes("--claude");
232
+ const forCursor = args.includes("--cursor");
233
+ const forWindsurf = args.includes("--windsurf");
234
+ const forAll = args.includes("--all");
235
+
236
+ // Install @donkeylabs/mcp if not skipped
237
+ if (!skipInstall) {
238
+ console.log(pc.cyan("Installing @donkeylabs/mcp..."));
239
+ const success = await installPackage("@donkeylabs/mcp");
240
+
241
+ if (!success) {
242
+ console.log(pc.yellow("\n⚠ Package installation failed, but continuing with config setup."));
243
+ console.log(pc.dim(" You can manually install with: bun add -d @donkeylabs/mcp\n"));
244
+ } else {
245
+ console.log(pc.green("āœ“ Installed @donkeylabs/mcp\n"));
246
+ }
247
+ }
248
+
249
+ // Create/update .mcp.json
250
+ console.log(pc.cyan("Configuring .mcp.json..."));
251
+
252
+ const config = await readMcpConfig();
253
+ config.mcpServers = config.mcpServers || {};
254
+
255
+ config.mcpServers.donkeylabs = {
256
+ command: "bunx",
257
+ args: ["@donkeylabs/mcp"],
258
+ cwd: "${workspaceFolder}",
259
+ };
260
+
261
+ await writeMcpConfig(config);
262
+ console.log(pc.green("āœ“ Created .mcp.json\n"));
263
+
264
+ // Show IDE-specific instructions
265
+ if (forAll || (!forClaude && !forCursor && !forWindsurf)) {
266
+ // Interactive mode or --all
267
+ if (!forClaude && !forCursor && !forWindsurf && !forAll) {
268
+ const { ide } = await prompts({
269
+ type: "select",
270
+ name: "ide",
271
+ message: "Which IDE are you using?",
272
+ choices: [
273
+ { title: "Claude Code", value: "claude" },
274
+ { title: "Cursor", value: "cursor" },
275
+ { title: "Windsurf", value: "windsurf" },
276
+ { title: "Show all", value: "all" },
277
+ ],
278
+ });
279
+
280
+ if (ide === "claude") await setupClaudeCode();
281
+ else if (ide === "cursor") await setupCursor();
282
+ else if (ide === "windsurf") await setupWindsurf();
283
+ else {
284
+ await setupClaudeCode();
285
+ await setupCursor();
286
+ await setupWindsurf();
287
+ }
288
+ } else {
289
+ await setupClaudeCode();
290
+ await setupCursor();
291
+ await setupWindsurf();
292
+ }
293
+ } else {
294
+ if (forClaude) await setupClaudeCode();
295
+ if (forCursor) await setupCursor();
296
+ if (forWindsurf) await setupWindsurf();
297
+ }
298
+
299
+ console.log(pc.green("\nāœ“ MCP setup complete!\n"));
300
+ console.log(pc.dim("The AI assistant can now help you with:"));
301
+ console.log(pc.dim(" - Creating plugins, routes, and handlers"));
302
+ console.log(pc.dim(" - Adding migrations and service methods"));
303
+ console.log(pc.dim(" - Setting up cron jobs and background tasks"));
304
+ console.log(pc.dim(" - Generating types and API clients\n"));
305
+ }
package/src/index.ts CHANGED
@@ -35,6 +35,7 @@ ${pc.bold("Commands:")}
35
35
  ${pc.cyan("init")} Initialize a new project
36
36
  ${pc.cyan("generate")} Generate types (registry, context, client)
37
37
  ${pc.cyan("plugin")} Plugin management
38
+ ${pc.cyan("mcp")} Setup MCP server for AI-assisted development
38
39
 
39
40
  ${pc.bold("Options:")}
40
41
  -h, --help Show this help message
@@ -95,6 +96,11 @@ async function main() {
95
96
  await pluginCommand(positionals.slice(1));
96
97
  break;
97
98
 
99
+ case "mcp":
100
+ const { mcpCommand } = await import("./commands/mcp");
101
+ await mcpCommand(positionals.slice(1));
102
+ break;
103
+
98
104
  default:
99
105
  console.error(pc.red(`Unknown command: ${command}`));
100
106
  console.log(`Run ${pc.cyan("donkeylabs --help")} for available commands.`);
@@ -4,9 +4,9 @@
4
4
  "": {
5
5
  "name": "my-sveltekit-app",
6
6
  "dependencies": {
7
- "@donkeylabs/adapter-sveltekit": "0.1.2",
8
- "@donkeylabs/cli": "0.1.1",
9
- "@donkeylabs/server": "0.3.1",
7
+ "@donkeylabs/adapter-sveltekit": "^0.4.0",
8
+ "@donkeylabs/cli": "^0.4.0",
9
+ "@donkeylabs/server": "^0.4.0",
10
10
  "bits-ui": "^2.15.4",
11
11
  "clsx": "^2.1.1",
12
12
  "kysely": "^0.27.6",
@@ -28,11 +28,11 @@
28
28
  },
29
29
  },
30
30
  "packages": {
31
- "@donkeylabs/adapter-sveltekit": ["@donkeylabs/adapter-sveltekit@0.1.2", "", { "peerDependencies": { "@donkeylabs/server": "*", "@sveltejs/kit": "^2.0.0" } }, "sha512-VvjRQ8XZQw6T+DYLjq5Sjz+u1k8LqkcZOTVZuB4SfC6TdGxGD7CkU/7NQ7gwlk/2pDNP7tqwMD3yGfP86A6j4Q=="],
31
+ "@donkeylabs/adapter-sveltekit": ["@donkeylabs/adapter-sveltekit@0.4.0", "", { "peerDependencies": { "@donkeylabs/server": "^0.4.0", "@sveltejs/kit": "^2.0.0" } }, "sha512-J1kcwhp8X0egAhjC/HyxEizR3aEP7ea08rfAh0++C31UldM3ahAjO3Ux0QYp+fAV76lh8mKrDNAWIsKTyPmqNg=="],
32
32
 
33
- "@donkeylabs/cli": ["@donkeylabs/cli@0.1.1", "", { "dependencies": { "picocolors": "^1.1.1", "prompts": "^2.4.2" }, "peerDependencies": { "@donkeylabs/server": "*" }, "bin": { "donkeylabs": "src/index.ts" } }, "sha512-lMGXz/KGCoN15siUf05Sl3NxoR5Qn0h44gXqRocHr51SLV8R/o2On2ELF4iHdTNV9hPJp3sjhNmcO3mMzQex2A=="],
33
+ "@donkeylabs/cli": ["@donkeylabs/cli@0.4.0", "", { "dependencies": { "picocolors": "^1.1.1", "prompts": "^2.4.2" }, "peerDependencies": { "@donkeylabs/server": "^0.4.0" }, "bin": { "donkeylabs": "src/index.ts" } }, "sha512-5Y5oXOzFrhdPMAprij5d/D1r9tcjDRuZo1Ud3zSkGTvVxfejMQjbzrB8KmxFM4+7TBJdjuduvhNewL5V6XnUlA=="],
34
34
 
35
- "@donkeylabs/server": ["@donkeylabs/server@0.3.1", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.25.2", "picocolors": "^1.1.1", "prompts": "^2.4.2" }, "peerDependencies": { "kysely": "^0.27.0 || ^0.28.0", "typescript": "^5", "zod": "^3.20.0" }, "bin": { "donkeylabs-mcp": "mcp/server.ts" } }, "sha512-rpYKD7dKNwpcw51aO/pL1Zh1r6FlAqp0UIUJ+9nV1Cm/4hKq6WQHM/AtmPhM25ZRvnSbriB4X8o28UVJR8dN2w=="],
35
+ "@donkeylabs/server": ["@donkeylabs/server@0.4.0", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.25.2", "picocolors": "^1.1.1", "prompts": "^2.4.2" }, "peerDependencies": { "kysely": "^0.27.0 || ^0.28.0", "typescript": "^5", "zod": "^3.20.0" } }, "sha512-Nr7kgKtyqM5EDFTmUL2kyEqCW/NeJiQgYQL4Y3DnphM9rgHz07Qi2mgPs8McYFvrWGUVFRMvzXa6CZILY896bw=="],
36
36
 
37
37
  "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="],
38
38
 
@@ -24,9 +24,9 @@
24
24
  "vite": "^7.2.6"
25
25
  },
26
26
  "dependencies": {
27
- "@donkeylabs/cli": "0.1.1",
28
- "@donkeylabs/adapter-sveltekit": "0.1.2",
29
- "@donkeylabs/server": "0.3.2",
27
+ "@donkeylabs/cli": "^0.4.2",
28
+ "@donkeylabs/adapter-sveltekit": "^0.4.2",
29
+ "@donkeylabs/server": "^0.4.2",
30
30
  "bits-ui": "^2.15.4",
31
31
  "clsx": "^2.1.1",
32
32
  "kysely": "^0.27.6",
@@ -27,101 +27,112 @@ export { type ServerContext as AppContext } from "@donkeylabs/server";
27
27
  // ============================================
28
28
 
29
29
  export namespace Routes {
30
- export namespace Counter {
31
- export namespace get {
32
- export type Input = Expand<Record<string, never>>;
33
- export type Output = Expand<{
30
+ export namespace Api {
31
+ export namespace Counter {
32
+ export namespace Get {
33
+ export type Input = Expand<Record<string, never>>;
34
+ export type Output = Expand<{
34
35
  count: number;
35
36
  }>;
36
- }
37
+ }
38
+ export type Get = { Input: Get.Input; Output: Get.Output };
37
39
 
38
- export namespace increment {
39
- export type Input = Expand<Record<string, never>>;
40
- export type Output = Expand<{
40
+ export namespace Increment {
41
+ export type Input = Expand<Record<string, never>>;
42
+ export type Output = Expand<{
41
43
  count: number;
42
44
  }>;
43
- }
45
+ }
46
+ export type Increment = { Input: Increment.Input; Output: Increment.Output };
44
47
 
45
- export namespace decrement {
46
- export type Input = Expand<Record<string, never>>;
47
- export type Output = Expand<{
48
+ export namespace Decrement {
49
+ export type Input = Expand<Record<string, never>>;
50
+ export type Output = Expand<{
48
51
  count: number;
49
52
  }>;
50
- }
53
+ }
54
+ export type Decrement = { Input: Decrement.Input; Output: Decrement.Output };
51
55
 
52
- export namespace reset {
53
- export type Input = Expand<Record<string, never>>;
54
- export type Output = Expand<{
56
+ export namespace Reset {
57
+ export type Input = Expand<Record<string, never>>;
58
+ export type Output = Expand<{
55
59
  count: number;
56
60
  }>;
61
+ }
62
+ export type Reset = { Input: Reset.Input; Output: Reset.Output };
57
63
  }
58
- }
59
64
 
60
- export namespace Cache {
61
- export namespace set {
62
- export type Input = Expand<{
65
+ export namespace Cache {
66
+ export namespace Set {
67
+ export type Input = Expand<{
63
68
  key: string;
64
69
  value: any;
65
70
  ttl?: number;
66
71
  }>;
67
- export type Output = Expand<{
72
+ export type Output = Expand<{
68
73
  success: boolean;
69
74
  }>;
70
- }
75
+ }
76
+ export type Set = { Input: Set.Input; Output: Set.Output };
71
77
 
72
- export namespace get {
73
- export type Input = Expand<{
78
+ export namespace Get {
79
+ export type Input = Expand<{
74
80
  key: string;
75
81
  }>;
76
- export type Output = Expand<{
82
+ export type Output = Expand<{
77
83
  value?: any;
78
84
  exists: boolean;
79
85
  }>;
80
- }
86
+ }
87
+ export type Get = { Input: Get.Input; Output: Get.Output };
81
88
 
82
- export namespace delete {
83
- export type Input = Expand<{
89
+ export namespace Delete {
90
+ export type Input = Expand<{
84
91
  key: string;
85
92
  }>;
86
- export type Output = Expand<{
93
+ export type Output = Expand<{
87
94
  success: boolean;
88
95
  }>;
89
- }
96
+ }
97
+ export type Delete = { Input: Delete.Input; Output: Delete.Output };
90
98
 
91
- export namespace keys {
92
- export type Input = Expand<Record<string, never>>;
93
- export type Output = Expand<{
99
+ export namespace Keys {
100
+ export type Input = Expand<Record<string, never>>;
101
+ export type Output = Expand<{
94
102
  keys: string[];
95
103
  }>;
104
+ }
105
+ export type Keys = { Input: Keys.Input; Output: Keys.Output };
96
106
  }
97
- }
98
107
 
99
- export namespace Jobs {
100
- export namespace enqueue {
101
- export type Input = Expand<{
108
+ export namespace Jobs {
109
+ export namespace Enqueue {
110
+ export type Input = Expand<{
102
111
  name: string;
103
112
  data: any;
104
113
  delay?: number;
105
114
  }>;
106
- export type Output = Expand<{
115
+ export type Output = Expand<{
107
116
  jobId: string;
108
117
  }>;
109
- }
118
+ }
119
+ export type Enqueue = { Input: Enqueue.Input; Output: Enqueue.Output };
110
120
 
111
- export namespace stats {
112
- export type Input = Expand<Record<string, never>>;
113
- export type Output = Expand<{
121
+ export namespace Stats {
122
+ export type Input = Expand<Record<string, never>>;
123
+ export type Output = Expand<{
114
124
  pending: number;
115
125
  running: number;
116
126
  completed: number;
117
127
  }>;
128
+ }
129
+ export type Stats = { Input: Stats.Input; Output: Stats.Output };
118
130
  }
119
- }
120
131
 
121
- export namespace Cron {
122
- export namespace list {
123
- export type Input = Expand<Record<string, never>>;
124
- export type Output = Expand<{
132
+ export namespace Cron {
133
+ export namespace List {
134
+ export type Input = Expand<Record<string, never>>;
135
+ export type Output = Expand<{
125
136
  tasks: {
126
137
  id: string;
127
138
  name: string;
@@ -131,64 +142,71 @@ export namespace Routes {
131
142
  nextRun?: string;
132
143
  }[];
133
144
  }>;
145
+ }
146
+ export type List = { Input: List.Input; Output: List.Output };
134
147
  }
135
- }
136
148
 
137
- export namespace Ratelimit {
138
- export namespace check {
139
- export type Input = Expand<{
149
+ export namespace Ratelimit {
150
+ export namespace Check {
151
+ export type Input = Expand<{
140
152
  key: string;
141
153
  limit: number;
142
154
  window: number;
143
155
  }>;
144
- export type Output = Expand<{
156
+ export type Output = Expand<{
145
157
  allowed: boolean;
146
158
  remaining: number;
147
159
  resetAt: Date;
148
160
  }>;
149
- }
161
+ }
162
+ export type Check = { Input: Check.Input; Output: Check.Output };
150
163
 
151
- export namespace reset {
152
- export type Input = Expand<{
164
+ export namespace Reset {
165
+ export type Input = Expand<{
153
166
  key: string;
154
167
  }>;
155
- export type Output = Expand<{
168
+ export type Output = Expand<{
156
169
  success: boolean;
157
170
  }>;
171
+ }
172
+ export type Reset = { Input: Reset.Input; Output: Reset.Output };
158
173
  }
159
- }
160
174
 
161
- export namespace Events {
162
- export namespace emit {
163
- export type Input = Expand<{
175
+ export namespace Events {
176
+ export namespace Emit {
177
+ export type Input = Expand<{
164
178
  event: string;
165
179
  data: any;
166
180
  }>;
167
- export type Output = Expand<{
181
+ export type Output = Expand<{
168
182
  success: boolean;
169
183
  }>;
184
+ }
185
+ export type Emit = { Input: Emit.Input; Output: Emit.Output };
170
186
  }
171
- }
172
187
 
173
- export namespace Sse {
174
- export namespace broadcast {
175
- export type Input = Expand<{
188
+ export namespace Sse {
189
+ export namespace Broadcast {
190
+ export type Input = Expand<{
176
191
  channel: string;
177
192
  event: string;
178
193
  data: any;
179
194
  }>;
180
- export type Output = Expand<{
195
+ export type Output = Expand<{
181
196
  success: boolean;
182
197
  recipients: number;
183
198
  }>;
184
- }
199
+ }
200
+ export type Broadcast = { Input: Broadcast.Input; Output: Broadcast.Output };
185
201
 
186
- export namespace clients {
187
- export type Input = Expand<Record<string, never>>;
188
- export type Output = Expand<{
202
+ export namespace Clients {
203
+ export type Input = Expand<Record<string, never>>;
204
+ export type Output = Expand<{
189
205
  total: number;
190
206
  byChannel: number;
191
207
  }>;
208
+ }
209
+ export type Clients = { Input: Clients.Input; Output: Clients.Output };
192
210
  }
193
211
  }
194
212
  }
@@ -202,41 +220,37 @@ export class ApiClient extends UnifiedApiClientBase {
202
220
  super(options);
203
221
  }
204
222
 
205
- counter = {
206
- get: (input: Routes.Counter.get.Input): Promise<Routes.Counter.get.Output> => this.request("api.counter.get", input),
207
- increment: (input: Routes.Counter.increment.Input): Promise<Routes.Counter.increment.Output> => this.request("api.counter.increment", input),
208
- decrement: (input: Routes.Counter.decrement.Input): Promise<Routes.Counter.decrement.Output> => this.request("api.counter.decrement", input),
209
- reset: (input: Routes.Counter.reset.Input): Promise<Routes.Counter.reset.Output> => this.request("api.counter.reset", input)
210
- };
211
-
212
- cache = {
213
- set: (input: Routes.Cache.set.Input): Promise<Routes.Cache.set.Output> => this.request("api.cache.set", input),
214
- get: (input: Routes.Cache.get.Input): Promise<Routes.Cache.get.Output> => this.request("api.cache.get", input),
215
- delete: (input: Routes.Cache.delete.Input): Promise<Routes.Cache.delete.Output> => this.request("api.cache.delete", input),
216
- keys: (input: Routes.Cache.keys.Input): Promise<Routes.Cache.keys.Output> => this.request("api.cache.keys", input)
217
- };
218
-
219
- jobs = {
220
- enqueue: (input: Routes.Jobs.enqueue.Input): Promise<Routes.Jobs.enqueue.Output> => this.request("api.jobs.enqueue", input),
221
- stats: (input: Routes.Jobs.stats.Input): Promise<Routes.Jobs.stats.Output> => this.request("api.jobs.stats", input)
222
- };
223
-
224
- cron = {
225
- list: (input: Routes.Cron.list.Input): Promise<Routes.Cron.list.Output> => this.request("api.cron.list", input)
226
- };
227
-
228
- ratelimit = {
229
- check: (input: Routes.Ratelimit.check.Input): Promise<Routes.Ratelimit.check.Output> => this.request("api.ratelimit.check", input),
230
- reset: (input: Routes.Ratelimit.reset.Input): Promise<Routes.Ratelimit.reset.Output> => this.request("api.ratelimit.reset", input)
231
- };
232
-
233
- events = {
234
- emit: (input: Routes.Events.emit.Input): Promise<Routes.Events.emit.Output> => this.request("api.events.emit", input)
235
- };
236
-
237
- sse = {
238
- broadcast: (input: Routes.Sse.broadcast.Input): Promise<Routes.Sse.broadcast.Output> => this.request("api.sse.broadcast", input),
239
- clients: (input: Routes.Sse.clients.Input): Promise<Routes.Sse.clients.Output> => this.request("api.sse.clients", input)
223
+ api = {
224
+ counter: {
225
+ get: (input: Routes.Api.Counter.Get.Input): Promise<Routes.Api.Counter.Get.Output> => this.request("api.counter.get", input),
226
+ increment: (input: Routes.Api.Counter.Increment.Input): Promise<Routes.Api.Counter.Increment.Output> => this.request("api.counter.increment", input),
227
+ decrement: (input: Routes.Api.Counter.Decrement.Input): Promise<Routes.Api.Counter.Decrement.Output> => this.request("api.counter.decrement", input),
228
+ reset: (input: Routes.Api.Counter.Reset.Input): Promise<Routes.Api.Counter.Reset.Output> => this.request("api.counter.reset", input)
229
+ },
230
+ cache: {
231
+ set: (input: Routes.Api.Cache.Set.Input): Promise<Routes.Api.Cache.Set.Output> => this.request("api.cache.set", input),
232
+ get: (input: Routes.Api.Cache.Get.Input): Promise<Routes.Api.Cache.Get.Output> => this.request("api.cache.get", input),
233
+ delete: (input: Routes.Api.Cache.Delete.Input): Promise<Routes.Api.Cache.Delete.Output> => this.request("api.cache.delete", input),
234
+ keys: (input: Routes.Api.Cache.Keys.Input): Promise<Routes.Api.Cache.Keys.Output> => this.request("api.cache.keys", input)
235
+ },
236
+ jobs: {
237
+ enqueue: (input: Routes.Api.Jobs.Enqueue.Input): Promise<Routes.Api.Jobs.Enqueue.Output> => this.request("api.jobs.enqueue", input),
238
+ stats: (input: Routes.Api.Jobs.Stats.Input): Promise<Routes.Api.Jobs.Stats.Output> => this.request("api.jobs.stats", input)
239
+ },
240
+ cron: {
241
+ list: (input: Routes.Api.Cron.List.Input): Promise<Routes.Api.Cron.List.Output> => this.request("api.cron.list", input)
242
+ },
243
+ ratelimit: {
244
+ check: (input: Routes.Api.Ratelimit.Check.Input): Promise<Routes.Api.Ratelimit.Check.Output> => this.request("api.ratelimit.check", input),
245
+ reset: (input: Routes.Api.Ratelimit.Reset.Input): Promise<Routes.Api.Ratelimit.Reset.Output> => this.request("api.ratelimit.reset", input)
246
+ },
247
+ events: {
248
+ emit: (input: Routes.Api.Events.Emit.Input): Promise<Routes.Api.Events.Emit.Output> => this.request("api.events.emit", input)
249
+ },
250
+ sse: {
251
+ broadcast: (input: Routes.Api.Sse.Broadcast.Input): Promise<Routes.Api.Sse.Broadcast.Output> => this.request("api.sse.broadcast", input),
252
+ clients: (input: Routes.Api.Sse.Clients.Input): Promise<Routes.Api.Sse.Clients.Output> => this.request("api.sse.clients", input)
253
+ }
240
254
  };
241
255
  }
242
256
 
@@ -8,7 +8,7 @@ export const load: PageServerLoad = async ({ locals }) => {
8
8
 
9
9
  try {
10
10
  // Direct service call through typed client
11
- const result = await client.counter.get({});
11
+ const result = await client.api.counter.get({});
12
12
  return {
13
13
  count: result.count,
14
14
  loadedAt: new Date().toISOString(),
@@ -72,37 +72,37 @@
72
72
  ) {
73
73
  counterLoading = true;
74
74
 
75
- const result = await client.counter[action]({});
75
+ const result = await client.api.counter[action]({});
76
76
  count = result.count;
77
77
  counterLoading = false;
78
78
  }
79
79
 
80
80
  // Cache actions - using typed client
81
81
  async function cacheSet() {
82
- await client.cache.set({ key: cacheKey, value: cacheValue, ttl: cacheTTL });
82
+ await client.api.cache.set({ key: cacheKey, value: cacheValue, ttl: cacheTTL });
83
83
  cacheResult = { action: "set", success: true };
84
84
  refreshCacheKeys();
85
85
  }
86
86
 
87
87
  async function cacheGet() {
88
- cacheResult = await client.cache.get({ key: cacheKey });
88
+ cacheResult = await client.api.cache.get({ key: cacheKey });
89
89
  refreshCacheKeys();
90
90
  }
91
91
 
92
92
  async function cacheDelete() {
93
- await client.cache.delete({ key: cacheKey });
93
+ await client.api.cache.delete({ key: cacheKey });
94
94
  cacheResult = { action: "deleted", success: true };
95
95
  refreshCacheKeys();
96
96
  }
97
97
 
98
98
  async function refreshCacheKeys() {
99
- const result = await client.cache.keys({});
99
+ const result = await client.api.cache.keys({});
100
100
  cacheKeys = result.keys || [];
101
101
  }
102
102
 
103
103
  // Jobs actions - using typed client
104
104
  async function enqueueJob() {
105
- const result = (await client.jobs.enqueue({
105
+ const result = (await client.api.jobs.enqueue({
106
106
  name: "demo-job",
107
107
  data: { message: jobMessage },
108
108
  delay: jobDelay > 0 ? jobDelay : undefined,
@@ -112,7 +112,7 @@
112
112
  }
113
113
 
114
114
  async function refreshJobStats() {
115
- jobStats = (await client.jobs.stats({})) as {
115
+ jobStats = (await client.api.jobs.stats({})) as {
116
116
  pending: number;
117
117
  running: number;
118
118
  completed: number;
@@ -121,7 +121,7 @@
121
121
 
122
122
  // Rate limiter actions - using typed client
123
123
  async function checkRateLimit() {
124
- rateLimitResult = await client.ratelimit.check({
124
+ rateLimitResult = await client.api.ratelimit.check({
125
125
  key: rateLimitKey,
126
126
  limit: rateLimitMax,
127
127
  window: rateLimitWindow,
@@ -129,13 +129,13 @@
129
129
  }
130
130
 
131
131
  async function resetRateLimit() {
132
- await client.ratelimit.reset({ key: rateLimitKey });
132
+ await client.api.ratelimit.reset({ key: rateLimitKey });
133
133
  rateLimitResult = { reset: true, message: "Rate limit reset" };
134
134
  }
135
135
 
136
136
  // Cron actions - using typed client
137
137
  async function refreshCronTasks() {
138
- const result = (await client.cron.list({})) as { tasks: CronTask[] };
138
+ const result = (await client.api.cron.list({})) as { tasks: CronTask[] };
139
139
  cronTasks = result.tasks;
140
140
  }
141
141
 
@@ -143,7 +143,7 @@
143
143
  async function emitEvent() {
144
144
  try {
145
145
  const parsedData = JSON.parse(eventData);
146
- await client.events.emit({ event: eventName, data: parsedData });
146
+ await client.api.events.emit({ event: eventName, data: parsedData });
147
147
  } catch (e) {
148
148
  console.error("Invalid JSON:", e);
149
149
  }
@@ -151,7 +151,7 @@
151
151
 
152
152
  // SSE actions - using typed client
153
153
  async function manualBroadcast() {
154
- await client.sseRoutes.broadcast({
154
+ await client.api.sse.broadcast({
155
155
  channel: "events",
156
156
  event: "manual",
157
157
  data: {
@@ -164,7 +164,7 @@
164
164
  }
165
165
 
166
166
  async function refreshSSEClients() {
167
- sseClients = (await client.sseRoutes.clients({})) as {
167
+ sseClients = (await client.api.sse.clients({})) as {
168
168
  total: number;
169
169
  byChannel: number;
170
170
  };