@morebeans/cli 2.3.5 → 2.5.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 +199 -21
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -7,15 +7,16 @@
7
7
  * bunx beans config # Configure API keys interactively
8
8
  * bunx beans config --valyu # Set Valyu API key
9
9
  * bunx beans doctor # Check installation status
10
- * bunx beans upgrade # Update to latest version
10
+ * bunx beans research # Manage research database
11
11
  */
12
12
 
13
13
  import { $ } from "bun";
14
+ import { Database } from "bun:sqlite";
14
15
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
15
16
  import { join, resolve } from "path";
16
17
  import { homedir } from "os";
17
18
 
18
- const VERSION = "2.3.5";
19
+ const VERSION = "2.5.0";
19
20
  const BEANS_HOME = join(homedir(), ".beans");
20
21
  const BEANS_CONFIG = join(BEANS_HOME, "config.json");
21
22
 
@@ -221,8 +222,12 @@ async function cmdInit() {
221
222
  const commandsDir = join(cwd, ".claude/commands");
222
223
  mkdirSync(commandsDir, { recursive: true });
223
224
 
224
- // Register the BEANS commands
225
- const beansCommands = ["beans.md", "beans-status.md", "beans-land.md", "beans-agents.md"];
225
+ // Register ALL BEANS commands (core + phase commands)
226
+ const beansCommands = [
227
+ "beans.md", "beans-status.md", "beans-land.md", "beans-agents.md",
228
+ "beans-new.md", "beans-research.md", "beans-requirements.md",
229
+ "beans-design.md", "beans-tasks.md", "beans-implement.md"
230
+ ];
226
231
  for (const cmd of beansCommands) {
227
232
  const src = join(pluginSource, "commands", cmd);
228
233
  if (existsSync(src)) {
@@ -230,13 +235,22 @@ async function cmdInit() {
230
235
  }
231
236
  }
232
237
 
238
+ // Install beans-loop script to ~/.local/bin
239
+ const loopScript = join(beansHome, "scripts/beans-loop.sh");
240
+ const localBin = join(homedir(), ".local/bin");
241
+ if (existsSync(loopScript)) {
242
+ mkdirSync(localBin, { recursive: true });
243
+ await $`cp ${loopScript} ${join(localBin, "beans-loop")}`.nothrow();
244
+ await $`chmod +x ${join(localBin, "beans-loop")}`.nothrow();
245
+ }
246
+
233
247
  // Copy settings.json if not exists
234
248
  const settingsSrc = join(pluginSource, "settings.json");
235
249
  const settingsDest = join(cwd, ".claude/settings.json");
236
250
  if (existsSync(settingsSrc) && !existsSync(settingsDest)) {
237
251
  await $`cp ${settingsSrc} ${settingsDest}`.nothrow();
238
252
  }
239
- success("Commands registered (4 BEANS commands)");
253
+ success("Commands registered (10 BEANS commands + beans-loop)");
240
254
 
241
255
  // Initialize beads issue tracker (uses .beans directory now)
242
256
  info("Initializing issue tracker...");
@@ -270,9 +284,9 @@ async function cmdInit() {
270
284
  log(`\n${c.green}${c.bold}✅ BEANS initialized!${c.reset}\n`);
271
285
  log("Next steps:");
272
286
  log(` ${c.cyan}beans doctor${c.reset} # Verify setup`);
273
- log(` ${c.cyan}beans config${c.reset} # Configure API keys`);
274
- log(` ${c.cyan}/beans${c.reset} # In Claude Code: start building`);
287
+ log(` ${c.cyan}/beans:new${c.reset} # Create new spec with PRD flow`);
275
288
  log(` ${c.cyan}/beans:agents${c.reset} # Browse 127+ specialized subagents`);
289
+ log(` ${c.cyan}beans-loop${c.reset} # Autonomous execution (bash)`);
276
290
  log("");
277
291
  }
278
292
 
@@ -510,6 +524,164 @@ async function cmdDoctor(args: string[]) {
510
524
  log("");
511
525
  }
512
526
 
527
+ // ═══════════════════════════════════════════════════════════════════════════════
528
+ // RESEARCH DATABASE
529
+ // ═══════════════════════════════════════════════════════════════════════════════
530
+
531
+ function getResearchDb() {
532
+ const dbPath = join(process.cwd(), ".beans/research.db");
533
+ if (!existsSync(join(process.cwd(), ".beans"))) {
534
+ mkdirSync(join(process.cwd(), ".beans"), { recursive: true });
535
+ }
536
+ const db = new Database(dbPath);
537
+
538
+ // Init schema
539
+ db.run(`
540
+ CREATE TABLE IF NOT EXISTS research (
541
+ id TEXT PRIMARY KEY,
542
+ issue_id TEXT,
543
+ query TEXT NOT NULL,
544
+ source TEXT NOT NULL CHECK(source IN ('valyu', 'web', 'codebase')),
545
+ title TEXT NOT NULL,
546
+ content TEXT NOT NULL,
547
+ url TEXT,
548
+ relevance REAL DEFAULT 0.5,
549
+ metadata TEXT DEFAULT '{}',
550
+ created_at TEXT DEFAULT (datetime('now')),
551
+ UNIQUE(issue_id, url, title)
552
+ )
553
+ `);
554
+
555
+ db.run(`
556
+ CREATE VIRTUAL TABLE IF NOT EXISTS research_fts USING fts5(
557
+ title, content, query, content='research', content_rowid='rowid'
558
+ )
559
+ `);
560
+
561
+ return db;
562
+ }
563
+
564
+ async function cmdResearch(args: string[]) {
565
+ const subCmd = args[0];
566
+ const db = getResearchDb();
567
+
568
+ if (subCmd === "list" || !subCmd) {
569
+ const issueId = args.find(a => a.startsWith("--issue="))?.split("=")[1];
570
+ const source = args.find(a => a.startsWith("--source="))?.split("=")[1];
571
+ const limit = parseInt(args.find(a => a.startsWith("--limit="))?.split("=")[1] || "20");
572
+
573
+ let sql = `SELECT id, issue_id, source, title, substr(content, 1, 100) as preview, relevance, created_at FROM research WHERE 1=1`;
574
+ const params: any[] = [];
575
+
576
+ if (issueId) { sql += ` AND issue_id = ?`; params.push(issueId); }
577
+ if (source) { sql += ` AND source = ?`; params.push(source); }
578
+ sql += ` ORDER BY created_at DESC LIMIT ?`;
579
+ params.push(limit);
580
+
581
+ const rows = db.query(sql).all(...params) as any[];
582
+
583
+ log(`\n${c.bold}Research Findings${c.reset} (${rows.length})\n`);
584
+
585
+ if (rows.length === 0) {
586
+ info("No research stored yet. Use Valyu MCP or /beans:research to gather findings.");
587
+ return;
588
+ }
589
+
590
+ for (const row of rows) {
591
+ const srcColor = row.source === "valyu" ? c.blue : row.source === "web" ? c.green : c.yellow;
592
+ log(`${c.dim}${row.id}${c.reset} ${srcColor}[${row.source}]${c.reset} ${row.title}`);
593
+ if (row.issue_id) log(` ${c.dim}Issue: ${row.issue_id}${c.reset}`);
594
+ log(` ${c.dim}${row.preview}...${c.reset}`);
595
+ log("");
596
+ }
597
+ return;
598
+ }
599
+
600
+ if (subCmd === "search") {
601
+ const query = args.slice(1).join(" ");
602
+ if (!query) {
603
+ error("Usage: beans research search <query>");
604
+ return;
605
+ }
606
+
607
+ const rows = db.query(`
608
+ SELECT r.id, r.issue_id, r.source, r.title, substr(r.content, 1, 200) as preview
609
+ FROM research r
610
+ JOIN research_fts fts ON r.rowid = fts.rowid
611
+ WHERE research_fts MATCH ?
612
+ ORDER BY rank LIMIT 20
613
+ `).all(query) as any[];
614
+
615
+ log(`\n${c.bold}Search: "${query}"${c.reset} (${rows.length} results)\n`);
616
+
617
+ for (const row of rows) {
618
+ const srcColor = row.source === "valyu" ? c.blue : row.source === "web" ? c.green : c.yellow;
619
+ log(`${c.dim}${row.id}${c.reset} ${srcColor}[${row.source}]${c.reset} ${row.title}`);
620
+ log(` ${c.dim}${row.preview}...${c.reset}\n`);
621
+ }
622
+ return;
623
+ }
624
+
625
+ if (subCmd === "show") {
626
+ const id = args[1];
627
+ if (!id) {
628
+ error("Usage: beans research show <id>");
629
+ return;
630
+ }
631
+
632
+ const row = db.query(`SELECT * FROM research WHERE id = ?`).get(id) as any;
633
+ if (!row) {
634
+ error(`Research ${id} not found`);
635
+ return;
636
+ }
637
+
638
+ log(`\n${c.bold}${row.title}${c.reset}`);
639
+ log(`${c.dim}ID: ${row.id} | Source: ${row.source} | Relevance: ${row.relevance}${c.reset}`);
640
+ if (row.issue_id) log(`${c.dim}Issue: ${row.issue_id}${c.reset}`);
641
+ if (row.url) log(`${c.dim}URL: ${row.url}${c.reset}`);
642
+ log(`\n${row.content}\n`);
643
+ return;
644
+ }
645
+
646
+ if (subCmd === "for") {
647
+ const issueId = args[1];
648
+ if (!issueId) {
649
+ error("Usage: beans research for <issue-id>");
650
+ return;
651
+ }
652
+
653
+ const rows = db.query(`
654
+ SELECT id, source, title, substr(content, 1, 100) as preview, relevance
655
+ FROM research WHERE issue_id = ? ORDER BY relevance DESC
656
+ `).all(issueId) as any[];
657
+
658
+ log(`\n${c.bold}Research for ${issueId}${c.reset} (${rows.length} findings)\n`);
659
+
660
+ for (const row of rows) {
661
+ log(`${c.dim}${row.id}${c.reset} [${row.source}] ${row.title} (${(row.relevance * 100).toFixed(0)}%)`);
662
+ }
663
+ log("");
664
+ return;
665
+ }
666
+
667
+ // Default help
668
+ log(`
669
+ ${c.bold}beans research${c.reset} - Manage research database
670
+
671
+ ${c.bold}Commands:${c.reset}
672
+ ${c.cyan}list${c.reset} List all research (--issue=X, --source=valyu|web|codebase)
673
+ ${c.cyan}search <query>${c.reset} Full-text search
674
+ ${c.cyan}show <id>${c.reset} Show full research entry
675
+ ${c.cyan}for <issue-id>${c.reset} List research linked to an issue
676
+
677
+ ${c.bold}Storage:${c.reset}
678
+ Database at: ${c.dim}.beans/research.db${c.reset}
679
+
680
+ Research is auto-stored by Valyu MCP when you pass issue_id.
681
+ Use research_store tool to manually add web/codebase findings.
682
+ `);
683
+ }
684
+
513
685
  async function cmdHelp() {
514
686
  log(`
515
687
  ${c.bold}${c.blue}🫘 BEANS CLI v${VERSION}${c.reset}
@@ -519,24 +691,27 @@ ${c.bold}Usage:${c.reset}
519
691
 
520
692
  ${c.bold}Commands:${c.reset}
521
693
  ${c.cyan}init${c.reset} Initialize BEANS in current project
522
- ${c.cyan}config${c.reset} Configure API keys interactively
523
- ${c.cyan}config --valyu${c.reset} Set Valyu API key
524
- ${c.cyan}config --github${c.reset} Set GitHub token
525
- ${c.cyan}config --show${c.reset} Show current configuration
694
+ ${c.cyan}config${c.reset} Configure API keys
526
695
  ${c.cyan}doctor${c.reset} Check installation status
527
- ${c.cyan}doctor --fix${c.reset} Auto-fix issues (install tools, run bd doctor)
528
- ${c.cyan}help${c.reset} Show this help message
696
+ ${c.cyan}research${c.reset} Manage research database
697
+ ${c.cyan}help${c.reset} Show this help
698
+
699
+ ${c.bold}Research:${c.reset}
700
+ ${c.cyan}research list${c.reset} List stored research
701
+ ${c.cyan}research search${c.reset} Full-text search findings
702
+ ${c.cyan}research for${c.reset} Get research for an issue
703
+ ${c.cyan}research show${c.reset} Show full entry
529
704
 
530
705
  ${c.bold}In Claude Code:${c.reset}
531
- ${c.cyan}/beans${c.reset} List ready issues
532
- ${c.cyan}/beans "Add feature"${c.reset} Full autonomous flow
533
- ${c.cyan}/beans task-001${c.reset} Continue existing issue
534
- ${c.cyan}/beans:status${c.reset} Check progress
535
- ${c.cyan}/beans:land${c.reset} Commit, push, close
706
+ ${c.cyan}/beans "Add feature"${c.reset} Full autonomous flow
707
+ ${c.cyan}/beans:status${c.reset} Check progress
708
+ ${c.cyan}/beans:land${c.reset} Commit, push, close
709
+
710
+ ${c.bold}Data Model:${c.reset}
711
+ Issues → ${c.dim}.beans/ (beads tracker)${c.reset}
712
+ Research → ${c.dim}.beans/research.db (SQLite)${c.reset}
713
+ Config → ${c.dim}~/.beans/config.json${c.reset}
536
714
 
537
- ${c.bold}Environment:${c.reset}
538
- Config stored at: ${c.dim}~/.beans/config.json${c.reset}
539
-
540
715
  ${c.bold}More info:${c.reset}
541
716
  https://github.com/shinyobjectz/beans
542
717
  `);
@@ -558,6 +733,9 @@ switch (command) {
558
733
  case "doctor":
559
734
  await cmdDoctor(args);
560
735
  break;
736
+ case "research":
737
+ await cmdResearch(args);
738
+ break;
561
739
  case "help":
562
740
  case "--help":
563
741
  case "-h":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morebeans/cli",
3
- "version": "2.3.5",
3
+ "version": "2.5.0",
4
4
  "description": "BEANS CLI - Setup and configure autonomous development for Claude Code",
5
5
  "type": "module",
6
6
  "main": "index.ts",