@loombrain/cli
v0.2.0
Published
CLI for LoomBrain — capture, search, and browse your personal knowledge graph
Maintainers
Readme
@loombrain/cli
CLI for LoomBrain — capture, search, and browse your personal knowledge graph from the terminal.
Install
Homebrew (macOS)
brew install amentistudio/tap/lbnpm
npm install -g @loombrain/cliAuthentication
Interactive login (recommended)
lb loginOpens your browser for device code authentication. Follow the prompts to authorize the CLI.
Token-based (CI / LLM agents)
Set the LB_TOKEN environment variable with an API key from your LoomBrain dashboard:
export LB_TOKEN=lb_sk_...
lb search "knowledge graphs"Commands
lb capture
Capture a URL, piped content, or a quick note into your knowledge graph.
# Capture a URL
lb capture https://example.com/article --why "relevant to my research"
# Capture a note
lb capture --note "Meeting takeaway: switch to weekly releases" --why "process change"
# Pipe content from stdin
cat notes.md | lb capture --why "imported notes" --title "March notes"Options:
--why(required) — Why are you capturing this?--note— Quick note to capture (instead of URL/stdin)--title— Override the title--type— Content type:article,tweet,repo,pdf,audio,note,video,image--json— Output as JSON
lb search
Search nodes in your knowledge graph.
lb search "distributed systems"
lb search --para "projects/my-project" --status active
lb search --tags "rust,wasm" --limit 5Options:
--para— Filter by PARA path--status— Filter by status--tags— Filter by tags (comma-separated)--limit— Results per page (default: 20)--page— Page number--mode— Search mode:keyword,semantic, orhybrid--json— Output as JSON
lb context
Get session context nodes for a topic within a PARA path. Useful for loading relevant context into an LLM conversation.
lb context "API design" --para "projects/loombrain"
lb context "testing strategies" --para "areas/engineering" --limit 5Options:
--para(required) — PARA path to scope context--limit— Number of context nodes (default: 10)--json— Output as JSON
lb tree
Browse your PARA tree.
lb tree
lb tree projects
lb tree areas --jsonOptions:
- Category filter:
projects,areas,resources,archive --status— Filter by status:activeorarchived--json— Output as JSON
lb health
Show graph health stats.
lb health
lb health --jsonOptions:
--json— Output as JSON
lb add
Add a node to your knowledge graph.
# Add with inline body
lb add "API Design Notes" --body "Key decisions from today's meeting" --tags "api,design" --why "architecture reference"
# Add from file
lb add "Weekly Report" --body-file report.md --para "projects/my-project"
# Pipe from stdin
cat notes.md | lb add "Meeting Notes" --why "team sync"Options:
--body— Node body content (markdown)--body-file— Read body from file path--summary— Node summary--tags— Tags (comma-separated)--para— PARA path (e.g.projects/my-project)--why— Why are you adding this node?--json— Output as JSON
lb related
Show nodes related to a given node.
lb related abc123
lb related abc123 --limit 5 --jsonOptions:
--limit— Max results to show (default: 20)--json— Output as JSON
lb para
Manage PARA items (projects, areas, resources).
# Create a new project
lb para create --category projects --label "Kitchen Renovation"
# Archive a project
lb para archive <id>
# Restore an archived item
lb para unarchive <id>Subcommands:
lb para create— Create a PARA item--category(required) — projects, areas, or resources--label(required) — Item label--description— Optional description
lb para archive <id>— Archive a PARA itemlb para unarchive <id>— Restore an archived item
All support --json.
lb retag
Update PARA assignments for a node.
# Replace all PARA assignments
lb retag <node-id> --set "para-id-1,para-id-2"
# Add a PARA assignment (non-destructive)
lb retag <node-id> --add "para-id-3"
# Remove a PARA assignment (non-destructive)
lb retag <node-id> --remove "para-id-2"Options:
--set— Replace all PARA assignments (comma-separated IDs)--add— Add PARA assignments (comma-separated IDs)--remove— Remove PARA assignments (comma-separated IDs)--json— Output as JSON
Exactly one of --set, --add, or --remove must be provided. Use --set "" to clear all PARA assignments.
Note:
--addand--removeuse a read-modify-write pattern and are not concurrency-safe. If multiple clients modify the same node simultaneously, the last write wins.
JSON output
All commands support --json for machine-readable output — useful for piping into jq, scripting, or feeding context to LLMs:
lb search "rust" --json | jq '.[].title'
lb context "API design" --para "projects/lb" --json > context.jsonGlobal options
--api-url— Override the API endpoint (default:https://api.loombrain.com)
Requirements
- Node.js >= 18
Development
Architecture
The CLI is a thin HTTP client over the API. Each command validates arguments locally, calls the corresponding API endpoint via ofetch, and formats the response for the terminal. No business logic lives in the CLI — it's all in the API/services layer.
User → citty (arg parsing) → createApiClient (auth + HTTP) → API endpoint → format + printKey libraries:
| Library | Purpose | |---------|---------| | citty | Command definitions, arg parsing, subcommands | | ofetch | HTTP client with interceptors | | consola | Logging and user-facing messages | | @loombrain/shared | Shared types and Zod schemas |
Adding a new command
- Create
apps/cli/src/commands/<name>.ts:
import { defineCommand } from "citty";
import { createApiClient } from "../lib/client";
import { handleError } from "../lib/output";
export const myCommand = defineCommand({
meta: { name: "my-command", description: "What it does" },
args: {
json: { type: "boolean", description: "Output as JSON", default: false },
"api-url": { type: "string", description: "API URL override" },
},
async run({ args }) {
const jsonOutput = Boolean(args.json);
let client;
try {
client = await createApiClient(args["api-url"] as string | undefined);
} catch (err) {
handleError(err, jsonOutput, "AUTH_REQUIRED");
return;
}
const { fetch: apiFetch } = client;
try {
const result = await apiFetch<MyResponseType>("/your-endpoint");
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
} catch (err) {
handleError(err, jsonOutput, "API_ERROR");
}
},
});- Register in
apps/cli/src/main.ts:
import { myCommand } from "./commands/my-command";
// Add to subCommands:
subCommands: { ..., myCommand },Add a formatter in
apps/cli/src/lib/output.tsif the command needs human-readable output.Add tests in
apps/cli/src/__tests__/— mockcreateApiClientat the boundary, verify the correct endpoint is called with correct params.
Testing
bun run test --filter=cli # Run CLI tests only
bun run test # Run all workspace testsTests mock ofetch and createApiClient at the module boundary. No real API calls are made. See apps/cli/src/__tests__/commands.test.ts for the pattern.
Local development
# Start the API locally (required — CLI calls the API)
bun run dev --filter=api
# Point CLI at local API
LB_API_URL=http://localhost:8787 node apps/cli/dist/index.js search "test"
# Or build + run
bun run build --filter=cli
LB_API_URL=http://localhost:8787 node apps/cli/dist/index.js healthPrerequisites
- Bun (package manager + runtime)
- Node.js >= 18
