composter-cli 1.0.6 → 1.0.8

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
@@ -314,48 +314,46 @@ npm dependencies are tracked with versions from your `package.json`:
314
314
 
315
315
  ## 🤖 AI Integration (MCP)
316
316
 
317
- Composter includes a built-in Model Context Protocol (MCP) server that lets AI assistants interact with your vault directly.
317
+ Let AI assistants like **Claude**, **Cursor**, and **GitHub Copilot** access your vault via MCP.
318
318
 
319
- ### Quick Setup
319
+ ### One-Command Setup
320
320
 
321
- ```bash
322
- # One command to configure your AI assistant
323
- npx composter-cli@latest mcp init --client claude
324
- ```
325
-
326
- **Supported clients:**
327
- - `claude` — Claude Desktop
328
- - `cursor` — Cursor IDE
329
- - `vscode` — VS Code with Copilot
330
- - `windsurf` — Windsurf IDE
331
-
332
- ### MCP Commands
333
-
334
- | Command | Description |
335
- |---------|-------------|
336
- | `composter mcp init --client <name>` | Configure MCP for an AI assistant |
337
- | `composter mcp serve` | Start MCP server directly (testing) |
338
- | `composter mcp info` | Show MCP configuration info |
339
-
340
- ### Options
321
+ Login once, then auto-configure your IDE/assistant:
341
322
 
342
323
  ```bash
343
- # Configure for Claude Desktop
344
- composter mcp init --client claude
324
+ # Login (if you haven't)
325
+ composter login
345
326
 
346
- # Configure for Cursor
347
- composter mcp init --client cursor
327
+ # Auto-configure (pick one)
328
+ npx composter-mcp init claude # Claude Desktop
329
+ npx composter-mcp init cursor # Cursor
330
+ npx composter-mcp init vscode # VS Code (Copilot)
331
+ npx composter-mcp init windsurf # Windsurf
332
+ ```
348
333
 
349
- # Use localhost backend (development)
350
- composter mcp init --client claude --dev
334
+ Then restart your AI assistant — done!
351
335
 
352
- # Show manual config instructions
353
- composter mcp info
336
+ #### Manual config (if you prefer)
337
+ ```json
338
+ {
339
+ "mcpServers": {
340
+ "composter": {
341
+ "command": "npx",
342
+ "args": ["composter-mcp"]
343
+ }
344
+ }
345
+ }
354
346
  ```
355
347
 
356
- ### Available AI Tools
348
+ Config file locations:
349
+ - Cursor: `.cursor/mcp.json` (project root)
350
+ - VS Code: `.vscode/mcp.json` (project root)
351
+ - Claude Desktop (Linux): `~/.config/claude/claude_desktop_config.json`
352
+ - Claude Desktop (macOS): `~/Library/Application Support/Claude/claude_desktop_config.json`
353
+ - Claude Desktop (Windows): `%APPDATA%\\Claude\\claude_desktop_config.json`
354
+ - Windsurf: `~/.codeium/windsurf/mcp_config.json`
357
355
 
358
- Once configured, your AI assistant can use these tools:
356
+ ### Available AI Tools
359
357
 
360
358
  | Tool | Description |
361
359
  |------|-------------|
@@ -364,25 +362,13 @@ Once configured, your AI assistant can use these tools:
364
362
  | `list_components` | List components in a category |
365
363
  | `read_component` | Read full source code of a component |
366
364
 
367
- ### Example AI Prompts
365
+ ### Example Prompts
368
366
 
369
- After setup, you can ask your AI:
370
367
  - *"Search my Composter vault for button components"*
371
368
  - *"What categories do I have in Composter?"*
372
369
  - *"Read the DataTable component from my ui category"*
373
- - *"List all components in my hooks category"*
374
370
 
