composter-cli 1.0.11 → 1.0.15

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.
@@ -1,389 +0,0 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { z } from "zod";
3
- import { getAuthToken, getBaseUrl } from "./auth.js";
4
-
5
- // API request helper with JWT auth
6
- async function apiRequest(path, options = {}) {
7
- const token = getAuthToken();
8
- const baseUrl = getBaseUrl();
9
-
10
- const headers = {
11
- "Content-Type": "application/json",
12
- "Authorization": `Bearer ${token}`,
13
- ...options.headers,
14
- };
15
-
16
- const res = await fetch(`${baseUrl}${path}`, {
17
- ...options,
18
- headers,
19
- });
20
-
21
- return res;
22
- }
23
-
24
- // Shared helpers
25
- async function fetchCategories() {
26
- const res = await apiRequest("/categories", { method: "GET" });
27
- if (!res.ok) {
28
- const error = await res.json().catch(() => ({}));
29
- throw new Error(error.message || error.error || res.statusText);
30
- }
31
- const data = await res.json();
32
- return data.categories || [];
33
- }
34
-
35
- async function fetchComponentsByCategory(category) {
36
- const res = await apiRequest(`/components/list-by-category?category=${encodeURIComponent(category)}`, { method: "GET" });
37
- if (res.status === 404) throw new Error(`Category "${category}" not found.`);
38
- if (!res.ok) {
39
- const error = await res.json().catch(() => ({}));
40
- throw new Error(error.message || error.error || res.statusText);
41
- }
42
- const data = await res.json();
43
- return data.components || [];
44
- }
45
-
46
- async function fetchComponent(category, title) {
47
- const res = await apiRequest(
48
- `/components?category=${encodeURIComponent(category)}&title=${encodeURIComponent(title)}`,
49
- { method: "GET" }
50
- );
51
- if (res.status === 404) throw new Error(`Component "${title}" not found in category "${category}".`);
52
- if (!res.ok) {
53
- const error = await res.json().catch(() => ({}));
54
- throw new Error(error.message || error.error || res.statusText);
55
- }
56
- const data = await res.json();
57
- return data.component;
58
- }
59
-
60
- async function searchComponents(query) {
61
- const res = await apiRequest(`/components/search?q=${encodeURIComponent(query)}`, { method: "GET" });
62
- if (!res.ok) {
63
- const error = await res.json().catch(() => ({}));
64
- throw new Error(error.message || error.error || res.statusText);
65
- }
66
- const data = await res.json();
67
- return data.components || [];
68
- }
69
-
70
- function renderComponent(category, component) {
71
- if (!component) return "Component not found.";
72
-
73
- let codeOutput = "";
74
- try {
75
- const files = JSON.parse(component.code);
76
- codeOutput = Object.entries(files)
77
- .map(([path, content]) => `### ${path}\n\`\`\`tsx\n${content}\n\`\`\``)
78
- .join("\n\n");
79
- } catch {
80
- codeOutput = `\`\`\`tsx\n${component.code}\n\`\`\``;
81
- }
82
-
83
- let depsOutput = "";
84
- if (component.dependencies && Object.keys(component.dependencies).length > 0) {
85
- const deps = Object.entries(component.dependencies)
86
- .map(([pkg, ver]) => `- ${pkg}: ${ver}`)
87
- .join("\n");
88
- depsOutput = `\n\n**Dependencies:**\n${deps}`;
89
- }
90
-
91
- return `# ${component.title}
92
-
93
- **Category:** ${category}
94
- **Created:** ${new Date(component.createdAt).toLocaleDateString()}${depsOutput}
95
-
96
- ## Source Code
97
-
98
- ${codeOutput}`;
99
- }
100
-
101
- export function createMcpServer() {
102
- const server = new McpServer({
103
- name: "Composter",
104
- version: "1.0.0",
105
- });
106
-
107
- // Tool: Search components
108
- server.tool(
109
- "search_components",
110
- "Search vault components by name or topic. Triggers on queries like 'find button components', 'search cards', 'look up forms'. Returns matches with IDs and categories.",
111
- {
112
- query: z.string().describe("Search term for component title or category name"),
113
- },
114
- async ({ query }) => {
115
- try {
116
- const res = await apiRequest(`/components/search?q=${encodeURIComponent(query)}`, { method: "GET" });
117
-
118
- if (!res.ok) {
119
- const error = await res.json().catch(() => ({}));
120
- return {
121
- content: [{ type: "text", text: `Error searching: ${error.message || error.error || res.statusText}` }],
122
- };
123
- }
124
-
125
- const data = await res.json();
126
- const components = data.components || [];
127
-
128
- if (components.length === 0) {
129
- return {
130
- content: [{ type: "text", text: "No components found matching that query." }],
131
- };
132
- }
133
-
134
- const formatted = components.map((c) =>
135
- `- **${c.title}** (Category: ${c.category?.name || "unknown"}) [ID: ${c.id}]`
136
- ).join("\n");
137
-
138
- return {
139
- content: [{ type: "text", text: `Found ${components.length} component(s):\n\n${formatted}` }],
140
- };
141
- } catch (err) {
142
- return {
143
- content: [{ type: "text", text: `Error: ${err.message}` }],
144
- };
145
- }
146
- }
147
- );
148
-
149
- // Tool: Natural language helper (catch-all)
150
- server.tool(
151
- "ask_composter",
152
- "Ask in plain English to list categories, show components in a category, search components, or read a component (e.g., 'list categories', 'show components in ui', 'read Simple Card from ui', 'find button components').",
153
- {
154
- query: z.string().describe("e.g. 'list categories', 'show components in ui', 'read Simple Card from ui', 'find button components'"),
155
- },
156
- async ({ query }) => {
157
- const q = query.trim();
158
- const qLower = q.toLowerCase();
159
- const respond = (text) => ({ content: [{ type: "text", text }] });
160
-
161
- try {
162
- // List categories
163
- if (/\b(list|show|what)\b.*\bcategories\b/.test(qLower)) {
164
- const categories = await fetchCategories();
165
- if (!categories.length) return respond("No categories found. Create one with 'composter mkcat <name>'.");
166
- const formatted = categories.map((c) => `- ${c.name}`).join("\n");
167
- return respond(`Your categories:\n\n${formatted}`);
168
- }
169
-
170
- // List components in a category
171
- const listCatMatch = qLower.match(/(?:list|show|what).*(?:components|items).*(?:in|for)\s+([a-z0-9_-]+)/);
172
- if (listCatMatch) {
173
- const cat = listCatMatch[1];
174
- const components = await fetchComponentsByCategory(cat);
175
- if (!components.length) return respond(`No components found in category "${cat}".`);
176
- const formatted = components
177
- .map((c) => `- ${c.title} (created: ${new Date(c.createdAt).toLocaleDateString()})`)
178
- .join("\n");
179
- return respond(`Components in "${cat}":\n\n${formatted}`);
180
- }
181
-
182
- // Read component with optional category
183
- const readMatch = q.match(/(?:read|show|get|open|fetch)\s+(.+?)(?:\s+from\s+([a-z0-9_-]+))?$/i);
184
- if (readMatch) {
185
- const titleRaw = readMatch[1].trim();
186
- const categoryRaw = readMatch[2]?.trim();
187
-
188
- if (!categoryRaw) {
189
- const hits = await searchComponents(titleRaw);
190
- if (!hits.length) return respond(`No components found matching "${titleRaw}".`);
191
- if (hits.length > 1) {
192
- const list = hits
193
- .slice(0, 5)
194
- .map((c) => `- ${c.title} (category: ${c.category?.name || "unknown"})`)
195
- .join("\n");
196
- return respond(
197
- `Found multiple components matching "${titleRaw}". Please specify a category:\n\n${list}${
198
- hits.length > 5 ? "\n(and more...)" : ""
199
- }`
200
- );
201
- }
202
- const hit = hits[0];
203
- const comp = await fetchComponent(hit.category?.name, hit.title);
204
- return respond(renderComponent(hit.category?.name, comp));
205
- }
206
-
207
- const comp = await fetchComponent(categoryRaw, titleRaw);
208
- return respond(renderComponent(categoryRaw, comp));
209
- }
210
-
211
- // Fallback: search
212
- const hits = await searchComponents(q);
213
- if (!hits.length) return respond("No components found matching that query.");
214
- const formatted = hits
215
- .map((c) => `- **${c.title}** (Category: ${c.category?.name || "unknown"}) [ID: ${c.id}]`)
216
- .join("\n");
217
- return respond(`Found ${hits.length} component(s):\n\n${formatted}`);
218
- } catch (err) {
219
- return respond(`Error: ${err.message}`);
220
- }
221
- }
222
- );
223
-
224
- // Tool: List categories
225
- server.tool(
226
- "list_categories",
227
- "List all categories in the vault. Trigger when user asks 'what categories do I have', 'show my categories', 'list vault categories'.",
228
- {},
229
- async () => {
230
- try {
231
- const res = await apiRequest("/categories", { method: "GET" });
232
-
233
- if (!res.ok) {
234
- const error = await res.json().catch(() => ({}));
235
- return {
236
- content: [{ type: "text", text: `Error: ${error.message || error.error || res.statusText}` }],
237
- };
238
- }
239
-
240
- const data = await res.json();
241
- const categories = data.categories || [];
242
-
243
- if (categories.length === 0) {
244
- return {
245
- content: [{ type: "text", text: "No categories found. Create one with 'composter mkcat <name>'." }],
246
- };
247
- }
248
-
249
- const formatted = categories.map((c) => `- ${c.name}`).join("\n");
250
-
251
- return {
252
- content: [{ type: "text", text: `Your categories:\n\n${formatted}` }],
253
- };
254
- } catch (err) {
255
- return {
256
- content: [{ type: "text", text: `Error: ${err.message}` }],
257
- };
258
- }
259
- }
260
- );
261
-
262
- // Tool: List components in category
263
- server.tool(
264
- "list_components",
265
- "List components inside a given category. Trigger on requests like 'show components in ui', 'what's in forms', 'list items in buttons'.",
266
- {
267
- category: z.string().describe("The category name to list components from"),
268
- },
269
- async ({ category }) => {
270
- try {
271
- const res = await apiRequest(`/components/list-by-category?category=${encodeURIComponent(category)}`, { method: "GET" });
272
-
273
- if (res.status === 404) {
274
- return {
275
- content: [{ type: "text", text: `Category "${category}" not found.` }],
276
- };
277
- }
278
-
279
- if (!res.ok) {
280
- const error = await res.json().catch(() => ({}));
281
- return {
282
- content: [{ type: "text", text: `Error: ${error.message || error.error || res.statusText}` }],
283
- };
284
- }
285
-
286
- const data = await res.json();
287
- const components = data.components || [];
288
-
289
- if (components.length === 0) {
290
- return {
291
- content: [{ type: "text", text: `No components found in category "${category}".` }],
292
- };
293
- }
294
-
295
- const formatted = components.map((c) =>
296
- `- **${c.title}** (created: ${new Date(c.createdAt).toLocaleDateString()})`
297
- ).join("\n");
298
-
299
- return {
300
- content: [{ type: "text", text: `Components in "${category}":\n\n${formatted}` }],
301
- };
302
- } catch (err) {
303
- return {
304
- content: [{ type: "text", text: `Error: ${err.message}` }],
305
- };
306
- }
307
- }
308
- );
309
-
310
- // Tool: Read component
311
- server.tool(
312
- "read_component",
313
- "Read a component's full source. Trigger on 'read/open/show/get <component> from <category>' or similar. Returns code, category, dependencies, and creation date.",
314
- {
315
- category: z.string().describe("The category name the component belongs to"),
316
- title: z.string().describe("The title/name of the component to read"),
317
- },
318
- async ({ category, title }) => {
319
- try {
320
- const res = await apiRequest(
321
- `/components?category=${encodeURIComponent(category)}&title=${encodeURIComponent(title)}`,
322
- { method: "GET" }
323
- );
324
-
325
- if (res.status === 404) {
326
- return {
327
- content: [{ type: "text", text: `Component "${title}" not found in category "${category}".` }],
328
- };
329
- }
330
-
331
- if (!res.ok) {
332
- const error = await res.json().catch(() => ({}));
333
- return {
334
- content: [{ type: "text", text: `Error: ${error.message || error.error || res.statusText}` }],
335
- };
336
- }
337
-
338
- const data = await res.json();
339
- const component = data.component;
340
-
341
- if (!component) {
342
- return {
343
- content: [{ type: "text", text: `Component "${title}" not found.` }],
344
- };
345
- }
346
-
347
- // Parse code - could be JSON (multi-file) or string (single file)
348
- let codeOutput = "";
349
- try {
350
- const files = JSON.parse(component.code);
351
- codeOutput = Object.entries(files)
352
- .map(([path, content]) => `### ${path}\n\`\`\`tsx\n${content}\n\`\`\``)
353
- .join("\n\n");
354
- } catch {
355
- codeOutput = `\`\`\`tsx\n${component.code}\n\`\`\``;
356
- }
357
-
358
- // Format dependencies
359
- let depsOutput = "";
360
- if (component.dependencies && Object.keys(component.dependencies).length > 0) {
361
- const deps = Object.entries(component.dependencies)
362
- .map(([pkg, ver]) => `- ${pkg}: ${ver}`)
363
- .join("\n");
364
- depsOutput = `\n\n**Dependencies:**\n${deps}`;
365
- }
366
-
367
- const output = `# ${component.title}
368
-
369
- **Category:** ${category}
370
- **Created:** ${new Date(component.createdAt).toLocaleDateString()}
371
- ${depsOutput}
372
-
373
- ## Source Code
374
-
375
- ${codeOutput}`;
376
-
377
- return {
378
- content: [{ type: "text", text: output }],
379
- };
380
- } catch (err) {
381
- return {
382
- content: [{ type: "text", text: `Error: ${err.message}` }],
383
- };
384
- }
385
- }
386
- );
387
-
388
- return server;
389
- }
package/mcp/src/init.js DELETED
@@ -1,195 +0,0 @@
1
- #!/usr/bin/env node
2
- import fs from "fs";
3
- import path from "path";
4
- import os from "os";
5
-
6
- // Client configuration templates
7
- const CLIENT_CONFIGS = {
8
- claude: {
9
- name: "Claude Desktop",
10
- configPath: () => {
11
- if (process.platform === "darwin") {
12
- return path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
13
- } else if (process.platform === "win32") {
14
- return path.join(process.env.APPDATA, "Claude", "claude_desktop_config.json");
15
- } else {
16
- return path.join(os.homedir(), ".config", "claude", "claude_desktop_config.json");
17
- }
18
- },
19
- generateConfig: () => ({
20
- mcpServers: {
21
- composter: {
22
- command: "npx",
23
- args: ["composter-mcp"],
24
- },
25
- },
26
- }),
27
- mergeKey: "mcpServers",
28
- },
29
- cursor: {
30
- name: "Cursor",
31
- configPath: () => path.join(process.cwd(), ".cursor", "mcp.json"),
32
- generateConfig: () => ({
33
- mcpServers: {
34
- composter: {
35
- command: "npx",
36
- args: ["composter-mcp"],
37
- },
38
- },
39
- }),
40
- mergeKey: "mcpServers",
41
- },
42
- vscode: {
43
- name: "VS Code (Copilot)",
44
- configPath: () => path.join(process.cwd(), ".vscode", "mcp.json"),
45
- generateConfig: () => ({
46
- mcpServers: {
47
- composter: {
48
- command: "npx",
49
- args: ["composter-mcp"],
50
- },
51
- },
52
- }),
53
- mergeKey: "mcpServers",
54
- },
55
- windsurf: {
56
- name: "Windsurf",
57
- configPath: () => {
58
- if (process.platform === "darwin") {
59
- return path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json");
60
- } else if (process.platform === "win32") {
61
- return path.join(process.env.APPDATA, "Codeium", "windsurf", "mcp_config.json");
62
- } else {
63
- return path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json");
64
- }
65
- },
66
- generateConfig: () => ({
67
- mcpServers: {
68
- composter: {
69
- command: "npx",
70
- args: ["composter-mcp"],
71
- },
72
- },
73
- }),
74
- mergeKey: "mcpServers",
75
- },
76
- };
77
-
78
- function printUsage() {
79
- console.log(`
80
- šŸ¤– Composter MCP - Setup Tool
81
-
82
- Usage:
83
- npx composter-mcp init <client>
84
-
85
- Supported clients:
86
- claude - Claude Desktop
87
- cursor - Cursor IDE
88
- vscode - VS Code with Copilot
89
- windsurf - Windsurf IDE
90
-
91
- Examples:
92
- npx composter-mcp init claude
93
- npx composter-mcp init cursor
94
- `);
95
- }
96
-
97
- function initClient(clientName) {
98
- const client = CLIENT_CONFIGS[clientName?.toLowerCase()];
99
-
100
- if (!client) {
101
- console.log(`āŒ Unknown client: ${clientName}`);
102
- console.log(`\nSupported clients: ${Object.keys(CLIENT_CONFIGS).join(", ")}`);
103
- process.exit(1);
104
- }
105
-
106
- const configPath = client.configPath();
107
- const configDir = path.dirname(configPath);
108
-
109
- console.log(`\nšŸ”§ Setting up Composter MCP for ${client.name}...\n`);
110
-
111
- // Read existing config if it exists
112
- let existingConfig = {};
113
- if (fs.existsSync(configPath)) {
114
- try {
115
- existingConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
116
- console.log(`šŸ“„ Found existing config at: ${configPath}`);
117
- } catch {
118
- console.log(`āš ļø Existing config is invalid JSON, will create new one.`);
119
- }
120
- }
121
-
122
- // Generate new config
123
- const newConfig = client.generateConfig();
124
-
125
- // Merge configs
126
- let mergedConfig;
127
- if (client.mergeKey) {
128
- mergedConfig = {
129
- ...existingConfig,
130
- [client.mergeKey]: {
131
- ...existingConfig[client.mergeKey],
132
- ...newConfig[client.mergeKey],
133
- },
134
- };
135
- } else {
136
- // Direct merge at root (e.g., Cursor)
137
- mergedConfig = {
138
- ...existingConfig,
139
- ...newConfig,
140
- };
141
- }
142
-
143
- // Create directory if needed
144
- if (!fs.existsSync(configDir)) {
145
- fs.mkdirSync(configDir, { recursive: true });
146
- console.log(`šŸ“ Created directory: ${configDir}`);
147
- }
148
-
149
- // Write config
150
- fs.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2), "utf-8");
151
- console.log(`āœ… Config written to: ${configPath}`);
152
-
153
- // Print success message
154
- console.log(`
155
- ╔═══════════════════════════════════════════════════════════════════╗
156
- ā•‘ šŸŽ‰ Setup Complete! ā•‘
157
- ╠═══════════════════════════════════════════════════════════════════╣
158
- ā•‘ ā•‘
159
- ā•‘ Composter MCP has been configured for ${client.name.padEnd(24)} ā•‘
160
- ā•‘ ā•‘
161
- ā•‘ Next steps: ā•‘
162
- ā•‘ 1. Make sure you're logged in: composter login ā•‘
163
- ā•‘ 2. Restart ${client.name.padEnd(47)} ā•‘
164
- ā•‘ 3. Look for "Composter" in your MCP tools ā•‘
165
- ā•‘ ā•‘
166
- ā•‘ Available tools: ā•‘
167
- ā•‘ • search_components - Search your component vault ā•‘
168
- ā•‘ • list_categories - List all your categories ā•‘
169
- ā•‘ • list_components - List components in a category ā•‘
170
- ā•‘ • read_component - Read full source code ā•‘
171
- ā•‘ ā•‘
172
- ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•
173
- `);
174
- }
175
-
176
- // Main
177
- const args = process.argv.slice(2);
178
- const command = args[0];
179
- const clientArg = args[1];
180
-
181
- if (command === "init") {
182
- if (!clientArg) {
183
- console.log("āŒ Please specify a client to configure.");
184
- printUsage();
185
- process.exit(1);
186
- }
187
- initClient(clientArg);
188
- } else if (command === "--help" || command === "-h" || !command) {
189
- printUsage();
190
- } else {
191
- console.log(`āŒ Unknown command: ${command}`);
192
- printUsage();
193
- process.exit(1);
194
- }
195
-
package/mcp/src/server.js DELETED
@@ -1,34 +0,0 @@
1
- #!/usr/bin/env node
2
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
- import { createMcpServer } from "../lib/factory.js";
4
- import { loadSession, getBaseUrl } from "../lib/auth.js";
5
-
6
- // Redirect console.log to stderr (MCP uses stdout for protocol communication)
7
- console.log = (...args) => console.error(...args);
8
-
9
- async function main() {
10
- try {
11
- // Check if user is logged in via CLI
12
- const session = loadSession();
13
- if (!session) {
14
- console.error("āŒ No session found. Please run 'composter login' first.");
15
- process.exit(1);
16
- }
17
-
18
- const baseUrl = getBaseUrl();
19
- console.error(`šŸš€ Composter MCP Server starting...`);
20
- console.error(`šŸ“” API: ${baseUrl}`);
21
-
22
- // Create and start MCP server
23
- const server = createMcpServer();
24
- const transport = new StdioServerTransport();
25
- await server.connect(transport);
26
-
27
- console.error("āœ… Composter MCP server running on stdio");
28
- } catch (error) {
29
- console.error("āŒ Fatal Error:", error.message);
30
- process.exit(1);
31
- }
32
- }
33
-
34
- main();