secondbrain-py 0.2.1__tar.gz
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.
- secondbrain_py-0.2.1/LICENSE +21 -0
- secondbrain_py-0.2.1/PKG-INFO +195 -0
- secondbrain_py-0.2.1/README.md +152 -0
- secondbrain_py-0.2.1/pyproject.toml +219 -0
- secondbrain_py-0.2.1/setup.cfg +4 -0
- secondbrain_py-0.2.1/src/brain/__init__.py +0 -0
- secondbrain_py-0.2.1/src/brain/__main__.py +18 -0
- secondbrain_py-0.2.1/src/brain/_capture_command.py +445 -0
- secondbrain_py-0.2.1/src/brain/_compose.py +52 -0
- secondbrain_py-0.2.1/src/brain/activity.py +206 -0
- secondbrain_py-0.2.1/src/brain/ask.py +631 -0
- secondbrain_py-0.2.1/src/brain/audio.py +591 -0
- secondbrain_py-0.2.1/src/brain/backfill/__init__.py +12 -0
- secondbrain_py-0.2.1/src/brain/backfill/search_extras.py +141 -0
- secondbrain_py-0.2.1/src/brain/backfill/source_rows.py +101 -0
- secondbrain_py-0.2.1/src/brain/bin/__init__.py +1 -0
- secondbrain_py-0.2.1/src/brain/bin/_launcher.py +107 -0
- secondbrain_py-0.2.1/src/brain/bin/down.py +8 -0
- secondbrain_py-0.2.1/src/brain/bin/launchd.py +268 -0
- secondbrain_py-0.2.1/src/brain/bin/monitor.py +570 -0
- secondbrain_py-0.2.1/src/brain/bin/rebuild.py +8 -0
- secondbrain_py-0.2.1/src/brain/bin/status.py +8 -0
- secondbrain_py-0.2.1/src/brain/bin/up.py +8 -0
- secondbrain_py-0.2.1/src/brain/brief.py +272 -0
- secondbrain_py-0.2.1/src/brain/capture.py +49 -0
- secondbrain_py-0.2.1/src/brain/chat.py +293 -0
- secondbrain_py-0.2.1/src/brain/cli.py +9760 -0
- secondbrain_py-0.2.1/src/brain/cli_claude.py +81 -0
- secondbrain_py-0.2.1/src/brain/cli_connect.py +285 -0
- secondbrain_py-0.2.1/src/brain/cli_demo.py +266 -0
- secondbrain_py-0.2.1/src/brain/config.py +1949 -0
- secondbrain_py-0.2.1/src/brain/connect.py +925 -0
- secondbrain_py-0.2.1/src/brain/db.py +540 -0
- secondbrain_py-0.2.1/src/brain/demo/__init__.py +452 -0
- secondbrain_py-0.2.1/src/brain/demo/corpus/manifest.json +403 -0
- secondbrain_py-0.2.1/src/brain/demo/embedder.py +74 -0
- secondbrain_py-0.2.1/src/brain/durations.py +84 -0
- secondbrain_py-0.2.1/src/brain/edit_session.py +156 -0
- secondbrain_py-0.2.1/src/brain/editor.py +67 -0
- secondbrain_py-0.2.1/src/brain/elicit/__init__.py +16 -0
- secondbrain_py-0.2.1/src/brain/elicit/detectors.py +250 -0
- secondbrain_py-0.2.1/src/brain/elicit/drafter.py +70 -0
- secondbrain_py-0.2.1/src/brain/elicit/queue.py +220 -0
- secondbrain_py-0.2.1/src/brain/elicit/schema.py +48 -0
- secondbrain_py-0.2.1/src/brain/elicit/session.py +445 -0
- secondbrain_py-0.2.1/src/brain/embedding_targets.py +54 -0
- secondbrain_py-0.2.1/src/brain/embeddings.py +424 -0
- secondbrain_py-0.2.1/src/brain/enrichment.py +808 -0
- secondbrain_py-0.2.1/src/brain/errors.py +357 -0
- secondbrain_py-0.2.1/src/brain/eval/__init__.py +129 -0
- secondbrain_py-0.2.1/src/brain/eval/answer_eval.py +281 -0
- secondbrain_py-0.2.1/src/brain/eval/baseline.py +265 -0
- secondbrain_py-0.2.1/src/brain/eval/concept_extraction.py +378 -0
- secondbrain_py-0.2.1/src/brain/eval/corpus.py +152 -0
- secondbrain_py-0.2.1/src/brain/eval/errors.py +19 -0
- secondbrain_py-0.2.1/src/brain/eval/graph_baseline.py +226 -0
- secondbrain_py-0.2.1/src/brain/eval/graph_retrieval.py +202 -0
- secondbrain_py-0.2.1/src/brain/eval/graph_runner.py +319 -0
- secondbrain_py-0.2.1/src/brain/eval/metrics.py +101 -0
- secondbrain_py-0.2.1/src/brain/eval/runner.py +223 -0
- secondbrain_py-0.2.1/src/brain/format.py +783 -0
- secondbrain_py-0.2.1/src/brain/gaps.py +390 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/__init__.py +94 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/_retrieval_common.py +113 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/aggregates.py +303 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/aliases/__init__.py +583 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/backends/__init__.py +10 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/backends/_age_helpers.py +473 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/backends/age.py +782 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/backends/base.py +272 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/build.py +344 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/communities.py +644 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/communities_summary.py +437 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/concepts.py +202 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/cooccur.py +193 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/cross_type.py +312 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/extract.py +885 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/fuse.py +371 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/global_.py +412 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/grouping.py +372 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/person_resolver.py +167 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/reconcile.py +792 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/relational.py +353 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/retrieve.py +526 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/router.py +288 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/schema.py +320 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/sync.py +237 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/tenancy.py +43 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/themes.py +501 -0
- secondbrain_py-0.2.1/src/brain/graph_rag/weighting.py +202 -0
- secondbrain_py-0.2.1/src/brain/ingest/__init__.py +1926 -0
- secondbrain_py-0.2.1/src/brain/ingest/chunker.py +249 -0
- secondbrain_py-0.2.1/src/brain/ingest/docx.py +40 -0
- secondbrain_py-0.2.1/src/brain/ingest/gmail.py +621 -0
- secondbrain_py-0.2.1/src/brain/ingest/markdown.py +37 -0
- secondbrain_py-0.2.1/src/brain/ingest/pdf.py +61 -0
- secondbrain_py-0.2.1/src/brain/ingest/stdin.py +22 -0
- secondbrain_py-0.2.1/src/brain/ingest/sub_tokens.py +91 -0
- secondbrain_py-0.2.1/src/brain/ingest/text.py +16 -0
- secondbrain_py-0.2.1/src/brain/interactions.py +205 -0
- secondbrain_py-0.2.1/src/brain/maintenance.py +355 -0
- secondbrain_py-0.2.1/src/brain/mcp_server.py +3405 -0
- secondbrain_py-0.2.1/src/brain/migrations/001_init.sql +43 -0
- secondbrain_py-0.2.1/src/brain/migrations/002_qwen3_embedding.sql +17 -0
- secondbrain_py-0.2.1/src/brain/migrations/003_vault_model.sql +41 -0
- secondbrain_py-0.2.1/src/brain/migrations/004_relax_content_hash_uniqueness.sql +18 -0
- secondbrain_py-0.2.1/src/brain/migrations/005_derived_links.sql +67 -0
- secondbrain_py-0.2.1/src/brain/migrations/006_dedup_file_by_source_path.sql +25 -0
- secondbrain_py-0.2.1/src/brain/migrations/007_email_thread_and_draft.sql +15 -0
- secondbrain_py-0.2.1/src/brain/migrations/008_gmail_thread_unique.sql +11 -0
- secondbrain_py-0.2.1/src/brain/migrations/009_chunks_weighted_tsv.sql +28 -0
- secondbrain_py-0.2.1/src/brain/migrations/010_interactions.sql +30 -0
- secondbrain_py-0.2.1/src/brain/migrations/011_documents_summary.sql +23 -0
- secondbrain_py-0.2.1/src/brain/migrations/012_graphrag.sql +171 -0
- secondbrain_py-0.2.1/src/brain/migrations/013_graphrag_communities.sql +125 -0
- secondbrain_py-0.2.1/src/brain/migrations/014_graphrag_community_summary_hash.sql +33 -0
- secondbrain_py-0.2.1/src/brain/migrations/015_interactions_graph_targets.sql +89 -0
- secondbrain_py-0.2.1/src/brain/migrations/016_index_hygiene.sql +61 -0
- secondbrain_py-0.2.1/src/brain/migrations/017_elicit.sql +30 -0
- secondbrain_py-0.2.1/src/brain/migrations/018_review_gap_signal_kinds.sql +40 -0
- secondbrain_py-0.2.1/src/brain/migrations/019_search_queries.sql +35 -0
- secondbrain_py-0.2.1/src/brain/migrations/020_link_suggestions.sql +40 -0
- secondbrain_py-0.2.1/src/brain/migrations/021_timeline_doc_date.sql +34 -0
- secondbrain_py-0.2.1/src/brain/migrations/022_link_suggestions_undirected.sql +84 -0
- secondbrain_py-0.2.1/src/brain/migrations/023_search_queries_fts_count.sql +28 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/__init__.py +8 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/bootstrap-cli.mjs +65 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/build.ts +568 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/cli/args.js +152 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/cli/build_partial_handler.js +544 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/cli/handlers.js +636 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/components/CommandPalette.tsx +172 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/components/Explorer.tsx +198 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/components/Footer.tsx +27 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/components/Graph.tsx +468 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/components/PageTitle.tsx +72 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/components/RelatedDocs.tsx +38 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/components/Search.tsx +161 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/components/SummaryLede.tsx +72 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/components/index.ts +92 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/components/pages/TagContent.tsx +272 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/components/scripts/commandPalette.inline.ts +665 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/components/scripts/explorer.inline.ts +768 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/components/scripts/graph.inline.ts +2302 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/components/scripts/relatedDocs.inline.ts +163 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/components/scripts/search.inline.ts +1011 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/plugins/emitters/contentIndex.ts +546 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/plugins/transformers/codeCopy.ts +94 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/plugins/transformers/derivedFenceMark.ts +302 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/plugins/transformers/emailThread.ts +148 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/plugins/transformers/emptyDoorFilter.ts +213 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/plugins/transformers/index.ts +114 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/plugins/transformers/linkKindMark.ts +205 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/plugins/transformers/linkSourceTag.ts +104 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/plugins/transformers/relativeDate.ts +100 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/plugins/transformers/reloadSignal.ts +131 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/processors/parse.ts +371 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/processors/parser_cache.ts +78 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/static/brain-logo-dark.png +0 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/static/brain-logo-light.png +0 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/static/codeCopy.js +196 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/static/emailThread.js +334 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/static/favicon.ico +0 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/static/icon.png +0 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/static/linkSourceTag.js +104 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/static/relativeDate.js +142 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/static/reload.js +168 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_article.scss +252 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_atmosphere.scss +113 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_callouts.scss +180 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_cmdk.scss +7 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_code.scss +208 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_command_palette.scss +369 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_email_thread.scss +228 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_explorer.scss +142 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_home.scss +182 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_links.scss +322 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_marginalia.scss +117 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_motion.scss +175 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_people_hub.scss +100 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_related_docs.scss +137 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_search.scss +252 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_sidebar.scss +468 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_summary_lede.scss +56 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_surface.scss +43 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_tag_content.scss +118 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_tokens.scss +197 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/brain/_typography.scss +92 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/custom.scss +89 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/styles/graph.scss +505 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/util/ctx.ts +92 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/util/fastpath_manifest.ts +608 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/util/path.ts +358 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz/util/sourceIcons.ts +55 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz.config.ts +270 -0
- secondbrain_py-0.2.1/src/brain/quartz_overrides/quartz.layout.ts +314 -0
- secondbrain_py-0.2.1/src/brain/queries.py +1188 -0
- secondbrain_py-0.2.1/src/brain/rank_fusion.py +8 -0
- secondbrain_py-0.2.1/src/brain/resurface.py +210 -0
- secondbrain_py-0.2.1/src/brain/review/__init__.py +26 -0
- secondbrain_py-0.2.1/src/brain/review/emit.py +27 -0
- secondbrain_py-0.2.1/src/brain/review/queries.py +436 -0
- secondbrain_py-0.2.1/src/brain/review/render.py +196 -0
- secondbrain_py-0.2.1/src/brain/review/scans.py +355 -0
- secondbrain_py-0.2.1/src/brain/review/weekly.py +413 -0
- secondbrain_py-0.2.1/src/brain/search.py +704 -0
- secondbrain_py-0.2.1/src/brain/set_similarity.py +15 -0
- secondbrain_py-0.2.1/src/brain/setup.py +1205 -0
- secondbrain_py-0.2.1/src/brain/tags.py +56 -0
- secondbrain_py-0.2.1/src/brain/templates/Caddyfile.j2 +9 -0
- secondbrain_py-0.2.1/src/brain/templates/__init__.py +1 -0
- secondbrain_py-0.2.1/src/brain/templates/bin/__init__.py +1 -0
- secondbrain_py-0.2.1/src/brain/templates/bin/_brain-brief-fg.sh +25 -0
- secondbrain_py-0.2.1/src/brain/templates/bin/_brain-build-fg.sh +53 -0
- secondbrain_py-0.2.1/src/brain/templates/bin/_brain-watcher-fg.sh +65 -0
- secondbrain_py-0.2.1/src/brain/templates/bin/brain-down.sh +89 -0
- secondbrain_py-0.2.1/src/brain/templates/bin/brain-status.sh +83 -0
- secondbrain_py-0.2.1/src/brain/templates/bin/brain-up.sh +221 -0
- secondbrain_py-0.2.1/src/brain/templates/docker/age/Dockerfile +79 -0
- secondbrain_py-0.2.1/src/brain/templates/docker-compose.stock.yml.j2 +26 -0
- secondbrain_py-0.2.1/src/brain/templates/docker-compose.yml.j2 +34 -0
- secondbrain_py-0.2.1/src/brain/templates/env.example +190 -0
- secondbrain_py-0.2.1/src/brain/templates/launchd/__init__.py +1 -0
- secondbrain_py-0.2.1/src/brain/templates/launchd/com.brain.brief.plist.j2 +45 -0
- secondbrain_py-0.2.1/src/brain/templates/launchd/com.brain.build.plist.j2 +46 -0
- secondbrain_py-0.2.1/src/brain/templates/launchd/com.brain.watcher.plist.j2 +46 -0
- secondbrain_py-0.2.1/src/brain/templates/skill/SKILL.md +63 -0
- secondbrain_py-0.2.1/src/brain/templates/skill/__init__.py +1 -0
- secondbrain_py-0.2.1/src/brain/timeline.py +834 -0
- secondbrain_py-0.2.1/src/brain/todo.py +124 -0
- secondbrain_py-0.2.1/src/brain/uninstall.py +185 -0
- secondbrain_py-0.2.1/src/brain/vault/__init__.py +115 -0
- secondbrain_py-0.2.1/src/brain/vault/_atomic.py +25 -0
- secondbrain_py-0.2.1/src/brain/vault/daily_index.py +228 -0
- secondbrain_py-0.2.1/src/brain/vault/derived_links/__init__.py +50 -0
- secondbrain_py-0.2.1/src/brain/vault/derived_links/directory.py +683 -0
- secondbrain_py-0.2.1/src/brain/vault/derived_links/fence.py +408 -0
- secondbrain_py-0.2.1/src/brain/vault/derived_links/gws.py +64 -0
- secondbrain_py-0.2.1/src/brain/vault/derived_links/participants.py +143 -0
- secondbrain_py-0.2.1/src/brain/vault/derived_links/pass_runner.py +362 -0
- secondbrain_py-0.2.1/src/brain/vault/derived_links/rules.py +137 -0
- secondbrain_py-0.2.1/src/brain/vault/export.py +683 -0
- secondbrain_py-0.2.1/src/brain/vault/frontmatter.py +165 -0
- secondbrain_py-0.2.1/src/brain/vault/graph.py +620 -0
- secondbrain_py-0.2.1/src/brain/vault/graph_format.py +388 -0
- secondbrain_py-0.2.1/src/brain/vault/link_rewrite.py +235 -0
- secondbrain_py-0.2.1/src/brain/vault/links.py +260 -0
- secondbrain_py-0.2.1/src/brain/vault/note_builder.py +211 -0
- secondbrain_py-0.2.1/src/brain/vault/paths.py +55 -0
- secondbrain_py-0.2.1/src/brain/vault/quartz_overlay.py +236 -0
- secondbrain_py-0.2.1/src/brain/vault/rename.py +591 -0
- secondbrain_py-0.2.1/src/brain/vault/resolver.py +304 -0
- secondbrain_py-0.2.1/src/brain/vault/slug.py +127 -0
- secondbrain_py-0.2.1/src/brain/vault/sync.py +1513 -0
- secondbrain_py-0.2.1/src/brain/vault/sync_summaries.py +264 -0
- secondbrain_py-0.2.1/src/brain/vault/templates.py +145 -0
- secondbrain_py-0.2.1/src/brain/vault/watch.py +1052 -0
- secondbrain_py-0.2.1/src/brain/wiki/__init__.py +6 -0
- secondbrain_py-0.2.1/src/brain/wiki/_github_slugger.py +76 -0
- secondbrain_py-0.2.1/src/brain/wiki/_person_name.py +314 -0
- secondbrain_py-0.2.1/src/brain/wiki/build_homepage.py +541 -0
- secondbrain_py-0.2.1/src/brain/wiki/build_partial.py +273 -0
- secondbrain_py-0.2.1/src/brain/wiki/build_people.py +934 -0
- secondbrain_py-0.2.1/src/brain/wiki/build_related.py +758 -0
- secondbrain_py-0.2.1/src/brain/wiki/build_swap.py +585 -0
- secondbrain_py-0.2.1/src/brain/wiki/build_watcher.py +975 -0
- secondbrain_py-0.2.1/src/brain/wiki/edit_classifier.py +215 -0
- secondbrain_py-0.2.1/src/brain/wiki/errors.py +10 -0
- secondbrain_py-0.2.1/src/brain/wiki/fastpath_manifest.py +475 -0
- secondbrain_py-0.2.1/src/brain/wiki/fastpath_state.py +174 -0
- secondbrain_py-0.2.1/src/brain/wiki/install.py +296 -0
- secondbrain_py-0.2.1/src/brain/wiki/slug.py +111 -0
- secondbrain_py-0.2.1/src/secondbrain_py.egg-info/PKG-INFO +195 -0
- secondbrain_py-0.2.1/src/secondbrain_py.egg-info/SOURCES.txt +572 -0
- secondbrain_py-0.2.1/src/secondbrain_py.egg-info/dependency_links.txt +1 -0
- secondbrain_py-0.2.1/src/secondbrain_py.egg-info/entry_points.txt +11 -0
- secondbrain_py-0.2.1/src/secondbrain_py.egg-info/requires.txt +26 -0
- secondbrain_py-0.2.1/src/secondbrain_py.egg-info/top_level.txt +1 -0
- secondbrain_py-0.2.1/tests/test_activity.py +195 -0
- secondbrain_py-0.2.1/tests/test_answer_eval.py +126 -0
- secondbrain_py-0.2.1/tests/test_apply_tags.py +136 -0
- secondbrain_py-0.2.1/tests/test_arctic_embedder.py +198 -0
- secondbrain_py-0.2.1/tests/test_ask.py +598 -0
- secondbrain_py-0.2.1/tests/test_audio.py +971 -0
- secondbrain_py-0.2.1/tests/test_backfill_email_columns.py +812 -0
- secondbrain_py-0.2.1/tests/test_backfill_search.py +259 -0
- secondbrain_py-0.2.1/tests/test_backfill_source_rows.py +258 -0
- secondbrain_py-0.2.1/tests/test_bin_launchd.py +275 -0
- secondbrain_py-0.2.1/tests/test_bin_launcher.py +270 -0
- secondbrain_py-0.2.1/tests/test_bin_monitor.py +301 -0
- secondbrain_py-0.2.1/tests/test_bin_scripts.py +1039 -0
- secondbrain_py-0.2.1/tests/test_body_hash.py +95 -0
- secondbrain_py-0.2.1/tests/test_brain_edit_syncs_chunks.py +80 -0
- secondbrain_py-0.2.1/tests/test_brain_recent_homepage.py +825 -0
- secondbrain_py-0.2.1/tests/test_brain_skills_sync.py +258 -0
- secondbrain_py-0.2.1/tests/test_brain_tag_syncs_chunks.py +110 -0
- secondbrain_py-0.2.1/tests/test_brief.py +394 -0
- secondbrain_py-0.2.1/tests/test_build_people.py +1623 -0
- secondbrain_py-0.2.1/tests/test_build_related.py +269 -0
- secondbrain_py-0.2.1/tests/test_build_related_idempotence.py +231 -0
- secondbrain_py-0.2.1/tests/test_build_related_schema.py +198 -0
- secondbrain_py-0.2.1/tests/test_build_related_signal.py +1119 -0
- secondbrain_py-0.2.1/tests/test_buildid_etag.py +445 -0
- secondbrain_py-0.2.1/tests/test_capture.py +68 -0
- secondbrain_py-0.2.1/tests/test_chat.py +426 -0
- secondbrain_py-0.2.1/tests/test_chunker.py +189 -0
- secondbrain_py-0.2.1/tests/test_claude_install_skill.py +154 -0
- secondbrain_py-0.2.1/tests/test_cli_analyze.py +124 -0
- secondbrain_py-0.2.1/tests/test_cli_ask.py +216 -0
- secondbrain_py-0.2.1/tests/test_cli_auto_analyze.py +263 -0
- secondbrain_py-0.2.1/tests/test_cli_backfill_normalize_tags.py +333 -0
- secondbrain_py-0.2.1/tests/test_cli_backlinks.py +259 -0
- secondbrain_py-0.2.1/tests/test_cli_capture.py +1097 -0
- secondbrain_py-0.2.1/tests/test_cli_daily.py +199 -0
- secondbrain_py-0.2.1/tests/test_cli_directory.py +333 -0
- secondbrain_py-0.2.1/tests/test_cli_doctor.py +920 -0
- secondbrain_py-0.2.1/tests/test_cli_edit.py +511 -0
- secondbrain_py-0.2.1/tests/test_cli_edit_editor.py +464 -0
- secondbrain_py-0.2.1/tests/test_cli_edit_vault_tier.py +191 -0
- secondbrain_py-0.2.1/tests/test_cli_enrich.py +425 -0
- secondbrain_py-0.2.1/tests/test_cli_eval.py +300 -0
- secondbrain_py-0.2.1/tests/test_cli_explain.py +338 -0
- secondbrain_py-0.2.1/tests/test_cli_filter_validation.py +59 -0
- secondbrain_py-0.2.1/tests/test_cli_graph.py +542 -0
- secondbrain_py-0.2.1/tests/test_cli_graphrag_entity_cap.py +196 -0
- secondbrain_py-0.2.1/tests/test_cli_graphrag_search.py +830 -0
- secondbrain_py-0.2.1/tests/test_cli_ingest.py +434 -0
- secondbrain_py-0.2.1/tests/test_cli_ingest_gmail.py +1340 -0
- secondbrain_py-0.2.1/tests/test_cli_ingest_stdin.py +639 -0
- secondbrain_py-0.2.1/tests/test_cli_init.py +279 -0
- secondbrain_py-0.2.1/tests/test_cli_links.py +304 -0
- secondbrain_py-0.2.1/tests/test_cli_note_new.py +540 -0
- secondbrain_py-0.2.1/tests/test_cli_note_rename.py +662 -0
- secondbrain_py-0.2.1/tests/test_cli_orphans.py +138 -0
- secondbrain_py-0.2.1/tests/test_cli_owner.py +248 -0
- secondbrain_py-0.2.1/tests/test_cli_people.py +407 -0
- secondbrain_py-0.2.1/tests/test_cli_prune_orphans.py +181 -0
- secondbrain_py-0.2.1/tests/test_cli_rate.py +337 -0
- secondbrain_py-0.2.1/tests/test_cli_reembed.py +488 -0
- secondbrain_py-0.2.1/tests/test_cli_relink_derived.py +616 -0
- secondbrain_py-0.2.1/tests/test_cli_resurface.py +140 -0
- secondbrain_py-0.2.1/tests/test_cli_review.py +100 -0
- secondbrain_py-0.2.1/tests/test_cli_review_scan.py +182 -0
- secondbrain_py-0.2.1/tests/test_cli_rm.py +332 -0
- secondbrain_py-0.2.1/tests/test_cli_search.py +315 -0
- secondbrain_py-0.2.1/tests/test_cli_setup.py +83 -0
- secondbrain_py-0.2.1/tests/test_cli_show_interaction.py +165 -0
- secondbrain_py-0.2.1/tests/test_cli_show_list.py +236 -0
- secondbrain_py-0.2.1/tests/test_cli_show_summary.py +97 -0
- secondbrain_py-0.2.1/tests/test_cli_smoke.py +27 -0
- secondbrain_py-0.2.1/tests/test_cli_status.py +97 -0
- secondbrain_py-0.2.1/tests/test_cli_tag.py +426 -0
- secondbrain_py-0.2.1/tests/test_cli_tag_auto.py +270 -0
- secondbrain_py-0.2.1/tests/test_cli_tag_rm.py +145 -0
- secondbrain_py-0.2.1/tests/test_cli_todo.py +165 -0
- secondbrain_py-0.2.1/tests/test_cli_vault_render.py +502 -0
- secondbrain_py-0.2.1/tests/test_cli_vault_sync.py +242 -0
- secondbrain_py-0.2.1/tests/test_cli_vault_watch.py +184 -0
- secondbrain_py-0.2.1/tests/test_collapse_gmail_threads.py +966 -0
- secondbrain_py-0.2.1/tests/test_compose_helper.py +71 -0
- secondbrain_py-0.2.1/tests/test_compose_no_raw_calls.py +84 -0
- secondbrain_py-0.2.1/tests/test_concept_extraction_metric.py +339 -0
- secondbrain_py-0.2.1/tests/test_config.py +1211 -0
- secondbrain_py-0.2.1/tests/test_config_audio_envvars.py +104 -0
- secondbrain_py-0.2.1/tests/test_config_enrich_envvars.py +166 -0
- secondbrain_py-0.2.1/tests/test_conftest_prod_guard.py +87 -0
- secondbrain_py-0.2.1/tests/test_connect.py +1739 -0
- secondbrain_py-0.2.1/tests/test_contentindex_slim.py +717 -0
- secondbrain_py-0.2.1/tests/test_daily_index.py +471 -0
- secondbrain_py-0.2.1/tests/test_database_url_isolation.py +140 -0
- secondbrain_py-0.2.1/tests/test_db.py +524 -0
- secondbrain_py-0.2.1/tests/test_db_age_bootstrap.py +460 -0
- secondbrain_py-0.2.1/tests/test_demo_cli.py +292 -0
- secondbrain_py-0.2.1/tests/test_demo_core.py +192 -0
- secondbrain_py-0.2.1/tests/test_demo_corpus.py +152 -0
- secondbrain_py-0.2.1/tests/test_demo_embedder.py +71 -0
- secondbrain_py-0.2.1/tests/test_demo_seed.py +140 -0
- secondbrain_py-0.2.1/tests/test_dev_dependency_pins.py +16 -0
- secondbrain_py-0.2.1/tests/test_docs_assets.py +160 -0
- secondbrain_py-0.2.1/tests/test_docs_assets_ask.py +129 -0
- secondbrain_py-0.2.1/tests/test_docs_assets_graphrag.py +153 -0
- secondbrain_py-0.2.1/tests/test_docs_assets_mcp.py +134 -0
- secondbrain_py-0.2.1/tests/test_docs_assets_proactivity.py +160 -0
- secondbrain_py-0.2.1/tests/test_docs_assets_wiki.py +186 -0
- secondbrain_py-0.2.1/tests/test_doctor_chunks_stats.py +104 -0
- secondbrain_py-0.2.1/tests/test_doctor_inbox.py +189 -0
- secondbrain_py-0.2.1/tests/test_doctor_npx_check.py +269 -0
- secondbrain_py-0.2.1/tests/test_draft_quarantine.py +416 -0
- secondbrain_py-0.2.1/tests/test_durations.py +107 -0
- secondbrain_py-0.2.1/tests/test_editor.py +120 -0
- secondbrain_py-0.2.1/tests/test_elicit_cli.py +306 -0
- secondbrain_py-0.2.1/tests/test_elicit_detectors.py +332 -0
- secondbrain_py-0.2.1/tests/test_elicit_drafter.py +231 -0
- secondbrain_py-0.2.1/tests/test_elicit_queue.py +627 -0
- secondbrain_py-0.2.1/tests/test_elicit_schema.py +57 -0
- secondbrain_py-0.2.1/tests/test_elicit_session.py +661 -0
- secondbrain_py-0.2.1/tests/test_email_columns.py +659 -0
- secondbrain_py-0.2.1/tests/test_embedding_smoke.py +201 -0
- secondbrain_py-0.2.1/tests/test_embeddings.py +113 -0
- secondbrain_py-0.2.1/tests/test_embeddings_none.py +292 -0
- secondbrain_py-0.2.1/tests/test_enrichment.py +709 -0
- secondbrain_py-0.2.1/tests/test_eval_answer_harness.py +133 -0
- secondbrain_py-0.2.1/tests/test_eval_baseline.py +338 -0
- secondbrain_py-0.2.1/tests/test_eval_corpus.py +333 -0
- secondbrain_py-0.2.1/tests/test_eval_fail_below.py +232 -0
- secondbrain_py-0.2.1/tests/test_eval_graph_baseline.py +290 -0
- secondbrain_py-0.2.1/tests/test_eval_harness_live.py +135 -0
- secondbrain_py-0.2.1/tests/test_eval_metrics.py +201 -0
- secondbrain_py-0.2.1/tests/test_eval_runner.py +243 -0
- secondbrain_py-0.2.1/tests/test_explorer_filter.py +339 -0
- secondbrain_py-0.2.1/tests/test_explorer_groupfn.py +445 -0
- secondbrain_py-0.2.1/tests/test_format.py +381 -0
- secondbrain_py-0.2.1/tests/test_gaps.py +700 -0
- secondbrain_py-0.2.1/tests/test_gmail_boilerplate.py +181 -0
- secondbrain_py-0.2.1/tests/test_gmail_slug.py +166 -0
- secondbrain_py-0.2.1/tests/test_gmail_thread.py +789 -0
- secondbrain_py-0.2.1/tests/test_gmail_thread_upsert.py +516 -0
- secondbrain_py-0.2.1/tests/test_graph_backend_age.py +999 -0
- secondbrain_py-0.2.1/tests/test_graph_backend_base.py +548 -0
- secondbrain_py-0.2.1/tests/test_graph_chip_toggle.py +472 -0
- secondbrain_py-0.2.1/tests/test_graph_format.py +851 -0
- secondbrain_py-0.2.1/tests/test_graph_label_placement.py +145 -0
- secondbrain_py-0.2.1/tests/test_graph_queries.py +678 -0
- secondbrain_py-0.2.1/tests/test_graph_retrieval_scorer.py +153 -0
- secondbrain_py-0.2.1/tests/test_graph_workbench_static.py +186 -0
- secondbrain_py-0.2.1/tests/test_graphrag_aliases.py +663 -0
- secondbrain_py-0.2.1/tests/test_graphrag_benchmark_fixture.py +266 -0
- secondbrain_py-0.2.1/tests/test_graphrag_benchmark_gate.py +800 -0
- secondbrain_py-0.2.1/tests/test_graphrag_build.py +1308 -0
- secondbrain_py-0.2.1/tests/test_graphrag_cli_mcp_parity.py +131 -0
- secondbrain_py-0.2.1/tests/test_graphrag_communities.py +502 -0
- secondbrain_py-0.2.1/tests/test_graphrag_communities_summary.py +656 -0
- secondbrain_py-0.2.1/tests/test_graphrag_concept_gate.py +203 -0
- secondbrain_py-0.2.1/tests/test_graphrag_concepts.py +917 -0
- secondbrain_py-0.2.1/tests/test_graphrag_cooccur.py +255 -0
- secondbrain_py-0.2.1/tests/test_graphrag_cross_type.py +635 -0
- secondbrain_py-0.2.1/tests/test_graphrag_extract.py +1151 -0
- secondbrain_py-0.2.1/tests/test_graphrag_fuse.py +616 -0
- secondbrain_py-0.2.1/tests/test_graphrag_global.py +690 -0
- secondbrain_py-0.2.1/tests/test_graphrag_grouping.py +385 -0
- secondbrain_py-0.2.1/tests/test_graphrag_listing.py +828 -0
- secondbrain_py-0.2.1/tests/test_graphrag_perf_t4_batching.py +726 -0
- secondbrain_py-0.2.1/tests/test_graphrag_reconcile.py +1142 -0
- secondbrain_py-0.2.1/tests/test_graphrag_retrieval_eval.py +345 -0
- secondbrain_py-0.2.1/tests/test_graphrag_retrieve.py +903 -0
- secondbrain_py-0.2.1/tests/test_graphrag_router.py +465 -0
- secondbrain_py-0.2.1/tests/test_graphrag_schema.py +329 -0
- secondbrain_py-0.2.1/tests/test_graphrag_sync_wiring.py +1139 -0
- secondbrain_py-0.2.1/tests/test_graphrag_tenancy.py +63 -0
- secondbrain_py-0.2.1/tests/test_graphrag_themes.py +1160 -0
- secondbrain_py-0.2.1/tests/test_graphrag_weighting.py +196 -0
- secondbrain_py-0.2.1/tests/test_import_hygiene.py +15 -0
- secondbrain_py-0.2.1/tests/test_ingest_chunks_search_columns.py +260 -0
- secondbrain_py-0.2.1/tests/test_ingest_docx.py +21 -0
- secondbrain_py-0.2.1/tests/test_ingest_enrich_hook.py +580 -0
- secondbrain_py-0.2.1/tests/test_ingest_gmail_drafts.py +587 -0
- secondbrain_py-0.2.1/tests/test_ingest_markdown.py +62 -0
- secondbrain_py-0.2.1/tests/test_ingest_pdf.py +35 -0
- secondbrain_py-0.2.1/tests/test_ingest_perf_t3.py +525 -0
- secondbrain_py-0.2.1/tests/test_ingest_pipeline.py +2037 -0
- secondbrain_py-0.2.1/tests/test_ingest_stdin_action_items.py +113 -0
- secondbrain_py-0.2.1/tests/test_ingest_text.py +13 -0
- secondbrain_py-0.2.1/tests/test_init_auto_runs_backfill.py +171 -0
- secondbrain_py-0.2.1/tests/test_install_sh.py +179 -0
- secondbrain_py-0.2.1/tests/test_interactions.py +390 -0
- secondbrain_py-0.2.1/tests/test_maintenance.py +376 -0
- secondbrain_py-0.2.1/tests/test_make_embedder.py +91 -0
- secondbrain_py-0.2.1/tests/test_mcp_ask.py +194 -0
- secondbrain_py-0.2.1/tests/test_mcp_brief.py +105 -0
- secondbrain_py-0.2.1/tests/test_mcp_capture.py +337 -0
- secondbrain_py-0.2.1/tests/test_mcp_connect.py +223 -0
- secondbrain_py-0.2.1/tests/test_mcp_gaps.py +109 -0
- secondbrain_py-0.2.1/tests/test_mcp_graphrag.py +1307 -0
- secondbrain_py-0.2.1/tests/test_mcp_limit_guard.py +114 -0
- secondbrain_py-0.2.1/tests/test_mcp_perf_t6.py +328 -0
- secondbrain_py-0.2.1/tests/test_mcp_rate.py +297 -0
- secondbrain_py-0.2.1/tests/test_mcp_resurface.py +160 -0
- secondbrain_py-0.2.1/tests/test_mcp_review.py +82 -0
- secondbrain_py-0.2.1/tests/test_mcp_review_scan.py +170 -0
- secondbrain_py-0.2.1/tests/test_mcp_search_filters.py +233 -0
- secondbrain_py-0.2.1/tests/test_mcp_server.py +1445 -0
- secondbrain_py-0.2.1/tests/test_mcp_server_protocol.py +126 -0
- secondbrain_py-0.2.1/tests/test_mcp_session_tracking.py +309 -0
- secondbrain_py-0.2.1/tests/test_mcp_show_summary.py +104 -0
- secondbrain_py-0.2.1/tests/test_mcp_vault_tools.py +1043 -0
- secondbrain_py-0.2.1/tests/test_migration_003.py +220 -0
- secondbrain_py-0.2.1/tests/test_migration_004.py +100 -0
- secondbrain_py-0.2.1/tests/test_migration_006.py +68 -0
- secondbrain_py-0.2.1/tests/test_migration_012.py +688 -0
- secondbrain_py-0.2.1/tests/test_migration_013.py +451 -0
- secondbrain_py-0.2.1/tests/test_migration_015.py +227 -0
- secondbrain_py-0.2.1/tests/test_migration_016.py +150 -0
- secondbrain_py-0.2.1/tests/test_none_backend_mcp.py +131 -0
- secondbrain_py-0.2.1/tests/test_packaging_demo.py +93 -0
- secondbrain_py-0.2.1/tests/test_packaging_entrypoints.py +138 -0
- secondbrain_py-0.2.1/tests/test_packaging_overlay.py +118 -0
- secondbrain_py-0.2.1/tests/test_packaging_templates.py +261 -0
- secondbrain_py-0.2.1/tests/test_person_name.py +280 -0
- secondbrain_py-0.2.1/tests/test_quartz_branding.py +47 -0
- secondbrain_py-0.2.1/tests/test_quartz_build_overlay_static.py +256 -0
- secondbrain_py-0.2.1/tests/test_quartz_command_palette_static.py +81 -0
- secondbrain_py-0.2.1/tests/test_quartz_contentindex_draft_filter.py +102 -0
- secondbrain_py-0.2.1/tests/test_quartz_e2e.py +866 -0
- secondbrain_py-0.2.1/tests/test_quartz_email_thread_static.py +537 -0
- secondbrain_py-0.2.1/tests/test_quartz_fastpath_manifest_static.py +376 -0
- secondbrain_py-0.2.1/tests/test_quartz_overlay.py +944 -0
- secondbrain_py-0.2.1/tests/test_quartz_overrides_parse.py +188 -0
- secondbrain_py-0.2.1/tests/test_quartz_parser_cache_static.py +494 -0
- secondbrain_py-0.2.1/tests/test_quartz_people_hub_static.py +133 -0
- secondbrain_py-0.2.1/tests/test_quartz_related_docs_static.py +127 -0
- secondbrain_py-0.2.1/tests/test_quartz_reload_static.py +58 -0
- secondbrain_py-0.2.1/tests/test_quartz_search_static.py +642 -0
- secondbrain_py-0.2.1/tests/test_quartz_sidebar_static.py +155 -0
- secondbrain_py-0.2.1/tests/test_quartz_tag_content_static.py +467 -0
- secondbrain_py-0.2.1/tests/test_quartz_tags_static.py +184 -0
- secondbrain_py-0.2.1/tests/test_queries.py +880 -0
- secondbrain_py-0.2.1/tests/test_queries_analyze.py +72 -0
- secondbrain_py-0.2.1/tests/test_queries_list_existing_tags.py +66 -0
- secondbrain_py-0.2.1/tests/test_queries_sync_chunk_metadata.py +153 -0
- secondbrain_py-0.2.1/tests/test_qwen3_embedder.py +185 -0
- secondbrain_py-0.2.1/tests/test_rank_fusion.py +72 -0
- secondbrain_py-0.2.1/tests/test_reembed_none_upgrade.py +108 -0
- secondbrain_py-0.2.1/tests/test_rename_helpers.py +68 -0
- secondbrain_py-0.2.1/tests/test_rename_plan.py +436 -0
- secondbrain_py-0.2.1/tests/test_resolve_person.py +247 -0
- secondbrain_py-0.2.1/tests/test_resolver.py +304 -0
- secondbrain_py-0.2.1/tests/test_resurface.py +452 -0
- secondbrain_py-0.2.1/tests/test_review.py +754 -0
- secondbrain_py-0.2.1/tests/test_review_emit.py +49 -0
- secondbrain_py-0.2.1/tests/test_review_render.py +145 -0
- secondbrain_py-0.2.1/tests/test_review_weekly.py +490 -0
- secondbrain_py-0.2.1/tests/test_search.py +227 -0
- secondbrain_py-0.2.1/tests/test_search_canary_queries.py +121 -0
- secondbrain_py-0.2.1/tests/test_search_compact_query_expansion.py +149 -0
- secondbrain_py-0.2.1/tests/test_search_diagnostics.py +100 -0
- secondbrain_py-0.2.1/tests/test_search_explain.py +272 -0
- secondbrain_py-0.2.1/tests/test_search_floor_default_excludes_known_bad.py +157 -0
- secondbrain_py-0.2.1/tests/test_search_metadata_filters.py +665 -0
- secondbrain_py-0.2.1/tests/test_search_nofilter_fastpath.py +161 -0
- secondbrain_py-0.2.1/tests/test_search_none_backend.py +196 -0
- secondbrain_py-0.2.1/tests/test_search_per_doc_cap.py +130 -0
- secondbrain_py-0.2.1/tests/test_search_query_embed_cache.py +131 -0
- secondbrain_py-0.2.1/tests/test_search_recency_boost.py +252 -0
- secondbrain_py-0.2.1/tests/test_search_size_budget.py +166 -0
- secondbrain_py-0.2.1/tests/test_search_snippet_context.py +247 -0
- secondbrain_py-0.2.1/tests/test_search_vector_floor.py +215 -0
- secondbrain_py-0.2.1/tests/test_set_similarity.py +46 -0
- secondbrain_py-0.2.1/tests/test_setup.py +1309 -0
- secondbrain_py-0.2.1/tests/test_sub_tokens.py +228 -0
- secondbrain_py-0.2.1/tests/test_summary_counts_cte.py +109 -0
- secondbrain_py-0.2.1/tests/test_sync_one_file.py +346 -0
- secondbrain_py-0.2.1/tests/test_t5_llm_keep_alive.py +460 -0
- secondbrain_py-0.2.1/tests/test_tags.py +133 -0
- secondbrain_py-0.2.1/tests/test_template_render.py +107 -0
- secondbrain_py-0.2.1/tests/test_timeline.py +1246 -0
- secondbrain_py-0.2.1/tests/test_todo_parse.py +55 -0
- secondbrain_py-0.2.1/tests/test_uninstall.py +236 -0
- secondbrain_py-0.2.1/tests/test_vault_atomic.py +96 -0
- secondbrain_py-0.2.1/tests/test_vault_export.py +1324 -0
- secondbrain_py-0.2.1/tests/test_vault_export_gmail_slug.py +196 -0
- secondbrain_py-0.2.1/tests/test_vault_frontmatter.py +368 -0
- secondbrain_py-0.2.1/tests/test_vault_init.py +132 -0
- secondbrain_py-0.2.1/tests/test_vault_link_rewrite.py +752 -0
- secondbrain_py-0.2.1/tests/test_vault_note_builder.py +358 -0
- secondbrain_py-0.2.1/tests/test_vault_paths.py +64 -0
- secondbrain_py-0.2.1/tests/test_vault_slug.py +67 -0
- secondbrain_py-0.2.1/tests/test_vault_sync.py +2321 -0
- secondbrain_py-0.2.1/tests/test_vault_sync_link_rewrite.py +433 -0
- secondbrain_py-0.2.1/tests/test_vault_sync_path_classification.py +257 -0
- secondbrain_py-0.2.1/tests/test_vault_sync_summaries.py +361 -0
- secondbrain_py-0.2.1/tests/test_vault_watcher.py +2460 -0
- secondbrain_py-0.2.1/tests/test_verify_fastpath_static.py +348 -0
- secondbrain_py-0.2.1/tests/test_wiki_install.py +328 -0
- secondbrain_py-0.2.1/tests/test_wiki_link_parser.py +275 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ali Sarkis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: secondbrain-py
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Local personal knowledge base with hybrid search, queryable by Claude
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/mshtawythug/second-brain
|
|
7
|
+
Project-URL: Repository, https://github.com/mshtawythug/second-brain
|
|
8
|
+
Project-URL: Issues, https://github.com/mshtawythug/second-brain/issues
|
|
9
|
+
Keywords: second-brain,knowledge-base,rag,pgvector,hybrid-search,mcp,ollama,local-first
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Topic :: Text Processing :: Indexing
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: psycopg[binary]>=3.2
|
|
18
|
+
Requires-Dist: pgvector>=0.3
|
|
19
|
+
Requires-Dist: httpx>=0.27
|
|
20
|
+
Requires-Dist: pypdf>=5.0
|
|
21
|
+
Requires-Dist: pdfplumber>=0.11
|
|
22
|
+
Requires-Dist: python-docx>=1.1
|
|
23
|
+
Requires-Dist: markdown-it-py>=3.0
|
|
24
|
+
Requires-Dist: typer>=0.13
|
|
25
|
+
Requires-Dist: rich>=13.9
|
|
26
|
+
Requires-Dist: python-dotenv>=1.0
|
|
27
|
+
Requires-Dist: tiktoken>=0.8
|
|
28
|
+
Requires-Dist: mcp<2.0,>=1.0
|
|
29
|
+
Requires-Dist: voyageai>=0.3
|
|
30
|
+
Requires-Dist: pyyaml>=6.0
|
|
31
|
+
Requires-Dist: watchdog>=4.0
|
|
32
|
+
Requires-Dist: networkx>=3.2
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=8.3; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
36
|
+
Requires-Dist: coverage<7.13; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-mock>=3.14; extra == "dev"
|
|
38
|
+
Requires-Dist: ruff>=0.7; extra == "dev"
|
|
39
|
+
Requires-Dist: mypy>=1.13; extra == "dev"
|
|
40
|
+
Requires-Dist: reportlab>=4.0; extra == "dev"
|
|
41
|
+
Requires-Dist: types-PyYAML>=6.0; extra == "dev"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
<!-- mcp-name: io.github.mshtawythug/second-brain -->
|
|
45
|
+
|
|
46
|
+
# Second Brain
|
|
47
|
+
|
|
48
|
+
Local, queryable knowledge base and note vault with hybrid search and an entity-graph layer — searchable by any AI coding agent or assistant from any conversation.
|
|
49
|
+
|
|
50
|
+
[](LICENSE)
|
|
51
|
+
[](https://github.com/mshtawythug/second-brain/actions/workflows/eval.yml)
|
|
52
|
+
[](https://www.python.org/downloads/)
|
|
53
|
+
|
|
54
|
+
Stores career docs, interview prep, Krisp transcripts, Slack threads, Gmail, and authored Markdown notes in Postgres + pgvector. Any agent reaches all of it through the `brain` CLI or the bundled `brain-mcp` MCP server — no re-pasting context into every chat.
|
|
55
|
+
|
|
56
|
+
The everyday loop — capture a note, search it back, read the top hit, check the corpus:
|
|
57
|
+
|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
## Token savings
|
|
61
|
+
|
|
62
|
+
Querying `brain` returns a ranked snippet instead of dumping whole threads and files into an agent's context, so a lookup costs a fraction of the tokens a direct MCP or file read would:
|
|
63
|
+
|
|
64
|
+
| Source | Direct read (MCP / `Read` tool) | Via `brain` (search + optional `show`) | Savings |
|
|
65
|
+
|---|---|---|---|
|
|
66
|
+
| **Gmail thread** | 15–50k (metadata + full threads with quoted replies / headers) | ~2–4k (snippets + one targeted `brain show`) | **~5–15×** |
|
|
67
|
+
| **Krisp transcript** | 25–75k (search + full transcripts across candidates) | ~6–16k (search + one relevant transcript) | **~4–10×** |
|
|
68
|
+
| **Long PDF / DOCX (30+ pages)** | 15–25k (read the whole file) | ~1–9k (one snippet + targeted `show`) | **~5–15×** |
|
|
69
|
+
|
|
70
|
+
Brain stores pre-extracted, quote-stripped bodies and hybrid-ranks *before* fetching, so only the passage that matched enters context. See the [full per-source breakdown](docs/configuration.md#token-economics).
|
|
71
|
+
|
|
72
|
+
## See it in 60 seconds
|
|
73
|
+
|
|
74
|
+
Install the CLI, then run the offline demo — a throwaway Postgres seeded with a synthetic *Larkspur* compliance corpus. No Ollama, no personal data, no model downloads:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pipx install secondbrain-py
|
|
78
|
+
# or, with uv:
|
|
79
|
+
uv tool install secondbrain-py
|
|
80
|
+
# or, pin the git tag (no PyPI needed):
|
|
81
|
+
pipx install git+https://github.com/mshtawythug/second-brain.git@v0.2.1
|
|
82
|
+
|
|
83
|
+
brain demo # spins up a sandbox Postgres, seeds 22 docs, runs a hero query
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+

|
|
87
|
+
|
|
88
|
+
_(IDs and scores are per-run; the seeded corpus and its ranking are deterministic — the top hit is always the "Compliance Horror Stories" note.)_
|
|
89
|
+
|
|
90
|
+
Try these next:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
brain demo query "SOC 2 evidence request" # a targeted follow-up query
|
|
94
|
+
brain demo query "vendor risk" --source gmail # narrow by source
|
|
95
|
+
brain show <id> # read the top hit in full (in your own brain)
|
|
96
|
+
brain demo teardown # remove the sandbox when done
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`brain demo` needs only Docker; `brain demo teardown` destroys the sandbox.
|
|
100
|
+
|
|
101
|
+
Already ran the demo? You have `brain` installed — jump straight to `brain setup --profile …` below.
|
|
102
|
+
|
|
103
|
+
## Quick start
|
|
104
|
+
|
|
105
|
+
Full install — the `brain` CLI plus the runtime (Postgres, and optionally Ollama / graph / wiki):
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
curl -fsSL https://raw.githubusercontent.com/mshtawythug/second-brain/v0.2.1/install.sh | bash
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The installer pipx-installs `brain` from the `v0.2.1` tag, then runs `brain setup` to provision `$BRAIN_HOME`, start the Postgres container, and (optionally) install the wiki + Claude Code skill. Choose how much to stand up with `--profile`:
|
|
112
|
+
|
|
113
|
+
| Profile | Search | Extra dependencies beyond the core |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `minimal` | FTS-only (`BRAIN_EMBEDDER=none`) | None — core only (no Ollama, no models). |
|
|
116
|
+
| `standard` *(default)* | Hybrid (FTS + vector) | + Ollama + one ~1 GB embedding model (`snowflake-arctic-embed2`). |
|
|
117
|
+
| `full` | Hybrid + GraphRAG + wiki | + Apache AGE image, concept-extraction LLM, Quartz/Caddy wiki, and (opt-in) launchd daemons via `--daemons`. |
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
brain setup --profile minimal # Docker-only, FTS search — fastest to stand up
|
|
121
|
+
brain setup --profile standard # default: local hybrid search
|
|
122
|
+
brain setup --profile full --daemons
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Prerequisites (core):** Git, Python 3.11+, and a running Docker (Desktop or Engine) — the Postgres + pgvector database runs in a container on port 55432.
|
|
126
|
+
|
|
127
|
+
**Optional extras** (only if you want more than FTS-only search):
|
|
128
|
+
|
|
129
|
+
- **[Ollama](https://ollama.com/)** — local, free embeddings + LLM enrichment. To upgrade a `minimal` (FTS-only) brain to hybrid search: install Ollama, then `ollama pull snowflake-arctic-embed2`, set `BRAIN_EMBEDDER=arctic` in `.env`, and run `brain init && brain reembed`.
|
|
130
|
+
- **Node.js 18+ and [Caddy](https://caddyserver.com/)** — only for the rendered [wiki](docs/vault-and-wiki.md).
|
|
131
|
+
- **`gws` CLI** — only for Gmail ingest and Google-backed directory linking.
|
|
132
|
+
|
|
133
|
+
Prefer to drive every step yourself, or hacking on the code? See [Installing from source](docs/configuration.md#installing-from-source-manual).
|
|
134
|
+
|
|
135
|
+
## Core usage
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
brain ingest ~/Documents/resume.pdf --tag career # ingest a file (TXT / MD / PDF / DOCX)
|
|
139
|
+
brain ingest-dir ~/Documents/career # recursive, idempotent by content hash
|
|
140
|
+
brain search "what did I tell my manager about the migration" # hybrid FTS + vector search
|
|
141
|
+
brain show <id-prefix> # full document body (6+ hex prefix)
|
|
142
|
+
brain list --source gmail --limit 20 # browse by source
|
|
143
|
+
brain tag <id-prefix> +interview +career -old-tag # add (+name) / remove (-name) tags
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Add `--json` to `search` / `show` / `list` for machine-readable output, and `--fts-only` to `search` to skip the embedding call. The full command surface — Gmail ingest, enrichment, tacit-knowledge elicitation, GraphRAG, the proactivity/synthesis commands, and vault authoring — lives in the [CLI reference](docs/cli-reference.md).
|
|
147
|
+
|
|
148
|
+
## Claude integrations
|
|
149
|
+
|
|
150
|
+
`brain` and the bundled `brain-mcp` server are harness-agnostic — any agent that runs a shell command or speaks MCP can query the corpus.
|
|
151
|
+
|
|
152
|
+
Register the MCP server once, then just ask — the agent searches the brain and answers from it, no context pasted in:
|
|
153
|
+
|
|
154
|
+

|
|
155
|
+
|
|
156
|
+
**Claude Code** — register the MCP server (or symlink the [skills](docs/configuration.md#claude-code-consult-brain-skill)):
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
claude mcp add brain -- brain-mcp
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json` (replace `/Users/you` with your home directory):
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"mcpServers": {
|
|
167
|
+
"brain": {
|
|
168
|
+
"command": "/Users/you/workspace/second-brain/.venv/bin/brain-mcp",
|
|
169
|
+
"env": {
|
|
170
|
+
"DATABASE_URL": "postgresql://brain:brain@localhost:55432/second_brain",
|
|
171
|
+
"BRAIN_EMBEDDER": "arctic"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`uvx secondbrain-py` also launches the MCP server. Full walkthrough (symlink, smoke-test, troubleshooting): [docs/guides/claude-desktop-setup.md](docs/guides/claude-desktop-setup.md).
|
|
179
|
+
|
|
180
|
+
## How it works
|
|
181
|
+
|
|
182
|
+
**Hybrid search.** Every document is chunked, embedded, and indexed for Postgres full-text search. A query runs both legs — lexical `tsvector` ranking and vector cosine similarity — and fuses them with [Reciprocal Rank Fusion](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf) (k=60), then applies a recency boost and metadata filters. Lexical alone misses paraphrases ("what did I say about X"); vector alone misses exact names (a coworker, a former employer); RRF gets both in one ranked list without tuning weights. Set `BRAIN_EMBEDDER=none` for an FTS-only brain with no embedding dependency at all.
|
|
183
|
+
|
|
184
|
+
**GraphRAG.** Alongside search, brain builds an entity graph of the people and concepts that co-occur across the corpus ([Apache AGE](https://age.apache.org/) inside the same Postgres) and retrieves over that structure — answering "what themes come up in my conversations with X" or "which clusters of people and topics dominate my notes" by traversing relationships instead of matching text. It runs alongside plain search, not instead of it. See [docs/graphrag.md](docs/graphrag.md).
|
|
185
|
+
|
|
186
|
+
## Documentation
|
|
187
|
+
|
|
188
|
+
- [docs/](docs/README.md) — full documentation index
|
|
189
|
+
- [CLI reference](docs/cli-reference.md) — every command beyond the core five
|
|
190
|
+
- [GraphRAG](docs/graphrag.md) — entity-graph retrieval (themes, patterns, connections)
|
|
191
|
+
- [Vault and Wiki](docs/vault-and-wiki.md) — the two-tier note vault + optional rendered wiki
|
|
192
|
+
- [Configuration](docs/configuration.md) — tech stack, tuning knobs, integrations, embedder backends, uninstall
|
|
193
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md) — dev setup, codebase layout, running the tests
|
|
194
|
+
- [CHANGELOG.md](CHANGELOG.md) — release history
|
|
195
|
+
- [LICENSE](LICENSE) — MIT
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
<!-- mcp-name: io.github.mshtawythug/second-brain -->
|
|
2
|
+
|
|
3
|
+
# Second Brain
|
|
4
|
+
|
|
5
|
+
Local, queryable knowledge base and note vault with hybrid search and an entity-graph layer — searchable by any AI coding agent or assistant from any conversation.
|
|
6
|
+
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://github.com/mshtawythug/second-brain/actions/workflows/eval.yml)
|
|
9
|
+
[](https://www.python.org/downloads/)
|
|
10
|
+
|
|
11
|
+
Stores career docs, interview prep, Krisp transcripts, Slack threads, Gmail, and authored Markdown notes in Postgres + pgvector. Any agent reaches all of it through the `brain` CLI or the bundled `brain-mcp` MCP server — no re-pasting context into every chat.
|
|
12
|
+
|
|
13
|
+
The everyday loop — capture a note, search it back, read the top hit, check the corpus:
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
## Token savings
|
|
18
|
+
|
|
19
|
+
Querying `brain` returns a ranked snippet instead of dumping whole threads and files into an agent's context, so a lookup costs a fraction of the tokens a direct MCP or file read would:
|
|
20
|
+
|
|
21
|
+
| Source | Direct read (MCP / `Read` tool) | Via `brain` (search + optional `show`) | Savings |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| **Gmail thread** | 15–50k (metadata + full threads with quoted replies / headers) | ~2–4k (snippets + one targeted `brain show`) | **~5–15×** |
|
|
24
|
+
| **Krisp transcript** | 25–75k (search + full transcripts across candidates) | ~6–16k (search + one relevant transcript) | **~4–10×** |
|
|
25
|
+
| **Long PDF / DOCX (30+ pages)** | 15–25k (read the whole file) | ~1–9k (one snippet + targeted `show`) | **~5–15×** |
|
|
26
|
+
|
|
27
|
+
Brain stores pre-extracted, quote-stripped bodies and hybrid-ranks *before* fetching, so only the passage that matched enters context. See the [full per-source breakdown](docs/configuration.md#token-economics).
|
|
28
|
+
|
|
29
|
+
## See it in 60 seconds
|
|
30
|
+
|
|
31
|
+
Install the CLI, then run the offline demo — a throwaway Postgres seeded with a synthetic *Larkspur* compliance corpus. No Ollama, no personal data, no model downloads:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pipx install secondbrain-py
|
|
35
|
+
# or, with uv:
|
|
36
|
+
uv tool install secondbrain-py
|
|
37
|
+
# or, pin the git tag (no PyPI needed):
|
|
38
|
+
pipx install git+https://github.com/mshtawythug/second-brain.git@v0.2.1
|
|
39
|
+
|
|
40
|
+
brain demo # spins up a sandbox Postgres, seeds 22 docs, runs a hero query
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
_(IDs and scores are per-run; the seeded corpus and its ranking are deterministic — the top hit is always the "Compliance Horror Stories" note.)_
|
|
46
|
+
|
|
47
|
+
Try these next:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
brain demo query "SOC 2 evidence request" # a targeted follow-up query
|
|
51
|
+
brain demo query "vendor risk" --source gmail # narrow by source
|
|
52
|
+
brain show <id> # read the top hit in full (in your own brain)
|
|
53
|
+
brain demo teardown # remove the sandbox when done
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`brain demo` needs only Docker; `brain demo teardown` destroys the sandbox.
|
|
57
|
+
|
|
58
|
+
Already ran the demo? You have `brain` installed — jump straight to `brain setup --profile …` below.
|
|
59
|
+
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
Full install — the `brain` CLI plus the runtime (Postgres, and optionally Ollama / graph / wiki):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
curl -fsSL https://raw.githubusercontent.com/mshtawythug/second-brain/v0.2.1/install.sh | bash
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The installer pipx-installs `brain` from the `v0.2.1` tag, then runs `brain setup` to provision `$BRAIN_HOME`, start the Postgres container, and (optionally) install the wiki + Claude Code skill. Choose how much to stand up with `--profile`:
|
|
69
|
+
|
|
70
|
+
| Profile | Search | Extra dependencies beyond the core |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `minimal` | FTS-only (`BRAIN_EMBEDDER=none`) | None — core only (no Ollama, no models). |
|
|
73
|
+
| `standard` *(default)* | Hybrid (FTS + vector) | + Ollama + one ~1 GB embedding model (`snowflake-arctic-embed2`). |
|
|
74
|
+
| `full` | Hybrid + GraphRAG + wiki | + Apache AGE image, concept-extraction LLM, Quartz/Caddy wiki, and (opt-in) launchd daemons via `--daemons`. |
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
brain setup --profile minimal # Docker-only, FTS search — fastest to stand up
|
|
78
|
+
brain setup --profile standard # default: local hybrid search
|
|
79
|
+
brain setup --profile full --daemons
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Prerequisites (core):** Git, Python 3.11+, and a running Docker (Desktop or Engine) — the Postgres + pgvector database runs in a container on port 55432.
|
|
83
|
+
|
|
84
|
+
**Optional extras** (only if you want more than FTS-only search):
|
|
85
|
+
|
|
86
|
+
- **[Ollama](https://ollama.com/)** — local, free embeddings + LLM enrichment. To upgrade a `minimal` (FTS-only) brain to hybrid search: install Ollama, then `ollama pull snowflake-arctic-embed2`, set `BRAIN_EMBEDDER=arctic` in `.env`, and run `brain init && brain reembed`.
|
|
87
|
+
- **Node.js 18+ and [Caddy](https://caddyserver.com/)** — only for the rendered [wiki](docs/vault-and-wiki.md).
|
|
88
|
+
- **`gws` CLI** — only for Gmail ingest and Google-backed directory linking.
|
|
89
|
+
|
|
90
|
+
Prefer to drive every step yourself, or hacking on the code? See [Installing from source](docs/configuration.md#installing-from-source-manual).
|
|
91
|
+
|
|
92
|
+
## Core usage
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
brain ingest ~/Documents/resume.pdf --tag career # ingest a file (TXT / MD / PDF / DOCX)
|
|
96
|
+
brain ingest-dir ~/Documents/career # recursive, idempotent by content hash
|
|
97
|
+
brain search "what did I tell my manager about the migration" # hybrid FTS + vector search
|
|
98
|
+
brain show <id-prefix> # full document body (6+ hex prefix)
|
|
99
|
+
brain list --source gmail --limit 20 # browse by source
|
|
100
|
+
brain tag <id-prefix> +interview +career -old-tag # add (+name) / remove (-name) tags
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Add `--json` to `search` / `show` / `list` for machine-readable output, and `--fts-only` to `search` to skip the embedding call. The full command surface — Gmail ingest, enrichment, tacit-knowledge elicitation, GraphRAG, the proactivity/synthesis commands, and vault authoring — lives in the [CLI reference](docs/cli-reference.md).
|
|
104
|
+
|
|
105
|
+
## Claude integrations
|
|
106
|
+
|
|
107
|
+
`brain` and the bundled `brain-mcp` server are harness-agnostic — any agent that runs a shell command or speaks MCP can query the corpus.
|
|
108
|
+
|
|
109
|
+
Register the MCP server once, then just ask — the agent searches the brain and answers from it, no context pasted in:
|
|
110
|
+
|
|
111
|
+

|
|
112
|
+
|
|
113
|
+
**Claude Code** — register the MCP server (or symlink the [skills](docs/configuration.md#claude-code-consult-brain-skill)):
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
claude mcp add brain -- brain-mcp
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json` (replace `/Users/you` with your home directory):
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"mcpServers": {
|
|
124
|
+
"brain": {
|
|
125
|
+
"command": "/Users/you/workspace/second-brain/.venv/bin/brain-mcp",
|
|
126
|
+
"env": {
|
|
127
|
+
"DATABASE_URL": "postgresql://brain:brain@localhost:55432/second_brain",
|
|
128
|
+
"BRAIN_EMBEDDER": "arctic"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`uvx secondbrain-py` also launches the MCP server. Full walkthrough (symlink, smoke-test, troubleshooting): [docs/guides/claude-desktop-setup.md](docs/guides/claude-desktop-setup.md).
|
|
136
|
+
|
|
137
|
+
## How it works
|
|
138
|
+
|
|
139
|
+
**Hybrid search.** Every document is chunked, embedded, and indexed for Postgres full-text search. A query runs both legs — lexical `tsvector` ranking and vector cosine similarity — and fuses them with [Reciprocal Rank Fusion](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf) (k=60), then applies a recency boost and metadata filters. Lexical alone misses paraphrases ("what did I say about X"); vector alone misses exact names (a coworker, a former employer); RRF gets both in one ranked list without tuning weights. Set `BRAIN_EMBEDDER=none` for an FTS-only brain with no embedding dependency at all.
|
|
140
|
+
|
|
141
|
+
**GraphRAG.** Alongside search, brain builds an entity graph of the people and concepts that co-occur across the corpus ([Apache AGE](https://age.apache.org/) inside the same Postgres) and retrieves over that structure — answering "what themes come up in my conversations with X" or "which clusters of people and topics dominate my notes" by traversing relationships instead of matching text. It runs alongside plain search, not instead of it. See [docs/graphrag.md](docs/graphrag.md).
|
|
142
|
+
|
|
143
|
+
## Documentation
|
|
144
|
+
|
|
145
|
+
- [docs/](docs/README.md) — full documentation index
|
|
146
|
+
- [CLI reference](docs/cli-reference.md) — every command beyond the core five
|
|
147
|
+
- [GraphRAG](docs/graphrag.md) — entity-graph retrieval (themes, patterns, connections)
|
|
148
|
+
- [Vault and Wiki](docs/vault-and-wiki.md) — the two-tier note vault + optional rendered wiki
|
|
149
|
+
- [Configuration](docs/configuration.md) — tech stack, tuning knobs, integrations, embedder backends, uninstall
|
|
150
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md) — dev setup, codebase layout, running the tests
|
|
151
|
+
- [CHANGELOG.md](CHANGELOG.md) — release history
|
|
152
|
+
- [LICENSE](LICENSE) — MIT
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "secondbrain-py"
|
|
3
|
+
version = "0.2.1"
|
|
4
|
+
description = "Local personal knowledge base with hybrid search, queryable by Claude"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
keywords = [
|
|
10
|
+
"second-brain",
|
|
11
|
+
"knowledge-base",
|
|
12
|
+
"rag",
|
|
13
|
+
"pgvector",
|
|
14
|
+
"hybrid-search",
|
|
15
|
+
"mcp",
|
|
16
|
+
"ollama",
|
|
17
|
+
"local-first",
|
|
18
|
+
]
|
|
19
|
+
# NOTE: no `License ::` trove classifier here — the license is declared via the
|
|
20
|
+
# PEP 639 SPDX expression (`license = "MIT"` + `license-files`) above. setuptools
|
|
21
|
+
# >= 77 rejects a `License ::` classifier alongside an SPDX license expression
|
|
22
|
+
# ("License classifiers have been superseded by license expressions"), so the
|
|
23
|
+
# classifier would break the build. PyPI derives the license from the expression.
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Environment :: Console",
|
|
28
|
+
"Topic :: Text Processing :: Indexing",
|
|
29
|
+
]
|
|
30
|
+
dependencies = [
|
|
31
|
+
"psycopg[binary]>=3.2",
|
|
32
|
+
"pgvector>=0.3",
|
|
33
|
+
"httpx>=0.27",
|
|
34
|
+
"pypdf>=5.0",
|
|
35
|
+
"pdfplumber>=0.11",
|
|
36
|
+
"python-docx>=1.1",
|
|
37
|
+
"markdown-it-py>=3.0",
|
|
38
|
+
"typer>=0.13",
|
|
39
|
+
"rich>=13.9",
|
|
40
|
+
"python-dotenv>=1.0",
|
|
41
|
+
"tiktoken>=0.8",
|
|
42
|
+
"mcp>=1.0,<2.0",
|
|
43
|
+
"voyageai>=0.3",
|
|
44
|
+
"pyyaml>=6.0",
|
|
45
|
+
"watchdog>=4.0",
|
|
46
|
+
"networkx>=3.2",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[project.optional-dependencies]
|
|
50
|
+
dev = ["pytest>=8.3", "pytest-cov>=5.0", "coverage<7.13", "pytest-mock>=3.14", "ruff>=0.7", "mypy>=1.13", "reportlab>=4.0", "types-PyYAML>=6.0"]
|
|
51
|
+
|
|
52
|
+
[project.scripts]
|
|
53
|
+
brain = "brain.cli:app"
|
|
54
|
+
brain-mcp = "brain.mcp_server:main"
|
|
55
|
+
# Alias so `uvx secondbrain-py` launches the MCP server (uvx matches the console
|
|
56
|
+
# script to the normalized distribution name; matches server.json's uvx
|
|
57
|
+
# runtimeHint for the MCP registry; the human CLI remains `brain`).
|
|
58
|
+
secondbrain-py = "brain.mcp_server:main"
|
|
59
|
+
brain-up = "brain.bin.up:main"
|
|
60
|
+
brain-down = "brain.bin.down:main"
|
|
61
|
+
brain-status = "brain.bin.status:main"
|
|
62
|
+
brain-rebuild = "brain.bin.rebuild:main"
|
|
63
|
+
brain-install-launchd = "brain.bin.launchd:install_main"
|
|
64
|
+
brain-uninstall-launchd = "brain.bin.launchd:uninstall_main"
|
|
65
|
+
brain-monitor = "brain.bin.monitor:cli_main"
|
|
66
|
+
|
|
67
|
+
[project.urls]
|
|
68
|
+
Homepage = "https://github.com/mshtawythug/second-brain"
|
|
69
|
+
Repository = "https://github.com/mshtawythug/second-brain"
|
|
70
|
+
Issues = "https://github.com/mshtawythug/second-brain/issues"
|
|
71
|
+
|
|
72
|
+
[build-system]
|
|
73
|
+
requires = ["setuptools>=68"]
|
|
74
|
+
build-backend = "setuptools.build_meta"
|
|
75
|
+
|
|
76
|
+
[tool.setuptools.packages.find]
|
|
77
|
+
where = ["src"]
|
|
78
|
+
|
|
79
|
+
[tool.setuptools.package-data]
|
|
80
|
+
# Include only the specific asset extensions used by the Quartz overlay tree.
|
|
81
|
+
# Explicit patterns rather than "**/*" so .pyc bytecode and __pycache__ dirs
|
|
82
|
+
# are never shipped into the wheel.
|
|
83
|
+
# Root-level files: quartz.config.ts, quartz.layout.ts (*.ts covers them)
|
|
84
|
+
# Nested files under quartz/: TS/TSX components, JS/MJS CLI scripts, SCSS
|
|
85
|
+
# stylesheets, and PNG/ICO branding assets.
|
|
86
|
+
"brain.quartz_overrides" = [
|
|
87
|
+
"*.ts",
|
|
88
|
+
"**/*.ts",
|
|
89
|
+
"**/*.tsx",
|
|
90
|
+
"**/*.js",
|
|
91
|
+
"**/*.mjs",
|
|
92
|
+
"**/*.scss",
|
|
93
|
+
"**/*.png",
|
|
94
|
+
"**/*.ico",
|
|
95
|
+
]
|
|
96
|
+
# Templates shipped with the brain package for use by the installer.
|
|
97
|
+
# Explicit extension patterns (not "**/*") to avoid shipping bytecode.
|
|
98
|
+
# brain.templates root: docker-compose.yml.j2, Caddyfile.j2, env.example
|
|
99
|
+
# brain.templates.docker.age: pinned Dockerfile (PG16 + pgvector + AGE), a plain
|
|
100
|
+
# data subdir (no __init__.py) materialized into $BRAIN_HOME by `brain setup`
|
|
101
|
+
# brain.templates.bin: shell launcher/helper scripts
|
|
102
|
+
# brain.templates.launchd: launchd plist Jinja2 templates
|
|
103
|
+
# brain.templates.skill: SKILL.md prose for Claude Code skill setup
|
|
104
|
+
"brain.templates" = ["*.j2", "env.example", "docker/age/Dockerfile"]
|
|
105
|
+
"brain.templates.bin" = ["*.sh"]
|
|
106
|
+
"brain.templates.launchd" = ["*.j2"]
|
|
107
|
+
"brain.templates.skill" = ["*.md"]
|
|
108
|
+
# brain.demo: the synthetic `brain demo` corpus manifest (a plain data subdir
|
|
109
|
+
# under corpus/, no __init__.py) shipped so the zero-Ollama taste test works on
|
|
110
|
+
# pipx/wheel installs. Explicit "corpus/*.json" (not "**/*") avoids bytecode.
|
|
111
|
+
"brain.demo" = ["corpus/*.json"]
|
|
112
|
+
# brain/migrations: the numbered SQL schema migrations `brain init` applies in
|
|
113
|
+
# name order (discovered by db.migrations_dir()). They live INSIDE the package —
|
|
114
|
+
# migrations/ under src/brain/, NOT the repo root — so wheel / pip installs
|
|
115
|
+
# bundle them; without this `brain init` globs an empty dir and never creates the
|
|
116
|
+
# schema. Explicit "migrations/*.sql" (not "**/*") ships only the SQL.
|
|
117
|
+
"brain" = ["migrations/*.sql"]
|
|
118
|
+
|
|
119
|
+
[tool.setuptools.exclude-package-data]
|
|
120
|
+
# Strip bytecode caches that Python creates on-disk when the package is imported
|
|
121
|
+
# during a dev install — they are local artefacts and must not appear in wheels.
|
|
122
|
+
"brain.quartz_overrides" = ["__pycache__/*", "*.pyc"]
|
|
123
|
+
"brain.templates" = ["__pycache__/*", "*.pyc"]
|
|
124
|
+
"brain.templates.bin" = ["__pycache__/*", "*.pyc"]
|
|
125
|
+
"brain.templates.launchd" = ["__pycache__/*", "*.pyc"]
|
|
126
|
+
"brain.templates.skill" = ["__pycache__/*", "*.pyc"]
|
|
127
|
+
"brain.demo" = ["__pycache__/*", "*.pyc"]
|
|
128
|
+
|
|
129
|
+
[tool.pytest.ini_options]
|
|
130
|
+
testpaths = ["tests"]
|
|
131
|
+
addopts = "-ra --strict-markers --cov=brain --cov-report=term-missing --cov-fail-under=85 -m 'not eval and not benchmark and not e2e and not phase3'"
|
|
132
|
+
markers = [
|
|
133
|
+
# P3.5 — frontend integration harness. Tests under this marker shell
|
|
134
|
+
# out to `npx quartz build` and serve the artifact over HTTP (or rsync the
|
|
135
|
+
# live vault for the FastPath T0 perf benchmarks), so they require a Node
|
|
136
|
+
# toolchain + the live brain Quartz workspace at `~/brain-vault/.quartz/`.
|
|
137
|
+
# EXCLUDED from the default suite (see the ``addopts`` -m filter, alongside
|
|
138
|
+
# ``eval`` / ``benchmark``): they are non-hermetic (need a live vault, and
|
|
139
|
+
# the FastPath benchmarks rsync it — racing the live build daemon) and far
|
|
140
|
+
# too slow for every local run. Opt IN with `-m e2e`; CI runs them in a
|
|
141
|
+
# dedicated workflow.
|
|
142
|
+
"e2e: end-to-end Quartz build + serve test (requires npx + live workspace)",
|
|
143
|
+
# Phase D — search-ranking-fix. Tests under this marker connect to the
|
|
144
|
+
# live local Postgres (`second_brain` database) and to a real Ollama
|
|
145
|
+
# instance to embed a known-bad query and measure cosine similarity to
|
|
146
|
+
# a known-bad doc. Used to tune ``DEFAULT_VECTOR_SIM_FLOOR`` and to
|
|
147
|
+
# canary-check the post-fix corpus. Skipped on hosts without those
|
|
148
|
+
# services; opt out via `-m "not live_db"`.
|
|
149
|
+
"live_db: requires live local Postgres + Ollama (Phase D verification)",
|
|
150
|
+
# Wave Q1-B — Eval harness. Tests under this marker run the 20-query
|
|
151
|
+
# golden corpus against the live local Postgres + Ollama, scoring
|
|
152
|
+
# nDCG@5 / MRR / Recall@20. Excluded from the default suite; opt-IN
|
|
153
|
+
# via `-m eval`. Skips cleanly when Postgres or Ollama is unreachable.
|
|
154
|
+
"eval: live golden-corpus run; gated, requires explicit -m eval invocation",
|
|
155
|
+
# Wave G1-d / G2 — GraphRAG benchmark perf gate. Tests under this marker
|
|
156
|
+
# load the full-scale synthetic graph (50k entities / 500k CO_OCCURS / 1M
|
|
157
|
+
# mentions / 10 tenants — tests/graphrag/benchmark_fixture.py) and measure
|
|
158
|
+
# P95 traversal/themes latency. EXCLUDED from the default suite (see
|
|
159
|
+
# ``addopts`` -m filter) because the load is far too slow for every run;
|
|
160
|
+
# G2's perf gate opts IN via ``pytest -m benchmark``. The default suite
|
|
161
|
+
# exercises the SAME generator via the tiny ≈50-entity smoke instead.
|
|
162
|
+
"benchmark: GraphRAG full-scale perf gate; requires explicit -m benchmark invocation",
|
|
163
|
+
# brain-rebuild advisory-lock + real-DB tests. These need the test Postgres
|
|
164
|
+
# container (port 5434) to be up but do NOT require Ollama. Included in the
|
|
165
|
+
# default suite (not excluded by the addopts -m filter) because the test DB
|
|
166
|
+
# is required for most other tests anyway; marked for easy targeted runs via
|
|
167
|
+
# ``pytest -m integration``.
|
|
168
|
+
"integration: requires the test Postgres container (port 5434, db second_brain_test)",
|
|
169
|
+
# Plan 05 — `brain timeline` migration-021 roundtrip. The only test that
|
|
170
|
+
# requires the optional generated ``documents.doc_date`` column (Phase 3).
|
|
171
|
+
# EXCLUDED from the default suite (see the ``addopts`` -m filter, alongside
|
|
172
|
+
# ``eval`` / ``benchmark``) so v1/v2 CI passes without migration 021 applied;
|
|
173
|
+
# opt IN with `-m phase3`.
|
|
174
|
+
"phase3: requires the optional migration-021 doc_date column (brain timeline Phase 3)",
|
|
175
|
+
# Wave 6.1 — test-DB reset strategy opt-out. The per-test ``test_db`` fixture
|
|
176
|
+
# normally does a cheap ``TRUNCATE`` reset on top of a schema migrated ONCE per
|
|
177
|
+
# session (conftest ``_truncate_reset``). Tests that mutate the *schema* itself
|
|
178
|
+
# (DROP/ALTER/CREATE on tables, indexes, extensions; ``ensure_embedding_column``
|
|
179
|
+
# resizes; ``finalize_embedding_index`` NOT NULL/index; re-running migrations;
|
|
180
|
+
# own-connection ``DROP SCHEMA``) must carry this marker: the fixture then falls
|
|
181
|
+
# back to the full ``DROP SCHEMA`` + migrate reset for that test AND an autouse
|
|
182
|
+
# teardown re-establishes the migrated-once baseline afterwards, so the next
|
|
183
|
+
# (TRUNCATE-only) test is not poisoned by the leaked schema mutation. INCLUDED
|
|
184
|
+
# in the default suite — it is a routing hint, not an exclusion filter.
|
|
185
|
+
"fresh_schema: needs a full drop+migrate reset (mutates schema, not just data)",
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
[tool.ruff]
|
|
189
|
+
line-length = 100
|
|
190
|
+
target-version = "py311"
|
|
191
|
+
|
|
192
|
+
[tool.ruff.lint]
|
|
193
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
194
|
+
|
|
195
|
+
[tool.ruff.lint.flake8-bugbear]
|
|
196
|
+
# Typer uses typer.Argument/Option/Typer as defaults — the standard idiom.
|
|
197
|
+
extend-immutable-calls = ["typer.Argument", "typer.Option", "typer.Typer"]
|
|
198
|
+
|
|
199
|
+
[tool.mypy]
|
|
200
|
+
python_version = "3.11"
|
|
201
|
+
mypy_path = "src"
|
|
202
|
+
explicit_package_bases = true
|
|
203
|
+
strict = true
|
|
204
|
+
|
|
205
|
+
[[tool.mypy.overrides]]
|
|
206
|
+
module = ["pgvector.*", "pdfplumber.*", "pypdf.*", "markdown_it.*", "docx.*", "tiktoken.*", "reportlab.*", "voyageai.*", "watchdog.*", "networkx.*"]
|
|
207
|
+
ignore_missing_imports = true
|
|
208
|
+
|
|
209
|
+
# numpy (transitive via networkx) ships PEP 695 `type X = ...` aliases in its
|
|
210
|
+
# bundled stubs, which are 3.12+ syntax. With our `python_version = "3.11"`
|
|
211
|
+
# target, mypy rejects that syntax in `numpy/__init__.pyi` and halts before it
|
|
212
|
+
# ever reaches brain code ("errors prevented further checking"). We never import
|
|
213
|
+
# numpy directly, so skip following into it entirely — keeps the 3.11 target for
|
|
214
|
+
# our own code while unblocking the gate. `follow_imports_for_stubs` makes the
|
|
215
|
+
# skip apply to the `.pyi` too (not just runtime modules).
|
|
216
|
+
[[tool.mypy.overrides]]
|
|
217
|
+
module = ["numpy", "numpy.*"]
|
|
218
|
+
follow_imports = "skip"
|
|
219
|
+
follow_imports_for_stubs = true
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Enable ``python -m brain [command]`` invocation of the brain CLI.
|
|
2
|
+
|
|
3
|
+
This module is the standard entry-point for Python's ``-m`` flag:
|
|
4
|
+
|
|
5
|
+
python -m brain init
|
|
6
|
+
python -m brain doctor
|
|
7
|
+
python -m brain search "..."
|
|
8
|
+
|
|
9
|
+
It delegates directly to the Typer app in :mod:`brain.cli`, which
|
|
10
|
+
processes ``sys.argv`` and dispatches to the appropriate subcommand.
|
|
11
|
+
``brain setup`` uses this path (via ``sys.executable``) to invoke ``brain
|
|
12
|
+
init`` and ``brain doctor`` inside the same virtualenv that is running the
|
|
13
|
+
installer, without relying on the ``brain`` console-script being on ``PATH``.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from brain.cli import app
|
|
17
|
+
|
|
18
|
+
app()
|