@morebeans/cli 2.0.0

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 (2) hide show
  1. package/index.ts +382 -0
  2. package/package.json +46 -0
package/index.ts ADDED
@@ -0,0 +1,382 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * BEANS CLI - Setup and configure BEANS for your project
4
+ *
5
+ * Usage:
6
+ * bunx beans init # Initialize BEANS in current project
7
+ * bunx beans config # Configure API keys interactively
8
+ * bunx beans config --valyu # Set Valyu API key
9
+ * bunx beans doctor # Check installation status
10
+ * bunx beans upgrade # Update to latest version
11
+ */
12
+
13
+ import { $ } from "bun";
14
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
15
+ import { join, resolve } from "path";
16
+ import { homedir } from "os";
17
+
18
+ const VERSION = "2.0.0";
19
+ const BEANS_HOME = join(homedir(), ".beans");
20
+ const BEANS_CONFIG = join(BEANS_HOME, "config.json");
21
+
22
+ // Colors
23
+ const c = {
24
+ reset: "\x1b[0m",
25
+ bold: "\x1b[1m",
26
+ red: "\x1b[31m",
27
+ green: "\x1b[32m",
28
+ yellow: "\x1b[33m",
29
+ blue: "\x1b[34m",
30
+ cyan: "\x1b[36m",
31
+ dim: "\x1b[2m",
32
+ };
33
+
34
+ function log(msg: string) { console.log(msg); }
35
+ function info(msg: string) { console.log(`${c.blue}ℹ${c.reset} ${msg}`); }
36
+ function success(msg: string) { console.log(`${c.green}✓${c.reset} ${msg}`); }
37
+ function warn(msg: string) { console.log(`${c.yellow}⚠${c.reset} ${msg}`); }
38
+ function error(msg: string) { console.log(`${c.red}✗${c.reset} ${msg}`); }
39
+
40
+ interface BeansConfig {
41
+ valyu_api_key?: string;
42
+ github_token?: string;
43
+ default_model?: string;
44
+ installed_at?: string[];
45
+ }
46
+
47
+ function loadConfig(): BeansConfig {
48
+ if (!existsSync(BEANS_CONFIG)) return {};
49
+ try {
50
+ return JSON.parse(readFileSync(BEANS_CONFIG, "utf-8"));
51
+ } catch {
52
+ return {};
53
+ }
54
+ }
55
+
56
+ function saveConfig(config: BeansConfig) {
57
+ mkdirSync(BEANS_HOME, { recursive: true });
58
+ writeFileSync(BEANS_CONFIG, JSON.stringify(config, null, 2));
59
+ }
60
+
61
+ function maskKey(key: string): string {
62
+ if (!key || key.length < 8) return "***";
63
+ return key.slice(0, 4) + "..." + key.slice(-4);
64
+ }
65
+
66
+ async function prompt(question: string, hidden = false): Promise<string> {
67
+ process.stdout.write(`${c.cyan}?${c.reset} ${question} `);
68
+
69
+ if (hidden) {
70
+ // For hidden input, we'd need readline or similar
71
+ // For now, just read normally with a note
72
+ process.stdout.write(`${c.dim}(input hidden)${c.reset} `);
73
+ }
74
+
75
+ const reader = Bun.stdin.stream().getReader();
76
+ const { value } = await reader.read();
77
+ reader.releaseLock();
78
+
79
+ return new TextDecoder().decode(value).trim();
80
+ }
81
+
82
+ // ═══════════════════════════════════════════════════════════════════════════════
83
+ // COMMANDS
84
+ // ═══════════════════════════════════════════════════════════════════════════════
85
+
86
+ async function cmdInit() {
87
+ log(`\n${c.bold}${c.blue}🫘 BEANS Setup${c.reset}\n`);
88
+
89
+ const cwd = process.cwd();
90
+ const config = loadConfig();
91
+
92
+ // Check if already initialized
93
+ if (existsSync(join(cwd, ".claude/plugins/beans"))) {
94
+ warn("BEANS already installed in this project");
95
+ info("Run 'beans doctor' to check status");
96
+ return;
97
+ }
98
+
99
+ // Create directories
100
+ info("Creating directories...");
101
+ const dirs = [
102
+ ".claude/plugins",
103
+ ".claude/agents",
104
+ ".beads/analysis",
105
+ ".beads/context",
106
+ ".beads/cache",
107
+ ".beads/logs",
108
+ ];
109
+
110
+ for (const dir of dirs) {
111
+ mkdirSync(join(cwd, dir), { recursive: true });
112
+ }
113
+ success("Directories created");
114
+
115
+ // Find plugin source
116
+ const pluginSources = [
117
+ join(cwd, "submodules/beans/plugin"),
118
+ join(homedir(), ".beans/plugin"),
119
+ join(import.meta.dir, "../plugin"),
120
+ ];
121
+
122
+ let pluginSource: string | null = null;
123
+ for (const src of pluginSources) {
124
+ if (existsSync(src)) {
125
+ pluginSource = src;
126
+ break;
127
+ }
128
+ }
129
+
130
+ if (!pluginSource) {
131
+ error("Could not find BEANS plugin source");
132
+ info("Clone the beans repo first:");
133
+ log(` git clone https://github.com/shinyobjectz/beans.git ~/.beans`);
134
+ return;
135
+ }
136
+
137
+ // Symlink plugin
138
+ info("Installing plugin...");
139
+ const pluginDest = join(cwd, ".claude/plugins/beans");
140
+
141
+ try {
142
+ await $`ln -sf ${pluginSource} ${pluginDest}`;
143
+ success(`Plugin linked from ${pluginSource}`);
144
+ } catch (e) {
145
+ error(`Failed to link plugin: ${e}`);
146
+ return;
147
+ }
148
+
149
+ // Copy agents
150
+ info("Installing agents...");
151
+ const agentsSrc = join(pluginSource, "agents");
152
+ if (existsSync(agentsSrc)) {
153
+ await $`cp -r ${agentsSrc}/* ${join(cwd, ".claude/agents/")}`.nothrow();
154
+ success("Agents installed");
155
+ }
156
+
157
+ // Track installation
158
+ config.installed_at = config.installed_at || [];
159
+ if (!config.installed_at.includes(cwd)) {
160
+ config.installed_at.push(cwd);
161
+ }
162
+ saveConfig(config);
163
+
164
+ // Check for API keys
165
+ log("");
166
+ if (!config.valyu_api_key) {
167
+ warn("Valyu API key not configured (optional, for research)");
168
+ info("Run 'beans config --valyu' to set it");
169
+ } else {
170
+ success(`Valyu API key: ${maskKey(config.valyu_api_key)}`);
171
+ }
172
+
173
+ log(`\n${c.green}${c.bold}✅ BEANS initialized!${c.reset}\n`);
174
+ log("Next steps:");
175
+ log(` ${c.cyan}beans doctor${c.reset} # Verify setup`);
176
+ log(` ${c.cyan}beans config${c.reset} # Configure API keys`);
177
+ log(` ${c.cyan}/beans${c.reset} # In Claude Code: start building`);
178
+ log("");
179
+ }
180
+
181
+ async function cmdConfig(args: string[]) {
182
+ log(`\n${c.bold}${c.blue}🔧 BEANS Configuration${c.reset}\n`);
183
+
184
+ const config = loadConfig();
185
+
186
+ if (args.includes("--valyu") || args.includes("-v")) {
187
+ log("Enter your Valyu API key (get one at https://valyu.ai):");
188
+ const key = await prompt("Valyu API Key:", true);
189
+ if (key) {
190
+ config.valyu_api_key = key;
191
+ saveConfig(config);
192
+ success(`Valyu API key saved: ${maskKey(key)}`);
193
+ }
194
+ return;
195
+ }
196
+
197
+ if (args.includes("--github") || args.includes("-g")) {
198
+ log("Enter your GitHub token (for PR creation):");
199
+ const key = await prompt("GitHub Token:", true);
200
+ if (key) {
201
+ config.github_token = key;
202
+ saveConfig(config);
203
+ success(`GitHub token saved: ${maskKey(key)}`);
204
+ }
205
+ return;
206
+ }
207
+
208
+ if (args.includes("--show")) {
209
+ log("Current configuration:");
210
+ log(` Config file: ${BEANS_CONFIG}`);
211
+ log(` Valyu API Key: ${config.valyu_api_key ? maskKey(config.valyu_api_key) : c.dim + "(not set)" + c.reset}`);
212
+ log(` GitHub Token: ${config.github_token ? maskKey(config.github_token) : c.dim + "(not set)" + c.reset}`);
213
+ log(` Installed at: ${(config.installed_at || []).length} projects`);
214
+ return;
215
+ }
216
+
217
+ // Interactive config
218
+ log("Configure BEANS (press Enter to skip):\n");
219
+
220
+ // Valyu
221
+ const currentValyu = config.valyu_api_key ? ` [${maskKey(config.valyu_api_key)}]` : "";
222
+ log(`Valyu API Key${currentValyu}:`);
223
+ const valyu = await prompt("", true);
224
+ if (valyu) config.valyu_api_key = valyu;
225
+
226
+ // GitHub
227
+ const currentGithub = config.github_token ? ` [${maskKey(config.github_token)}]` : "";
228
+ log(`GitHub Token${currentGithub}:`);
229
+ const github = await prompt("", true);
230
+ if (github) config.github_token = github;
231
+
232
+ saveConfig(config);
233
+ success("Configuration saved");
234
+
235
+ log(`\nConfig stored at: ${c.dim}${BEANS_CONFIG}${c.reset}`);
236
+ }
237
+
238
+ async function cmdDoctor() {
239
+ log(`\n${c.bold}${c.blue}🩺 BEANS Doctor${c.reset}\n`);
240
+
241
+ const cwd = process.cwd();
242
+ const config = loadConfig();
243
+ let issues = 0;
244
+
245
+ // Check CLI tools
246
+ log(`${c.bold}CLI Tools${c.reset}`);
247
+ const tools = ["bd", "ast-grep", "repomix"];
248
+ for (const tool of tools) {
249
+ try {
250
+ await $`which ${tool}`.quiet();
251
+ success(tool);
252
+ } catch {
253
+ warn(`${tool} not found (optional)`);
254
+ }
255
+ }
256
+
257
+ // Check directories
258
+ log(`\n${c.bold}Directories${c.reset}`);
259
+ const dirs = [".claude/plugins/beans", ".beads", ".claude/agents"];
260
+ for (const dir of dirs) {
261
+ if (existsSync(join(cwd, dir))) {
262
+ success(dir);
263
+ } else {
264
+ error(dir);
265
+ issues++;
266
+ }
267
+ }
268
+
269
+ // Check config
270
+ log(`\n${c.bold}Configuration${c.reset}`);
271
+ log(` Config: ${BEANS_CONFIG}`);
272
+ if (config.valyu_api_key) {
273
+ success(`Valyu API Key: ${maskKey(config.valyu_api_key)}`);
274
+ } else {
275
+ warn("Valyu API Key: not set (run 'beans config --valyu')");
276
+ }
277
+ if (config.github_token) {
278
+ success(`GitHub Token: ${maskKey(config.github_token)}`);
279
+ } else {
280
+ warn("GitHub Token: not set (run 'beans config --github')");
281
+ }
282
+
283
+ // Check plugin
284
+ log(`\n${c.bold}Plugin${c.reset}`);
285
+ const pluginPath = join(cwd, ".claude/plugins/beans");
286
+ if (existsSync(pluginPath)) {
287
+ const pluginJson = join(pluginPath, "plugin.json");
288
+ if (existsSync(pluginJson)) {
289
+ try {
290
+ const plugin = JSON.parse(readFileSync(pluginJson, "utf-8"));
291
+ success(`BEANS v${plugin.version || "unknown"}`);
292
+ } catch {
293
+ warn("plugin.json invalid");
294
+ }
295
+ }
296
+
297
+ // Count components
298
+ const commands = join(pluginPath, "commands");
299
+ const agents = join(pluginPath, "agents");
300
+ if (existsSync(commands)) {
301
+ const bdCount = existsSync(join(commands, "bd")) ?
302
+ (await $`ls ${join(commands, "bd")}`.text()).split("\n").filter(Boolean).length : 0;
303
+ const ralphCount = existsSync(join(commands, "ralph")) ?
304
+ (await $`ls ${join(commands, "ralph")}`.text()).split("\n").filter(Boolean).length : 0;
305
+ log(` Commands: ${bdCount} bd, ${ralphCount} ralph`);
306
+ }
307
+ if (existsSync(agents)) {
308
+ const agentCount = (await $`ls ${agents}`.text()).split("\n").filter(Boolean).length;
309
+ log(` Agents: ${agentCount}`);
310
+ }
311
+ } else {
312
+ error("Plugin not installed");
313
+ issues++;
314
+ }
315
+
316
+ log("");
317
+ if (issues === 0) {
318
+ log(`${c.green}${c.bold}✅ BEANS is healthy!${c.reset}`);
319
+ } else {
320
+ log(`${c.yellow}⚠ ${issues} issue(s) found. Run 'beans init' to fix.${c.reset}`);
321
+ }
322
+ log("");
323
+ }
324
+
325
+ async function cmdHelp() {
326
+ log(`
327
+ ${c.bold}${c.blue}🫘 BEANS CLI v${VERSION}${c.reset}
328
+
329
+ ${c.bold}Usage:${c.reset}
330
+ beans <command> [options]
331
+
332
+ ${c.bold}Commands:${c.reset}
333
+ ${c.cyan}init${c.reset} Initialize BEANS in current project
334
+ ${c.cyan}config${c.reset} Configure API keys interactively
335
+ ${c.cyan}config --valyu${c.reset} Set Valyu API key
336
+ ${c.cyan}config --github${c.reset} Set GitHub token
337
+ ${c.cyan}config --show${c.reset} Show current configuration
338
+ ${c.cyan}doctor${c.reset} Check installation status
339
+ ${c.cyan}help${c.reset} Show this help message
340
+
341
+ ${c.bold}In Claude Code:${c.reset}
342
+ ${c.cyan}/beans${c.reset} List ready issues
343
+ ${c.cyan}/beans "Add feature"${c.reset} Full autonomous flow
344
+ ${c.cyan}/beans task-001${c.reset} Build existing issue
345
+ ${c.cyan}/bd:create "Bug"${c.reset} Create beads issue
346
+ ${c.cyan}/ralph:start${c.reset} Start spec workflow
347
+
348
+ ${c.bold}Environment:${c.reset}
349
+ Config stored at: ${c.dim}~/.beans/config.json${c.reset}
350
+
351
+ ${c.bold}More info:${c.reset}
352
+ https://github.com/shinyobjectz/beans
353
+ `);
354
+ }
355
+
356
+ // ═══════════════════════════════════════════════════════════════════════════════
357
+ // MAIN
358
+ // ═══════════════════════════════════════════════════════════════════════════════
359
+
360
+ const [command, ...args] = process.argv.slice(2);
361
+
362
+ switch (command) {
363
+ case "init":
364
+ await cmdInit();
365
+ break;
366
+ case "config":
367
+ await cmdConfig(args);
368
+ break;
369
+ case "doctor":
370
+ await cmdDoctor();
371
+ break;
372
+ case "help":
373
+ case "--help":
374
+ case "-h":
375
+ case undefined:
376
+ await cmdHelp();
377
+ break;
378
+ default:
379
+ error(`Unknown command: ${command}`);
380
+ await cmdHelp();
381
+ process.exit(1);
382
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@morebeans/cli",
3
+ "version": "2.0.0",
4
+ "description": "BEANS CLI - Setup and configure autonomous development for Claude Code",
5
+ "type": "module",
6
+ "main": "index.ts",
7
+ "bin": {
8
+ "beans": "./index.ts"
9
+ },
10
+ "scripts": {
11
+ "start": "bun run index.ts"
12
+ },
13
+ "files": [
14
+ "index.ts",
15
+ "package.json"
16
+ ],
17
+ "keywords": [
18
+ "beans",
19
+ "claude-code",
20
+ "claude",
21
+ "ai",
22
+ "autonomous",
23
+ "development",
24
+ "beads",
25
+ "ralph",
26
+ "cli"
27
+ ],
28
+ "author": "Project.Social",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/shinyobjectz/beans.git",
33
+ "directory": "cli"
34
+ },
35
+ "homepage": "https://github.com/shinyobjectz/beans#readme",
36
+ "bugs": {
37
+ "url": "https://github.com/shinyobjectz/beans/issues"
38
+ },
39
+ "engines": {
40
+ "node": ">=18",
41
+ "bun": ">=1.0"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ }
46
+ }