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.
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "composter-cli",
3
- "version": "1.0.11",
3
+ "version": "1.0.15",
4
+ "private": false,
4
5
  "type": "module",
5
6
  "description": "Your personal vault for React components. Push, pull, and sync reusable components across projects — like shadcn/ui but for YOUR code.",
6
7
  "main": "src/index.js",
@@ -36,15 +37,14 @@
36
37
  "node": ">=18.0.0"
37
38
  },
38
39
  "dependencies": {
39
- "@modelcontextprotocol/sdk": "^1.24.3",
40
+ "chalk": "^5.6.2",
40
41
  "commander": "^14.0.2",
41
- "dotenv": "^16.4.5",
42
+ "dotenv": "^17.2.3",
42
43
  "inquirer": "^13.0.1",
43
44
  "node-fetch": "^3.3.2"
44
45
  },
45
46
  "files": [
46
47
  "src/",
47
- "bin/",
48
- "mcp/"
48
+ "bin/"
49
49
  ]
50
50
  }
@@ -1,47 +1,10 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { fileURLToPath } from "url";
4
- import { dirname } from "path";
5
-
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = dirname(__filename);
8
-
9
- export async function initVscode() {
10
- const cwd = process.cwd();
11
- const vscodeDir = path.join(cwd, ".vscode");
12
- const mcpConfigPath = path.join(vscodeDir, "mcp.json");
13
-
14
- // Find the globally installed composter-cli MCP server
15
- // When installed globally, the MCP server is in:
16
- // /usr/local/lib/node_modules/composter-cli/mcp/src/server.js
17
- const globalMcpServerPath = path.join(
18
- path.dirname(path.dirname(path.dirname(__dirname))),
19
- "mcp",
20
- "src",
21
- "server.js"
22
- );
23
-
24
- const mcpConfig = {
25
- servers: {
26
- "composter-mcp": {
27
- type: "stdio",
28
- command: "node",
29
- args: [globalMcpServerPath],
30
- }
31
- },
32
- inputs: []
33
- };
34
-
35
- // Create .vscode directory if it doesn't exist
36
- if (!fs.existsSync(vscodeDir)) {
37
- fs.mkdirSync(vscodeDir, { recursive: true });
38
- console.log("✓ Created .vscode directory");
39
- }
40
-
41
- // Write mcp.json
42
- fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
43
- console.log("✓ Created .vscode/mcp.json");
44
- console.log("\n📝 MCP Server configured for this project!");
45
- console.log("🔄 Reload VS Code window to activate the MCP server");
46
- console.log(" Press: Ctrl+Shift+P → 'Developer: Reload Window'");
1
+ // MCP configuration has moved to a separate package: composter-mcp
2
+ // This command now redirects users to the new package
3
+
4
+ export async function initMcp(client) {
5
+ console.log("\n📦 MCP Server has moved to a separate package!\n");
6
+ console.log("To configure MCP for your AI assistant, run:\n");
7
+ console.log(` npx composter-mcp init ${client || "<client>"}\n`);
8
+ console.log("Supported clients: claude, cursor, vscode, windsurf\n");
9
+ console.log("For more info: https://www.npmjs.com/package/composter-mcp\n");
47
10
  }
@@ -1,61 +1,33 @@
1
+ import chalk from "chalk";
1
2
  import { apiRequest } from "../utils/request.js";
2
- import { loadSession } from "../utils/session.js";
3
+ import { log } from "../utils/log.js";
3
4
 
4
5
  export async function listCategories() {
5
- const session = loadSession();
6
- if (!session || !session.jwt) {
7
- console.log("You must be logged in. Run: composter login");
8
- return;
9
- }
10
6
 
11
- try{
12
- const res = await apiRequest("/categories", {
13
- method: "GET",
14
- headers: { "Content-Type": "application/json" },
15
- });
16
- let body = null;
17
- try {
18
- body = await res.json();
19
- } catch {
20
- // Ignore if no JSON
21
- }
7
+ const res = await apiRequest("/categories", {
8
+ method: "GET",
9
+ headers: { "Content-Type": "application/json" },
10
+ });
22
11
 
23
- // Handle auth failure
24
- if (res.status === 401) {
25
- console.log("Session expired. Run composter login again.");
26
- return;
27
- }
12
+ let body = null;
28
13
 
29
- // Handle server errors
30
- if (res.status >= 500) {
31
- console.log("Server error. Try again later.");
32
- return;
33
- }
14
+ try {
15
+ body = await res.json();
16
+ } catch {
17
+ // Ignore if no JSON
18
+ }
34
19
 
35
- // Handle success
36
- if (res.ok) {
37
- const categories = body?.categories || [];
38
- if (categories.length === 0) {
39
- console.log("No categories found.");
40
- return;
41
- }
42
- categories.forEach((cat) => {
43
- //list them adjacent to each other with tab space between
44
- process.stdout.write(`${cat.name}\t\t`);
45
- });
46
- console.log();
47
- return;
48
- }
49
20
 
50
- // Handle other errors
51
- const errorMessage =
52
- (body && (body.message || body.error || JSON.stringify(body))) ||
53
- res.statusText ||
54
- `HTTP ${res.status}`;
55
- console.log("Error listing categories:", errorMessage);
56
- return;
57
- } catch (error) {
58
- console.log("Error fetching categories:", error);
21
+
22
+ const categories = body?.categories || [];
23
+ if (categories.length === 0) {
24
+ log.info("No categories found.");
59
25
  return;
60
26
  }
27
+ categories.forEach((cat) => {
28
+ //list them adjacent to each other with tab space between
29
+ process.stdout.write(chalk.cyan.bold(cat.name) + "\t\t");
30
+ });
31
+ console.log();
32
+ return;
61
33
  }
@@ -1,75 +1,70 @@
1
1
  import inquirer from "inquirer";
2
- import fetch from "node-fetch";
2
+ import { safeFetch } from "../utils/safeFetch.js";
3
3
  import { saveSession } from "../utils/session.js";
4
- import dotenv from "dotenv";
5
- import { fileURLToPath } from 'url';
6
- import { dirname, join } from 'path';
4
+ import { fileURLToPath } from "url";
5
+ import { dirname } from "path";
6
+ import { log } from "../utils/log.js";
7
+ import { handleFetchError } from "../utils/errorHandlers/fetchErrorHandler.js";
8
+ import chalk from "chalk";
9
+ import { composterLoginArtv2 } from "../constants/asciiArts.js";
7
10
 
8
11
  const __filename = fileURLToPath(import.meta.url);
9
12
  const __dirname = dirname(__filename);
10
- dotenv.config({ path: join(__dirname, '../../.env') });
11
13
 
12
- const BASE_URL = `${process.env.BASE_URL || "https://composter.onrender.com/api"}/auth`;
14
+ const BASE_URL = `${process.env.BASE_URL}/auth`;
13
15
 
14
16
  export async function login() {
15
- console.log("=== Composter Login ===");
17
+ console.log(chalk.bold.blue(composterLoginArtv2));
16
18
 
17
19
  const { email, password } = await inquirer.prompt([
18
20
  { type: "input", name: "email", message: "Email:" },
19
21
  { type: "password", name: "password", message: "Password:" }
20
22
  ]);
21
23
 
22
- // Step 1 — Sign in
23
- const res = await fetch(`${BASE_URL}/sign-in/email`, {
24
- method: "POST",
25
- headers: { "Content-Type": "application/json" },
26
- body: JSON.stringify({ email, password })
27
- });
28
-
29
- if (!res.ok) {
30
- // try to parse JSON error body, fall back to statusText
31
- let errBody = null;
32
- try {
33
- errBody = await res.json();
34
- } catch (e) {
35
- // body wasn't JSON or couldn't be parsed
36
- }
37
- const message =
38
- (errBody && (errBody.message || errBody.error || JSON.stringify(errBody))) ||
39
- res.statusText ||
40
- `HTTP ${res.status}`;
41
- console.log("\nLogin failed:", message);
24
+ const isValidEmail = /\S+@\S+\.\S+/.test(email);
25
+ if (!isValidEmail) {
26
+ log.error("Please enter a valid email address.");
42
27
  return;
43
28
  }
44
29
 
45
- // Step 2 — Extract session cookie
46
- const cookie = res.headers.get("set-cookie");
47
- if (!cookie) {
48
- console.log("Failed: No session cookie returned.");
49
- return;
50
- }
30
+ try {
31
+
32
+ // Step 1: Sign in with email and password to obtain session cookie
33
+ const res = await safeFetch(`${BASE_URL}/sign-in/email`, {
34
+ method: "POST",
35
+ headers: { "Content-Type": "application/json" },
36
+ body: JSON.stringify({ email, password })
37
+ });
51
38
 
52
- // Step 3 — Fetch JWT token
53
- const tokenRes = await fetch(`${BASE_URL}/token`, {
54
- method: "GET",
55
- headers: { "Cookie": cookie }
56
- });
39
+ const cookie = res.headers.get("set-cookie");
40
+ if (!cookie) {
41
+ log.error("Unable to retrieve session cookie. Login failed.");
42
+ return;
43
+ }
44
+
45
+ const tokenRes = await safeFetch(`${BASE_URL}/token`, {
46
+ method: "GET",
47
+ headers: { Cookie: cookie }
48
+ });
57
49
 
58
- let token = null;
59
- if (tokenRes.ok) {
60
50
  const json = await tokenRes.json();
61
- token = json.token;
62
- }
51
+ const token = json.token;
52
+
53
+ const expiresAt = new Date(
54
+ Date.now() + 30 * 24 * 60 * 60 * 1000
55
+ ).toISOString();
63
56
 
64
- // Step 4 — Save session + jwt locally with expiration
65
- const expiresAt = new Date(Date.now() + (30 * 24 * 60 * 60 * 1000)).toISOString(); // 30 days
66
- saveSession({
67
- cookies: cookie,
68
- jwt: token,
69
- createdAt: new Date().toISOString(),
70
- expiresAt: expiresAt
71
- });
57
+ saveSession({
58
+ cookies: cookie,
59
+ jwt: token,
60
+ createdAt: new Date().toISOString(),
61
+ expiresAt
62
+ });
63
+
64
+ log.success("You have successfully logged in");
65
+ log.info(`Session expires: ${expiresAt}`);
66
+ } catch (err) {
67
+ handleFetchError(err);
68
+ }
72
69
 
73
- console.log("\nLogged in successfully!");
74
- console.log(`Session expires: ${expiresAt}`);
75
70
  }
@@ -1,5 +1,5 @@
1
+ import { log } from "../utils/log.js";
1
2
  import { apiRequest } from "../utils/request.js";
2
- import { loadSession } from "../utils/session.js";
3
3
 
4
4
  export async function mkcat(categoryName) {
5
5
  // Validate input
@@ -9,21 +9,12 @@ export async function mkcat(categoryName) {
9
9
  categoryName.includes(" ") ||
10
10
  categoryName.length > 10
11
11
  ) {
12
- console.log(
13
- "Invalid category name. It must be non-empty, without spaces, and 10 characters."
12
+ log.warn(
13
+ "Invalid category name. It must be non-empty, without spaces, and at most 10 characters."
14
14
  );
15
- return;
15
+ process.exit(1);
16
16
  }
17
17
 
18
- // Check session
19
- const session = loadSession();
20
- if (!session || !session.jwt) {
21
- console.log("You must be logged in. Run: composter login");
22
- return;
23
- }
24
-
25
- try {
26
- // Send request
27
18
  const res = await apiRequest("/categories", {
28
19
  method: "POST",
29
20
  headers: { "Content-Type": "application/json" },
@@ -38,34 +29,16 @@ export async function mkcat(categoryName) {
38
29
  // Ignore if no JSON
39
30
  }
40
31
 
41
- // Handle auth failure
42
- if (res.status === 401) {
43
- console.log("Session expired. Run composter login again.");
44
- return;
45
- }
46
-
47
- // Handle server errors
48
- if (res.status >= 500) {
49
- console.log("Server error. Try again later.");
50
- return;
51
- }
52
-
53
- // Handle success
54
32
  if (res.ok) {
55
- console.log(`Category '${categoryName}' created successfully!`);
56
- console.log("ID:", body?.category?.id);
57
- return;
33
+ log.info(`Category '${categoryName}' created successfully!`);
34
+ process.exit(0);
58
35
  }
59
36
 
60
- // Handle other errors
37
+ // handling server sent errors, like duplicate category
61
38
  const msg =
62
- body?.error ||
63
- body?.message ||
64
- JSON.stringify(body) ||
39
+ (body && (body.error || body.message || JSON.stringify(body))) ||
65
40
  `HTTP ${res.status}`;
66
41
 
67
- console.log("Failed to create category:", msg);
68
- } catch (err) {
69
- console.log("Network or unexpected error:", err.message);
70
- }
42
+ log.error(`Failed to create category: ${msg}`);
43
+ process.exit(1);
71
44
  }
@@ -1,28 +1,22 @@
1
+ import chalk from "chalk";
2
+ import { log } from "../utils/log.js";
1
3
  import { apiRequest } from "../utils/request.js";
2
- import { loadSession } from "../utils/session.js";
3
4
  import fs from "fs";
4
5
  import path from "path";
5
6
 
6
7
  export async function pullComponent(category, title, targetDir) {
7
8
  // 1. Validate Input
9
+ // although commander ensures these are provided, we double-check here
8
10
  if (!category?.trim() || !title?.trim() || !targetDir?.trim()) {
9
- console.log("Category, title, and target directory are required.");
10
- return;
11
+ log.error("Category, title, and target directory are required.");
12
+ process.exit(1);
11
13
  }
12
14
 
13
15
  // 2. Resolve Target Directory
14
16
  // In multi-file mode, the target is usually a FOLDER, not a specific file.
15
17
  const absoluteRoot = path.resolve(targetDir);
16
18
 
17
- // 3. Check Session
18
- const session = loadSession();
19
- if (!session || !session.jwt) {
20
- console.log("❌ You must be logged in. Run: composter login");
21
- return;
22
- }
23
-
24
- try {
25
- console.log(`⏳ Fetching '${title}' from '${category}'...`);
19
+ log.info(`⏳ Fetching '${title}' from '${category}'...`);
26
20
 
27
21
  const res = await apiRequest(`/components?category=${encodeURIComponent(category)}&title=${encodeURIComponent(title)}`, {
28
22
  method: "GET",
@@ -33,22 +27,9 @@ export async function pullComponent(category, title, targetDir) {
33
27
  let body = null;
34
28
  try { body = await res.json(); } catch {}
35
29
 
36
- if (res.status === 401) {
37
- console.log("❌ Session expired. Run composter login again.");
38
- return;
39
- }
40
- if (res.status === 404) {
41
- console.log(`❌ Component '${title}' not found.`);
42
- return;
43
- }
44
- if (!res.ok) {
45
- console.log("❌ Server error:", body?.error || res.statusText);
46
- return;
47
- }
48
-
49
- const component = body.component;
30
+ const component = body.component ?? null;
50
31
 
51
- // --- STEP 4: PARSE FILES (Handle JSON vs String) ---
32
+ // PARSE FILES (Handle JSON vs String)
52
33
  let filesMap = {};
53
34
  try {
54
35
  // Try to parse new multi-file format
@@ -64,8 +45,8 @@ export async function pullComponent(category, title, targetDir) {
64
45
  filesMap[`/${fileName}`] = component.code;
65
46
  }
66
47
 
67
- // --- STEP 5: WRITE FILES TO DISK ---
68
- console.log(`📦 Unpacking ${Object.keys(filesMap).length} file(s) into: ${absoluteRoot}`);
48
+ // WRITE FILES TO DISK
49
+ log.info(`📦 Unpacking ${Object.keys(filesMap).length} file(s) into: ${absoluteRoot}`);
69
50
 
70
51
  // Ensure the root target folder exists
71
52
  if (!fs.existsSync(absoluteRoot)) {
@@ -90,19 +71,15 @@ export async function pullComponent(category, title, targetDir) {
90
71
  // Write file
91
72
  fs.writeFileSync(writePath, content, "utf-8");
92
73
  createdFiles.push(relPath);
93
- console.log(` + ${relPath}`);
74
+ console.log(chalk.cyan(` + ${relPath}`));
94
75
  }
95
76
 
96
- // --- STEP 6: CHECK DEPENDENCIES ---
77
+ // CHECK DEPENDENCIES
97
78
  if (component.dependencies && Object.keys(component.dependencies).length > 0) {
98
79
  checkDependencies(component.dependencies);
99
80
  }
100
81
 
101
- console.log(`\n✅ Component '${title}' pulled successfully!`);
102
-
103
- } catch (err) {
104
- console.log("❌ Error pulling component:", err);
105
- }
82
+ log.success(`Component '${title}' pulled successfully!`);
106
83
  }
107
84
 
108
85
  /**
@@ -113,8 +90,8 @@ function checkDependencies(requiredDeps) {
113
90
 
114
91
  // If no package.json, we can't check, so just list them all
115
92
  if (!fs.existsSync(localPkgPath)) {
116
- console.log("\n⚠️ This component requires these packages:");
117
- Object.entries(requiredDeps).forEach(([pkg, ver]) => console.log(` - ${pkg}@${ver}`));
93
+ log.warn("This component requires these packages:");
94
+ Object.entries(requiredDeps).forEach(([pkg, ver]) => log.warn(` - ${pkg}@${ver}`));
118
95
  return;
119
96
  }
120
97
 
@@ -130,10 +107,10 @@ function checkDependencies(requiredDeps) {
130
107
  }
131
108
 
132
109
  if (missing.length > 0) {
133
- console.log("\n⚠️ Missing Dependencies (Run this to fix):");
134
- console.log(` npm install ${missing.map(d => d.split('@')[0]).join(" ")}`);
110
+ log.warn("Missing Dependencies (Run this to fix):");
111
+ log.info(` npm install ${missing.map(d => d.split('@')[0]).join(" ")}`);
135
112
  } else {
136
- console.log("\n✨ All dependencies are already installed.");
113
+ log.info("All dependencies are already installed.");
137
114
  }
138
115
  } catch (e) {
139
116
  // Ignore JSON parse errors
@@ -1,42 +1,34 @@
1
1
  import { apiRequest } from "../utils/request.js";
2
- import { loadSession } from "../utils/session.js";
3
2
  import fs from "fs";
4
3
  import path from "path";
5
- // IMPORT THE NEW SPIDER
6
4
  import { scanComponent } from "../utils/crawler.js";
5
+ import { log } from "../utils/log.js";
7
6
 
8
7
  export async function pushComponent(category, title, filepath) {
9
8
  // 1. Validate Input
9
+ // although commander ensures these are provided, we double-check here
10
10
  if (!category?.trim() || !title?.trim() || !filepath?.trim()) {
11
- console.log("Category, title, and filepath are required.");
11
+ log.error("Category, title, and filepath are required.");
12
12
  return;
13
13
  }
14
14
 
15
15
  // 2. Validate Entry File
16
16
  const absolutePath = path.resolve(filepath);
17
17
  if (!fs.existsSync(absolutePath)) {
18
- console.log(`❌ File not found: ${absolutePath}`);
19
- return;
20
- }
21
-
22
- // 3. Check Session
23
- const session = loadSession();
24
- if (!session || !session.jwt) {
25
- console.log("❌ You must be logged in. Run: composter login");
18
+ log.error(`File not found: ${absolutePath}`);
26
19
  return;
27
20
  }
28
21
 
29
22
  // 4. RUN THE CRAWLER
30
- console.log(`Scanning ${path.basename(absolutePath)} and its dependencies...`);
23
+ log.info(`Scanning ${path.basename(absolutePath)} and its dependencies...`);
31
24
 
32
25
  const { files, dependencies } = scanComponent(absolutePath);
33
26
 
34
27
  const fileCount = Object.keys(files).length;
35
28
  const depCount = Object.keys(dependencies).length;
36
29
 
37
- console.log(`📦 Bundled ${fileCount} file(s) and detected ${depCount} external package(s).`);
30
+ log.info(`📦 Bundled ${fileCount} file(s) and detected ${depCount} external package(s).`);
38
31
 
39
- try {
40
32
  // 5. Send Request
41
33
  // We send 'files' as a JSON string because your DB 'code' column is a String.
42
34
  const res = await apiRequest("/components", {
@@ -54,20 +46,12 @@ export async function pushComponent(category, title, filepath) {
54
46
  let body = null;
55
47
  try { body = await res.json(); } catch {}
56
48
 
57
- if (res.status === 401) {
58
- console.log("❌ Session expired. Run composter login again.");
59
- return;
60
- }
61
-
62
49
  if (res.ok) {
63
- console.log(`✅ Success! Component '${title}' pushed to '${category}'.`);
50
+ log.success(`Success! Component '${title}' pushed to '${category}'.`);
64
51
  return;
65
52
  }
66
53
 
54
+ // handle client errors, e.g., 4xx (bad request, conflict, etc.)
67
55
  const errorMessage = body?.message || body?.error || res.statusText;
68
- console.log("❌ Error pushing component:", errorMessage);
69
-
70
- } catch (error) {
71
- console.log("❌ Network Error:", error.message);
72
- }
56
+ log.error(`Error pushing component: ${errorMessage}`);
73
57
  }
@@ -0,0 +1,21 @@
1
+ const composterLoginArt = `
2
+ █████████ ███████ ██████ ██████ ███████████ ███████ █████████ ███████████ ██████████ ███████████
3
+ ███░░░░░███ ███░░░░░███ ░░██████ ██████ ░░███░░░░░███ ███░░░░░███ ███░░░░░███░█░░░███░░░█░░███░░░░░█░░███░░░░░███
4
+ ███ ░░░ ███ ░░███ ░███░█████░███ ░███ ░███ ███ ░░███░███ ░░░ ░ ░███ ░ ░███ █ ░ ░███ ░███
5
+ ░███ ░███ ░███ ░███░░███ ░███ ░██████████ ░███ ░███░░█████████ ░███ ░██████ ░██████████
6
+ ░███ ░███ ░███ ░███ ░░░ ░███ ░███░░░░░░ ░███ ░███ ░░░░░░░░███ ░███ ░███░░█ ░███░░░░░███
7
+ ░░███ ███░░███ ███ ░███ ░███ ░███ ░░███ ███ ███ ░███ ░███ ░███ ░ █ ░███ ░███
8
+ ░░█████████ ░░░███████░ █████ █████ █████ ░░░███████░ ░░█████████ █████ ██████████ █████ █████
9
+ ░░░░░░░░░ ░░░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░░ ░░░░░░░░░ ░░░░░ ░░░░░░░░░░ ░░░░░ ░░░░░
10
+ `;
11
+
12
+ const composterLoginArtv2 = `
13
+ ██████╗ ██████╗ ███╗ ███╗██████╗ ██████╗ ███████╗████████╗███████╗██████╗
14
+ ██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔═══██╗██╔════╝╚══██╔══╝██╔════╝██╔══██╗
15
+ ██║ ██║ ██║██╔████╔██║██████╔╝██║ ██║███████╗ ██║ █████╗ ██████╔╝
16
+ ██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██║ ██║╚════██║ ██║ ██╔══╝ ██╔══██╗
17
+ ╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚██████╔╝███████║ ██║ ███████╗██║ ██║
18
+ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
19
+ `;
20
+
21
+ export { composterLoginArt, composterLoginArtv2 };
package/src/index.js CHANGED
@@ -1,13 +1,16 @@
1
1
  #!/usr/bin/env node
2
-
2
+ process.env.DOTENV_CONFIG_QUIET = "true";
3
+ process.env.DOTENVX_QUIET = "true";
4
+ import "dotenv/config";
3
5
  import { Command } from "commander";
4
6
  import { login } from "./commands/login.js";
5
7
  import { mkcat } from "./commands/mkcat.js";
6
8
  import { listCategories } from "./commands/listCat.js";
7
9
  import { pushComponent } from "./commands/push.js";
8
10
  import { pullComponent } from "./commands/pull.js";
9
- import { initVscode } from "./commands/init.js";
11
+ import { initMcp } from "./commands/init.js";
10
12
  import { createRequire } from "module";
13
+ import { log } from "./utils/log.js";
11
14
 
12
15
  const require = createRequire(import.meta.url);
13
16
  const packageJson = require("../package.json");
@@ -17,7 +20,17 @@ const program = new Command();
17
20
  program
18
21
  .name("composter")
19
22
  .description("CLI for Composter Platform")
20
- .version(packageJson.version);
23
+ .version(packageJson.version)
24
+ .configureOutput({
25
+ // Override the default error handling to use our custom handler
26
+ writeErr: (str) => {
27
+ if (str.includes("error:")) log.error(str.trim());
28
+ else log.info(str.trim());
29
+ },
30
+ writeOut: (str) => {
31
+ log.info(str.trim());
32
+ },
33
+ });
21
34
 
22
35
  program
23
36
  .command("login")
@@ -25,15 +38,10 @@ program
25
38
  .action(login);
26
39
 
27
40
  program
28
- .command("init <editor>")
29
- .description("Initialize MCP configuration for your editor (vscode)")
30
- .action((editor) => {
31
- if (editor.toLowerCase() === "vscode") {
32
- initVscode();
33
- } else {
34
- console.error("❌ Only 'vscode' is supported currently");
35
- process.exit(1);
36
- }
41
+ .command("init [client]")
42
+ .description("Shows how to configure MCP for your AI assistant")
43
+ .action((client) => {
44
+ initMcp(client);
37
45
  });
38
46
 
39
47
  program
@@ -62,4 +70,23 @@ program
62
70
  pullComponent(category, title, filepath);
63
71
  });
64
72
 
73
+ process.on("SIGINT", () => {
74
+ process.stdout.write("\n");
75
+ process.exit(130);
76
+ });
77
+
78
+ process.on("unhandledRejection", (err) => {
79
+ // Ctrl+C during fetch / inquirer
80
+ if (
81
+ err?.name === "AbortError" ||
82
+ err?.name === "ExitPromptError"
83
+ ) {
84
+ log.info("\nOperation cancelled by user.\n");
85
+ process.exit(130);
86
+ }
87
+
88
+ console.error(err);
89
+ process.exit(1);
90
+ });
91
+
65
92
  program.parse(process.argv);