@morebeans/cli 2.4.0 → 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.
- package/index.ts +181 -16
- 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
|
|
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.
|
|
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
|
|
|
@@ -523,6 +524,164 @@ async function cmdDoctor(args: string[]) {
|
|
|
523
524
|
log("");
|
|
524
525
|
}
|
|
525
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
|
+
|
|
526
685
|
async function cmdHelp() {
|
|
527
686
|
log(`
|
|
528
687
|
${c.bold}${c.blue}🫘 BEANS CLI v${VERSION}${c.reset}
|
|
@@ -532,24 +691,27 @@ ${c.bold}Usage:${c.reset}
|
|
|
532
691
|
|
|
533
692
|
${c.bold}Commands:${c.reset}
|
|
534
693
|
${c.cyan}init${c.reset} Initialize BEANS in current project
|
|
535
|
-
${c.cyan}config${c.reset} Configure API keys
|
|
536
|
-
${c.cyan}config --valyu${c.reset} Set Valyu API key
|
|
537
|
-
${c.cyan}config --github${c.reset} Set GitHub token
|
|
538
|
-
${c.cyan}config --show${c.reset} Show current configuration
|
|
694
|
+
${c.cyan}config${c.reset} Configure API keys
|
|
539
695
|
${c.cyan}doctor${c.reset} Check installation status
|
|
540
|
-
${c.cyan}
|
|
541
|
-
${c.cyan}help${c.reset} Show this help
|
|
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
|
|
542
704
|
|
|
543
705
|
${c.bold}In Claude Code:${c.reset}
|
|
544
|
-
${c.cyan}/beans${c.reset}
|
|
545
|
-
${c.cyan}/beans
|
|
546
|
-
${c.cyan}/beans
|
|
547
|
-
|
|
548
|
-
|
|
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}
|
|
549
714
|
|
|
550
|
-
${c.bold}Environment:${c.reset}
|
|
551
|
-
Config stored at: ${c.dim}~/.beans/config.json${c.reset}
|
|
552
|
-
|
|
553
715
|
${c.bold}More info:${c.reset}
|
|
554
716
|
https://github.com/shinyobjectz/beans
|
|
555
717
|
`);
|
|
@@ -571,6 +733,9 @@ switch (command) {
|
|
|
571
733
|
case "doctor":
|
|
572
734
|
await cmdDoctor(args);
|
|
573
735
|
break;
|
|
736
|
+
case "research":
|
|
737
|
+
await cmdResearch(args);
|
|
738
|
+
break;
|
|
574
739
|
case "help":
|
|
575
740
|
case "--help":
|
|
576
741
|
case "-h":
|