@donkeylabs/cli 0.4.1 → 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.1",
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.`);
@@ -24,9 +24,9 @@
24
24
  "vite": "^7.2.6"
25
25
  },
26
26
  "dependencies": {
27
- "@donkeylabs/cli": "^0.4.0",
28
- "@donkeylabs/adapter-sveltekit": "^0.4.0",
29
- "@donkeylabs/server": "^0.4.0",
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",