@booklib/core 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.
- package/.cursor/rules/booklib-standards.mdc +40 -0
- package/.gemini/context.md +372 -0
- package/AGENTS.md +166 -0
- package/CHANGELOG.md +226 -0
- package/CLAUDE.md +81 -0
- package/CODE_OF_CONDUCT.md +31 -0
- package/CONTRIBUTING.md +304 -0
- package/LICENSE +21 -0
- package/PLAN.md +28 -0
- package/README.ja.md +198 -0
- package/README.ko.md +198 -0
- package/README.md +503 -0
- package/README.pt-BR.md +198 -0
- package/README.uk.md +241 -0
- package/README.zh-CN.md +198 -0
- package/SECURITY.md +9 -0
- package/agents/architecture-reviewer.md +136 -0
- package/agents/booklib-reviewer.md +90 -0
- package/agents/data-reviewer.md +107 -0
- package/agents/jvm-reviewer.md +146 -0
- package/agents/python-reviewer.md +128 -0
- package/agents/rust-reviewer.md +115 -0
- package/agents/ts-reviewer.md +110 -0
- package/agents/ui-reviewer.md +117 -0
- package/assets/logo.svg +36 -0
- package/bin/booklib-mcp.js +304 -0
- package/bin/booklib.js +1705 -0
- package/bin/skills.cjs +1292 -0
- package/booklib-router.mdc +36 -0
- package/booklib.config.json +19 -0
- package/commands/animation-at-work.md +10 -0
- package/commands/clean-code-reviewer.md +10 -0
- package/commands/data-intensive-patterns.md +10 -0
- package/commands/data-pipelines.md +10 -0
- package/commands/design-patterns.md +10 -0
- package/commands/domain-driven-design.md +10 -0
- package/commands/effective-java.md +10 -0
- package/commands/effective-kotlin.md +10 -0
- package/commands/effective-python.md +10 -0
- package/commands/effective-typescript.md +10 -0
- package/commands/kotlin-in-action.md +10 -0
- package/commands/lean-startup.md +10 -0
- package/commands/microservices-patterns.md +10 -0
- package/commands/programming-with-rust.md +10 -0
- package/commands/refactoring-ui.md +10 -0
- package/commands/rust-in-action.md +10 -0
- package/commands/skill-router.md +10 -0
- package/commands/spring-boot-in-action.md +10 -0
- package/commands/storytelling-with-data.md +10 -0
- package/commands/system-design-interview.md +10 -0
- package/commands/using-asyncio-python.md +10 -0
- package/commands/web-scraping-python.md +10 -0
- package/community/registry.json +1616 -0
- package/hooks/hooks.json +23 -0
- package/hooks/posttooluse-capture.mjs +67 -0
- package/hooks/suggest.js +153 -0
- package/lib/agent-behaviors.js +40 -0
- package/lib/agent-detector.js +96 -0
- package/lib/config-loader.js +39 -0
- package/lib/conflict-resolver.js +148 -0
- package/lib/context-builder.js +574 -0
- package/lib/discovery-engine.js +298 -0
- package/lib/doctor/hook-installer.js +83 -0
- package/lib/doctor/usage-tracker.js +87 -0
- package/lib/engine/ai-features.js +253 -0
- package/lib/engine/auditor.js +103 -0
- package/lib/engine/bm25-index.js +178 -0
- package/lib/engine/capture.js +120 -0
- package/lib/engine/corrections.js +198 -0
- package/lib/engine/doctor.js +195 -0
- package/lib/engine/graph-injector.js +137 -0
- package/lib/engine/graph.js +161 -0
- package/lib/engine/handoff.js +405 -0
- package/lib/engine/indexer.js +242 -0
- package/lib/engine/parser.js +53 -0
- package/lib/engine/query-expander.js +42 -0
- package/lib/engine/reranker.js +40 -0
- package/lib/engine/rrf.js +59 -0
- package/lib/engine/scanner.js +151 -0
- package/lib/engine/searcher.js +139 -0
- package/lib/engine/session-coordinator.js +306 -0
- package/lib/engine/session-manager.js +429 -0
- package/lib/engine/synthesizer.js +70 -0
- package/lib/installer.js +70 -0
- package/lib/instinct-block.js +33 -0
- package/lib/mcp-config-writer.js +88 -0
- package/lib/paths.js +57 -0
- package/lib/profiles/design.md +19 -0
- package/lib/profiles/general.md +16 -0
- package/lib/profiles/research-analysis.md +22 -0
- package/lib/profiles/software-development.md +23 -0
- package/lib/profiles/writing-content.md +19 -0
- package/lib/project-initializer.js +916 -0
- package/lib/registry/skills.js +102 -0
- package/lib/registry-searcher.js +99 -0
- package/lib/rules/rules-manager.js +169 -0
- package/lib/skill-fetcher.js +333 -0
- package/lib/well-known-builder.js +70 -0
- package/lib/wizard/index.js +404 -0
- package/lib/wizard/integration-detector.js +41 -0
- package/lib/wizard/project-detector.js +100 -0
- package/lib/wizard/prompt.js +156 -0
- package/lib/wizard/registry-embeddings.js +107 -0
- package/lib/wizard/skill-recommender.js +69 -0
- package/llms-full.txt +254 -0
- package/llms.txt +70 -0
- package/package.json +45 -0
- package/research-reports/2026-04-01-current-architecture.md +160 -0
- package/research-reports/IDEAS.md +93 -0
- package/rules/common/clean-code.md +42 -0
- package/rules/java/effective-java.md +42 -0
- package/rules/kotlin/effective-kotlin.md +37 -0
- package/rules/python/effective-python.md +38 -0
- package/rules/rust/rust.md +37 -0
- package/rules/typescript/effective-typescript.md +42 -0
- package/scripts/gen-llms-full.mjs +36 -0
- package/scripts/gen-og.mjs +142 -0
- package/scripts/validate-frontmatter.js +25 -0
- package/skills/animation-at-work/SKILL.md +270 -0
- package/skills/animation-at-work/assets/example_asset.txt +1 -0
- package/skills/animation-at-work/evals/evals.json +44 -0
- package/skills/animation-at-work/evals/results.json +13 -0
- package/skills/animation-at-work/examples/after.md +64 -0
- package/skills/animation-at-work/examples/before.md +35 -0
- package/skills/animation-at-work/references/api_reference.md +369 -0
- package/skills/animation-at-work/references/review-checklist.md +79 -0
- package/skills/animation-at-work/scripts/audit_animations.py +295 -0
- package/skills/animation-at-work/scripts/example.py +1 -0
- package/skills/clean-code-reviewer/SKILL.md +444 -0
- package/skills/clean-code-reviewer/audit.json +35 -0
- package/skills/clean-code-reviewer/evals/evals.json +185 -0
- package/skills/clean-code-reviewer/evals/results.json +13 -0
- package/skills/clean-code-reviewer/examples/after.md +48 -0
- package/skills/clean-code-reviewer/examples/before.md +33 -0
- package/skills/clean-code-reviewer/references/api_reference.md +158 -0
- package/skills/clean-code-reviewer/references/practices-catalog.md +282 -0
- package/skills/clean-code-reviewer/references/review-checklist.md +254 -0
- package/skills/clean-code-reviewer/scripts/pre-review.py +206 -0
- package/skills/data-intensive-patterns/SKILL.md +267 -0
- package/skills/data-intensive-patterns/assets/example_asset.txt +1 -0
- package/skills/data-intensive-patterns/evals/evals.json +54 -0
- package/skills/data-intensive-patterns/evals/results.json +13 -0
- package/skills/data-intensive-patterns/examples/after.md +61 -0
- package/skills/data-intensive-patterns/examples/before.md +38 -0
- package/skills/data-intensive-patterns/references/api_reference.md +34 -0
- package/skills/data-intensive-patterns/references/patterns-catalog.md +551 -0
- package/skills/data-intensive-patterns/references/review-checklist.md +193 -0
- package/skills/data-intensive-patterns/scripts/adr.py +213 -0
- package/skills/data-intensive-patterns/scripts/example.py +1 -0
- package/skills/data-pipelines/SKILL.md +259 -0
- package/skills/data-pipelines/assets/example_asset.txt +1 -0
- package/skills/data-pipelines/evals/evals.json +45 -0
- package/skills/data-pipelines/evals/results.json +13 -0
- package/skills/data-pipelines/examples/after.md +97 -0
- package/skills/data-pipelines/examples/before.md +37 -0
- package/skills/data-pipelines/references/api_reference.md +301 -0
- package/skills/data-pipelines/references/review-checklist.md +181 -0
- package/skills/data-pipelines/scripts/example.py +1 -0
- package/skills/data-pipelines/scripts/new_pipeline.py +444 -0
- package/skills/design-patterns/SKILL.md +271 -0
- package/skills/design-patterns/assets/example_asset.txt +1 -0
- package/skills/design-patterns/evals/evals.json +46 -0
- package/skills/design-patterns/evals/results.json +13 -0
- package/skills/design-patterns/examples/after.md +52 -0
- package/skills/design-patterns/examples/before.md +29 -0
- package/skills/design-patterns/references/api_reference.md +1 -0
- package/skills/design-patterns/references/patterns-catalog.md +726 -0
- package/skills/design-patterns/references/review-checklist.md +173 -0
- package/skills/design-patterns/scripts/example.py +1 -0
- package/skills/design-patterns/scripts/scaffold.py +807 -0
- package/skills/domain-driven-design/SKILL.md +142 -0
- package/skills/domain-driven-design/assets/example_asset.txt +1 -0
- package/skills/domain-driven-design/evals/evals.json +48 -0
- package/skills/domain-driven-design/evals/results.json +13 -0
- package/skills/domain-driven-design/examples/after.md +80 -0
- package/skills/domain-driven-design/examples/before.md +43 -0
- package/skills/domain-driven-design/references/api_reference.md +1 -0
- package/skills/domain-driven-design/references/patterns-catalog.md +545 -0
- package/skills/domain-driven-design/references/review-checklist.md +158 -0
- package/skills/domain-driven-design/scripts/example.py +1 -0
- package/skills/domain-driven-design/scripts/scaffold.py +421 -0
- package/skills/effective-java/SKILL.md +227 -0
- package/skills/effective-java/assets/example_asset.txt +1 -0
- package/skills/effective-java/evals/evals.json +46 -0
- package/skills/effective-java/evals/results.json +13 -0
- package/skills/effective-java/examples/after.md +83 -0
- package/skills/effective-java/examples/before.md +37 -0
- package/skills/effective-java/references/api_reference.md +1 -0
- package/skills/effective-java/references/items-catalog.md +955 -0
- package/skills/effective-java/references/review-checklist.md +216 -0
- package/skills/effective-java/scripts/checkstyle_setup.py +211 -0
- package/skills/effective-java/scripts/example.py +1 -0
- package/skills/effective-kotlin/SKILL.md +271 -0
- package/skills/effective-kotlin/assets/example_asset.txt +1 -0
- package/skills/effective-kotlin/audit.json +29 -0
- package/skills/effective-kotlin/evals/evals.json +45 -0
- package/skills/effective-kotlin/evals/results.json +13 -0
- package/skills/effective-kotlin/examples/after.md +36 -0
- package/skills/effective-kotlin/examples/before.md +38 -0
- package/skills/effective-kotlin/references/api_reference.md +1 -0
- package/skills/effective-kotlin/references/practices-catalog.md +1228 -0
- package/skills/effective-kotlin/references/review-checklist.md +126 -0
- package/skills/effective-kotlin/scripts/example.py +1 -0
- package/skills/effective-python/SKILL.md +441 -0
- package/skills/effective-python/evals/evals.json +44 -0
- package/skills/effective-python/evals/results.json +13 -0
- package/skills/effective-python/examples/after.md +56 -0
- package/skills/effective-python/examples/before.md +40 -0
- package/skills/effective-python/ref-01-pythonic-thinking.md +202 -0
- package/skills/effective-python/ref-02-lists-and-dicts.md +146 -0
- package/skills/effective-python/ref-03-functions.md +186 -0
- package/skills/effective-python/ref-04-comprehensions-generators.md +211 -0
- package/skills/effective-python/ref-05-classes-interfaces.md +188 -0
- package/skills/effective-python/ref-06-metaclasses-attributes.md +209 -0
- package/skills/effective-python/ref-07-concurrency.md +213 -0
- package/skills/effective-python/ref-08-robustness-performance.md +248 -0
- package/skills/effective-python/ref-09-testing-debugging.md +253 -0
- package/skills/effective-python/ref-10-collaboration.md +175 -0
- package/skills/effective-python/references/api_reference.md +218 -0
- package/skills/effective-python/references/practices-catalog.md +483 -0
- package/skills/effective-python/references/review-checklist.md +190 -0
- package/skills/effective-python/scripts/lint.py +173 -0
- package/skills/effective-typescript/SKILL.md +262 -0
- package/skills/effective-typescript/audit.json +29 -0
- package/skills/effective-typescript/evals/evals.json +37 -0
- package/skills/effective-typescript/evals/results.json +13 -0
- package/skills/effective-typescript/examples/after.md +70 -0
- package/skills/effective-typescript/examples/before.md +47 -0
- package/skills/effective-typescript/references/api_reference.md +118 -0
- package/skills/effective-typescript/references/practices-catalog.md +371 -0
- package/skills/effective-typescript/scripts/review.py +169 -0
- package/skills/kotlin-in-action/SKILL.md +261 -0
- package/skills/kotlin-in-action/assets/example_asset.txt +1 -0
- package/skills/kotlin-in-action/evals/evals.json +43 -0
- package/skills/kotlin-in-action/evals/results.json +13 -0
- package/skills/kotlin-in-action/examples/after.md +53 -0
- package/skills/kotlin-in-action/examples/before.md +39 -0
- package/skills/kotlin-in-action/references/api_reference.md +1 -0
- package/skills/kotlin-in-action/references/practices-catalog.md +436 -0
- package/skills/kotlin-in-action/references/review-checklist.md +204 -0
- package/skills/kotlin-in-action/scripts/example.py +1 -0
- package/skills/kotlin-in-action/scripts/setup_detekt.py +224 -0
- package/skills/lean-startup/SKILL.md +160 -0
- package/skills/lean-startup/assets/example_asset.txt +1 -0
- package/skills/lean-startup/evals/evals.json +43 -0
- package/skills/lean-startup/evals/results.json +13 -0
- package/skills/lean-startup/examples/after.md +80 -0
- package/skills/lean-startup/examples/before.md +34 -0
- package/skills/lean-startup/references/api_reference.md +319 -0
- package/skills/lean-startup/references/review-checklist.md +137 -0
- package/skills/lean-startup/scripts/example.py +1 -0
- package/skills/lean-startup/scripts/new_experiment.py +286 -0
- package/skills/microservices-patterns/SKILL.md +384 -0
- package/skills/microservices-patterns/evals/evals.json +45 -0
- package/skills/microservices-patterns/evals/results.json +13 -0
- package/skills/microservices-patterns/examples/after.md +69 -0
- package/skills/microservices-patterns/examples/before.md +40 -0
- package/skills/microservices-patterns/references/patterns-catalog.md +391 -0
- package/skills/microservices-patterns/references/review-checklist.md +169 -0
- package/skills/microservices-patterns/scripts/new_service.py +583 -0
- package/skills/programming-with-rust/SKILL.md +209 -0
- package/skills/programming-with-rust/evals/evals.json +37 -0
- package/skills/programming-with-rust/evals/results.json +13 -0
- package/skills/programming-with-rust/examples/after.md +107 -0
- package/skills/programming-with-rust/examples/before.md +59 -0
- package/skills/programming-with-rust/references/api_reference.md +152 -0
- package/skills/programming-with-rust/references/practices-catalog.md +335 -0
- package/skills/programming-with-rust/scripts/review.py +142 -0
- package/skills/refactoring-ui/SKILL.md +362 -0
- package/skills/refactoring-ui/assets/example_asset.txt +1 -0
- package/skills/refactoring-ui/evals/evals.json +45 -0
- package/skills/refactoring-ui/evals/results.json +13 -0
- package/skills/refactoring-ui/examples/after.md +85 -0
- package/skills/refactoring-ui/examples/before.md +58 -0
- package/skills/refactoring-ui/references/api_reference.md +355 -0
- package/skills/refactoring-ui/references/review-checklist.md +114 -0
- package/skills/refactoring-ui/scripts/audit_css.py +250 -0
- package/skills/refactoring-ui/scripts/example.py +1 -0
- package/skills/rust-in-action/SKILL.md +350 -0
- package/skills/rust-in-action/evals/evals.json +38 -0
- package/skills/rust-in-action/evals/results.json +13 -0
- package/skills/rust-in-action/examples/after.md +156 -0
- package/skills/rust-in-action/examples/before.md +56 -0
- package/skills/rust-in-action/references/practices-catalog.md +346 -0
- package/skills/rust-in-action/scripts/review.py +147 -0
- package/skills/skill-router/SKILL.md +186 -0
- package/skills/skill-router/evals/evals.json +38 -0
- package/skills/skill-router/evals/results.json +13 -0
- package/skills/skill-router/examples/after.md +63 -0
- package/skills/skill-router/examples/before.md +39 -0
- package/skills/skill-router/references/api_reference.md +24 -0
- package/skills/skill-router/references/routing-heuristics.md +89 -0
- package/skills/skill-router/references/skill-catalog.md +174 -0
- package/skills/skill-router/scripts/route.py +266 -0
- package/skills/spring-boot-in-action/SKILL.md +340 -0
- package/skills/spring-boot-in-action/evals/evals.json +39 -0
- package/skills/spring-boot-in-action/evals/results.json +13 -0
- package/skills/spring-boot-in-action/examples/after.md +185 -0
- package/skills/spring-boot-in-action/examples/before.md +84 -0
- package/skills/spring-boot-in-action/references/practices-catalog.md +403 -0
- package/skills/spring-boot-in-action/scripts/review.py +184 -0
- package/skills/storytelling-with-data/SKILL.md +241 -0
- package/skills/storytelling-with-data/assets/example_asset.txt +1 -0
- package/skills/storytelling-with-data/evals/evals.json +47 -0
- package/skills/storytelling-with-data/evals/results.json +13 -0
- package/skills/storytelling-with-data/examples/after.md +50 -0
- package/skills/storytelling-with-data/examples/before.md +33 -0
- package/skills/storytelling-with-data/references/api_reference.md +379 -0
- package/skills/storytelling-with-data/references/review-checklist.md +111 -0
- package/skills/storytelling-with-data/scripts/chart_review.py +301 -0
- package/skills/storytelling-with-data/scripts/example.py +1 -0
- package/skills/system-design-interview/SKILL.md +233 -0
- package/skills/system-design-interview/assets/example_asset.txt +1 -0
- package/skills/system-design-interview/evals/evals.json +46 -0
- package/skills/system-design-interview/evals/results.json +13 -0
- package/skills/system-design-interview/examples/after.md +94 -0
- package/skills/system-design-interview/examples/before.md +27 -0
- package/skills/system-design-interview/references/api_reference.md +582 -0
- package/skills/system-design-interview/references/review-checklist.md +201 -0
- package/skills/system-design-interview/scripts/example.py +1 -0
- package/skills/system-design-interview/scripts/new_design.py +421 -0
- package/skills/using-asyncio-python/SKILL.md +290 -0
- package/skills/using-asyncio-python/assets/example_asset.txt +1 -0
- package/skills/using-asyncio-python/evals/evals.json +43 -0
- package/skills/using-asyncio-python/evals/results.json +13 -0
- package/skills/using-asyncio-python/examples/after.md +68 -0
- package/skills/using-asyncio-python/examples/before.md +39 -0
- package/skills/using-asyncio-python/references/api_reference.md +267 -0
- package/skills/using-asyncio-python/references/review-checklist.md +149 -0
- package/skills/using-asyncio-python/scripts/check_blocking.py +270 -0
- package/skills/using-asyncio-python/scripts/example.py +1 -0
- package/skills/web-scraping-python/SKILL.md +280 -0
- package/skills/web-scraping-python/assets/example_asset.txt +1 -0
- package/skills/web-scraping-python/evals/evals.json +46 -0
- package/skills/web-scraping-python/evals/results.json +13 -0
- package/skills/web-scraping-python/examples/after.md +109 -0
- package/skills/web-scraping-python/examples/before.md +40 -0
- package/skills/web-scraping-python/references/api_reference.md +393 -0
- package/skills/web-scraping-python/references/review-checklist.md +163 -0
- package/skills/web-scraping-python/scripts/example.py +1 -0
- package/skills/web-scraping-python/scripts/new_scraper.py +231 -0
- package/skills/writing-plans/audit.json +34 -0
- package/tests/agent-detector.test.js +83 -0
- package/tests/corrections.test.js +245 -0
- package/tests/doctor/hook-installer.test.js +72 -0
- package/tests/doctor/usage-tracker.test.js +140 -0
- package/tests/engine/benchmark-eval.test.js +31 -0
- package/tests/engine/bm25-index.test.js +85 -0
- package/tests/engine/capture-command.test.js +35 -0
- package/tests/engine/capture.test.js +17 -0
- package/tests/engine/graph-augmented-search.test.js +107 -0
- package/tests/engine/graph-injector.test.js +44 -0
- package/tests/engine/graph.test.js +216 -0
- package/tests/engine/hybrid-searcher.test.js +74 -0
- package/tests/engine/indexer-bm25.test.js +37 -0
- package/tests/engine/mcp-tools.test.js +73 -0
- package/tests/engine/project-initializer-mcp.test.js +99 -0
- package/tests/engine/query-expander.test.js +36 -0
- package/tests/engine/reranker.test.js +51 -0
- package/tests/engine/rrf.test.js +49 -0
- package/tests/engine/srag-prefix.test.js +47 -0
- package/tests/instinct-block.test.js +23 -0
- package/tests/mcp-config-writer.test.js +60 -0
- package/tests/project-initializer-new-agents.test.js +48 -0
- package/tests/rules/rules-manager.test.js +230 -0
- package/tests/well-known-builder.test.js +40 -0
- package/tests/wizard/integration-detector.test.js +31 -0
- package/tests/wizard/project-detector.test.js +51 -0
- package/tests/wizard/prompt-session.test.js +61 -0
- package/tests/wizard/prompt.test.js +16 -0
- package/tests/wizard/registry-embeddings.test.js +35 -0
- package/tests/wizard/skill-recommender.test.js +34 -0
- package/tests/wizard/slot-count.test.js +25 -0
- package/vercel.json +21 -0
package/bin/booklib.js
ADDED
|
@@ -0,0 +1,1705 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Suppress noisy ML model initialisation warnings from @huggingface/transformers
|
|
3
|
+
// The library uses console.warn for dtype/device messages — filter them here.
|
|
4
|
+
const _origWarn = console.warn.bind(console);
|
|
5
|
+
console.warn = (...args) => {
|
|
6
|
+
const msg = typeof args[0] === 'string' ? args[0] : '';
|
|
7
|
+
if (msg.includes('dtype not specified') || msg.includes('Using the default dtype')) return;
|
|
8
|
+
_origWarn(...args);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import os from 'os';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { createInterface } from 'node:readline';
|
|
16
|
+
|
|
17
|
+
const PACKAGE_ROOT = path.resolve(fileURLToPath(import.meta.url), '..', '..');
|
|
18
|
+
const BUNDLED_SKILLS_DIR = path.join(PACKAGE_ROOT, 'skills');
|
|
19
|
+
import { BookLibIndexer } from '../lib/engine/indexer.js';
|
|
20
|
+
import { BookLibSearcher } from '../lib/engine/searcher.js';
|
|
21
|
+
import { BookLibHandoff } from '../lib/engine/handoff.js';
|
|
22
|
+
import { BookLibAuditor } from '../lib/engine/auditor.js';
|
|
23
|
+
import { BookLibRegistrySearcher } from '../lib/registry-searcher.js';
|
|
24
|
+
import { BookLibInstaller } from '../lib/installer.js';
|
|
25
|
+
import { BookLibSynthesizer } from '../lib/engine/synthesizer.js';
|
|
26
|
+
import { BookLibScanner } from '../lib/engine/scanner.js';
|
|
27
|
+
import { BookLibSessionCoordinator } from '../lib/engine/session-coordinator.js';
|
|
28
|
+
import { BookLibSessionManager } from '../lib/engine/session-manager.js';
|
|
29
|
+
import { BookLibAIFeatures } from '../lib/engine/ai-features.js';
|
|
30
|
+
import { resolveBookLibPaths } from '../lib/paths.js';
|
|
31
|
+
import { SkillFetcher, RequiresConfirmationError, listInstalledSkillNames, countInstalledSlots } from '../lib/skill-fetcher.js';
|
|
32
|
+
import { runWizard } from '../lib/wizard/index.js';
|
|
33
|
+
import { SKILL_LIMIT } from '../lib/wizard/skill-recommender.js';
|
|
34
|
+
import {
|
|
35
|
+
generateNodeId, serializeNode, saveNode, loadNode,
|
|
36
|
+
listNodes, appendEdge, parseNodeFrontmatter, resolveKnowledgePaths,
|
|
37
|
+
resolveNodeRef, EDGE_TYPES, parseCaptureLinkArgs,
|
|
38
|
+
} from '../lib/engine/graph.js';
|
|
39
|
+
import { DiscoveryEngine } from '../lib/discovery-engine.js';
|
|
40
|
+
import { ProjectInitializer } from '../lib/project-initializer.js';
|
|
41
|
+
import { ContextBuilder } from '../lib/context-builder.js';
|
|
42
|
+
import {
|
|
43
|
+
buildDictatePrompt, buildSummarizePrompt, callAnthropicAPI,
|
|
44
|
+
openEditor, readStdin, readInteractive,
|
|
45
|
+
} from '../lib/engine/capture.js';
|
|
46
|
+
import { readUsage, summarize } from '../lib/doctor/usage-tracker.js';
|
|
47
|
+
import { installTrackingHook } from '../lib/doctor/hook-installer.js';
|
|
48
|
+
import { listAvailable as listAvailableRules, installRule as installRuleFn, status as rulesStatus } from '../lib/rules/rules-manager.js';
|
|
49
|
+
import { addCorrection, listCorrections, removeCorrection, levelFromMentions } from '../lib/engine/corrections.js';
|
|
50
|
+
import { WellKnownBuilder } from '../lib/well-known-builder.js';
|
|
51
|
+
|
|
52
|
+
const args = process.argv.slice(2);
|
|
53
|
+
const command = args[0];
|
|
54
|
+
|
|
55
|
+
// Handle --version / -v before anything else
|
|
56
|
+
if (command === '--version' || command === '-v' || args.includes('--version')) {
|
|
57
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
|
|
58
|
+
console.log(`booklib v${pkg.version}`);
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseFlag(args, flag) {
|
|
63
|
+
const long = args.find(a => a.startsWith(`--${flag}=`))?.replace(`--${flag}=`, '');
|
|
64
|
+
if (long !== undefined) return long;
|
|
65
|
+
const idx = args.indexOf(`--${flag}`);
|
|
66
|
+
return idx !== -1 ? args[idx + 1] : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function formatBytes(bytes) {
|
|
70
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
71
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
72
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Auto-index a freshly saved node so it's immediately searchable. Silently skips on error. */
|
|
76
|
+
async function autoIndexNode(filePath) {
|
|
77
|
+
const { nodesDir } = resolveKnowledgePaths();
|
|
78
|
+
try {
|
|
79
|
+
const indexer = new BookLibIndexer();
|
|
80
|
+
await indexer.indexNodeFile(filePath, nodesDir);
|
|
81
|
+
} catch {
|
|
82
|
+
// Index may not exist yet — user can run `booklib index` to build it
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
const TOOL_MENU = [
|
|
88
|
+
{ num: 1, name: 'Claude Code', target: 'claude', file: 'CLAUDE.md' },
|
|
89
|
+
{ num: 2, name: 'Cursor', target: 'cursor', file: '.cursor/rules/' },
|
|
90
|
+
{ num: 3, name: 'Copilot', target: 'copilot', file: '.github/copilot-instructions.md' },
|
|
91
|
+
{ num: 4, name: 'Gemini CLI', target: 'gemini', file: '.gemini/context.md' },
|
|
92
|
+
{ num: 5, name: 'Codex', target: 'codex', file: 'AGENTS.md' },
|
|
93
|
+
{ num: 6, name: 'Windsurf', target: 'windsurf', file: '.windsurfrules' },
|
|
94
|
+
{ num: 7, name: 'Roo Code', target: 'roo-code', file: '.roo/rules/' },
|
|
95
|
+
{ num: 8, name: 'OpenHands', target: 'openhands', file: '.openhands/instructions.md' },
|
|
96
|
+
{ num: 9, name: 'Junie', target: 'junie', file: '.junie/guidelines.md' },
|
|
97
|
+
{ num: 10, name: 'Goose', target: 'goose', file: '.goose/context.md' },
|
|
98
|
+
{ num: 11, name: 'OpenCode', target: 'opencode', file: 'opencode.toml' },
|
|
99
|
+
{ num: 12, name: 'Letta', target: 'letta', file: '.letta/instructions.md' },
|
|
100
|
+
{ num: 13, name: 'All', target: 'all', file: null },
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
const MCP_TOOL_MENU = [
|
|
104
|
+
{ num: 1, name: 'Claude Code', target: 'claude', file: '.claude/settings.json' },
|
|
105
|
+
{ num: 2, name: 'Cursor', target: 'cursor', file: '.cursor/mcp.json' },
|
|
106
|
+
{ num: 3, name: 'Copilot', target: 'copilot', file: '.vscode/mcp.json' },
|
|
107
|
+
{ num: 4, name: 'Gemini CLI', target: 'gemini', file: '.gemini/settings.json' },
|
|
108
|
+
{ num: 5, name: 'Codex', target: 'codex', file: '.codex/config.toml' },
|
|
109
|
+
{ num: 6, name: 'Roo Code', target: 'roo-code', file: '.roo/mcp.json' },
|
|
110
|
+
{ num: 7, name: 'Windsurf', target: 'windsurf', file: '~/.codeium/windsurf/mcp_config.json' },
|
|
111
|
+
{ num: 8, name: 'Goose', target: 'goose', file: '.goose/config.yaml' },
|
|
112
|
+
{ num: 9, name: 'Zed', target: 'zed', file: '.zed/settings.json' },
|
|
113
|
+
{ num: 10, name: 'Continue', target: 'continue', file: '.continue/mcpServers/booklib.yaml' },
|
|
114
|
+
{ num: 11, name: 'All of the above', target: 'all', file: null },
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
async function promptToolSelection() {
|
|
118
|
+
process.stdout.write('\nWhich AI tool do you use?\n\n');
|
|
119
|
+
for (const t of TOOL_MENU) {
|
|
120
|
+
const fileInfo = t.file ? ` → ${t.file}` : '';
|
|
121
|
+
process.stdout.write(` ${t.num}) ${t.name.padEnd(12)}${fileInfo}\n`);
|
|
122
|
+
}
|
|
123
|
+
process.stdout.write('\nSelect [1-13, or comma-separated for multiple]: ');
|
|
124
|
+
|
|
125
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
126
|
+
const answer = await new Promise(resolve => {
|
|
127
|
+
rl.once('line', line => { rl.close(); resolve(line.trim()); });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (!answer) return 'all';
|
|
131
|
+
const nums = answer.split(',').map(n => parseInt(n.trim(), 10)).filter(n => !isNaN(n));
|
|
132
|
+
if (nums.length === 0 || nums.includes(13)) return 'all';
|
|
133
|
+
const selected = nums.map(n => TOOL_MENU.find(t => t.num === n)?.target).filter(Boolean);
|
|
134
|
+
return selected.length > 0 ? selected.join(',') : 'all';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function promptMcpToolSelection() {
|
|
138
|
+
const SEP = '━'.repeat(51);
|
|
139
|
+
process.stdout.write(`\n${SEP}\n MCP Server Setup\n${SEP}\n\n`);
|
|
140
|
+
process.stdout.write(' BookLib has an MCP server — your AI tools can call it\n');
|
|
141
|
+
process.stdout.write(' directly to search knowledge, fetch context, and create\n');
|
|
142
|
+
process.stdout.write(' notes without leaving the conversation.\n\n');
|
|
143
|
+
process.stdout.write(' Wire up the MCP server? (Y/n): ');
|
|
144
|
+
|
|
145
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
146
|
+
const yn = await new Promise(resolve => {
|
|
147
|
+
rl.once('line', line => { rl.close(); resolve(line.trim().toLowerCase()); });
|
|
148
|
+
});
|
|
149
|
+
if (yn === 'n' || yn === 'no') return null;
|
|
150
|
+
|
|
151
|
+
process.stdout.write('\n Which tools should I configure? (select all that apply)\n\n');
|
|
152
|
+
for (const t of MCP_TOOL_MENU) {
|
|
153
|
+
const fileInfo = t.file ? ` → ${t.file}` : '';
|
|
154
|
+
process.stdout.write(` ${t.num}. ${t.name.padEnd(18)}${fileInfo}\n`);
|
|
155
|
+
}
|
|
156
|
+
process.stdout.write('\n Enter numbers separated by commas (1,2,5) or 11 for all: ');
|
|
157
|
+
|
|
158
|
+
const rl2 = createInterface({ input: process.stdin, output: process.stdout });
|
|
159
|
+
const answer = await new Promise(resolve => {
|
|
160
|
+
rl2.once('line', line => { rl2.close(); resolve(line.trim()); });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (!answer) return 'all';
|
|
164
|
+
const nums = answer.split(',').map(n => parseInt(n.trim(), 10)).filter(n => !isNaN(n));
|
|
165
|
+
if (nums.length === 0 || nums.includes(11)) return 'all';
|
|
166
|
+
const selected = nums.map(n => MCP_TOOL_MENU.find(t => t.num === n)?.target).filter(Boolean);
|
|
167
|
+
return selected.length > 0 ? selected.join(',') : 'all';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function main() {
|
|
171
|
+
switch (command) {
|
|
172
|
+
case 'index': {
|
|
173
|
+
const { skillsPath, cachePath } = resolveBookLibPaths();
|
|
174
|
+
const explicitDir = args[1] && !args[1].startsWith('--') ? args[1] : null;
|
|
175
|
+
const indexer = new BookLibIndexer();
|
|
176
|
+
const verboseIndex = args.includes('--verbose');
|
|
177
|
+
const indexStart = Date.now();
|
|
178
|
+
|
|
179
|
+
process.stdout.write('► Building index...\n');
|
|
180
|
+
if (explicitDir) {
|
|
181
|
+
// Explicit directory: just index that one
|
|
182
|
+
await indexer.indexDirectory(explicitDir, true, { quiet: !verboseIndex });
|
|
183
|
+
} else {
|
|
184
|
+
// Always index bundled skills first (clear on first pass)
|
|
185
|
+
await indexer.indexDirectory(BUNDLED_SKILLS_DIR, true, { quiet: !verboseIndex });
|
|
186
|
+
// Add community/user skills — deduplicate to avoid double-indexing same dir
|
|
187
|
+
const communitySkillsDir = path.join(cachePath, 'skills');
|
|
188
|
+
const dirsToAdd = new Set();
|
|
189
|
+
if (skillsPath !== BUNDLED_SKILLS_DIR) dirsToAdd.add(skillsPath);
|
|
190
|
+
if (communitySkillsDir !== skillsPath) dirsToAdd.add(communitySkillsDir);
|
|
191
|
+
for (const dir of dirsToAdd) {
|
|
192
|
+
if (fs.existsSync(dir) && fs.readdirSync(dir).length > 0) {
|
|
193
|
+
const count = fs.readdirSync(dir).length;
|
|
194
|
+
if (verboseIndex) console.log(`Indexing ${count} community skill(s) from ${dir}...`);
|
|
195
|
+
await indexer.indexDirectory(dir, false, { quiet: !verboseIndex });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Index knowledge nodes from .booklib/knowledge/nodes/
|
|
200
|
+
const { resolveKnowledgePaths } = await import('../lib/engine/graph.js');
|
|
201
|
+
const { nodesDir } = resolveKnowledgePaths();
|
|
202
|
+
await indexer.indexKnowledgeNodes(nodesDir);
|
|
203
|
+
const elapsed = ((Date.now() - indexStart) / 1000).toFixed(0);
|
|
204
|
+
console.log(`✅ Index built in ${elapsed}s`);
|
|
205
|
+
console.log(`\n → Now try: booklib search "your query"\n`);
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
case 'search': {
|
|
210
|
+
const autoFetch = args.includes('--auto-fetch');
|
|
211
|
+
const useGraph = args.includes('--graph');
|
|
212
|
+
const roleFilter = (args.find(a => a.startsWith('--role=')) ?? '').replace('--role=', '') || null;
|
|
213
|
+
const query = args.slice(1).filter(a => !a.startsWith('--')).join(' ');
|
|
214
|
+
if (!query) { console.error('Usage: booklib search "<query>" [--auto-fetch] [--role=<role>] [--graph]'); process.exit(1); }
|
|
215
|
+
|
|
216
|
+
const regSearcher = new BookLibRegistrySearcher();
|
|
217
|
+
let { local, suggested, conflicts } = await regSearcher.searchHybrid(query, { useGraph });
|
|
218
|
+
|
|
219
|
+
// Role filter — narrow results to skills tagged for the requested agent role
|
|
220
|
+
if (roleFilter) {
|
|
221
|
+
const roleMatch = s => !s.roles || s.roles.includes(roleFilter);
|
|
222
|
+
local = local.filter(r => roleMatch(r.metadata ?? r));
|
|
223
|
+
suggested = suggested.filter(roleMatch);
|
|
224
|
+
if (local.length === 0 && suggested.length === 0) {
|
|
225
|
+
console.log(`No results for "${query}" in role "${roleFilter}". Try without --role to see all matches.`);
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Auto-fetch: silently fetch trusted suggestions and re-search
|
|
231
|
+
if (autoFetch && suggested.length > 0) {
|
|
232
|
+
const trustedSuggestions = suggested.filter(s => s.trusted);
|
|
233
|
+
if (trustedSuggestions.length > 0) {
|
|
234
|
+
const fetcher = new SkillFetcher();
|
|
235
|
+
for (const skill of trustedSuggestions) {
|
|
236
|
+
if (!fetcher.isCached(skill)) {
|
|
237
|
+
process.stderr.write(`[booklib] Fetching ${skill.name}...\n`);
|
|
238
|
+
try { await fetcher.fetch(skill); } catch { /* non-fatal */ }
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Re-index and re-search with newly fetched skills
|
|
242
|
+
const { cachePath } = resolveBookLibPaths();
|
|
243
|
+
const indexer = new BookLibIndexer();
|
|
244
|
+
await indexer.indexDirectory(path.join(cachePath, 'skills'));
|
|
245
|
+
({ local, suggested, conflicts } = await regSearcher.searchHybrid(query, { useGraph }));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (local.length > 0) {
|
|
250
|
+
console.log('\n📚 Local results:\n');
|
|
251
|
+
local.forEach(r => {
|
|
252
|
+
const rationale = r._rationale ? ` ↳ ${r._rationale}` : '';
|
|
253
|
+
const isNode = r.metadata?.nodeKind === 'knowledge';
|
|
254
|
+
const label = isNode
|
|
255
|
+
? `📝 ${r.metadata?.title ?? r.metadata?.filePath ?? '?'} [${r.metadata?.type ?? 'note'}]`
|
|
256
|
+
: `📚 ${r.metadata?.name ?? r.metadata?.filePath ?? '?'} (${r.metadata?.type ?? 'chunk'})`;
|
|
257
|
+
const s = r.score ?? 0;
|
|
258
|
+
const bar = s >= 0.7 ? '████' : s >= 0.5 ? '███░' : s >= 0.35 ? '██░░' : '█░░░';
|
|
259
|
+
console.log(` ${bar} ${label}`);
|
|
260
|
+
if (rationale) console.log(rationale);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (suggested.length > 0) {
|
|
264
|
+
console.log('\n💡 Community skills available (not yet indexed):');
|
|
265
|
+
suggested.forEach(s => {
|
|
266
|
+
const stars = s.stars ? ` ★${s.stars.toLocaleString()}` : '';
|
|
267
|
+
const rationale = s._rationale ? `\n ↳ ${s._rationale}` : '';
|
|
268
|
+
console.log(` • ${s.name}${stars} — ${s.description}${rationale}`);
|
|
269
|
+
});
|
|
270
|
+
if (!autoFetch) console.log('\nTip: run with --auto-fetch to install and search in one step');
|
|
271
|
+
}
|
|
272
|
+
if (local.length === 0 && suggested.length === 0) {
|
|
273
|
+
console.log('No results found.');
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
case 'audit': {
|
|
279
|
+
const auditor = new BookLibAuditor();
|
|
280
|
+
const skillName = args[1];
|
|
281
|
+
const filePath = args[2];
|
|
282
|
+
if (!skillName || !filePath) { console.error('Usage: booklib audit <skill-name> <file-path>'); process.exit(1); }
|
|
283
|
+
const { skillsPath } = resolveBookLibPaths();
|
|
284
|
+
const candidates = [
|
|
285
|
+
path.join(skillsPath, skillName),
|
|
286
|
+
path.join(BUNDLED_SKILLS_DIR, skillName),
|
|
287
|
+
];
|
|
288
|
+
const skillPath = candidates.find(p => fs.existsSync(p)) ?? candidates[0];
|
|
289
|
+
if (!fs.existsSync(skillPath)) {
|
|
290
|
+
const available = fs.readdirSync(BUNDLED_SKILLS_DIR)
|
|
291
|
+
.filter(d => fs.statSync(path.join(BUNDLED_SKILLS_DIR, d)).isDirectory())
|
|
292
|
+
.sort();
|
|
293
|
+
console.error(` Unknown skill: '${skillName}'`);
|
|
294
|
+
console.error(` Available: ${available.join(', ')}`);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
const report = await auditor.audit(skillPath, filePath);
|
|
298
|
+
const divider = '─'.repeat(60);
|
|
299
|
+
console.log(`\n► Audit prompt — paste into Claude, ChatGPT, or your AI assistant:\n${divider}\n`);
|
|
300
|
+
console.log(report);
|
|
301
|
+
console.log(`\n${divider}`);
|
|
302
|
+
console.log(`Tip: pipe to clipboard → booklib audit ${skillName} ${filePath} | pbcopy (mac)`);
|
|
303
|
+
console.log(` → booklib audit ${skillName} ${filePath} | xclip (linux)\n`);
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
case 'scan': {
|
|
308
|
+
const scanner = new BookLibScanner();
|
|
309
|
+
const docsMode = args.includes('--docs');
|
|
310
|
+
const scanDir = args.filter(a => !a.startsWith('--'))[1] || process.cwd();
|
|
311
|
+
const report = await scanner.scan(scanDir, { mode: docsMode ? 'docs' : 'code' });
|
|
312
|
+
console.log(report);
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
case 'context': {
|
|
317
|
+
const promptOnly = args.includes('--prompt-only');
|
|
318
|
+
const task = args.slice(1).filter(a => !a.startsWith('--')).join(' ');
|
|
319
|
+
if (!task) {
|
|
320
|
+
console.error('Usage: booklib context "<task description>" [--prompt-only] [--file=<path>] [--no-graph]');
|
|
321
|
+
console.error('Example: booklib context "implement a payment service in Kotlin with async error handling"');
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
const builder = new ContextBuilder();
|
|
325
|
+
const useGraph = !args.includes('--no-graph') && !promptOnly;
|
|
326
|
+
const fileArg = parseFlag(args, 'file');
|
|
327
|
+
const result = useGraph
|
|
328
|
+
? await builder.buildWithGraph(task, fileArg)
|
|
329
|
+
: await builder.build(task, { promptOnly });
|
|
330
|
+
console.log(result);
|
|
331
|
+
|
|
332
|
+
if (fileArg && useGraph && !result.includes('## Knowledge Graph Context')) {
|
|
333
|
+
process.stderr.write(
|
|
334
|
+
`\nTip: no component is mapped to "${fileArg}".\n` +
|
|
335
|
+
` To enable graph context injection: booklib component add <name> "<glob>"\n` +
|
|
336
|
+
` Example: booklib component add auth "src/auth/**"\n`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
case 'save-state': {
|
|
343
|
+
const handoff = new BookLibHandoff();
|
|
344
|
+
const parsed = {};
|
|
345
|
+
args.slice(1).forEach(a => {
|
|
346
|
+
const [k, ...v] = a.replace('--', '').split('=');
|
|
347
|
+
if (k && v.length) parsed[k] = v.join('=');
|
|
348
|
+
});
|
|
349
|
+
handoff.saveState(parsed);
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
case 'resume': {
|
|
354
|
+
const handoff = new BookLibHandoff();
|
|
355
|
+
console.log(handoff.resume(args[1]));
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
case 'recover-auto': {
|
|
360
|
+
const handoff = new BookLibHandoff();
|
|
361
|
+
console.log(handoff.recoverFromSessionOrGit());
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
case 'sessions': {
|
|
366
|
+
const mgr = new BookLibSessionManager(process.cwd());
|
|
367
|
+
const subCmd = args[1];
|
|
368
|
+
|
|
369
|
+
if (subCmd === 'cleanup') {
|
|
370
|
+
const beforeDays = parseInt(args[2]?.split('=')[1]) || 90;
|
|
371
|
+
const result = mgr.cleanupSessions({ beforeDays, archive: true });
|
|
372
|
+
console.log(`✅ Archived ${result.archived} sessions, deleted ${result.deleted}`);
|
|
373
|
+
console.log(`Preview: ${JSON.stringify(result.preview.slice(0, 3), null, 2)}`);
|
|
374
|
+
} else if (subCmd === 'diff') {
|
|
375
|
+
const diff = mgr.diffSessions(args[2], args[3]);
|
|
376
|
+
if (diff.error) console.error(diff.error);
|
|
377
|
+
else {
|
|
378
|
+
console.log(`\n📊 Comparing: ${diff.session1} vs ${diff.session2}`);
|
|
379
|
+
console.log(`\nGoal Changed: ${diff.goal.changed}`);
|
|
380
|
+
console.log(` ${diff.session1}: ${diff.goal.s1}`);
|
|
381
|
+
console.log(` ${diff.session2}: ${diff.goal.s2}`);
|
|
382
|
+
console.log(`\nConflicting Tasks: ${diff.tasks.conflicts.length}`);
|
|
383
|
+
diff.tasks.conflicts.forEach(t => console.log(` ⚠️ ${t}`));
|
|
384
|
+
console.log(`\nNew Skills: ${diff.skills.added.join(', ') || 'none'}`);
|
|
385
|
+
}
|
|
386
|
+
} else if (subCmd === 'find') {
|
|
387
|
+
const result = mgr.findSession(args[2], { searchGlobal: true });
|
|
388
|
+
if (result) {
|
|
389
|
+
console.log(`✅ Found: ${result.path} (${result.scope})`);
|
|
390
|
+
} else {
|
|
391
|
+
console.log(`❌ Session not found: ${args[2]}`);
|
|
392
|
+
}
|
|
393
|
+
} else if (subCmd === 'search') {
|
|
394
|
+
const results = mgr.searchSessions(args[2]);
|
|
395
|
+
if (results.length === 0) {
|
|
396
|
+
console.log(`No sessions found matching: ${args[2]}`);
|
|
397
|
+
} else {
|
|
398
|
+
console.log(`\n🔍 Found ${results.length} session(s):`);
|
|
399
|
+
results.forEach(r => {
|
|
400
|
+
console.log(`\n 📝 ${r.name}`);
|
|
401
|
+
console.log(` Goal: ${r.goal}`);
|
|
402
|
+
console.log(` Tags: ${r.tags.join(', ') || 'none'}`);
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
} else if (subCmd === 'tag') {
|
|
406
|
+
const sessionId = args[2];
|
|
407
|
+
const tagArg = args.find(a => a.startsWith('--add='));
|
|
408
|
+
if (!tagArg) {
|
|
409
|
+
console.error('Usage: booklib sessions tag <id> --add=tag1,tag2');
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
const tags = tagArg.split('=')[1].split(',');
|
|
413
|
+
const result = mgr.tagSession(sessionId, tags, 'add');
|
|
414
|
+
console.log(`✅ Tagged: ${result.session}`);
|
|
415
|
+
console.log(` Tags: ${result.tags.join(', ')}`);
|
|
416
|
+
} else if (subCmd === 'validate') {
|
|
417
|
+
const result = mgr.validateSession(args[2]);
|
|
418
|
+
console.log(`\n${result.valid ? '✅' : '⚠️'} Validation Result:`);
|
|
419
|
+
if (result.errors.length > 0) {
|
|
420
|
+
console.log('Errors:');
|
|
421
|
+
result.errors.forEach(e => console.log(` ❌ ${e}`));
|
|
422
|
+
}
|
|
423
|
+
if (result.warnings.length > 0) {
|
|
424
|
+
console.log('Warnings:');
|
|
425
|
+
result.warnings.forEach(w => console.log(` ⚠️ ${w}`));
|
|
426
|
+
}
|
|
427
|
+
console.log(`Score: ${result.score}/100`);
|
|
428
|
+
} else if (subCmd === 'create') {
|
|
429
|
+
const templateArg = args.find(a => a.startsWith('--template='));
|
|
430
|
+
const template = templateArg?.split('=')[1];
|
|
431
|
+
const sessionName = args[3];
|
|
432
|
+
if (!template || !sessionName) {
|
|
433
|
+
console.error('Usage: booklib sessions create --template=<type> <name>');
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
const result = mgr.createFromTemplate(template, sessionName);
|
|
437
|
+
if (result.error) console.error(result.error);
|
|
438
|
+
else console.log(`✅ Created session from template: ${result.created}`);
|
|
439
|
+
} else if (subCmd === 'report') {
|
|
440
|
+
const sinceArg = args.find(a => a.startsWith('--since='));
|
|
441
|
+
const since = sinceArg?.split('=')[1];
|
|
442
|
+
const stats = mgr.generateReport({ since });
|
|
443
|
+
console.log(`\n📊 Session Report`);
|
|
444
|
+
console.log(`Total sessions: ${stats.total_sessions}`);
|
|
445
|
+
console.log(`Pending tasks: ${stats.total_tasks}`);
|
|
446
|
+
console.log(`Active skills: ${stats.unique_skills}`);
|
|
447
|
+
console.log(`\nTop Skills: ${stats.unique_skills.slice(0, 3).join(', ')}`);
|
|
448
|
+
console.log(`\nRecent Activity:`);
|
|
449
|
+
stats.recent_activity.forEach(a => {
|
|
450
|
+
console.log(` 📝 ${a.name}: ${a.goal} (${new Date(a.timestamp).toLocaleDateString()})`);
|
|
451
|
+
});
|
|
452
|
+
} else if (subCmd === 'history') {
|
|
453
|
+
const history = mgr.getVersionHistory(args[2]);
|
|
454
|
+
console.log(`\n📜 Version History: ${args[2]}`);
|
|
455
|
+
console.log(`Total versions: ${history.length}`);
|
|
456
|
+
history.slice(0, 5).forEach(v => {
|
|
457
|
+
console.log(` Version ${v.version}: ${v.timestamp}`);
|
|
458
|
+
});
|
|
459
|
+
} else if (subCmd === 'encrypt') {
|
|
460
|
+
const result = mgr.encryptSession(args[2]);
|
|
461
|
+
if (result.error) console.error(result.error);
|
|
462
|
+
else console.log(`🔒 Encrypted: ${result.encrypted}`);
|
|
463
|
+
} else if (subCmd === 'summarize') {
|
|
464
|
+
const ai = new BookLibAIFeatures(process.cwd());
|
|
465
|
+
const hasAiFlag = args.includes('--ai');
|
|
466
|
+
if (!hasAiFlag) {
|
|
467
|
+
console.log('Use: booklib sessions summarize <id> --ai');
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
const sessionPath = path.join(process.cwd(), '.booklib/sessions', `${args[2]}.md`);
|
|
471
|
+
if (!fs.existsSync(sessionPath)) {
|
|
472
|
+
console.error(`Session not found: ${args[2]}`);
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
const content = fs.readFileSync(sessionPath, 'utf8');
|
|
476
|
+
const parseSessionContent = (c) => {
|
|
477
|
+
const extract = (tag) => {
|
|
478
|
+
const regex = new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`);
|
|
479
|
+
const match = c.match(regex);
|
|
480
|
+
return match ? match[1].trim() : '';
|
|
481
|
+
};
|
|
482
|
+
return { session_id: extract('session_id'), goal: extract('goal') };
|
|
483
|
+
};
|
|
484
|
+
const summary = parseSessionContent(content);
|
|
485
|
+
const rec = ai.recommendSkills(summary.goal);
|
|
486
|
+
console.log(`\n📝 Session: ${summary.session_id}`);
|
|
487
|
+
console.log(`Goal: ${summary.goal}`);
|
|
488
|
+
console.log(`\n💡 Suggested Skills:`);
|
|
489
|
+
rec.recommendations.forEach(r => {
|
|
490
|
+
console.log(` • ${r.skill} (${r.confidence}%): ${r.reason}`);
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
case 'sessions-list': {
|
|
497
|
+
const coord = new BookLibSessionCoordinator();
|
|
498
|
+
const sessions = coord.listAllSessions();
|
|
499
|
+
if (sessions.length === 0) { console.log('No sessions found.'); break; }
|
|
500
|
+
sessions.forEach(s => console.log(` 📝 ${s.id} — ${s.goal} [${s.branch}]`));
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
case 'sessions-merge': {
|
|
505
|
+
const coord = new BookLibSessionCoordinator();
|
|
506
|
+
const ids = args[1]?.split(',');
|
|
507
|
+
const output = args[2];
|
|
508
|
+
if (!ids || !output) { console.error('Usage: booklib sessions-merge <id1,id2,...> <output-name>'); process.exit(1); }
|
|
509
|
+
const result = coord.mergeSessions(ids, output);
|
|
510
|
+
console.log(result.message || `✅ Merged into: ${output}`);
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
case 'sessions-lineage': {
|
|
515
|
+
const coord = new BookLibSessionCoordinator();
|
|
516
|
+
if (args[1] && args[2]) {
|
|
517
|
+
coord.trackLineage(args[1], args[2], args[3] || '');
|
|
518
|
+
console.log(`✅ Lineage tracked: ${args[1]} → ${args[2]}`);
|
|
519
|
+
} else {
|
|
520
|
+
console.log(coord.displayLineageTree());
|
|
521
|
+
}
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
case 'sessions-compare': {
|
|
526
|
+
const coord = new BookLibSessionCoordinator();
|
|
527
|
+
const ids = args[1]?.split(',');
|
|
528
|
+
const targetFile = args[2];
|
|
529
|
+
const output = args[3];
|
|
530
|
+
if (!ids || !targetFile || !output) { console.error('Usage: booklib sessions-compare <id1,id2,...> <file> <output-name>'); process.exit(1); }
|
|
531
|
+
const result = coord.compareAudits(ids, targetFile, output);
|
|
532
|
+
console.log(result.message || `✅ Comparison saved: ${output}`);
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
case 'hooks': {
|
|
537
|
+
const mgr = new BookLibSessionManager(process.cwd());
|
|
538
|
+
if (args[1] === 'install') {
|
|
539
|
+
const result = mgr.installGitHooks();
|
|
540
|
+
console.log(`✅ Installed hooks: ${result.installed.join(', ')}`);
|
|
541
|
+
}
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
case 'extension-data': {
|
|
546
|
+
const ai = new BookLibAIFeatures(process.cwd());
|
|
547
|
+
const data = ai.getExtensionData();
|
|
548
|
+
console.log(JSON.stringify(data, null, 2));
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
case 'github-integration': {
|
|
553
|
+
const ai = new BookLibAIFeatures(process.cwd());
|
|
554
|
+
const data = ai.getGitHubIntegrationData(args[1]);
|
|
555
|
+
console.log(JSON.stringify(data, null, 2));
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
case 'slack-integration': {
|
|
560
|
+
const ai = new BookLibAIFeatures(process.cwd());
|
|
561
|
+
const data = ai.getSlackIntegrationData(args[1]);
|
|
562
|
+
console.log(JSON.stringify(data, null, 2));
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
case 'init': {
|
|
567
|
+
// Backwards-compat: if legacy flags are passed, run old init flow
|
|
568
|
+
const hasLegacyFlags = args.some(a =>
|
|
569
|
+
a.startsWith('--tool=') || a.startsWith('--skills=') ||
|
|
570
|
+
a.includes('--dry-run') || a.includes('--ecc')
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
if (hasLegacyFlags) {
|
|
574
|
+
// ── Legacy init path ─────────────────────────────────────────────────
|
|
575
|
+
const orchestratorArg = args.find(a => a.startsWith('--orchestrator='))?.split('=')[1] ?? null;
|
|
576
|
+
const dryRun = args.includes('--dry-run');
|
|
577
|
+
const hasToolFlag = args.some(a => a.startsWith('--tool='));
|
|
578
|
+
const targetFlag = args.find(a => a.startsWith('--target='))?.split('=')[1] ?? null;
|
|
579
|
+
let targetArg;
|
|
580
|
+
if (hasToolFlag) {
|
|
581
|
+
targetArg = args.find(a => a.startsWith('--tool='))?.split('=')[1];
|
|
582
|
+
} else if (targetFlag) {
|
|
583
|
+
targetArg = targetFlag;
|
|
584
|
+
} else if (!dryRun) {
|
|
585
|
+
const { configPath } = resolveBookLibPaths();
|
|
586
|
+
let savedConfig = {};
|
|
587
|
+
try { savedConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch { /* no config yet */ }
|
|
588
|
+
if (savedConfig.tools?.length) {
|
|
589
|
+
targetArg = savedConfig.tools.join(',');
|
|
590
|
+
console.log(`Using saved tool selection: ${targetArg} (pass --tool=X to override)\n`);
|
|
591
|
+
} else {
|
|
592
|
+
targetArg = await promptToolSelection();
|
|
593
|
+
const updatedConfig = { ...savedConfig, tools: targetArg === 'all'
|
|
594
|
+
? ['claude', 'cursor', 'copilot', 'gemini', 'codex', 'windsurf', 'roo-code', 'openhands', 'junie', 'goose', 'opencode', 'letta']
|
|
595
|
+
: targetArg.split(',') };
|
|
596
|
+
try { fs.writeFileSync(configPath, JSON.stringify(updatedConfig, null, 2)); } catch { /* best-effort */ }
|
|
597
|
+
}
|
|
598
|
+
} else {
|
|
599
|
+
targetArg = 'auto';
|
|
600
|
+
}
|
|
601
|
+
const skillsArg = args.find(a => a.startsWith('--skills='))?.split('=')[1];
|
|
602
|
+
const rulesArg = args.find(a => a.startsWith('--rules='))?.split('=')[1];
|
|
603
|
+
const pullEcc = args.includes('--ecc');
|
|
604
|
+
const includeAgents = pullEcc || args.includes('--agents');
|
|
605
|
+
const includeCommands = pullEcc || args.includes('--commands');
|
|
606
|
+
const includeRules = pullEcc || args.includes('--rules') || rulesArg != null;
|
|
607
|
+
const skillList = skillsArg?.split(',').map(s => s.trim());
|
|
608
|
+
const langList = rulesArg ? rulesArg.split(',').map(s => s.trim()) : (includeRules ? null : false);
|
|
609
|
+
const initializer = new ProjectInitializer();
|
|
610
|
+
|
|
611
|
+
if (!skillList) {
|
|
612
|
+
const detected = initializer.detectRelevantSkills();
|
|
613
|
+
if (detected.length === 0 && !includeAgents && !includeCommands && !includeRules) {
|
|
614
|
+
console.log('No skills auto-detected. Specify with --skills=skill1,skill2 or use --ecc to pull agents/commands/rules.');
|
|
615
|
+
process.exit(1);
|
|
616
|
+
}
|
|
617
|
+
if (detected.length > 0) console.log(`Auto-detected skills: ${detected.join(', ')}\n`);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (hasToolFlag && !dryRun) {
|
|
621
|
+
const { configPath } = resolveBookLibPaths();
|
|
622
|
+
let savedConfig = {};
|
|
623
|
+
try { savedConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch { /* no config yet */ }
|
|
624
|
+
const toolList = targetArg === 'all'
|
|
625
|
+
? ['claude', 'cursor', 'copilot', 'gemini', 'codex', 'windsurf', 'roo-code', 'openhands', 'junie', 'goose', 'opencode', 'letta']
|
|
626
|
+
: targetArg.split(',');
|
|
627
|
+
try { fs.writeFileSync(configPath, JSON.stringify({ ...savedConfig, tools: toolList }, null, 2)); } catch { /* best-effort */ }
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (skillList || initializer.detectRelevantSkills().length > 0) {
|
|
631
|
+
console.log(`Generating context files for: ${targetArg === 'all' ? 'claude, cursor, copilot, gemini, codex, windsurf, roo-code, openhands, junie, goose, opencode, letta' : targetArg}\n`);
|
|
632
|
+
const effectiveSkills = skillList ?? initializer.detectRelevantSkills();
|
|
633
|
+
// Load saved config for profile-based rendering (Spec ⑨)
|
|
634
|
+
const { configPath: legacyConfigPath } = resolveBookLibPaths();
|
|
635
|
+
let legacySavedConfig = {};
|
|
636
|
+
try { legacySavedConfig = JSON.parse(fs.readFileSync(legacyConfigPath, 'utf8')); } catch { /* no config yet */ }
|
|
637
|
+
const written = await initializer.init({
|
|
638
|
+
skills: effectiveSkills,
|
|
639
|
+
target: targetArg,
|
|
640
|
+
dryRun,
|
|
641
|
+
profile: legacySavedConfig.profile ?? 'software-development',
|
|
642
|
+
stack: legacySavedConfig.stack ?? effectiveSkills.join(', '),
|
|
643
|
+
});
|
|
644
|
+
if (!dryRun && written.length > 0) console.log('');
|
|
645
|
+
|
|
646
|
+
// Discovery hint: suggest related skills
|
|
647
|
+
const related = initializer.suggestRelatedSkills(effectiveSkills, process.cwd());
|
|
648
|
+
if (related.length > 0) {
|
|
649
|
+
console.log(' \u{1F4A1} Also consider for your stack:');
|
|
650
|
+
related.forEach(s => console.log(` booklib init --skills=${effectiveSkills.join(',')},${s}`));
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (includeAgents || includeCommands || includeRules) {
|
|
655
|
+
const pulling = [];
|
|
656
|
+
if (includeRules) pulling.push(langList ? `rules (${langList.join(',')})` : 'rules (all languages)');
|
|
657
|
+
if (includeAgents) pulling.push('agents → .claude/agents/');
|
|
658
|
+
if (includeCommands) pulling.push('commands → .claude/commands/');
|
|
659
|
+
console.log(`Pulling ECC artifacts: ${pulling.join(', ')}\n`);
|
|
660
|
+
try {
|
|
661
|
+
const eccWritten = await initializer.fetchEccArtifacts({ languages: langList, includeAgents, includeCommands, dryRun });
|
|
662
|
+
if (!dryRun && eccWritten.length > 0) console.log(`\nPulled ${eccWritten.length} artifact(s) from ECC.`);
|
|
663
|
+
} catch (err) {
|
|
664
|
+
console.error(`ECC fetch failed: ${err.message}`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// ── New guided wizard ─────────────────────────────────────────────────
|
|
671
|
+
const reset = args.includes('--reset');
|
|
672
|
+
await runWizard(process.cwd(), { reset });
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
case 'setup': {
|
|
677
|
+
const engine = new DiscoveryEngine();
|
|
678
|
+
const fetcher = new SkillFetcher();
|
|
679
|
+
console.log('Discovering skills...');
|
|
680
|
+
const skills = await engine.refresh();
|
|
681
|
+
const trusted = skills.filter(s => s.trusted);
|
|
682
|
+
const untrusted = skills.filter(s => !s.trusted);
|
|
683
|
+
if (trusted.length === 0) {
|
|
684
|
+
console.log('No trusted skills found. Check your booklib.config.json sources.');
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
console.log(`Found ${trusted.length} trusted skill(s) to install, ${untrusted.length} require confirmation.\n`);
|
|
688
|
+
let installed = 0;
|
|
689
|
+
for (const skill of trusted) {
|
|
690
|
+
if (fetcher.isCached(skill)) {
|
|
691
|
+
console.log(` ✓ ${skill.name} (already installed)`);
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
process.stdout.write(` ↓ Fetching ${skill.name}...`);
|
|
695
|
+
try {
|
|
696
|
+
await fetcher.fetch(skill);
|
|
697
|
+
console.log(' done');
|
|
698
|
+
installed++;
|
|
699
|
+
} catch (err) {
|
|
700
|
+
console.log(` failed: ${err.message}`);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (installed > 0) {
|
|
704
|
+
console.log(`\nRe-indexing...`);
|
|
705
|
+
const { skillsPath, cachePath } = resolveBookLibPaths();
|
|
706
|
+
const indexer = new BookLibIndexer();
|
|
707
|
+
await indexer.indexDirectory(skillsPath);
|
|
708
|
+
await indexer.indexDirectory(path.join(cachePath, 'skills'));
|
|
709
|
+
}
|
|
710
|
+
console.log('\n✅ Setup complete. Run: booklib search "<query>"');
|
|
711
|
+
console.log(' Skills synced to ~/.claude/skills/ — pair with an orchestrator if needed:');
|
|
712
|
+
console.log(' obra/superpowers: /plugin install superpowers ruflo: npm install -g ruflo');
|
|
713
|
+
if (untrusted.length > 0) {
|
|
714
|
+
console.log(`\nTo install remaining skills, run: booklib install <skill-name>`);
|
|
715
|
+
untrusted.forEach(s => console.log(` • ${s.name}`));
|
|
716
|
+
}
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
case 'add': {
|
|
721
|
+
console.error('⚠ "booklib add" is deprecated. Use: booklib install <name>');
|
|
722
|
+
const installer = new BookLibInstaller();
|
|
723
|
+
const skillId = args[1];
|
|
724
|
+
if (!skillId) { console.error('Usage: booklib add <skill-id-or-url>'); process.exit(1); }
|
|
725
|
+
await installer.add(skillId);
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
case 'install': {
|
|
730
|
+
const names = args.slice(1).filter(a => !a.startsWith('--'));
|
|
731
|
+
if (names.length === 0) {
|
|
732
|
+
console.error('Usage: booklib install <skill-name> [skill-name...]');
|
|
733
|
+
process.exit(1);
|
|
734
|
+
}
|
|
735
|
+
const { installSkill } = await import('../lib/skill-fetcher.js');
|
|
736
|
+
for (const name of names) {
|
|
737
|
+
const result = installSkill(name);
|
|
738
|
+
if (result === 'installed') console.log(` ✓ ${name}`);
|
|
739
|
+
else if (result === 'already-installed') console.log(` · ${name} (already installed)`);
|
|
740
|
+
else console.log(` ✗ ${name}: not found in any catalog`);
|
|
741
|
+
}
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
case 'fetch': {
|
|
746
|
+
console.error('⚠ "booklib fetch" is deprecated. Use: booklib install <name>');
|
|
747
|
+
const { SKILL_REGISTRY } = await import('../lib/registry/skills.js');
|
|
748
|
+
const skillName = args[1];
|
|
749
|
+
if (!skillName) { console.error('Usage: booklib fetch <skill-name>'); process.exit(1); }
|
|
750
|
+
const skill = SKILL_REGISTRY.find(s => s.name === skillName || s.name.endsWith(`/${skillName}`));
|
|
751
|
+
if (!skill) { console.error(`Skill not found in registry: ${skillName}`); process.exit(1); }
|
|
752
|
+
const fetcher = new SkillFetcher();
|
|
753
|
+
try {
|
|
754
|
+
await fetcher.fetch(skill, {
|
|
755
|
+
onPrompt: async (s) => {
|
|
756
|
+
process.stdout.write(`Index "${s.name}" from ${s.source.type} (untrusted)? [y/N] `);
|
|
757
|
+
const answer = await new Promise(r => {
|
|
758
|
+
process.stdin.once('data', d => r(d.toString().trim().toLowerCase()));
|
|
759
|
+
});
|
|
760
|
+
return answer === 'y' || answer === 'yes';
|
|
761
|
+
},
|
|
762
|
+
});
|
|
763
|
+
} catch (err) {
|
|
764
|
+
if (err instanceof RequiresConfirmationError) {
|
|
765
|
+
console.error(err.message);
|
|
766
|
+
process.exit(1);
|
|
767
|
+
}
|
|
768
|
+
throw err;
|
|
769
|
+
}
|
|
770
|
+
// Slot limit warning
|
|
771
|
+
const slotCount = countInstalledSlots();
|
|
772
|
+
if (slotCount >= SKILL_LIMIT) {
|
|
773
|
+
console.log(`\n ⚠ You now have ${slotCount}/${SKILL_LIMIT} skill slots used.`);
|
|
774
|
+
console.log(' Claude may truncate skill descriptions. Run "booklib doctor" to clean up.');
|
|
775
|
+
} else if (slotCount >= SKILL_LIMIT - 4) {
|
|
776
|
+
console.log(`\n ⚠ ${slotCount}/${SKILL_LIMIT} slots used — approaching limit.`);
|
|
777
|
+
console.log(' Run "booklib doctor" to review installed skills.');
|
|
778
|
+
}
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
case 'sync': {
|
|
783
|
+
// Retroactively sync all already-fetched BookLib skills to ~/.claude/skills/
|
|
784
|
+
const { cachePath } = resolveBookLibPaths();
|
|
785
|
+
const skillsDir = path.join(cachePath, 'skills');
|
|
786
|
+
if (!fs.existsSync(skillsDir)) { console.log('No fetched skills found. Run: booklib setup'); break; }
|
|
787
|
+
const fetcher = new SkillFetcher();
|
|
788
|
+
const dirs = fs.readdirSync(skillsDir).filter(d => fs.existsSync(path.join(skillsDir, d, 'SKILL.md')));
|
|
789
|
+
let synced = 0;
|
|
790
|
+
for (const d of dirs) {
|
|
791
|
+
const skillFile = path.join(skillsDir, d, 'SKILL.md');
|
|
792
|
+
const head = fs.readFileSync(skillFile, 'utf8').split('\n').slice(0, 15).join('\n');
|
|
793
|
+
const nameMatch = head.match(/^name:\s*["']?(.+?)["']?\s*$/m);
|
|
794
|
+
const descMatch = head.match(/^description:\s*(.+)$/m);
|
|
795
|
+
const name = nameMatch ? nameMatch[1].trim() : d;
|
|
796
|
+
const description = descMatch ? descMatch[1].trim().replace(/^["']|["']$/g, '') : '';
|
|
797
|
+
fetcher._syncToClaudeSkills({ name, description }, path.join(skillsDir, d));
|
|
798
|
+
synced++;
|
|
799
|
+
}
|
|
800
|
+
console.log(`Synced ${synced} skills to ~/.claude/skills/ — available via Claude Code Skill tool`);
|
|
801
|
+
console.log(` Pair with an orchestrator: /plugin install superpowers (obra) · npm install -g ruflo (ruflo)`);
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
case 'discover': {
|
|
806
|
+
const engine = new DiscoveryEngine();
|
|
807
|
+
const flag = args[1];
|
|
808
|
+
if (flag === '--refresh') {
|
|
809
|
+
console.log('Refreshing discovery cache...');
|
|
810
|
+
const skills = await engine.refresh();
|
|
811
|
+
console.log(`Found ${skills.length} skills from external sources.`);
|
|
812
|
+
skills.forEach(s => {
|
|
813
|
+
const stars = s.stars ? ` ★${s.stars.toLocaleString()}` : '';
|
|
814
|
+
const trust = s.trusted ? '' : ' (requires confirmation)';
|
|
815
|
+
console.log(` • ${s.name}${stars} [${s.source.type}]${trust} — ${s.description}`);
|
|
816
|
+
});
|
|
817
|
+
} else {
|
|
818
|
+
const skills = await engine.discover();
|
|
819
|
+
if (skills.length === 0) {
|
|
820
|
+
console.log('No external sources configured. Add sources to booklib.config.json.');
|
|
821
|
+
} else {
|
|
822
|
+
console.log(`Discovered ${skills.length} skills:`);
|
|
823
|
+
skills.forEach(s => {
|
|
824
|
+
const stars = s.stars ? ` ★${s.stars.toLocaleString()}` : '';
|
|
825
|
+
const trust = s.trusted ? '' : ' (requires confirmation)';
|
|
826
|
+
console.log(` • ${s.name}${stars} [${s.source.type}]${trust} — ${s.description}`);
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
break;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
case 'profile': {
|
|
834
|
+
const role = args[1];
|
|
835
|
+
const ALL_ROLES = ['architect', 'coder', 'reviewer', 'tester', 'security', 'frontend', 'optimizer', 'devops', 'ai-engineer', 'manager', 'product', 'legal', 'writer', 'strategist', 'designer'];
|
|
836
|
+
if (!role || role === '--list') {
|
|
837
|
+
console.log('\nAvailable agent roles:\n');
|
|
838
|
+
ALL_ROLES.forEach(r => console.log(` • ${r}`));
|
|
839
|
+
console.log('\nUsage: booklib profile <role>\n');
|
|
840
|
+
break;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const engine = new DiscoveryEngine();
|
|
844
|
+
const all = await engine.discover();
|
|
845
|
+
// Merge with registry for role metadata
|
|
846
|
+
const { skills: regSkills } = JSON.parse(
|
|
847
|
+
(await import('fs')).default.readFileSync(
|
|
848
|
+
(await import('path')).default.join(
|
|
849
|
+
(await import('url')).default.fileURLToPath(new URL('.', import.meta.url)),
|
|
850
|
+
'../community/registry.json'
|
|
851
|
+
), 'utf8'
|
|
852
|
+
)
|
|
853
|
+
);
|
|
854
|
+
const roleMap = new Map(regSkills.map(s => [s.name, s.roles ?? []]));
|
|
855
|
+
|
|
856
|
+
const matches = all.filter(s => {
|
|
857
|
+
const roles = roleMap.get(s.name) ?? s.roles ?? [];
|
|
858
|
+
return roles.includes(role);
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
if (matches.length === 0) {
|
|
862
|
+
console.log(`No skills found for role "${role}". Try: booklib profile --list`);
|
|
863
|
+
break;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
console.log(`\n🤖 Skill profile for agent role: ${role}\n`);
|
|
867
|
+
console.log(` ${matches.length} skills pre-selected from ${all.length} available\n`);
|
|
868
|
+
matches.forEach(s => {
|
|
869
|
+
const stars = s.stars ? ` ★${s.stars.toLocaleString()}` : '';
|
|
870
|
+
console.log(` • ${s.name}${stars}`);
|
|
871
|
+
if (s.description) console.log(` ${s.description.slice(0, 100)}`);
|
|
872
|
+
});
|
|
873
|
+
console.log(`\nTo load all: booklib setup (then each skill is available to inject)`);
|
|
874
|
+
console.log(`To search within role: booklib search "<query>" --role=${role}\n`);
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
case 'swarm-config': {
|
|
879
|
+
const trigger = args[1];
|
|
880
|
+
|
|
881
|
+
// Trigger → roles → skill domains mapping (extends ruflo's worker-integration concept)
|
|
882
|
+
const SWARM_TRIGGERS = {
|
|
883
|
+
audit: { roles: ['security', 'tester'], phases: ['security-scan', 'coverage', 'vulnerability-check'] },
|
|
884
|
+
refactor: { roles: ['coder', 'reviewer'], phases: ['complexity', 'naming', 'patterns', 'solid'] },
|
|
885
|
+
architect: { roles: ['architect'], phases: ['system-design', 'ddd', 'api-design'] },
|
|
886
|
+
frontend: { roles: ['frontend', 'tester'], phases: ['components', 'state', 'performance', 'a11y'] },
|
|
887
|
+
release: { roles: ['devops', 'security'], phases: ['docker', 'secrets', 'headers', 'changelog'] },
|
|
888
|
+
research: { roles: ['ai-engineer', 'architect'], phases: ['prompt-design', 'rag', 'reliability'] },
|
|
889
|
+
manage: { roles: ['manager'], phases: ['leadership', 'retro', 'process'] },
|
|
890
|
+
product: { roles: ['product', 'writer'], phases: ['requirements', 'user-stories', 'prioritization'] },
|
|
891
|
+
legal: { roles: ['legal'], phases: ['contract-review', 'risk-assessment', 'compliance'] },
|
|
892
|
+
write: { roles: ['writer'], phases: ['outline', 'draft', 'edit', 'review'] },
|
|
893
|
+
strategy: { roles: ['strategist', 'product'], phases: ['discovery', 'positioning', 'roadmap'] },
|
|
894
|
+
design: { roles: ['designer', 'frontend'], phases: ['visual-hierarchy', 'typography', 'brand'] },
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
if (!trigger || trigger === '--list') {
|
|
898
|
+
console.log('\n🐝 BookLib Swarm Trigger Config\n');
|
|
899
|
+
console.log(' Maps swarm triggers → agent roles → skill domains\n');
|
|
900
|
+
console.log(' Usage: booklib swarm-config <trigger>\n');
|
|
901
|
+
Object.entries(SWARM_TRIGGERS).forEach(([t, cfg]) => {
|
|
902
|
+
console.log(` ${t.padEnd(12)} → roles: ${cfg.roles.join(', ')}`);
|
|
903
|
+
});
|
|
904
|
+
console.log('\n booklib swarm-config <trigger> Show skills for a trigger');
|
|
905
|
+
console.log(' booklib profile <role> Show skills for a role\n');
|
|
906
|
+
break;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const cfg = SWARM_TRIGGERS[trigger];
|
|
910
|
+
if (!cfg) {
|
|
911
|
+
console.log(`Unknown trigger "${trigger}". Available: ${Object.keys(SWARM_TRIGGERS).join(', ')}`);
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
const engine = new DiscoveryEngine();
|
|
916
|
+
const all = await engine.discover();
|
|
917
|
+
const { skills: regSkills } = JSON.parse(
|
|
918
|
+
(await import('fs')).default.readFileSync(
|
|
919
|
+
(await import('path')).default.join(
|
|
920
|
+
(await import('url')).default.fileURLToPath(new URL('.', import.meta.url)),
|
|
921
|
+
'../community/registry.json'
|
|
922
|
+
), 'utf8'
|
|
923
|
+
)
|
|
924
|
+
);
|
|
925
|
+
const roleMap = new Map(regSkills.map(s => [s.name, s.roles ?? []]));
|
|
926
|
+
|
|
927
|
+
console.log(`\n🐝 Swarm config for trigger: ${trigger}\n`);
|
|
928
|
+
console.log(` Phases: ${cfg.phases.join(' → ')}\n`);
|
|
929
|
+
|
|
930
|
+
for (const role of cfg.roles) {
|
|
931
|
+
const roleSkills = all.filter(s => (roleMap.get(s.name) ?? s.roles ?? []).includes(role));
|
|
932
|
+
console.log(` Agent role: ${role} (${roleSkills.length} skills)`);
|
|
933
|
+
roleSkills.slice(0, 5).forEach(s => {
|
|
934
|
+
const stars = s.stars ? ` ★${s.stars.toLocaleString()}` : '';
|
|
935
|
+
console.log(` • ${s.name}${stars} — ${(s.description ?? '').slice(0, 75)}`);
|
|
936
|
+
});
|
|
937
|
+
if (roleSkills.length > 5) console.log(` … and ${roleSkills.length - 5} more (booklib profile ${role})`);
|
|
938
|
+
console.log();
|
|
939
|
+
}
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
case 'note': {
|
|
944
|
+
const title = args.slice(1).join(' ');
|
|
945
|
+
if (!title) { console.error('Usage: booklib note "<title>"'); process.exit(1); }
|
|
946
|
+
const id = generateNodeId('node');
|
|
947
|
+
let body = await readStdin();
|
|
948
|
+
if (!body) body = openEditor('') ?? '';
|
|
949
|
+
if (!body) body = await readInteractive('Enter note content (Ctrl+D to finish):\n');
|
|
950
|
+
const noteContent = serializeNode({ id, type: 'note', title, content: body ?? '' });
|
|
951
|
+
const filePath = saveNode(noteContent, id);
|
|
952
|
+
await autoIndexNode(filePath);
|
|
953
|
+
console.log(`✅ Note created: ${filePath}`);
|
|
954
|
+
console.log(` ID: ${id}`);
|
|
955
|
+
break;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
case 'component': {
|
|
959
|
+
const subcommand = args[1];
|
|
960
|
+
const name = args[2];
|
|
961
|
+
const glob = args[3];
|
|
962
|
+
if (subcommand !== 'add' || !name || !glob) {
|
|
963
|
+
console.error('Usage: booklib component add <name> "<glob>"');
|
|
964
|
+
process.exit(1);
|
|
965
|
+
}
|
|
966
|
+
const id = `comp_${name.toLowerCase().replace(/[^a-z0-9]/g, '_')}`;
|
|
967
|
+
const content = serializeNode({
|
|
968
|
+
id,
|
|
969
|
+
type: 'component',
|
|
970
|
+
title: name,
|
|
971
|
+
nodePaths: [glob],
|
|
972
|
+
content: '',
|
|
973
|
+
});
|
|
974
|
+
const filePath = saveNode(content, id);
|
|
975
|
+
console.log(`✅ Component created: ${filePath}`);
|
|
976
|
+
console.log(` ID: ${id} paths: ${glob}`);
|
|
977
|
+
break;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
case 'link': {
|
|
981
|
+
const [, fromRef, toRef] = args;
|
|
982
|
+
const typeArg = parseFlag(args, 'type');
|
|
983
|
+
const weightArg = parseFlag(args, 'weight');
|
|
984
|
+
if (!fromRef || !toRef || !typeArg) {
|
|
985
|
+
console.error('Usage: booklib link "<title-or-id>" "<title-or-id>" --type <edge-type> [--weight 0.9]');
|
|
986
|
+
process.exit(1);
|
|
987
|
+
}
|
|
988
|
+
if (!EDGE_TYPES.includes(typeArg)) {
|
|
989
|
+
console.error(`Invalid edge type "${typeArg}". Valid: ${EDGE_TYPES.join(', ')}`);
|
|
990
|
+
process.exit(1);
|
|
991
|
+
}
|
|
992
|
+
let from, to;
|
|
993
|
+
try {
|
|
994
|
+
from = resolveNodeRef(fromRef);
|
|
995
|
+
to = resolveNodeRef(toRef);
|
|
996
|
+
} catch (err) {
|
|
997
|
+
console.error(err.message);
|
|
998
|
+
process.exit(1);
|
|
999
|
+
}
|
|
1000
|
+
const edge = {
|
|
1001
|
+
from,
|
|
1002
|
+
to,
|
|
1003
|
+
type: typeArg,
|
|
1004
|
+
weight: weightArg ? parseFloat(weightArg) : 1.0,
|
|
1005
|
+
created: new Date().toISOString().split('T')[0],
|
|
1006
|
+
};
|
|
1007
|
+
appendEdge(edge);
|
|
1008
|
+
console.log(`✅ Edge added: ${from} --[${typeArg}]--> ${to} (weight: ${edge.weight})`);
|
|
1009
|
+
break;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
case 'nodes': {
|
|
1013
|
+
const subcommand = args[1];
|
|
1014
|
+
if (!subcommand || subcommand === 'list') {
|
|
1015
|
+
const ids = listNodes();
|
|
1016
|
+
if (ids.length === 0) { console.log('No knowledge nodes yet. Try: booklib note "title"'); break; }
|
|
1017
|
+
console.log(`\n📝 Knowledge nodes (${ids.length}):\n`);
|
|
1018
|
+
for (const id of ids) {
|
|
1019
|
+
const raw = loadNode(id);
|
|
1020
|
+
const parsed = raw ? parseNodeFrontmatter(raw) : {};
|
|
1021
|
+
const tags = Array.isArray(parsed.tags) ? parsed.tags.join(', ') : '';
|
|
1022
|
+
console.log(` ${id} [${parsed.type ?? '?'}] ${parsed.title ?? '?'}${tags ? ` (${tags})` : ''}`);
|
|
1023
|
+
}
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
if (subcommand === 'show') {
|
|
1027
|
+
const id = args[2];
|
|
1028
|
+
if (!id) { console.error('Usage: booklib nodes show <id>'); process.exit(1); }
|
|
1029
|
+
const raw = loadNode(id);
|
|
1030
|
+
if (!raw) { console.error(`Node "${id}" not found.`); process.exit(1); }
|
|
1031
|
+
console.log(raw);
|
|
1032
|
+
break;
|
|
1033
|
+
}
|
|
1034
|
+
console.error('Usage: booklib nodes list | booklib nodes show <id>');
|
|
1035
|
+
process.exit(1);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
case 'dictate': {
|
|
1039
|
+
const isRaw = args.includes('--raw');
|
|
1040
|
+
const titleArg = parseFlag(args, 'title');
|
|
1041
|
+
|
|
1042
|
+
const stdinText = await readStdin();
|
|
1043
|
+
const rawText = stdinText || await readInteractive();
|
|
1044
|
+
|
|
1045
|
+
if (!rawText) { console.error('No input provided.'); process.exit(1); }
|
|
1046
|
+
|
|
1047
|
+
const id = generateNodeId('node');
|
|
1048
|
+
let nodeContent;
|
|
1049
|
+
|
|
1050
|
+
if (isRaw) {
|
|
1051
|
+
const title = titleArg ?? rawText.split('\n')[0].slice(0, 60);
|
|
1052
|
+
nodeContent = serializeNode({ id, type: 'note', title, content: rawText });
|
|
1053
|
+
} else {
|
|
1054
|
+
console.log('Structuring with AI...');
|
|
1055
|
+
let structured;
|
|
1056
|
+
try {
|
|
1057
|
+
structured = await callAnthropicAPI(buildDictatePrompt(rawText));
|
|
1058
|
+
} catch (err) {
|
|
1059
|
+
console.error(`AI structuring failed: ${err.message}`);
|
|
1060
|
+
console.error('Tip: use --raw to save without AI processing.');
|
|
1061
|
+
const title = titleArg ?? rawText.split('\n')[0].slice(0, 60);
|
|
1062
|
+
nodeContent = serializeNode({ id, type: 'note', title, content: rawText });
|
|
1063
|
+
}
|
|
1064
|
+
if (structured) {
|
|
1065
|
+
nodeContent = `---\nid: "${id}"\n` + structured.replace(/^---\n?/, '');
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
const filePath = saveNode(nodeContent, id);
|
|
1070
|
+
await autoIndexNode(filePath);
|
|
1071
|
+
console.log(`✅ Note saved: ${filePath}`);
|
|
1072
|
+
console.log(` ID: ${id}`);
|
|
1073
|
+
break;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
case 'save-chat': {
|
|
1077
|
+
const doSummarize = args.includes('--summarize');
|
|
1078
|
+
const titleArg = parseFlag(args, 'title');
|
|
1079
|
+
// Skip flag values consumed by --flag value pairs so they aren't mistaken for a file path
|
|
1080
|
+
const titleIdx = args.indexOf('--title');
|
|
1081
|
+
const consumedIndices = new Set(titleIdx !== -1 ? [titleIdx, titleIdx + 1] : []);
|
|
1082
|
+
const fileArg = args.slice(1).find((a, i) => !a.startsWith('--') && !consumedIndices.has(i + 1));
|
|
1083
|
+
|
|
1084
|
+
let transcript;
|
|
1085
|
+
if (fileArg) {
|
|
1086
|
+
transcript = fs.readFileSync(fileArg, 'utf8').trim();
|
|
1087
|
+
} else {
|
|
1088
|
+
transcript = await readStdin();
|
|
1089
|
+
}
|
|
1090
|
+
if (!transcript) {
|
|
1091
|
+
transcript = openEditor('# Paste or type the conversation here\n\n');
|
|
1092
|
+
}
|
|
1093
|
+
if (!transcript) { console.error('No conversation content provided.'); process.exit(1); }
|
|
1094
|
+
|
|
1095
|
+
const id = generateNodeId('node');
|
|
1096
|
+
let nodeContent;
|
|
1097
|
+
|
|
1098
|
+
if (doSummarize) {
|
|
1099
|
+
console.log('Summarizing conversation with AI...');
|
|
1100
|
+
try {
|
|
1101
|
+
const summary = await callAnthropicAPI(buildSummarizePrompt(transcript, titleArg ?? ''));
|
|
1102
|
+
nodeContent = `---\nid: "${id}"\n` + summary.replace(/^---\n?/, '');
|
|
1103
|
+
} catch (err) {
|
|
1104
|
+
console.error(`AI summarization failed: ${err.message}`);
|
|
1105
|
+
nodeContent = serializeNode({
|
|
1106
|
+
id, type: 'note',
|
|
1107
|
+
title: titleArg ?? 'Conversation transcript',
|
|
1108
|
+
content: transcript,
|
|
1109
|
+
sources: ['conversation'],
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
} else {
|
|
1113
|
+
nodeContent = serializeNode({
|
|
1114
|
+
id, type: 'note',
|
|
1115
|
+
title: titleArg ?? 'Conversation transcript',
|
|
1116
|
+
content: transcript,
|
|
1117
|
+
sources: ['conversation'],
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
const filePath = saveNode(nodeContent, id);
|
|
1122
|
+
await autoIndexNode(filePath);
|
|
1123
|
+
console.log(`✅ Conversation saved: ${filePath}`);
|
|
1124
|
+
console.log(` ID: ${id}`);
|
|
1125
|
+
break;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
case 'research': {
|
|
1129
|
+
const topic = args.slice(1).join(' ');
|
|
1130
|
+
if (!topic) { console.error('Usage: booklib research "<topic>"'); process.exit(1); }
|
|
1131
|
+
const id = generateNodeId('node');
|
|
1132
|
+
const template = `## Sources\n\n<!-- Add URLs, papers, docs -->\n\n## Key Findings\n\n<!-- Fill in after researching -->\n\n## Summary\n\n<!-- 2-3 sentence summary -->\n`;
|
|
1133
|
+
const nodeContent = serializeNode({
|
|
1134
|
+
id,
|
|
1135
|
+
type: 'research',
|
|
1136
|
+
title: topic,
|
|
1137
|
+
content: template,
|
|
1138
|
+
confidence: 'low',
|
|
1139
|
+
});
|
|
1140
|
+
const filePath = saveNode(nodeContent, id);
|
|
1141
|
+
await autoIndexNode(filePath);
|
|
1142
|
+
console.log(`✅ Research template created: ${filePath}`);
|
|
1143
|
+
console.log(` ID: ${id}`);
|
|
1144
|
+
console.log(` Fill in the findings — this node is already indexed and searchable.`);
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
case 'uninstall': {
|
|
1149
|
+
const skillName = args[1];
|
|
1150
|
+
if (!skillName) {
|
|
1151
|
+
console.error('Usage: booklib uninstall <skill-name>');
|
|
1152
|
+
process.exit(1);
|
|
1153
|
+
}
|
|
1154
|
+
const fetcher = new SkillFetcher();
|
|
1155
|
+
fetcher.desyncFromClaudeSkills({ name: skillName });
|
|
1156
|
+
const remaining = countInstalledSlots();
|
|
1157
|
+
console.log(`✓ Removed ${skillName} from ~/.claude/skills/`);
|
|
1158
|
+
console.log(` ${remaining}/${SKILL_LIMIT} slots now used`);
|
|
1159
|
+
break;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
case 'list': {
|
|
1163
|
+
const names = listInstalledSkillNames();
|
|
1164
|
+
const slots = countInstalledSlots();
|
|
1165
|
+
if (names.length === 0) {
|
|
1166
|
+
console.log('No BookLib-managed skills installed. Run "booklib init" to get started.');
|
|
1167
|
+
break;
|
|
1168
|
+
}
|
|
1169
|
+
console.log(`\nInstalled skills (${slots}/${SKILL_LIMIT} slots):\n`);
|
|
1170
|
+
for (const name of names) console.log(` · ${name}`);
|
|
1171
|
+
console.log('');
|
|
1172
|
+
if (slots > SKILL_LIMIT - 4) console.log(' ⚠ Approaching slot limit. Run "booklib doctor" to review.');
|
|
1173
|
+
break;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
case 'doctor': {
|
|
1177
|
+
const installHook = args.includes('--install-hook');
|
|
1178
|
+
const showUsage = args.includes('--usage');
|
|
1179
|
+
const cure = args.includes('--cure');
|
|
1180
|
+
|
|
1181
|
+
if (installHook) {
|
|
1182
|
+
try {
|
|
1183
|
+
const result = installTrackingHook();
|
|
1184
|
+
if (result.alreadyInstalled) {
|
|
1185
|
+
console.log(' Hook already installed — nothing changed.');
|
|
1186
|
+
} else {
|
|
1187
|
+
console.log('✓ Tracking hook installed');
|
|
1188
|
+
console.log(` Script: ${result.scriptPath}`);
|
|
1189
|
+
console.log(` Hook: ${result.settingsPath} → PreToolUse[Skill]`);
|
|
1190
|
+
console.log('');
|
|
1191
|
+
console.log(' Skill usage will be tracked from now on.');
|
|
1192
|
+
console.log(' Run `booklib doctor` after a few sessions to see your report.');
|
|
1193
|
+
}
|
|
1194
|
+
} catch (err) {
|
|
1195
|
+
console.error(`Failed to install hook: ${err.message}`);
|
|
1196
|
+
process.exit(1);
|
|
1197
|
+
}
|
|
1198
|
+
break;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
if (showUsage) {
|
|
1202
|
+
// Legacy usage report (moved behind --usage flag)
|
|
1203
|
+
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
1204
|
+
const SKILL_NAME_PAD = 24;
|
|
1205
|
+
const USE_LABEL_PAD = 9;
|
|
1206
|
+
|
|
1207
|
+
const claudeSkillsDir = path.join(os.homedir(), '.claude', 'skills');
|
|
1208
|
+
const usagePath = path.join(os.homedir(), '.booklib', 'usage.json');
|
|
1209
|
+
const installedNames = listInstalledSkillNames();
|
|
1210
|
+
const usageData = readUsage(usagePath);
|
|
1211
|
+
|
|
1212
|
+
if (installedNames.length === 0) {
|
|
1213
|
+
console.log('\n No BookLib-managed skills installed. Run "booklib init" to get started.\n');
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
const installDates = {};
|
|
1218
|
+
for (const name of installedNames) {
|
|
1219
|
+
try {
|
|
1220
|
+
const stat = fs.statSync(path.join(claudeSkillsDir, name, '.booklib'));
|
|
1221
|
+
installDates[name] = stat.mtime;
|
|
1222
|
+
} catch { /* unknown install date */ }
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
const summary = summarize(usageData, installedNames, installDates);
|
|
1226
|
+
const suggestions = summary.filter(s => s.suggestion !== null);
|
|
1227
|
+
|
|
1228
|
+
console.log('\n► Skill usage report\n');
|
|
1229
|
+
|
|
1230
|
+
const active = summary.filter(s => s.uses > 0 || s.suggestion !== null);
|
|
1231
|
+
const silentCount = summary.length - active.length;
|
|
1232
|
+
|
|
1233
|
+
const noUsageFile = !fs.existsSync(usagePath);
|
|
1234
|
+
if (active.length === 0 && noUsageFile) {
|
|
1235
|
+
console.log(` ${installedNames.length} community skill${installedNames.length === 1 ? '' : 's'} in ~/.booklib/skills/. No usage data yet.\n`);
|
|
1236
|
+
console.log(' Tip: run `booklib doctor --install-hook` to start tracking usage automatically.');
|
|
1237
|
+
} else if (active.length === 0) {
|
|
1238
|
+
console.log(` ${installedNames.length} community skill${installedNames.length === 1 ? '' : 's'} in ~/.booklib/skills/. No usage data yet.\n`);
|
|
1239
|
+
} else {
|
|
1240
|
+
for (const item of active) {
|
|
1241
|
+
const icon = item.suggestion ? '⚠' : '✓';
|
|
1242
|
+
const useLabel = item.uses === 1 ? '1 use ' : `${item.uses} uses`;
|
|
1243
|
+
let whenLabel;
|
|
1244
|
+
if (item.lastUsed === null) {
|
|
1245
|
+
const days = installDates[item.name]
|
|
1246
|
+
? Math.floor((Date.now() - installDates[item.name].getTime()) / MS_PER_DAY)
|
|
1247
|
+
: null;
|
|
1248
|
+
whenLabel = days !== null ? `never — installed ${days} days ago` : 'never';
|
|
1249
|
+
} else {
|
|
1250
|
+
whenLabel = `${item.daysSinceLastUse} day${item.daysSinceLastUse === 1 ? '' : 's'} ago`;
|
|
1251
|
+
}
|
|
1252
|
+
console.log(` ${icon} ${item.name.padEnd(SKILL_NAME_PAD)} ${useLabel.padEnd(USE_LABEL_PAD)} (${whenLabel})`);
|
|
1253
|
+
}
|
|
1254
|
+
if (silentCount > 0) {
|
|
1255
|
+
console.log(`\n ${silentCount} other skill${silentCount === 1 ? '' : 's'} — no usage recorded`);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
if (suggestions.length > 0) {
|
|
1260
|
+
console.log('\n Suggestions:');
|
|
1261
|
+
for (const item of suggestions) {
|
|
1262
|
+
if (item.suggestion === 'remove') {
|
|
1263
|
+
console.log(` · ${item.name}: never used — consider removing (booklib uninstall ${item.name})`);
|
|
1264
|
+
} else {
|
|
1265
|
+
const days = item.daysSinceLastUse ?? 60;
|
|
1266
|
+
console.log(` · ${item.name}: ${item.uses} use${item.uses === 1 ? '' : 's'} in ${days} days — low activity`);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
console.log('\n Run `booklib uninstall <skill>` to free up slots.');
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
if (noUsageFile && active.length > 0) {
|
|
1273
|
+
console.log('\n Tip: run `booklib doctor --install-hook` to start tracking usage.');
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
console.log('');
|
|
1277
|
+
break;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// Default: run diagnostics
|
|
1281
|
+
const { runDiagnostics, printDiagnostics } = await import('../lib/engine/doctor.js');
|
|
1282
|
+
|
|
1283
|
+
console.log('\n BookLib Health Check\n');
|
|
1284
|
+
const findings = runDiagnostics(process.cwd());
|
|
1285
|
+
printDiagnostics(findings);
|
|
1286
|
+
|
|
1287
|
+
if (cure && findings.some(f => f.fixable)) {
|
|
1288
|
+
console.log(' Applying fixes...\n');
|
|
1289
|
+
|
|
1290
|
+
for (const f of findings) {
|
|
1291
|
+
if (!f.fixable) continue;
|
|
1292
|
+
|
|
1293
|
+
if (f.check === 'missing-index') {
|
|
1294
|
+
console.log(' Building search index...');
|
|
1295
|
+
const indexer = new BookLibIndexer();
|
|
1296
|
+
const { skillsPath } = resolveBookLibPaths();
|
|
1297
|
+
await indexer.indexDirectory(skillsPath, false, { quiet: true });
|
|
1298
|
+
console.log(' Index built.\n');
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// Re-run diagnostics to show updated state
|
|
1303
|
+
const updated = runDiagnostics(process.cwd());
|
|
1304
|
+
const remaining = updated.filter(f => f.fixable).length;
|
|
1305
|
+
if (remaining === 0) {
|
|
1306
|
+
console.log(' All fixable issues resolved.\n');
|
|
1307
|
+
} else {
|
|
1308
|
+
console.log(` ${remaining} issue(s) remain that require manual intervention.\n`);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
break;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
case 'correction': {
|
|
1316
|
+
const sub = args[1];
|
|
1317
|
+
|
|
1318
|
+
if (!sub || sub === 'help') {
|
|
1319
|
+
console.log('\nUsage:');
|
|
1320
|
+
console.log(' booklib correction add "<text>" — record a correction');
|
|
1321
|
+
console.log(' booklib correction list — show all corrections');
|
|
1322
|
+
console.log(' booklib correction remove <id> — delete a correction\n');
|
|
1323
|
+
break;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
if (sub === 'add') {
|
|
1327
|
+
const text = args.slice(2).join(' ').replace(/^["']|["']$/g, '');
|
|
1328
|
+
if (!text) {
|
|
1329
|
+
console.error(' Usage: booklib correction add "text of the correction"');
|
|
1330
|
+
process.exit(1);
|
|
1331
|
+
}
|
|
1332
|
+
process.stdout.write(' Recording correction (loading embedding model)...\n');
|
|
1333
|
+
const result = await addCorrection(text);
|
|
1334
|
+
const levelUp = result.wasExisting && result.level > levelFromMentions(result.mentions - 1);
|
|
1335
|
+
const action = result.wasExisting ? 'Updated' : 'Recorded';
|
|
1336
|
+
const arrow = levelUp ? ' ↑' : '';
|
|
1337
|
+
console.log(`✓ ${action}: "${result.text}" (mentions: ${result.mentions}, level: ${result.level}${arrow})`);
|
|
1338
|
+
if (levelUp && result.level >= 3) {
|
|
1339
|
+
console.log(` → ~/.claude/CLAUDE.md updated`);
|
|
1340
|
+
}
|
|
1341
|
+
break;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
if (sub === 'list') {
|
|
1345
|
+
const all = listCorrections();
|
|
1346
|
+
if (all.length === 0) {
|
|
1347
|
+
console.log('\n No corrections recorded yet.\n');
|
|
1348
|
+
break;
|
|
1349
|
+
}
|
|
1350
|
+
console.log(`\n► Learned corrections (${all.length} total)\n`);
|
|
1351
|
+
console.log(` ${'ID'.padEnd(8)} ${'Mentions'.padEnd(10)} ${'Level'.padEnd(7)} Text`);
|
|
1352
|
+
for (const c of all) {
|
|
1353
|
+
const marker = c.level >= 3 ? '●' : ' ';
|
|
1354
|
+
const lvl = `${c.level} ${marker}`;
|
|
1355
|
+
console.log(` ${c.id.padEnd(8)} ${String(c.mentions).padEnd(10)} ${lvl.padEnd(7)} ${c.text.slice(0, 60)}`);
|
|
1356
|
+
}
|
|
1357
|
+
console.log('\n ● = injected into ~/.claude/CLAUDE.md\n');
|
|
1358
|
+
break;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
if (sub === 'remove') {
|
|
1362
|
+
const id = args[2];
|
|
1363
|
+
if (!id) {
|
|
1364
|
+
console.error(' Usage: booklib correction remove <id>');
|
|
1365
|
+
process.exit(1);
|
|
1366
|
+
}
|
|
1367
|
+
const removed = removeCorrection(id);
|
|
1368
|
+
if (!removed) {
|
|
1369
|
+
console.error(` Not found: ${id}`);
|
|
1370
|
+
process.exit(1);
|
|
1371
|
+
}
|
|
1372
|
+
console.log(`✓ Removed "${removed.text}"`);
|
|
1373
|
+
console.log(` → ~/.claude/CLAUDE.md updated`);
|
|
1374
|
+
break;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
console.error(` Unknown subcommand: ${sub}`);
|
|
1378
|
+
console.error(' Use: booklib correction add|list|remove');
|
|
1379
|
+
process.exit(1);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
case 'rules': {
|
|
1383
|
+
const subcommand = args[1];
|
|
1384
|
+
|
|
1385
|
+
switch (subcommand) {
|
|
1386
|
+
case 'list': {
|
|
1387
|
+
const available = listAvailableRules();
|
|
1388
|
+
console.log('\n► Available rule sets\n');
|
|
1389
|
+
console.log(` ${'Bundled:'.padEnd(22)} ${'project'.padEnd(12)} global`);
|
|
1390
|
+
for (const item of available) {
|
|
1391
|
+
const icon = (item.installedProject || item.installedGlobal) ? '✓' : '·';
|
|
1392
|
+
const proj = item.installedProject ? 'installed' : '—';
|
|
1393
|
+
const glob = item.installedGlobal ? 'installed' : '—';
|
|
1394
|
+
console.log(` ${icon} ${item.lang.padEnd(22)} ${proj.padEnd(12)} ${glob}`);
|
|
1395
|
+
}
|
|
1396
|
+
console.log('');
|
|
1397
|
+
console.log(' booklib rules install <lang> → add to .cursor/rules/');
|
|
1398
|
+
console.log(' booklib rules install <lang> --global → add to ~/.claude/CLAUDE.md');
|
|
1399
|
+
console.log('');
|
|
1400
|
+
break;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
case 'install': {
|
|
1404
|
+
const lang = args[2];
|
|
1405
|
+
if (!lang || lang.startsWith('--')) {
|
|
1406
|
+
console.error(' Usage: booklib rules install <lang> [--global]');
|
|
1407
|
+
process.exit(1);
|
|
1408
|
+
}
|
|
1409
|
+
const isGlobal = args.includes('--global');
|
|
1410
|
+
try {
|
|
1411
|
+
const written = installRuleFn(lang, { global: isGlobal });
|
|
1412
|
+
if (written.length === 0) {
|
|
1413
|
+
console.log(`\n No rule files found for '${lang}'.\n`);
|
|
1414
|
+
break;
|
|
1415
|
+
}
|
|
1416
|
+
if (isGlobal) {
|
|
1417
|
+
const st = rulesStatus();
|
|
1418
|
+
const entry = st.global.find(g => g.lang === lang);
|
|
1419
|
+
const sizeLabel = entry ? formatBytes(entry.sizeBytes) : '';
|
|
1420
|
+
console.log(`\n✓ Installed ${lang} rules globally`);
|
|
1421
|
+
console.log(` ~/.claude/CLAUDE.md → added ${lang} section (${sizeLabel})\n`);
|
|
1422
|
+
} else {
|
|
1423
|
+
console.log(`\n✓ Installed ${lang} rules`);
|
|
1424
|
+
for (const p of written) {
|
|
1425
|
+
console.log(` ${path.relative(process.cwd(), p)} (${formatBytes(fs.statSync(p).size)})`);
|
|
1426
|
+
}
|
|
1427
|
+
console.log('');
|
|
1428
|
+
}
|
|
1429
|
+
} catch (err) {
|
|
1430
|
+
const msg = err.message;
|
|
1431
|
+
const availIdx = msg.indexOf('. Available:');
|
|
1432
|
+
if (availIdx !== -1) {
|
|
1433
|
+
console.error(` ${msg.slice(0, availIdx)}`);
|
|
1434
|
+
console.error(` ${msg.slice(availIdx + 2)}`);
|
|
1435
|
+
} else {
|
|
1436
|
+
console.error(` ${msg}`);
|
|
1437
|
+
}
|
|
1438
|
+
process.exit(1);
|
|
1439
|
+
}
|
|
1440
|
+
break;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
case 'status': {
|
|
1444
|
+
const st = rulesStatus();
|
|
1445
|
+
console.log('\n► Rules status\n');
|
|
1446
|
+
|
|
1447
|
+
if (st.cursor.length === 0 && st.global.length === 0) {
|
|
1448
|
+
console.log(' No rules installed in current project.\n');
|
|
1449
|
+
console.log(' Tip: booklib rules install <lang> to add standards.\n');
|
|
1450
|
+
break;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
if (st.cursor.length > 0) {
|
|
1454
|
+
console.log(' .cursor/rules/ (project)');
|
|
1455
|
+
for (const item of st.cursor) {
|
|
1456
|
+
console.log(` ${path.basename(item.path).padEnd(42)} ${formatBytes(item.sizeBytes)}`);
|
|
1457
|
+
}
|
|
1458
|
+
console.log('');
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
if (st.global.length > 0) {
|
|
1462
|
+
console.log(' ~/.claude/CLAUDE.md (global)');
|
|
1463
|
+
for (const item of st.global) {
|
|
1464
|
+
console.log(` ${item.lang.padEnd(42)} ${formatBytes(item.sizeBytes)}`);
|
|
1465
|
+
}
|
|
1466
|
+
console.log('');
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
const projCount = st.cursor.length;
|
|
1470
|
+
const globCount = st.global.length;
|
|
1471
|
+
console.log(` Total: ${formatBytes(st.totalBytes)} across ${projCount} project + ${globCount} global rule(s)\n`);
|
|
1472
|
+
break;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
default:
|
|
1476
|
+
console.log('\n booklib rules list — show available rule sets');
|
|
1477
|
+
console.log(' booklib rules install <lang> — install to .cursor/rules/');
|
|
1478
|
+
console.log(' booklib rules install <lang> --global — install to ~/.claude/CLAUDE.md');
|
|
1479
|
+
console.log(' booklib rules status — show installed rules + sizes\n');
|
|
1480
|
+
}
|
|
1481
|
+
break;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
case 'capture': {
|
|
1485
|
+
const title = parseFlag(args, 'title');
|
|
1486
|
+
const type = parseFlag(args, 'type') ?? 'insight';
|
|
1487
|
+
const tagsArg = parseFlag(args, 'tags') ?? '';
|
|
1488
|
+
const linksArg = parseFlag(args, 'links') ?? '';
|
|
1489
|
+
|
|
1490
|
+
if (!title) {
|
|
1491
|
+
console.error('Usage: booklib capture --title "<title>" [--type insight] [--tags tag1,tag2] [--links "skill:edge-type,...]"');
|
|
1492
|
+
process.exit(1);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
const tags = tagsArg ? tagsArg.split(',').map(t => t.trim()).filter(Boolean) : [];
|
|
1496
|
+
const links = parseCaptureLinkArgs(linksArg);
|
|
1497
|
+
|
|
1498
|
+
for (const link of links) {
|
|
1499
|
+
if (!EDGE_TYPES.includes(link.type)) {
|
|
1500
|
+
console.error(`Invalid edge type "${link.type}". Valid: ${EDGE_TYPES.join(', ')}`);
|
|
1501
|
+
process.exit(1);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
const id = generateNodeId(type);
|
|
1506
|
+
const nodeContent = serializeNode({ id, type, title, tags });
|
|
1507
|
+
|
|
1508
|
+
const globalBookLibDir = path.join(os.homedir(), '.booklib');
|
|
1509
|
+
const globalNodesDir = path.join(globalBookLibDir, 'knowledge', 'nodes');
|
|
1510
|
+
const globalGraphFile = path.join(globalBookLibDir, 'knowledge', 'graph.jsonl');
|
|
1511
|
+
|
|
1512
|
+
const filePath = saveNode(nodeContent, id, { nodesDir: globalNodesDir });
|
|
1513
|
+
await autoIndexNode(filePath);
|
|
1514
|
+
|
|
1515
|
+
const today = new Date().toISOString().split('T')[0];
|
|
1516
|
+
for (const link of links) {
|
|
1517
|
+
appendEdge({ from: id, to: link.to, type: link.type, weight: 1.0, created: today }, { graphFile: globalGraphFile });
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
console.log(`✅ Knowledge node created: ${filePath}`);
|
|
1521
|
+
console.log(` ID: ${id}`);
|
|
1522
|
+
if (links.length > 0) {
|
|
1523
|
+
console.log(` Linked: ${links.map(l => `${l.to} (${l.type})`).join(', ')}`);
|
|
1524
|
+
}
|
|
1525
|
+
break;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
case 'benchmark': {
|
|
1529
|
+
const { run } = await import('../benchmark/run-eval.js');
|
|
1530
|
+
await run();
|
|
1531
|
+
break;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
case 'build-wellknown': {
|
|
1535
|
+
const builder = new WellKnownBuilder();
|
|
1536
|
+
const outPath = await builder.build();
|
|
1537
|
+
console.log(`Generated: ${outPath}`);
|
|
1538
|
+
process.exit(0);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
default: {
|
|
1542
|
+
const showAll = args.includes('--all');
|
|
1543
|
+
if (showAll) {
|
|
1544
|
+
console.log(`
|
|
1545
|
+
BookLib — AI Agent Skill Library (full reference)
|
|
1546
|
+
|
|
1547
|
+
CORE:
|
|
1548
|
+
booklib index [dir] [--clear] Build semantic index (skills + knowledge nodes)
|
|
1549
|
+
booklib search "<query>" Search skills and your knowledge nodes
|
|
1550
|
+
booklib audit <skill> <file> Deep-audit a file against a skill
|
|
1551
|
+
booklib scan [dir] [--docs] Project-wide heatmap
|
|
1552
|
+
booklib capture --title "<title>" [--type insight] [--tags t1,t2] [--links "skill:edge-type,...]"
|
|
1553
|
+
booklib benchmark Run retrieval quality benchmark (MRR/Recall/NDCG)
|
|
1554
|
+
booklib context "<task>" [--prompt-only] Cross-skill context + conflict resolution
|
|
1555
|
+
booklib context "<task>" --file <path> Also injects graph context for the file's component
|
|
1556
|
+
|
|
1557
|
+
KNOWLEDGE GRAPH:
|
|
1558
|
+
booklib note "<title>" Create a note (pipe content via stdin, or opens editor)
|
|
1559
|
+
booklib dictate [--raw] [--title "<t>"] Type/speak rough thoughts → AI structures → note
|
|
1560
|
+
booklib research "<topic>" Create a research template node to fill in later
|
|
1561
|
+
booklib save-chat [--summarize] [--title "<t>"] Save current conversation as a knowledge node
|
|
1562
|
+
booklib component add <name> "<glob>" Define a project component (e.g. "auth" "src/auth/**")
|
|
1563
|
+
booklib link "<title-or-id>" "<title-or-id>" --type <edge-type> Connect two nodes
|
|
1564
|
+
booklib nodes list List all knowledge nodes
|
|
1565
|
+
booklib nodes show <id> View a specific node
|
|
1566
|
+
|
|
1567
|
+
Edge types: implements · contradicts · extends · applies-to · see-also · inspired-by · supersedes · depends-on
|
|
1568
|
+
|
|
1569
|
+
SKILLS:
|
|
1570
|
+
booklib init [--reset] [--tool=claude|cursor|copilot|gemini|codex|windsurf|roo-code|openhands|junie|goose|opencode|letta|all|auto] [--skills=s1,s2]
|
|
1571
|
+
[--ecc] [--agents] [--commands] [--rules[=kotlin,python]]
|
|
1572
|
+
[--orchestrator=obra|ruflo] [--dry-run]
|
|
1573
|
+
booklib setup Fetch & index all trusted community skills
|
|
1574
|
+
booklib discover [--refresh] List available community skills
|
|
1575
|
+
booklib install <skill-name> Install a skill
|
|
1576
|
+
booklib fetch <skill-name> (deprecated) Use: booklib install
|
|
1577
|
+
booklib add <skill-id-or-url> (deprecated) Use: booklib install
|
|
1578
|
+
booklib rules list|install <lang>|status Manage always-on language rules
|
|
1579
|
+
|
|
1580
|
+
SESSION HANDOFF:
|
|
1581
|
+
booklib save-state --goal=".." --next=".." Save agent context
|
|
1582
|
+
booklib resume [session-name] Resume last session
|
|
1583
|
+
booklib recover-auto Auto-recover from session or git
|
|
1584
|
+
|
|
1585
|
+
SESSION MANAGEMENT:
|
|
1586
|
+
booklib sessions cleanup --before 90days Archive old sessions
|
|
1587
|
+
booklib sessions diff <id1> <id2> Compare two sessions
|
|
1588
|
+
booklib sessions find <name> Find session (local+global)
|
|
1589
|
+
booklib sessions search <query> Search by content
|
|
1590
|
+
booklib sessions tag <id> --add=tag1,tag2 Tag sessions
|
|
1591
|
+
booklib sessions validate [id] Check quality
|
|
1592
|
+
booklib sessions report [--since "2 weeks"] Team report
|
|
1593
|
+
booklib sessions create --template=<t> <n> Create from template
|
|
1594
|
+
booklib sessions history <id> Version history
|
|
1595
|
+
|
|
1596
|
+
ORCHESTRATOR COMPATIBILITY:
|
|
1597
|
+
booklib sync Sync all fetched skills → ~/.claude/skills/
|
|
1598
|
+
|
|
1599
|
+
SWARM / MULTI-AGENT:
|
|
1600
|
+
booklib profile <role>|--list Skill bundle for an agent role
|
|
1601
|
+
booklib swarm-config [trigger] Trigger → roles → skills pipeline
|
|
1602
|
+
booklib sessions-list|merge|lineage|compare Multi-agent session coordination
|
|
1603
|
+
|
|
1604
|
+
`);
|
|
1605
|
+
} else {
|
|
1606
|
+
console.log(`
|
|
1607
|
+
BookLib — AI Agent Skill Library
|
|
1608
|
+
|
|
1609
|
+
QUICK START (new project):
|
|
1610
|
+
1. booklib init → guided setup for this project
|
|
1611
|
+
2. booklib index → build the search index
|
|
1612
|
+
3. booklib search "<query>" → find relevant skills and patterns
|
|
1613
|
+
|
|
1614
|
+
EVERYDAY USE:
|
|
1615
|
+
booklib search "<query>" Find skills matching your task
|
|
1616
|
+
booklib context "<task>" Cross-skill context for your AI
|
|
1617
|
+
booklib audit <skill> <file> Get a review prompt for a file
|
|
1618
|
+
booklib scan Project-wide code quality heatmap
|
|
1619
|
+
booklib capture --title "<title>" [--type insight] [--tags t1,t2] [--links "skill:edge-type,...]"
|
|
1620
|
+
booklib benchmark Run retrieval quality benchmark (MRR/Recall/NDCG)
|
|
1621
|
+
booklib doctor Check skill health & usage
|
|
1622
|
+
|
|
1623
|
+
SKILLS:
|
|
1624
|
+
booklib init [--reset] Set up BookLib for this project (--reset to re-run from scratch)
|
|
1625
|
+
booklib rules list See available language rule sets
|
|
1626
|
+
booklib rules install <lang> Add rules to .cursor/rules/
|
|
1627
|
+
booklib rules install <lang> --global Add rules to ~/.claude/CLAUDE.md
|
|
1628
|
+
booklib install <skill-name> Install a skill
|
|
1629
|
+
booklib fetch <skill-name> (deprecated) Use: booklib install
|
|
1630
|
+
booklib discover Browse the community skill catalog
|
|
1631
|
+
|
|
1632
|
+
KNOWLEDGE GRAPH:
|
|
1633
|
+
booklib note "<title>" Save a note (pipe or type content)
|
|
1634
|
+
booklib dictate Speak/type rough thoughts → structured note
|
|
1635
|
+
booklib context "<task>" --file <f> Include file-component context
|
|
1636
|
+
|
|
1637
|
+
booklib --help --all Show all commands including advanced
|
|
1638
|
+
|
|
1639
|
+
`);
|
|
1640
|
+
}
|
|
1641
|
+
break;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
const NO_NUDGE_COMMANDS = new Set(['help', 'search', 'context', 'audit', 'scan', 'nodes', 'sessions', 'sessions-list']);
|
|
1647
|
+
const BOOKLIB_DIR = path.join(os.homedir(), '.booklib');
|
|
1648
|
+
|
|
1649
|
+
function readCounter(file) {
|
|
1650
|
+
try { return parseInt(fs.readFileSync(file, 'utf8'), 10) || 0; } catch { return 0; }
|
|
1651
|
+
}
|
|
1652
|
+
function writeCounter(file, value) {
|
|
1653
|
+
fs.mkdirSync(BOOKLIB_DIR, { recursive: true });
|
|
1654
|
+
fs.writeFileSync(file, String(value));
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
async function maybeAskFeedback() {
|
|
1658
|
+
// Only in interactive terminals, only on action commands
|
|
1659
|
+
if (!process.stderr.isTTY || !command || NO_NUDGE_COMMANDS.has(command) || args.includes('--help')) return;
|
|
1660
|
+
|
|
1661
|
+
const FEEDBACK_EVERY = 25;
|
|
1662
|
+
const counterFile = path.join(BOOKLIB_DIR, 'feedback-count');
|
|
1663
|
+
const count = readCounter(counterFile);
|
|
1664
|
+
const next = count + 1;
|
|
1665
|
+
writeCounter(counterFile, next);
|
|
1666
|
+
if (next % FEEDBACK_EVERY !== 0) return;
|
|
1667
|
+
|
|
1668
|
+
return new Promise(resolve => {
|
|
1669
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
1670
|
+
rl.question('\n Quick question: is BookLib useful to you? [y/n/skip] ', answer => {
|
|
1671
|
+
rl.close();
|
|
1672
|
+
const a = answer.trim().toLowerCase();
|
|
1673
|
+
if (a === 'y' || a === 'yes') {
|
|
1674
|
+
console.error(' Glad to hear it! A ⭐ helps others find it: https://github.com/booklib-ai/booklib\n');
|
|
1675
|
+
} else if (a === 'n' || a === 'no') {
|
|
1676
|
+
console.error(' Thanks for the honesty. Tell us what\'s missing: https://github.com/booklib-ai/booklib/issues\n');
|
|
1677
|
+
}
|
|
1678
|
+
resolve();
|
|
1679
|
+
});
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
function maybeNudgeStar() {
|
|
1684
|
+
if (!command || NO_NUDGE_COMMANDS.has(command) || args.includes('--help')) return;
|
|
1685
|
+
const NUDGE_EVERY = 50;
|
|
1686
|
+
const counterFile = path.join(BOOKLIB_DIR, 'nudge-count');
|
|
1687
|
+
try {
|
|
1688
|
+
const next = readCounter(counterFile) + 1;
|
|
1689
|
+
writeCounter(counterFile, next);
|
|
1690
|
+
if (next % NUDGE_EVERY === 0) {
|
|
1691
|
+
console.error('\n ⭐ If BookLib is useful, a star helps: https://github.com/booklib-ai/booklib\n');
|
|
1692
|
+
}
|
|
1693
|
+
} catch {
|
|
1694
|
+
// never block the CLI for a nudge
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
main()
|
|
1699
|
+
.then(() => maybeAskFeedback())
|
|
1700
|
+
.then(() => maybeNudgeStar())
|
|
1701
|
+
.catch(err => {
|
|
1702
|
+
console.error(err.message);
|
|
1703
|
+
console.error('\n If this looks like a bug, please report it: https://github.com/booklib-ai/booklib/issues\n');
|
|
1704
|
+
process.exit(1);
|
|
1705
|
+
});
|