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 +5 -5
- package/src/commands/init.js +9 -46
- package/src/commands/listCat.js +22 -50
- package/src/commands/login.js +47 -52
- package/src/commands/mkcat.js +10 -37
- package/src/commands/pull.js +18 -41
- package/src/commands/push.js +9 -25
- package/src/constants/asciiArts.js +21 -0
- package/src/index.js +39 -12
- package/src/utils/crawler.js +5 -2
- package/src/utils/errorHandlers/fetchErrorHandler.js +36 -0
- package/src/utils/errorHandlers/sessionErrorHandler.js +32 -0
- package/src/utils/log.js +8 -0
- package/src/utils/request.js +36 -27
- package/src/utils/safeFetch.js +49 -0
- package/src/utils/session.js +33 -15
- package/mcp/lib/auth.js +0 -78
- package/mcp/lib/factory.js +0 -389
- package/mcp/src/init.js +0 -195
- package/mcp/src/server.js +0 -34
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "composter-cli",
|
|
3
|
-
"version": "1.0.
|
|
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
|
-
"
|
|
40
|
+
"chalk": "^5.6.2",
|
|
40
41
|
"commander": "^14.0.2",
|
|
41
|
-
"dotenv": "^
|
|
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
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -1,47 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
}
|
package/src/commands/listCat.js
CHANGED
|
@@ -1,61 +1,33 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
1
2
|
import { apiRequest } from "../utils/request.js";
|
|
2
|
-
import {
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
24
|
-
if (res.status === 401) {
|
|
25
|
-
console.log("Session expired. Run composter login again.");
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
12
|
+
let body = null;
|
|
28
13
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
}
|
package/src/commands/login.js
CHANGED
|
@@ -1,75 +1,70 @@
|
|
|
1
1
|
import inquirer from "inquirer";
|
|
2
|
-
import
|
|
2
|
+
import { safeFetch } from "../utils/safeFetch.js";
|
|
3
3
|
import { saveSession } from "../utils/session.js";
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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
|
|
14
|
+
const BASE_URL = `${process.env.BASE_URL}/auth`;
|
|
13
15
|
|
|
14
16
|
export async function login() {
|
|
15
|
-
console.log(
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
}
|
package/src/commands/mkcat.js
CHANGED
|
@@ -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
|
-
|
|
13
|
-
"Invalid category name. It must be non-empty, without spaces, and
|
|
12
|
+
log.warn(
|
|
13
|
+
"Invalid category name. It must be non-empty, without spaces, and at most 10 characters."
|
|
14
14
|
);
|
|
15
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
return;
|
|
33
|
+
log.info(`Category '${categoryName}' created successfully!`);
|
|
34
|
+
process.exit(0);
|
|
58
35
|
}
|
|
59
36
|
|
|
60
|
-
//
|
|
37
|
+
// handling server sent errors, like duplicate category
|
|
61
38
|
const msg =
|
|
62
|
-
body
|
|
63
|
-
body?.message ||
|
|
64
|
-
JSON.stringify(body) ||
|
|
39
|
+
(body && (body.error || body.message || JSON.stringify(body))) ||
|
|
65
40
|
`HTTP ${res.status}`;
|
|
66
41
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
console.log("Network or unexpected error:", err.message);
|
|
70
|
-
}
|
|
42
|
+
log.error(`Failed to create category: ${msg}`);
|
|
43
|
+
process.exit(1);
|
|
71
44
|
}
|
package/src/commands/pull.js
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
68
|
-
|
|
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
|
-
//
|
|
77
|
+
// CHECK DEPENDENCIES
|
|
97
78
|
if (component.dependencies && Object.keys(component.dependencies).length > 0) {
|
|
98
79
|
checkDependencies(component.dependencies);
|
|
99
80
|
}
|
|
100
81
|
|
|
101
|
-
|
|
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
|
-
|
|
117
|
-
Object.entries(requiredDeps).forEach(([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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
113
|
+
log.info("All dependencies are already installed.");
|
|
137
114
|
}
|
|
138
115
|
} catch (e) {
|
|
139
116
|
// Ignore JSON parse errors
|
package/src/commands/push.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
29
|
-
.description("
|
|
30
|
-
.action((
|
|
31
|
-
|
|
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);
|