knowledge-assistant 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 jycd
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.
package/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # Knowledge Assistant
2
+
3
+ A local-first personal knowledge base. Import PDFs and notes, search them by meaning and
4
+ keyword, and ask questions that are answered with citations — all from one SQLite file on
5
+ your machine. Indexing and search never touch the network; an LLM (local Ollama or a cloud
6
+ API) is only needed for answers and AI note tidying.
7
+
8
+ ```
9
+ node launcher/bin/ka.js
10
+ ```
11
+
12
+ That installs a private Python environment on first run, downloads a small embedding model
13
+ (~130 MB), and opens the app in your browser. The launcher is not published to npm.
14
+
15
+ ## Features
16
+
17
+ - **Import** — drop PDFs or paste text. Documents are split into passages, embedded locally,
18
+ and indexed in the background with live progress.
19
+ - **Search** — hybrid retrieval: vector similarity (sqlite-vec) fused with BM25 keyword
20
+ search (FTS5). Scope by category or topic.
21
+ - **Ask** — answers stream in with `[n]` citations that link to the exact passages used.
22
+ - **Library** — organise entries into categories and topics. Edits re-index automatically.
23
+ - **Notes** — write, tidy into a structured note (rule-based or AI), promote to the library.
24
+ - **Email** — sync a mailbox over IMAP; each message becomes a searchable entry, deduplicated by Message-ID, resumable by UID.
25
+ - **Templates** — five built-in structures plus your own.
26
+ - **Preferences** — describe how you like notes formatted in plain language.
27
+
28
+ ## Requirements
29
+
30
+ - Python 3.10+ (the npm launcher finds it; `pip` users install directly)
31
+ - Node 18+ only if using the npm launcher
32
+ - Optional: [Ollama](https://ollama.com) for fully offline answers, or an OpenAI/Anthropic key
33
+
34
+ ## Install without npm
35
+
36
+ ```bash
37
+ pip install knowledge-assistant[ollama] # or [openai] / [anthropic] / [all]
38
+ ka serve --open
39
+ ```
40
+
41
+ ## Configuration
42
+
43
+ Environment variables (or a `.env` file in the working directory), all prefixed `KA_`:
44
+
45
+ | Variable | Default | Meaning |
46
+ |---|---|---|
47
+ | `KA_DATA_DIR` | `~/.knowledge-assistant` | Where the database, uploads and models live |
48
+ | `KA_PORT` | `8765` | HTTP port |
49
+ | `KA_LLM_PROVIDER` | `ollama` | `ollama`, `openai`, `anthropic`, or `none` |
50
+ | `KA_LLM_MODEL` | `llama3.2` | Model name for the chosen provider |
51
+ | `KA_OLLAMA_HOST` | `http://127.0.0.1:11434` | |
52
+ | `KA_OPENAI_API_KEY` / `KA_ANTHROPIC_API_KEY` | | Only for cloud providers |
53
+ | `KA_EMBEDDING_MODEL` | `BAAI/bge-small-en-v1.5` | Local embedding model (fastembed) |
54
+ | `KA_CHUNK_TOKENS` | `512` | Passage size |
55
+ | `KA_SEARCH_MAX_DISTANCE` | `0.45` | Vector cutoff (cosine distance); re-tune if you change the embedding model |
56
+ | `KA_WORKER_THREADS` | `2` | Background import workers |
57
+ | `KA_IMAP_HOST` / `KA_IMAP_USER` / `KA_IMAP_PASSWORD` | | Set all three to enable the email source (Gmail: use an app password) |
58
+ | `KA_IMAP_FOLDER` | `INBOX` | Folder to sync |
59
+ | `KA_IMAP_MAX_PER_SYNC` | `200` | Cap per sync run |
60
+
61
+ ## CLI
62
+
63
+ ```
64
+ ka serve [--open] [--port N] start API, worker and web UI
65
+ ka ingest FILE import a PDF or text file without the server
66
+ ka search "query" search from the terminal
67
+ ka db path | ka db reset locate or wipe the database
68
+ ```
69
+
70
+ ## Architecture
71
+
72
+ One Python process: FastAPI serves the API and the built React app; a thread pool consumes a
73
+ job table in the same SQLite database; PDF parsing runs in a subprocess so a parser crash
74
+ fails one job instead of the server.
75
+
76
+ ```
77
+ npm launcher ─► ka serve
78
+ ├─ FastAPI /api/v1 + static SPA
79
+ ├─ Worker pool ◄── jobs table
80
+ │ └─ pdf_extract (subprocess)
81
+ ├─ Embedder: fastembed (ONNX, local)
82
+ ├─ LLM: ollama | openai | anthropic | none
83
+ └─ knowledge.db: tables + vec0 + fts5
84
+ ```
85
+
86
+ The job queue is the producer/consumer seam: producers call `JobQueue.enqueue`, handlers are
87
+ registered by kind. Backing it with Redis or a separate worker process later means replacing
88
+ `jobs/queue.py` only. New sources (e.g. an email connector) are new job kinds.
89
+
90
+ ```
91
+ src/knowledge_assistant/
92
+ core/ models, repositories, chunking, embeddings, search, llm/, notes, preferences
93
+ jobs/ queue, worker, handlers, pdf_extract
94
+ api/ FastAPI routers and schemas
95
+ cli.py `ka`
96
+ web/ React 19 + Vite + Tailwind 4 (builds into src/knowledge_assistant/static)
97
+ launcher/ npm package: finds Python, creates a venv, runs `ka serve`
98
+ tests/ pytest (core, jobs, API) — no network, no model download
99
+ ```
100
+
101
+ ## Development
102
+
103
+ ```bash
104
+ python -m venv .venv && . .venv/bin/activate
105
+ pip install -e ".[dev,all]"
106
+ pytest
107
+
108
+ cd web && npm install && npm run dev # UI with API proxy to :8765
109
+ ka serve # in another terminal
110
+ ```
111
+
112
+ Build for local use: `cd web && npm run build`, then `./scripts/build-launcher.sh` to produce
113
+ the wheel and stage it in `launcher/` for the Node launcher.
114
+
115
+ ## License
116
+
117
+ MIT
package/bin/ka.js ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * npm launcher for Knowledge Assistant.
4
+ *
5
+ * The app is a Python service; this script finds a Python >= 3.10, creates a private
6
+ * virtualenv under ~/.knowledge-assistant/venv, installs the bundled wheel (or the pinned
7
+ * PyPI release), and execs `ka <args>`. Re-running is fast: the venv is reused.
8
+ *
9
+ * No native Node modules, no network beyond pip. Works on macOS, Linux, Windows.
10
+ */
11
+ "use strict";
12
+ const { spawnSync, spawn } = require("node:child_process");
13
+ const fs = require("node:fs");
14
+ const os = require("node:os");
15
+ const path = require("node:path");
16
+
17
+ const PKG = require("../package.json");
18
+ const HOME = process.env.KA_DATA_DIR || path.join(os.homedir(), ".knowledge-assistant");
19
+ const VENV = path.join(HOME, "venv");
20
+ const WIN = process.platform === "win32";
21
+ const VENV_PY = WIN ? path.join(VENV, "Scripts", "python.exe") : path.join(VENV, "bin", "python");
22
+ const STAMP = path.join(VENV, ".ka-version");
23
+
24
+ function log(msg) { process.stderr.write(`[knowledge-assistant] ${msg}\n`); }
25
+
26
+ function pythonVersion(bin) {
27
+ const r = spawnSync(bin, ["-c", "import sys;print('%d.%d'%sys.version_info[:2])"], { encoding: "utf8" });
28
+ if (r.status !== 0) return null;
29
+ const [maj, min] = r.stdout.trim().split(".").map(Number);
30
+ return maj > 3 || (maj === 3 && min >= 10) ? `${maj}.${min}` : null;
31
+ }
32
+
33
+ function findPython() {
34
+ const candidates = [process.env.KA_PYTHON, "python3.12", "python3.11", "python3.13", "python3.10", "python3", "python", WIN ? "py" : null].filter(Boolean);
35
+ for (const c of candidates) {
36
+ const args = c === "py" ? ["-3"] : [];
37
+ const r = spawnSync(c, [...args, "-c", "import sys;print('%d.%d'%sys.version_info[:2])"], { encoding: "utf8" });
38
+ if (r.status === 0) {
39
+ const [maj, min] = r.stdout.trim().split(".").map(Number);
40
+ if (maj === 3 && min >= 10) return c === "py" ? ["py", "-3"] : [c];
41
+ }
42
+ }
43
+ return null;
44
+ }
45
+
46
+ function ensureVenv() {
47
+ const upToDate = fs.existsSync(VENV_PY) && fs.existsSync(STAMP) && fs.readFileSync(STAMP, "utf8").trim() === PKG.version;
48
+ if (upToDate) return;
49
+ const py = findPython();
50
+ if (!py) {
51
+ log("Python 3.10+ is required but was not found. Install it from https://python.org and re-run.");
52
+ process.exit(1);
53
+ }
54
+ fs.mkdirSync(HOME, { recursive: true });
55
+ if (!fs.existsSync(VENV_PY)) {
56
+ log(`creating virtualenv in ${VENV} (one-time)`);
57
+ const r = spawnSync(py[0], [...py.slice(1), "-m", "venv", VENV], { stdio: "inherit" });
58
+ if (r.status !== 0) process.exit(r.status ?? 1);
59
+ }
60
+ const wheelDir = path.join(__dirname, "..", "python");
61
+ const wheel = fs.existsSync(wheelDir) ? fs.readdirSync(wheelDir).find((f) => f.endsWith(".whl")) : null;
62
+ const target = wheel ? path.join(wheelDir, wheel) : `knowledge-assistant==${PKG.version}`;
63
+ log(`installing ${wheel || target} (this downloads the embedding runtime; a few minutes the first time)`);
64
+ const r = spawnSync(VENV_PY, ["-m", "pip", "install", "--quiet", "--upgrade", "pip", target], { stdio: "inherit" });
65
+ if (r.status !== 0) process.exit(r.status ?? 1);
66
+ fs.writeFileSync(STAMP, PKG.version);
67
+ }
68
+
69
+ function main() {
70
+ ensureVenv();
71
+ const args = process.argv.slice(2);
72
+ const finalArgs = args.length ? args : ["serve", "--open"];
73
+ const child = spawn(VENV_PY, ["-m", "knowledge_assistant.cli", ...finalArgs], { stdio: "inherit", env: { ...process.env, KA_DATA_DIR: HOME } });
74
+ for (const sig of ["SIGINT", "SIGTERM"]) process.on(sig, () => child.kill(sig));
75
+ child.on("exit", (code) => process.exit(code ?? 0));
76
+ }
77
+
78
+ main();
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "knowledge-assistant",
3
+ "version": "0.1.0",
4
+ "description": "Local-first personal knowledge base: PDF import, hybrid search, RAG Q&A, notes. Runs entirely on your machine.",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "knowledge-assistant": "bin/ka.js",
8
+ "ka": "bin/ka.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "python/*.whl",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "keywords": [
20
+ "knowledge-base",
21
+ "rag",
22
+ "local-first",
23
+ "sqlite",
24
+ "vector-search",
25
+ "pdf"
26
+ ],
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/jycd25/knowledge-assistant"
30
+ },
31
+ "homepage": "https://github.com/jycd25/knowledge-assistant#readme",
32
+ "bugs": {
33
+ "url": "https://github.com/jycd25/knowledge-assistant/issues"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ }
38
+ }