@cruxhive/cli 0.3.0 → 0.3.1
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/lib/init.js +83 -45
- package/lib/ui.js +13 -6
- package/package.json +2 -2
package/lib/init.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const { spawnSync } = require("child_process");
|
|
4
|
-
const { mkdirSync, writeFileSync, existsSync, readFileSync } = require("fs");
|
|
5
|
-
const { join } = require("path");
|
|
6
|
-
const { createInterface } = require("readline");
|
|
4
|
+
const { mkdirSync, writeFileSync, existsSync, readFileSync, symlinkSync } = require("fs");
|
|
5
|
+
const { join, dirname } = require("path");
|
|
7
6
|
|
|
8
7
|
const CONTEXT_TEMPLATE = (projectName, date) => `---
|
|
9
8
|
type: fact
|
|
@@ -44,12 +43,6 @@ source: human
|
|
|
44
43
|
| **Personal** | \`~/.cruxhive/personal/\` | Developer preferences — never shared |
|
|
45
44
|
`;
|
|
46
45
|
|
|
47
|
-
const MCP_ENTRY = {
|
|
48
|
-
command: "uvx",
|
|
49
|
-
args: ["cruxhive-mcp"],
|
|
50
|
-
type: "stdio",
|
|
51
|
-
};
|
|
52
|
-
|
|
53
46
|
function ok(msg) { console.log(` \x1b[32m✓\x1b[0m ${msg}`); }
|
|
54
47
|
function info(msg) { console.log(` \x1b[36m·\x1b[0m ${msg}`); }
|
|
55
48
|
function warn(msg) { console.log(` \x1b[33m!\x1b[0m ${msg}`); }
|
|
@@ -59,23 +52,44 @@ function hasBin(name) {
|
|
|
59
52
|
return spawnSync(name, ["--version"], { stdio: "pipe" }).status === 0;
|
|
60
53
|
}
|
|
61
54
|
|
|
55
|
+
// ─── install cruxhive-mcp ──────────────────────────────────────────────────
|
|
56
|
+
|
|
62
57
|
function installMcp() {
|
|
58
|
+
// Skip if already on PATH
|
|
59
|
+
if (hasBin("cruxhive-mcp")) {
|
|
60
|
+
info("cruxhive-mcp already installed — skipped");
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
63
64
|
if (hasBin("uv")) {
|
|
64
|
-
const r = spawnSync("uv", ["
|
|
65
|
-
if (r.status !== 0) throw new Error("uv
|
|
66
|
-
return "uv";
|
|
65
|
+
const r = spawnSync("uv", ["tool", "install", "cruxhive-mcp"], { stdio: "inherit" });
|
|
66
|
+
if (r.status !== 0) throw new Error("uv tool install cruxhive-mcp failed");
|
|
67
|
+
return "uv tool";
|
|
67
68
|
}
|
|
69
|
+
|
|
68
70
|
if (hasBin("pip3") || hasBin("pip")) {
|
|
69
71
|
const pip = hasBin("pip3") ? "pip3" : "pip";
|
|
70
72
|
const r = spawnSync(pip, ["install", "cruxhive-mcp"], { stdio: "inherit" });
|
|
71
73
|
if (r.status !== 0) throw new Error(`${pip} install cruxhive-mcp failed`);
|
|
74
|
+
warn(`Installed via ${pip} — cruxhive-mcp binary may not be on PATH.`);
|
|
75
|
+
warn(`For a cleaner install: uv tool install cruxhive-mcp (https://docs.astral.sh/uv/)`);
|
|
72
76
|
return pip;
|
|
73
77
|
}
|
|
78
|
+
|
|
74
79
|
throw new Error(
|
|
75
|
-
"Neither uv nor pip found
|
|
80
|
+
"Neither uv nor pip found.\nInstall uv: curl -LsSf https://astral.sh/uv/install.sh | sh"
|
|
76
81
|
);
|
|
77
82
|
}
|
|
78
83
|
|
|
84
|
+
function mcpEntry() {
|
|
85
|
+
// If cruxhive-mcp binary is on PATH, use it directly (uv tool install path)
|
|
86
|
+
if (hasBin("cruxhive-mcp")) return { command: "cruxhive-mcp", type: "stdio" };
|
|
87
|
+
// Fallback: let uvx fetch it from PyPI at runtime
|
|
88
|
+
return { command: "uvx", args: ["cruxhive-mcp"], type: "stdio" };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─── wire .mcp.json ────────────────────────────────────────────────────────
|
|
92
|
+
|
|
79
93
|
function wireMcp(cwd) {
|
|
80
94
|
const mcpPath = join(cwd, ".mcp.json");
|
|
81
95
|
let cfg = existsSync(mcpPath)
|
|
@@ -89,50 +103,75 @@ function wireMcp(cwd) {
|
|
|
89
103
|
return;
|
|
90
104
|
}
|
|
91
105
|
|
|
92
|
-
cfg.mcpServers.cruxhive =
|
|
106
|
+
cfg.mcpServers.cruxhive = mcpEntry();
|
|
93
107
|
writeFileSync(mcpPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
94
108
|
ok("cruxhive-mcp registered in .mcp.json");
|
|
95
109
|
}
|
|
96
110
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
111
|
+
// ─── wire AI tool context files ────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
const CONTEXT_REL = ".llm/CONTEXT.md";
|
|
100
114
|
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
function trySymlink(target, linkPath, label) {
|
|
116
|
+
if (existsSync(linkPath)) {
|
|
117
|
+
info(`${label} already exists — skipped`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
mkdirSync(dirname(linkPath), { recursive: true });
|
|
122
|
+
symlinkSync(target, linkPath);
|
|
123
|
+
ok(`${label} → ${CONTEXT_REL} (symlink)`);
|
|
124
|
+
} catch {
|
|
125
|
+
warn(`Could not create ${label} symlink — create it manually: ln -s ${CONTEXT_REL} ${linkPath}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function patchOrSymlink(filePath, label) {
|
|
130
|
+
if (existsSync(filePath)) {
|
|
131
|
+
const content = readFileSync(filePath, "utf8");
|
|
103
132
|
if (content.includes("CONTEXT.md")) {
|
|
104
|
-
info(
|
|
133
|
+
info(`${label} already references CONTEXT.md`);
|
|
105
134
|
return;
|
|
106
135
|
}
|
|
107
|
-
writeFileSync(
|
|
108
|
-
ok(
|
|
136
|
+
writeFileSync(filePath, content.trimEnd() + "\n\n<!-- CruxHive canonical context: .llm/CONTEXT.md -->\n");
|
|
137
|
+
ok(`${label} patched with CONTEXT.md reference`);
|
|
109
138
|
} else {
|
|
110
|
-
|
|
111
|
-
// Create a thin symlink — requires filesystem support
|
|
112
|
-
try {
|
|
113
|
-
require("fs").symlinkSync(target, claudePath);
|
|
114
|
-
ok("CLAUDE.md → .llm/CONTEXT.md (symlink)");
|
|
115
|
-
} catch {
|
|
116
|
-
warn("Could not create CLAUDE.md symlink — copy .llm/CONTEXT.md to CLAUDE.md manually");
|
|
117
|
-
}
|
|
139
|
+
trySymlink(CONTEXT_REL, filePath, label);
|
|
118
140
|
}
|
|
119
141
|
}
|
|
120
142
|
|
|
121
|
-
|
|
143
|
+
function wireAiTools(cwd) {
|
|
144
|
+
const tools = [
|
|
145
|
+
// Claude Code
|
|
146
|
+
{ check: () => true, wire: () => patchOrSymlink(join(cwd, "CLAUDE.md"), "CLAUDE.md") },
|
|
147
|
+
// OpenCode
|
|
148
|
+
{ check: () => true, wire: () => trySymlink(CONTEXT_REL, join(cwd, "AGENT.md"), "AGENT.md") },
|
|
149
|
+
// Cursor
|
|
150
|
+
{ check: () => true, wire: () => trySymlink(CONTEXT_REL, join(cwd, ".cursor/rules/cruxhive.mdc"), ".cursor/rules/cruxhive.mdc") },
|
|
151
|
+
// Windsurf
|
|
152
|
+
{ check: () => true, wire: () => trySymlink(CONTEXT_REL, join(cwd, ".windsurfRules"), ".windsurfRules") },
|
|
153
|
+
// Gemini CLI
|
|
154
|
+
{ check: () => true, wire: () => trySymlink(CONTEXT_REL, join(cwd, "GEMINI.md"), "GEMINI.md") },
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
for (const t of tools) t.wire();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ─── main ──────────────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
async function init(_args) {
|
|
122
163
|
const cwd = process.cwd();
|
|
123
164
|
const date = new Date().toISOString().split("T")[0];
|
|
124
165
|
const projectName = cwd.split("/").pop();
|
|
125
166
|
|
|
126
167
|
console.log(`\n\x1b[1mCruxHive init\x1b[0m — ${projectName}`);
|
|
127
168
|
|
|
128
|
-
//
|
|
169
|
+
// 1. .llm/ structure
|
|
129
170
|
step("1/4 Creating .llm/ structure");
|
|
130
|
-
|
|
131
|
-
const dirs = [".llm", ".llm/plans", ".llm/context", ".llm/memory"];
|
|
132
|
-
for (const dir of dirs) {
|
|
171
|
+
for (const dir of [".llm", ".llm/plans", ".llm/pending", ".llm/context", ".llm/memory"]) {
|
|
133
172
|
mkdirSync(join(cwd, dir), { recursive: true });
|
|
134
173
|
}
|
|
135
|
-
ok("directories: .llm/ .llm/plans/ .llm/context/ .llm/memory/");
|
|
174
|
+
ok("directories: .llm/ .llm/plans/ .llm/pending/ .llm/context/ .llm/memory/");
|
|
136
175
|
|
|
137
176
|
const contextPath = join(cwd, ".llm", "CONTEXT.md");
|
|
138
177
|
if (!existsSync(contextPath)) {
|
|
@@ -150,29 +189,28 @@ async function init(args) {
|
|
|
150
189
|
info(".llm/plans/active.md already exists — skipped");
|
|
151
190
|
}
|
|
152
191
|
|
|
153
|
-
//
|
|
192
|
+
// 2. install cruxhive-mcp
|
|
154
193
|
step("2/4 Installing cruxhive-mcp");
|
|
155
194
|
const installer = installMcp();
|
|
156
|
-
ok(`cruxhive-mcp installed via ${installer}`);
|
|
195
|
+
if (installer) ok(`cruxhive-mcp installed via ${installer}`);
|
|
157
196
|
|
|
158
|
-
//
|
|
197
|
+
// 3. wire .mcp.json
|
|
159
198
|
step("3/4 Wiring .mcp.json");
|
|
160
199
|
wireMcp(cwd);
|
|
161
200
|
|
|
162
|
-
//
|
|
201
|
+
// 4. wire AI tools
|
|
163
202
|
step("4/4 Wiring AI tools");
|
|
164
|
-
|
|
203
|
+
wireAiTools(cwd);
|
|
165
204
|
|
|
166
|
-
// ─── done ─────────────────────────────────────────────────────────────────
|
|
167
205
|
console.log(`
|
|
168
206
|
\x1b[32m✓ CruxHive initialized in ${projectName}\x1b[0m
|
|
169
207
|
|
|
170
208
|
Next steps:
|
|
171
209
|
1. Edit \x1b[36m.llm/CONTEXT.md\x1b[0m — describe your project, stack, and conventions
|
|
172
|
-
2.
|
|
173
|
-
3.
|
|
210
|
+
2. Run \x1b[36mcruxhive index\x1b[0m to build the search index
|
|
211
|
+
3. Reload your AI tool — MCP tools are now available
|
|
174
212
|
|
|
175
|
-
|
|
213
|
+
Docs: https://cruxhive.com/guide.html
|
|
176
214
|
`);
|
|
177
215
|
}
|
|
178
216
|
|
package/lib/ui.js
CHANGED
|
@@ -15,9 +15,13 @@ function openBrowser(url) {
|
|
|
15
15
|
spawnSync(cmd, [url], { stdio: "ignore" });
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
function uvicornCmd() {
|
|
19
|
+
// Prefer the uvicorn inside the uv-managed cruxhive-mcp tool environment
|
|
20
|
+
const uv = spawnSync("uv", ["tool", "run", "--from", "cruxhive-mcp", "uvicorn", "--version"], { stdio: "pipe" });
|
|
21
|
+
if (uv.status === 0) return ["uv", "tool", "run", "--from", "cruxhive-mcp", "uvicorn"];
|
|
22
|
+
const direct = spawnSync("uvicorn", ["--version"], { stdio: "pipe" });
|
|
23
|
+
if (direct.status === 0) return ["uvicorn"];
|
|
24
|
+
return null;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
async function ui(args) {
|
|
@@ -31,18 +35,21 @@ async function ui(args) {
|
|
|
31
35
|
console.log(` Run: \x1b[36mcruxhive index\x1b[0m (or context_index MCP tool)\n`);
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
const uvcmd = uvicornCmd();
|
|
39
|
+
if (!uvcmd) {
|
|
35
40
|
err("uvicorn not found — install the [ui] extra:");
|
|
36
|
-
console.log(`\n \x1b[
|
|
41
|
+
console.log(`\n \x1b[36muv tool install "cruxhive-mcp[ui]"\x1b[0m\n`);
|
|
37
42
|
process.exit(1);
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
const url = `http://localhost:${PORT}`;
|
|
41
46
|
info(`Starting approval queue at ${url}`);
|
|
42
47
|
|
|
48
|
+
const [bin, ...binArgs] = uvcmd;
|
|
43
49
|
const proc = spawn(
|
|
44
|
-
|
|
50
|
+
bin,
|
|
45
51
|
[
|
|
52
|
+
...binArgs,
|
|
46
53
|
"cruxhive_mcp.ui:app",
|
|
47
54
|
"--host", "0.0.0.0",
|
|
48
55
|
"--port", String(PORT),
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cruxhive/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "CruxHive — team AI knowledge governance layer",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "https://github.com/cruxhive/cruxhive"
|
|
8
|
+
"url": "git+https://github.com/cruxhive/cruxhive.git"
|
|
9
9
|
},
|
|
10
10
|
"homepage": "https://cruxhive.com",
|
|
11
11
|
"keywords": ["ai", "mcp", "context", "knowledge", "claude", "cursor", "llm"],
|