375
- ### Development Mode
376
-
377
- For local development with `localhost:3000` backend:
378
-
379
- ```bash
380
- # Initialize with dev flag
381
- composter mcp init --client claude --dev
382
-
383
- # Or set environment variable
384
- COMPOSTER_DEV=true composter mcp serve
385
- ```
371
+ 📖 [Full MCP Documentation](https://www.npmjs.com/package/composter-mcp)
386
372
 
387
373
  ---
388
374
 
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "composter-cli",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "type": "module",
5
- "description": "Your personal vault for React components. Push, pull, and sync reusable components across projects — like shadcn/ui but for YOUR code. Includes MCP server for AI assistants.",
5
+ "description": "Your personal vault for React components. Push, pull, and sync reusable components across projects — like shadcn/ui but for YOUR code.",
6
6
  "main": "src/index.js",
7
7
  "bin": {
8
8
  "composter": "./bin/composter.js"
@@ -20,13 +20,7 @@
20
20
  "component-library",
21
21
  "code-sharing",
22
22
  "developer-tools",
23
- "productivity",
24
- "mcp",
25
- "ai-tools",
26
- "claude",
27
- "cursor",
28
- "copilot",
29
- "model-context-protocol"
23
+ "productivity"
30
24
  ],
31
25
  "author": "binit2-1",
32
26
  "license": "MIT",
@@ -42,11 +36,9 @@
42
36
  "node": ">=18.0.0"
43
37
  },
44
38
  "dependencies": {
45
- "@modelcontextprotocol/sdk": "^1.23.0",
46
39
  "commander": "^14.0.2",
47
40
  "dotenv": "^16.4.5",
48
41
  "inquirer": "^13.0.1",
49
- "node-fetch": "^3.3.2",
50
- "zod": "^3.23.0"
42
+ "node-fetch": "^3.3.2"
51
43
  }
52
44
  }
package/src/index.js CHANGED
@@ -6,7 +6,6 @@ import { mkcat } from "./commands/mkcat.js";
6
6
  import { listCategories } from "./commands/listCat.js";
7
7
  import { pushComponent } from "./commands/push.js";
8
8
  import { pullComponent } from "./commands/pull.js";
9
- import { mcpInit, mcpServe, mcpInfo } from "./commands/mcp.js";
10
9
  import { createRequire } from "module";
11
10
 
12
11
  const require = createRequire(import.meta.url);
@@ -50,34 +49,4 @@ program
50
49
  pullComponent(category, title, filepath);
51
50
  });
52
51
 
53
- // MCP Commands
54
- const mcp = program
55
- .command("mcp")
56
- .description("Manage MCP (Model Context Protocol) server for AI assistants");
57
-
58
- mcp
59
- .command("init")
60
- .description("Initialize MCP server for an AI assistant")
61
- .option("-c, --client <client>", "AI client to configure (claude, cursor, vscode, windsurf)", "claude")
62
- .option("-g, --global", "Configure globally instead of project-specific")
63
- .option("-d, --dev", "Use localhost:3000 backend (development mode)")
64
- .action((options) => {
65
- mcpInit(options.client, options);
66
- });
67
-
68
- mcp
69
- .command("serve")
70
- .description("Start the MCP server directly (for testing)")
71
- .option("-d, --dev", "Use localhost:3000 backend (development mode)")
72
- .action((options) => {
73
- mcpServe(options);
74
- });
75
-
76
- mcp
77
- .command("info")
78
- .description("Show MCP configuration information")
79
- .action(() => {
80
- mcpInfo();
81
- });
82
-
83
52
  program.parse(process.argv);
