@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.
Files changed (3) hide show
  1. package/lib/init.js +83 -45
  2. package/lib/ui.js +13 -6
  3. 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", ["pip", "install", "cruxhive-mcp"], { stdio: "inherit" });
65
- if (r.status !== 0) throw new Error("uv pip install cruxhive-mcp failed");
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. Install uv (https://docs.astral.sh/uv/) or pip first."
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 = MCP_ENTRY;
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
- function wireClaudeMd(cwd) {
98
- const claudePath = join(cwd, "CLAUDE.md");
99
- const contextPath = ".llm/CONTEXT.md";
111
+ // ─── wire AI tool context files ────────────────────────────────────────────
112
+
113
+ const CONTEXT_REL = ".llm/CONTEXT.md";
100
114
 
101
- if (existsSync(claudePath)) {
102
- const content = readFileSync(claudePath, "utf8");
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("CLAUDE.md already references CONTEXT.md");
133
+ info(`${label} already references CONTEXT.md`);
105
134
  return;
106
135
  }
107
- writeFileSync(claudePath, content.trimEnd() + "\n\n<!-- CruxHive canonical context: .llm/CONTEXT.md -->\n");
108
- ok("CLAUDE.md patched with CONTEXT.md reference");
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
- const target = contextPath;
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
- async function init(args) {
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
- // ─── .llm/ structure ─────────────────────────────────────────────────────
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
- // ─── install cruxhive-mcp ─────────────────────────────────────────────────
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
- // ─── wire .mcp.json ───────────────────────────────────────────────────────
197
+ // 3. wire .mcp.json
159
198
  step("3/4 Wiring .mcp.json");
160
199
  wireMcp(cwd);
161
200
 
162
- // ─── wire AI tool context files ───────────────────────────────────────────
201
+ // 4. wire AI tools
163
202
  step("4/4 Wiring AI tools");
164
- wireClaudeMd(cwd);
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. Reload your AI tool the cruxhive-mcp server is now available
173
- 3. Run \x1b[36mcruxhive health\x1b[0m to see knowledge base status
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
- MCP tools now available: context_radar, context_next_slice, context_write_plan, context_sync_memory
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 checkUvicorn() {
19
- const r = spawnSync("uvicorn", ["--version"], { stdio: "pipe" });
20
- return r.status === 0;
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
- if (!checkUvicorn()) {
38
+ const uvcmd = uvicornCmd();
39
+ if (!uvcmd) {
35
40
  err("uvicorn not found — install the [ui] extra:");
36
- console.log(`\n \x1b[36mpip install "cruxhive-mcp[ui]"\x1b[0m\n`);
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
- "uvicorn",
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.0",
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"],