@@ -1,238 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import os from "os";
4
- import { loadSession } from "../utils/session.js";
5
- import { fileURLToPath } from "url";
6
-
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = path.dirname(__filename);
9
-
10
- // Get the path to the MCP server script
11
- function getMcpServerPath() {
12
- return path.resolve(__dirname, "../mcp/server.js");
13
- }
14
-
15
- // Client configuration templates
16
- const CLIENT_CONFIGS = {
17
- claude: {
18
- name: "Claude Desktop",
19
- configPath: () => {
20
- if (process.platform === "darwin") {
21
- return path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
22
- } else if (process.platform === "win32") {
23
- return path.join(process.env.APPDATA, "Claude", "claude_desktop_config.json");
24
- } else {
25
- return path.join(os.homedir(), ".config", "claude", "claude_desktop_config.json");
26
- }
27
- },
28
- generateConfig: (serverPath, cwd) => ({
29
- mcpServers: {
30
- composter: {
31
- command: "node",
32
- args: [serverPath],
33
- cwd: cwd,
34
- },
35
- },
36
- }),
37
- },
38
- cursor: {
39
- name: "Cursor",
40
- configPath: () => {
41
- return path.join(process.cwd(), ".cursor", "mcp.json");
42
- },
43
- generateConfig: (serverPath, cwd) => ({
44
- composter: {
45
- command: "node",
46
- args: [serverPath],
47
- cwd: cwd,
48
- },
49
- }),
50
- },
51
- vscode: {
52
- name: "VS Code (Copilot)",
53
- configPath: () => {
54
- return path.join(process.cwd(), ".vscode", "mcp.json");
55
- },
56
- generateConfig: (serverPath, cwd) => ({
57
- servers: {
58
- composter: {
59
- command: "node",
60
- args: [serverPath],
61
- cwd: cwd,
62
- },
63
- },
64
- }),
65
- },
66
- windsurf: {
67
- name: "Windsurf",
68
- configPath: () => {
69
- return path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json");
70
- },
71
- generateConfig: (serverPath, cwd) => ({
72
- mcpServers: {
73
- composter: {
74
- command: "node",
75
- args: [serverPath],
76
- cwd: cwd,
77
- },
78
- },
79
- }),
80
- },
81
- };
82
-
83
- // Initialize MCP for a specific client
84
- export async function mcpInit(client, options = {}) {
85
- // Validate client
86
- const clientConfig = CLIENT_CONFIGS[client?.toLowerCase()];
87
- if (!clientConfig) {
88
- console.log(`❌ Unknown client: ${client}`);
89
- console.log(`\nSupported clients:`);
90
- Object.entries(CLIENT_CONFIGS).forEach(([key, val]) => {
91
- console.log(` - ${key} (${val.name})`);
92
- });
93
- return;
94
- }
95
-
96
- // Check if user is logged in
97
- const session = loadSession();
98
- if (!session?.jwt) {
99
- console.log("❌ You must be logged in first. Run: composter login");
100
- return;
101
- }
102
-
103
- const serverPath = getMcpServerPath();
104
- const cwd = options.global ? path.dirname(serverPath) : process.cwd();
105
- const configPath = clientConfig.configPath();
106
- const configDir = path.dirname(configPath);
107
-
108
- console.log(`\n🔧 Setting up Composter MCP for ${clientConfig.name}...\n`);
109
-
110
- // Generate the config snippet
111
- const newConfig = clientConfig.generateConfig(serverPath, cwd);
112
-
113
- // Check if config file exists
114
- let existingConfig = {};
115
- if (fs.existsSync(configPath)) {
116
- try {
117
- existingConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
118
- console.log(`📄 Found existing config at: ${configPath}`);
119
- } catch {
120
- console.log(`⚠️ Existing config is invalid, will create new one.`);
121
- }
122
- }
123
-
124
- // Merge configs
125
- let mergedConfig;
126
- if (client === "claude" || client === "windsurf") {
127
- mergedConfig = {
128
- ...existingConfig,
129
- mcpServers: {
130
- ...existingConfig.mcpServers,
131
- ...newConfig.mcpServers,
132
- },
133
- };
134
- } else if (client === "vscode") {
135
- mergedConfig = {
136
- ...existingConfig,
137
- servers: {
138
- ...existingConfig.servers,
139
- ...newConfig.servers,
140
- },
141
- };
142
- } else {
143
- // Cursor - direct merge
144
- mergedConfig = {
145
- ...existingConfig,
146
- ...newConfig,
147
- };
148
- }
149
-
150
- // Create directory if needed
151
- if (!fs.existsSync(configDir)) {
152
- fs.mkdirSync(configDir, { recursive: true });
153
- console.log(`📁 Created directory: ${configDir}`);
154
- }
155
-
156
- // Write config
157
- fs.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2), "utf-8");
158
- console.log(`✅ Config written to: ${configPath}`);
159
-
160
- // Print success message
161
- console.log(`
162
- ╔════════════════════════════════════════════════════════════════╗
163
- ║ 🎉 MCP Setup Complete! ║
164
- ╠════════════════════════════════════════════════════════════════╣
165
- ║ ║
166
- ║ Composter MCP server has been configured for ${clientConfig.name.padEnd(14)} ║
167
- ║ ║
168
- ║ Next steps: ║
169
- ║ 1. Restart ${clientConfig.name.padEnd(50)} ║
170
- ║ 2. Look for "Composter" in your MCP tools ║
171
- ║ ║
172
- ║ Available tools: ║
173
- ║ • search_components - Search your component vault ║
174
- ║ • list_categories - List all your categories ║
175
- ║ • list_components - List components in a category ║
176
- ║ • read_component - Read full source code ║
177
- ║ ║
178
- ╚════════════════════════════════════════════════════════════════╝
179
- `);
180
-
181
- // Dev mode hint
182
- if (options.dev) {
183
- console.log(`\n💡 Dev mode: Set COMPOSTER_DEV=true to use localhost:3000`);
184
- }
185
- }
186
-
187
- // Serve MCP server directly (for testing)
188
- export async function mcpServe(options = {}) {
189
- const session = loadSession();
190
- if (!session?.jwt) {
191
- console.log("❌ You must be logged in first. Run: composter login");
192
- return;
193
- }
194
-
195
- console.log("🚀 Starting Composter MCP server...");
196
- console.log(" Press Ctrl+C to stop\n");
197
-
198
- // Set dev mode if requested
199
- if (options.dev) {
200
- process.env.COMPOSTER_DEV = "true";
201
- console.log("📡 Dev mode: Using localhost:3000");
202
- }
203
-
204
- // Dynamic import and run the server
205
- const serverPath = getMcpServerPath();
206
- await import(serverPath);
207
- }
208
-
209
- // Show MCP configuration info
210
- export function mcpInfo() {
211
- const serverPath = getMcpServerPath();
212
- const session = loadSession();
213
-
214
- console.log(`
215
- ╔════════════════════════════════════════════════════════════════╗
216
- ║ Composter MCP Information ║
217
- ╠════════════════════════════════════════════════════════════════╣
218
-
219
- Server path: ${serverPath}
220
- Logged in: ${session?.jwt ? "✅ Yes" : "❌ No (run 'composter login')"}
221
-
222
- Manual configuration:
223
-
224
- {
225
- "composter": {
226
- "command": "node",
227
- "args": ["${serverPath}"]
228
- }
229
- }
230
-
231
- Environment variables:
232
- • COMPOSTER_DEV=true - Use localhost:3000 backend
233
- • COMPOSTER_API_URL=... - Custom API URL
234
-
235
- ╚════════════════════════════════════════════════════════════════╝
236
- `);
237
- }
238
-
package/src/mcp/server.js DELETED
@@ -1,289 +0,0 @@
1
- #!/usr/bin/env node
2
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
- import { z } from "zod";
5
- import { loadSession } from "../utils/session.js";
6
- import fetch from "node-fetch";
7
-
8
- // Redirect console.log to stderr (MCP uses stdout for protocol)
9
- const originalLog = console.log;
10
- console.log = (...args) => console.error(...args);
11
-
12
- // Determine base URL - supports both dev and production
13
- function getBaseUrl() {
14
- // Check for explicit env var first
15
- if (process.env.COMPOSTER_API_URL) {
16
- return process.env.COMPOSTER_API_URL;
17
- }
18
- // Check for dev mode
19
- if (process.env.COMPOSTER_DEV === "true" || process.env.NODE_ENV === "development") {
20
- return "http://localhost:3000/api";
21
- }
22
- // Default to production
23
- return "https://composter.onrender.com/api";
24
- }
25
-
26
- const BASE_URL = getBaseUrl();
27
-
28
- // API request helper with JWT auth
29
- async function apiRequest(path, options = {}) {
30
- const session = loadSession();
31
-
32
- if (!session?.jwt) {
33
- throw new Error("Not authenticated. Run 'composter login' first.");
34
- }
35
-
36
- const headers = {
37
- "Content-Type": "application/json",
38
- "Authorization": `Bearer ${session.jwt}`,
39
- ...options.headers,
40
- };
41
-
42
- const res = await fetch(`${BASE_URL}${path}`, {
43
- ...options,
44
- headers,
45
- });
46
-
47
- if (res.status === 401) {
48
- throw new Error("Session expired. Run 'composter login' again.");
49
- }
50
-
51
- return res;
52
- }
53
-
54
- // Create the MCP server with tools
55
- function createComposterMcpServer() {
56
- const server = new McpServer({
57
- name: "Composter",
58
- version: "1.0.0",
59
- });
60
-
61
- // Tool: Search components
62
- server.tool(
63
- "search_components",
64
- "Search for React components in your Composter vault by title or category name. Returns matching components with IDs and categories.",
65
- {
66
- query: z.string().describe("Search term for component title or category name"),
67
- },
68
- async ({ query }) => {
69
- try {
70
- const res = await apiRequest(`/components/search?q=${encodeURIComponent(query)}`, { method: "GET" });
71
-
72
- if (!res.ok) {
73
- const error = await res.json().catch(() => ({}));
74
- return {
75
- content: [{ type: "text", text: `Error searching: ${error.message || res.statusText}` }],
76
- };
77
- }
78
-
79
- const data = await res.json();
80
- const components = data.components || [];
81
-
82
- if (components.length === 0) {
83
- return {
84
- content: [{ type: "text", text: "No components found matching that query." }],
85
- };
86
- }
87
-
88
- const formatted = components.map((c) =>
89
- `- **${c.title}** (Category: ${c.category?.name || "unknown"}) [ID: ${c.id}]`
90
- ).join("\n");
91
-
92
- return {
93
- content: [{ type: "text", text: `Found ${components.length} component(s):\n\n${formatted}` }],
94
- };
95
- } catch (err) {
96
- return {
97
- content: [{ type: "text", text: `Error: ${err.message}` }],
98
- };
99
- }
100
- }
101
- );
102
-
103
- // Tool: List categories
104
- server.tool(
105
- "list_categories",
106
- "List all categories in your Composter vault.",
107
- {},
108
- async () => {
109
- try {
110
- const res = await apiRequest("/categories");
111
-
112
- if (!res.ok) {
113
- const error = await res.json().catch(() => ({}));
114
- return {
115
- content: [{ type: "text", text: `Error: ${error.message || res.statusText}` }],
116
- };
117
- }
118
-
119
- const data = await res.json();
120
- const categories = data.categories || [];
121
-
122
- if (categories.length === 0) {
123
- return {
124
- content: [{ type: "text", text: "No categories found. Create one with 'composter mkcat <name>'." }],
125
- };
126
- }
127
-
128
- const formatted = categories.map((c) => `- ${c.name}`).join("\n");
129
-
130
- return {
131
- content: [{ type: "text", text: `Your categories:\n\n${formatted}` }],
132
- };
133
- } catch (err) {
134
- return {
135
- content: [{ type: "text", text: `Error: ${err.message}` }],
136
- };
137
- }
138
- }
139
- );
140
-
141
- // Tool: Read component
142
- server.tool(
143
- "read_component",
144
- "Read the full source code of a React component from your vault. Returns the code, category, dependencies, and creation date.",
145
- {
146
- category: z.string().describe("The category name the component belongs to"),
147
- title: z.string().describe("The title/name of the component to read"),
148
- },
149
- async ({ category, title }) => {
150
- try {
151
- const res = await apiRequest(
152
- `/components?category=${encodeURIComponent(category)}&title=${encodeURIComponent(title)}`
153
- );
154
-
155
- if (res.status === 404) {
156
- return {
157
- content: [{ type: "text", text: `Component "${title}" not found in category "${category}".` }],
158
- };
159
- }
160
-
161
- if (!res.ok) {
162
- const error = await res.json().catch(() => ({}));
163
- return {
164
- content: [{ type: "text", text: `Error: ${error.message || res.statusText}` }],
165
- };
166
- }
167
-
168
- const data = await res.json();
169
- const component = data.component;
170
-
171
- if (!component) {
172
- return {
173
- content: [{ type: "text", text: `Component "${title}" not found.` }],
174
- };
175
- }
176
-
177
- // Parse code - could be JSON (multi-file) or string (single file)
178
- let codeOutput = "";
179
- try {
180
- const files = JSON.parse(component.code);
181
- codeOutput = Object.entries(files)
182
- .map(([path, content]) => `### ${path}\n\`\`\`tsx\n${content}\n\`\`\``)
183
- .join("\n\n");
184
- } catch {
185
- codeOutput = `\`\`\`tsx\n${component.code}\n\`\`\``;
186
- }
187
-
188
- // Format dependencies
189
- let depsOutput = "";
190
- if (component.dependencies && Object.keys(component.dependencies).length > 0) {
191
- const deps = Object.entries(component.dependencies)
192
- .map(([pkg, ver]) => `- ${pkg}: ${ver}`)
193
- .join("\n");
194
- depsOutput = `\n\n**Dependencies:**\n${deps}`;
195
- }
196
-
197
- const output = `# ${component.title}
198
-
199
- **Category:** ${category}
200
- **Created:** ${new Date(component.createdAt).toLocaleDateString()}
201
- ${depsOutput}
202
-
203
- ## Source Code
204
-
205
- ${codeOutput}`;
206
-
207
- return {
208
- content: [{ type: "text", text: output }],
209
- };
210
- } catch (err) {
211
- return {
212
- content: [{ type: "text", text: `Error: ${err.message}` }],
213
- };
214
- }
215
- }
216
- );
217
-
218
- // Tool: List components in category
219
- server.tool(
220
- "list_components",
221
- "List all components in a specific category.",
222
- {
223
- category: z.string().describe("The category name to list components from"),
224
- },
225
- async ({ category }) => {
226
- try {
227
- const res = await apiRequest(`/components/list-by-category?category=${encodeURIComponent(category)}`, { method: "GET" });
228
-
229
- if (!res.ok) {
230
- const error = await res.json().catch(() => ({}));
231
- return {
232
- content: [{ type: "text", text: `Error: ${error.message || res.statusText}` }],
233
- };
234
- }
235
-
236
- const data = await res.json();
237
- const components = data.components || [];
238
-
239
- if (components.length === 0) {
240
- return {
241
- content: [{ type: "text", text: `No components found in category "${category}".` }],
242
- };
243
- }
244
-
245
- const formatted = components.map((c) =>
246
- `- **${c.title}** (created: ${new Date(c.createdAt).toLocaleDateString()})`
247
- ).join("\n");
248
-
249
- return {
250
- content: [{ type: "text", text: `Components in "${category}":\n\n${formatted}` }],
251
- };
252
- } catch (err) {
253
- return {
254
- content: [{ type: "text", text: `Error: ${err.message}` }],
255
- };
256
- }
257
- }
258
- );
259
-
260
- return server;
261
- }
262
-
263
- // Main entry point
264
- async function main() {
265
- try {
266
- // Verify session exists
267
- const session = loadSession();
268
- if (!session?.jwt) {
269
- console.error("❌ No session found. Please run 'composter login' first.");
270
- process.exit(1);
271
- }
272
-
273
- console.error(`🚀 Composter MCP Server starting...`);
274
- console.error(`📡 API: ${BASE_URL}`);
275
-
276
- const server = createComposterMcpServer();
277
- const transport = new StdioServerTransport();
278
- await server.connect(transport);
279
-
280
- console.error("✅ Composter MCP server running on stdio");
281
- } catch (error) {
282
- console.error("❌ Fatal Error:", error.message);
283
- process.exit(1);
284
- }
285
- }
286
-
287
- // Run if called directly
288
- main();
289
-