{"_id":"@astragenie/astramemory-local","_rev":"3-4ebe5ef4a855d02d7440dd63f720dd58","name":"@astragenie/astramemory-local","dist-tags":{"latest":"0.8.1"},"versions":{"0.7.2":{"name":"@astragenie/astramemory-local","version":"0.7.2","license":"MIT","_id":"@astragenie/astramemory-local@0.7.2","maintainers":[{"name":"heroboec","email":"shishkosv@gmail.com"}],"bin":{"astramem-local":"dist/cli/index.js"},"dist":{"shasum":"c31e7f6082b0db482614af88fd28d6726bb227e5","tarball":"https://registry.npmjs.org/@astragenie/astramemory-local/-/astramemory-local-0.7.2.tgz","fileCount":493,"integrity":"sha512-3rzKJ4ntqgvFc3kdhZJamnjDmGQHjaLHq/3Izbz9mfmOs8n6fdd8hJtujCydJTJqxzD8oAGHERbgsVKbpTCmDQ==","signatures":[{"sig":"MEYCIQCfhnHjl4OjPr07sr9bntpo8aXI/OKIZKwiyMhJYAVKGQIhAOyZRhrGgithSObiMZ2foh2H10TdHYsB3mrb/iWNJs4b","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1554146},"type":"module","engines":{"node":">=20"},"gitHead":"5756e241eefa5084ceb8328e86ac8bdfb751fc6b","scripts":{"dev":"tsc -w -p .","test":"vitest run","build":"tsc -p .","test:smoke":"vitest run tests/smoke","test:watch":"vitest","prepublishOnly":"bun run build","contracts:validate":"node contracts/validate.mjs"},"_npmUser":{"name":"heroboec","email":"shishkosv@gmail.com"},"_npmVersion":"10.8.2","description":"Local-first memory daemon for AI coding agents — wire-compatible with `memory-plugin` (v0.2.0+).","directories":{},"_nodeVersion":"20.20.2","dependencies":{"zod":"^3.23.0","pino":"^10.3.1","fastify":"^5.9.0","pino-roll":"^4.0.0","sqlite-vec":"^0.1.0","@napi-rs/keyring":"^1.3.0","@inquirer/prompts":"^8.5.2","@modelcontextprotocol/sdk":"^1.29.0","better-sqlite3-multiple-ciphers":"^12.11.1"},"_hasShrinkwrap":false,"devDependencies":{"ajv":"^8.17.1","vitest":"^4.1.9","typescript":"^5.5.0","@types/node":"^20.0.0","ajv-formats":"^3.0.1"},"trustedDependencies":["@napi-rs/keyring","better-sqlite3-multiple-ciphers"],"_npmOperationalInternal":{"tmp":"tmp/astramemory-local_0.7.2_1783251082818_0.745135262260914","host":"s3://npm-registry-packages-npm-production"}},"0.8.0":{"name":"@astragenie/astramemory-local","version":"0.8.0","license":"MIT","_id":"@astragenie/astramemory-local@0.8.0","maintainers":[{"name":"heroboec","email":"shishkosv@gmail.com"}],"bin":{"astramem-local":"dist/cli/index.js"},"dist":{"shasum":"ea6f44930a99473a41d9bc987a1e00904c3a9b6d","tarball":"https://registry.npmjs.org/@astragenie/astramemory-local/-/astramemory-local-0.8.0.tgz","fileCount":506,"integrity":"sha512-v7ANuKCOIba2E/h6lTHXTdgOCZuYzfolyEMWi/eSo3rVT2R8VYVNIxijrfioYHsCdYyGqGGZmbKxvmcAwYHHcg==","signatures":[{"sig":"MEUCIQDCBsBG2aHXeiP8J0f/m57Ad+/q911xuQFwjtRbNvB6MwIgKG53z3DXcJcdy31WgAfh9qRDmbq0deTqpaLi6oI4OW8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1604659},"type":"module","engines":{"node":">=20"},"gitHead":"6e8a5753a66315390dd8d54ff214c9c6ea3908f5","scripts":{"dev":"tsc -w -p .","test":"vitest run","build":"tsc -p .","test:smoke":"vitest run tests/smoke","test:watch":"vitest","prepublishOnly":"bun run build","contracts:validate":"node contracts/validate.mjs"},"_npmUser":{"name":"heroboec","email":"shishkosv@gmail.com"},"_npmVersion":"10.8.2","description":"Local-first memory daemon for AI coding agents — wire-compatible with `memory-plugin` (v0.2.0+).","directories":{},"_nodeVersion":"20.20.2","dependencies":{"zod":"^3.23.0","pino":"^10.3.1","fastify":"^5.9.0","pino-roll":"^4.0.0","sqlite-vec":"^0.1.0","@napi-rs/keyring":"^1.3.0","@inquirer/prompts":"^8.5.2","@modelcontextprotocol/sdk":"^1.29.0","better-sqlite3-multiple-ciphers":"^12.11.1"},"_hasShrinkwrap":false,"devDependencies":{"ajv":"^8.17.1","vitest":"^4.1.9","typescript":"^5.5.0","@types/node":"^20.0.0","ajv-formats":"^3.0.1"},"trustedDependencies":["@napi-rs/keyring","better-sqlite3-multiple-ciphers"],"_npmOperationalInternal":{"tmp":"tmp/astramemory-local_0.8.0_1783342135038_0.0678368541643597","host":"s3://npm-registry-packages-npm-production"}},"0.8.1":{"name":"@astragenie/astramemory-local","version":"0.8.1","type":"module","bin":{"astramem-local":"dist/cli/index.js"},"scripts":{"build":"tsc -p .","dev":"tsc -w -p .","test":"vitest run","test:watch":"vitest","test:smoke":"vitest run tests/smoke","contracts:validate":"node contracts/validate.mjs","prepublishOnly":"bun run build"},"engines":{"node":">=20"},"trustedDependencies":["@napi-rs/keyring","better-sqlite3-multiple-ciphers"],"dependencies":{"@inquirer/prompts":"^8.5.2","@modelcontextprotocol/sdk":"^1.29.0","@napi-rs/keyring":"^1.3.0","better-sqlite3-multiple-ciphers":"^12.11.1","fastify":"^5.9.0","pino":"^10.3.1","pino-roll":"^4.0.0","sqlite-vec":"^0.1.0","zod":"^3.23.0"},"devDependencies":{"@types/node":"^20.0.0","ajv":"^8.17.1","ajv-formats":"^3.0.1","typescript":"^5.5.0","vitest":"^4.1.9"},"license":"MIT","_id":"@astragenie/astramemory-local@0.8.1","gitHead":"fe183272821fee458edb98a4d3354ddaa8383c62","description":"Local-first memory daemon for AI coding agents — wire-compatible with `memory-plugin` (v0.2.0+).","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-IRtbaMWdbH8+uM8n1DYwqtG/6hNzbKpITD3wr5wYfutIBd8IB7XfI8Abap/vC6opAy9xCXjtawzOmVNdmNvajQ==","shasum":"051491192d7766e7c32319b01aa6499ab0951e21","tarball":"https://registry.npmjs.org/@astragenie/astramemory-local/-/astramemory-local-0.8.1.tgz","fileCount":506,"unpackedSize":1608631,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD/NBwyMNUw8+ivm3I/47gluo3Myqz58prwAnBa4boxwQIgPwS0itBF1ko4iNgpDOlfojr3kqkV8TJXMaZq4lv1okM="}]},"_npmUser":{"name":"heroboec","email":"shishkosv@gmail.com"},"directories":{},"maintainers":[{"name":"heroboec","email":"shishkosv@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/astramemory-local_0.8.1_1783345992974_0.5456289388571871"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-05T11:31:22.638Z","modified":"2026-07-06T13:53:13.236Z","0.7.2":"2026-07-05T11:31:22.969Z","0.8.0":"2026-07-06T12:48:55.225Z","0.8.1":"2026-07-06T13:53:13.134Z"},"license":"MIT","description":"Local-first memory daemon for AI coding agents — wire-compatible with `memory-plugin` (v0.2.0+).","maintainers":[{"name":"heroboec","email":"shishkosv@gmail.com"}],"readme":"# AstraMemory Local\n\nLocal-first memory daemon for AI coding agents — wire-compatible with `memory-plugin` (v0.2.0+).\n\n> **v0.2.0 closes the schema gap**  \n> Daemon v0.2.0 now accepts the SaaS-canonical wire envelope: `{event, turns[], wire_version (required), scrub metadata, client_version, captured_at, project_id, cwd}` alongside backward-compatible `{session_id, source, content}`. The daemon and SaaS backend now share a wire contract. See the [astramemory-plugin FEAT 4a spec](https://github.com/astragenie/astramemory-plugin/blob/main/docs/superpowers/specs/2026-06-29-hooks-provider-migration-4a.md) for the unified wire contract and migration timeline.\n\n## Wire compatibility\n\nThe daemon's ingest endpoint now speaks the same wire protocol as the SaaS backend. Both old and new clients work:\n\n**Legacy plugin (v0.1.x):**\n```json\nPOST /ingest/transcript\nContent-Type: application/json\nAuthorization: Bearer <token>\n\n{\n  \"session_id\": \"claude-20260630-abc123\",\n  \"source\": \"precompact\",\n  \"content\": \"[Assistant]: Distilled 3 facts...\"\n}\n```\n\n**SaaS canonical (v0.2.0+):**\n```json\nPOST /ingest/transcript\nContent-Type: application/json\nAuthorization: Bearer <token>\n\n{\n  \"event\": \"pre_compact\",\n  \"session_id\": \"claude-20260630-abc123\",\n  \"turns\": [{\"role\": \"user\", \"content\": \"...\"}, ...],\n  \"wire_version\": \"v1.0\",\n  \"captured_at\": \"2026-06-30T12:34:56Z\",\n  \"client_version\": \"0.5.0\",\n  \"project_id\": \"my-project\",\n  \"cwd\": \"/home/user/src\"\n}\n```\n\nBoth shapes are accepted — no migration needed. See [src/contracts/wire.ts](src/contracts/wire.ts) for the complete schema definition.\n\n## Why it exists\n\nClaude Code sessions compact and terminate, taking context with them. AstraMemory Local captures\nevery session transcript, distills typed memories (decisions, facts, lessons, commands, todos),\nand serves them back via hybrid search (BM25 + vector + importance + freshness). It runs entirely\non your workstation — no cloud account, no data leaves your machine. The plugin's hooks post to\nthe local daemon instead of the SaaS endpoint through a single environment variable swap.\n\n---\n\n## Quick start (5 commands)\n\n```bash\nbun add -g @astragenie/astramemory-local\nastra-memory init\n# follow the wizard — picks Ollama or Azure, writes config.yaml + secrets.env\nastra-memory service install\nexport MEMORY_API_URL=http://127.0.0.1:7777\nexport MEMORY_BEARER=$(astra-memory token print)\n```\n\nRestart Claude Code. All plugin hooks (PreCompact, SessionEnd, SubagentStop) now post to the\nlocal daemon. No other plugin changes needed.\n\n---\n\n## Architecture\n\n```\n memory-plugin hooks\n    |\n    |  POST /ingest/transcript (v0.2.0+ — SaaS-canonical envelope)\n    |  Authorization: Bearer <token>\n    v\n+------------------+      SQLite (memory.sqlite)\n|  HTTP daemon     | ---> +-------------------+\n|  Fastify         |      | sessions          |\n|  127.0.0.1:7777  |      | messages          |\n|  (v0.2.0+)       |      | transcripts       |\n+------------------+      | ingest_idempotency|\n                          | jobs (queue)      |\n                          | memories          |\n                          | memories_fts (FTS5)|\n                          | memories_vec (vec0)|\n                          | budget_spend      |\n                          +-------------------+\n                                   |\n                          in-process worker loop\n                                   |\n                          8-stage distillation\n                          (cleanup -> normalize ->\n                           chunk -> compact ->\n                           extract -> reduce ->\n                           memory-normalize ->\n                           embed + index)\n                                   |\n                    +--------------+--------------+\n                    |              |              |\n              memories        FTS5 index    sqlite-vec\n                (rows)       (BM25 search)  (cosine ANN)\n                    |              |              |\n                    +--------------+--------------+\n                                   |\n                          hybrid score fusion\n                          a*BM25 + b*cosine +\n                          c*importance + d*freshness\n                                   |\n                          GET /search  POST /recall\n                                   |\n                          /recall in plugin slash commands\n```\n\nSingle Node process. Workers run in-process on a polling loop. SQLite is the source of truth.\nEverything derived (vectors, FTS rows, compactions) can be rebuilt by replaying the jobs table.\n\n---\n\n## Memory types\n\n| Type       | Description                                            | Example                                      |\n|------------|--------------------------------------------------------|----------------------------------------------|\n| `decision` | Architectural or design choice made during a session   | \"Use sqlite-vec for v1 vector storage\"       |\n| `fact`     | Objective project fact, configuration detail           | \"Port 7777 is the default daemon port\"       |\n| `lesson`   | Something that went wrong and how it was resolved      | \"sqlite-vec rowid must match memories rowid\" |\n| `command`  | CLI command or script worth remembering                | \"bun run build && bun run test -- migrate\"   |\n| `todo`     | Outstanding work item surfaced in conversation         | \"Add reembed job when provider changes\"      |\n\n---\n\n## Provider matrix\n\n| Concern         | Ollama (local, free)                     | Azure OpenAI (cloud)                         |\n|-----------------|------------------------------------------|----------------------------------------------|\n| LLM compaction  | qwen2.5-coder:7b (default)              | gpt-4.1 or any deployment                   |\n| LLM extraction  | qwen2.5-coder:7b (default)              | gpt-4.1 or any deployment                   |\n| Embedding       | nomic-embed-text-v2-moe (1024-dim)      | text-embedding-3-small (1024 via dimensions) |\n| Cost            | $0 (local inference)                     | ~$0.02/1K tokens + $0.0001/1K embed tokens  |\n| Setup           | `ollama serve` + `ollama pull <model>`   | Azure portal + endpoint + deployment name    |\n\nProviders are configurable independently per stage. Embedding provider is system-wide — switching\nrequires `astra-memory rebuild --reembed` to re-index all memories in the new model's vector space.\n\nSee [docs/providers.md](docs/providers.md) for full setup instructions.\n\n---\n\n## Public endpoints\n\nAll endpoints require Bearer token authentication except `GET /version` and `GET /health`.\n`GET /dashboard` additionally accepts an `HttpOnly` session cookie, bootstrapped via a one-time\n`?token=` visit (see [Dashboard](#dashboard) below).\n\n| Endpoint | Auth | Description |\n|---|---|---|\n| `GET /health` | — | Daemon health probe: `{ ok, version, wire_versions_supported, schema_version }` |\n| `GET /version` | — | Version discovery: `{ name, version, wire_versions_supported, schema_version, ts }` |\n| `POST /ingest/transcript` | Bearer | Capture protocol endpoint — accepts `transcript` and `events` kinds; idempotency via `Idempotency-Key` header. See [docs/capture-protocol.md](docs/capture-protocol.md). |\n| `GET /search` | Bearer | Hybrid search with type/repo/project/since filters |\n| `POST /recall` | Bearer | Top-K semantic recall (alias: `search` with k=5) |\n| `POST /recall/pack` | Bearer | Token-budgeted memory pack for a repo/project/branch — powers the SessionStart hook |\n| `POST /remember` | Bearer | Direct memory insert, bypasses distillation |\n| `GET /memory/:id` | Bearer | Single memory lookup |\n| `GET /memory/:id/why` | Bearer | Provenance receipt — extraction evidence + confidence for a memory |\n| `GET /memory/:id/history` | Bearer | Full `memory_events` log for a memory (invalidate/supersede/promote chain) |\n| `POST /memory/:id/invalidate` | Bearer | Soft-delete a memory (lifecycle op) |\n| `POST /memory/:id/supersede` | Bearer | Replace a memory with a newer one, linked via `memory_events` |\n| `POST /memory/:id/promote` | Bearer | Promote a memory's scope: personal → team → org |\n| `POST /memory/:id/used` | Bearer | Record an explicit recall-usefulness signal for a memory |\n| `GET /sessions/:id/digest` | Bearer | Session summary digest |\n| `GET /dashboard` | Bearer, cookie, or one-time `?token=` bootstrap | Read-only HTML metrics dashboard, auto-refreshing every 5s |\n| `POST /mcp` | Bearer | Model Context Protocol endpoint (auto-discovered tools, see below) |\n\n## MCP tools (Claude Code auto-discovery)\n\nThe daemon exposes a **Model Context Protocol** (Streamable HTTP) endpoint at `POST /mcp`.\nClaude Code discovers and calls the tools below automatically when configured in `.mcp.json`.\n\n| Tool | Description | Maps to |\n|---|---|---|\n| `search_memory` | Hybrid FTS + vector search with optional type/repo/project/since filters | `GET /search` |\n| `recall_memory` | Top-K semantic recall (default k=5) | `POST /recall` |\n| `remember` | Direct memory insert, bypasses distillation | `POST /remember` |\n| `get_health` | Daemon health probe: `{ ok, version, wire_versions_supported, schema_version }` | `GET /health` |\n| `why_memory` | Provenance receipt — extraction evidence + confidence for a memory | `GET /memory/:id/why` |\n| `session_digest` | Session summary digest | `GET /sessions/:id/digest` |\n| `invalidate_memory` | Soft-delete a memory (lifecycle op) | `POST /memory/:id/invalidate` |\n| `supersede_memory` | Replace an old memory with a new one, linked via `memory_events` | `POST /memory/:id/supersede` |\n| `promote_memory` | Promote a memory's scope: personal → team → org | `POST /memory/:id/promote` |\n| `memory_history` | Full `memory_events` log for a memory | `GET /memory/:id/history` |\n| `mark_memory_used` | Explicit recall-usefulness signal — \"this memory mattered\" | `POST /memory/:id/used` |\n\n**Plugin `.mcp.json` wiring:**\n\n```json\n{\n  \"mcpServers\": {\n    \"astramem\": {\n      \"type\": \"http\",\n      \"url\": \"${MEMORY_API_URL}/mcp\",\n      \"headers\": { \"Authorization\": \"Bearer ${MEMORY_BEARER}\" }\n    }\n  }\n}\n```\n\nSet `MEMORY_API_URL=http://127.0.0.1:7777` and `MEMORY_BEARER` to your token\n(printed by `astra-memory token print`).\n\n---\n\n## Budget cap\n\nThe daily LLM spend cap (default: **$10 USD**) is enforced before each LLM call.\n\n- Ollama always reports `$0` cost — the cap only applies to Azure usage.\n- When the cap is reached, pending distillation jobs move to `paused` state. Ingest continues\n  to accept transcripts (no data loss). Distillation resumes the next UTC day automatically.\n- Override: `astra-memory budget --reset` (logged).\n- Check current spend: `astra-memory budget`.\n\n---\n\n## Security\n\n### Encryption at rest\n\n`memory.sqlite` is encrypted by default using `better-sqlite3-multiple-ciphers` (SQLCipher-compatible\ncipher driver). The 32-byte key is resolved through a provider chain:\n\n1. **OS credential store** — Windows Credential Manager / macOS Keychain / Linux libsecret, via\n   `@napi-rs/keyring`.\n2. **Key-file fallback** — `<configDir>/db.key` (mode `0600`) with a WARN log, used only when the\n   credential store throws (e.g. headless Linux with no secret-service session).\n\nA pre-existing plaintext `memory.sqlite` (from a version predating encryption) is **auto-migrated**\ntransparently on daemon startup: the file is checkpointed, re-keyed via `PRAGMA rekey`, and\nverified (row-count match) before the encrypted copy replaces the original. The pre-migration\nplaintext file is preserved at `memory.sqlite.pre-encryption.bak` — nothing is deleted. Migration\nis idempotent; an already-encrypted file is a no-op.\n\nDisabling encryption (`security.encryption.enabled: false`) is a deliberate trust trade-off — the\ndaemon logs a prominent WARN at startup, and `astra-memory doctor` reports\n`encryption: OFF — memory.sqlite is stored in PLAINTEXT`.\n\n### Stage-0 secret redaction\n\nEvery transcript turn and manual `/remember` write passes through a redaction choke point *before*\nit is persisted — downstream pipeline stages only ever see already-redacted text. Detection runs in\nthree passes:\n\n1. **PEM private-key blocks** (multiline, whole block).\n2. **Vendor/pattern detectors** — AWS access keys, GitHub tokens, Azure storage keys/SAS tokens, GCP\n   API keys, Slack tokens, JWTs, generic `key=value` credentials, connection-string userinfo — plus\n   any org-specific regexes from `security.redaction.customPatterns`.\n3. **Shannon-entropy fallback** — flags high-entropy strings (default threshold 4.0 bits/char) that\n   pattern detectors missed.\n\nMatches are replaced with a placeholder — `[REDACTED:<type>:<hash8>]`, where `hash8` is the first 8\nhex chars of `SHA-256(secret value)` — so the same secret always redacts to the same placeholder\n(dedup-safe) while the raw value is **never stored or logged**. Only counts are persisted, in the\n`redaction_log` table (`type`, `count`, `session_id`, `created_at`) — `astra-memory doctor` surfaces\na 7-day breakdown, e.g. `redaction: on — 12 secrets redacted (3 aws_access_key, 9 generic_credential) in last 7d`.\nToggle with `security.redaction.enabled` (default `true`).\n\n### Bearer token storage\n\nThe daemon's Bearer token is stored the same way as the DB encryption key: OS credential store\nfirst, `secrets.env` (mode `0600`) only as a fallback when the credential store is unavailable. A\ntoken found only in `secrets.env` is opportunistically promoted into the credential store the next\ntime the daemon resolves it — `secrets.env` itself is never rewritten or deleted as part of that\npromotion.\n\n---\n\n## Capture protocol\n\n`astramem-local` accepts session capture from any tool that can speak one small HTTP contract —\n`POST /ingest/transcript` with an `astramem-capture@1` envelope. Two kinds are supported:\n\n- **`transcript`** (default) — raw turns, run through the full 8-stage distillation pipeline.\n- **`events`** — pre-typed atom candidates (decision/fact/lesson/command/todo/note/event) that skip\n  the raw-text/LLM stages and enter directly at the reduce stage. Built for sources that already\n  know their own semantics (e.g. `runner-plugin` slice grades and lessons).\n\nBoth kinds pass through the same stage-0 redaction choke point described above. Writing a new tool\nintegration is a small translator — capture at the tool surface, shape into one envelope per session\nboundary, POST it. See [docs/capture-protocol.md](docs/capture-protocol.md) for the full contract,\nfield reference, and a curl example.\n\n---\n\n## Memory lifecycle\n\nEvery memory has an append-only history in the `memory_events` log. Lifecycle operations never\ndelete a row — they append an event and update derived state:\n\n| Operation | Effect |\n|---|---|\n| **Invalidate** | Soft-deletes a memory (optionally with a reason) — it stops surfacing in search/recall. |\n| **Supersede** | Replaces an old memory with a new one; the two are linked via the event log. |\n| **Promote** | Widens a memory's scope: `personal` → `team` → `org` (downward/same-scope transitions are rejected). |\n\n`GET /memory/:id/history` (and the `memory_history` MCP tool) return the full event chain for a\nmemory — the complete invalidate/supersede/promote provenance trail.\n\n**`why_memory` receipts** (`GET /memory/:id/why`, MCP `why_memory`) answer \"why does the daemon\nbelieve this?\" — they return the extraction evidence and confidence that produced the memory, so a\nrecalled fact or decision can be traced back to its source.\n\n---\n\n## Usefulness metric\n\nThe daemon tracks a **recall-usefulness rate**: of the memories served by a search/recall/pack call,\nhow many were later marked as actually used (`POST /memory/:id/used`, MCP `mark_memory_used`, or the\nREST twin). The rate is `distinct atoms used / distinct atoms served` in a given time window,\ncomputed per memory type and per surface (`mcp` / `rest` / `cli`).\n\nThis is a **v1 measure-only signal** — it does not yet feed ranking (see ADR-010). Query text is\nnever stored; only a truncated SHA-256 digest of the query is kept alongside the served/used events,\nthemselves appended to the same `memory_events` log lifecycle operations use.\n\n---\n\n## Dashboard\n\n`GET /dashboard` serves a single-file, auto-refreshing (every 5s) HTML metrics page — no\nJavaScript, no CDN, no external assets, dark mode by default. It shows memory counts by type,\nrecent captures, job-queue state, distill throughput, provider health, today/MTD budget spend vs\ncap, and the pending-capture queue depth.\n\nAuth accepts either the usual `Authorization: Bearer <token>` header, or an `HttpOnly` session\ncookie. To open the dashboard directly in a browser (which can't set an `Authorization` header),\nvisit it once with `?token=<bearer>` — the daemon exchanges that for the cookie via a 302 redirect\nto the clean URL, so the bearer never persists in browser history or gets re-sent by the\n`<meta refresh>` poll. A missing or wrong credential returns a plain-text 401, never HTML, and the\nquery string is stripped from the log line so a wrong `?token=` guess doesn't persist a candidate\nsecret.\n\n---\n\n## Commands reference\n\n| Command                                    | What it does                                       |\n|--------------------------------------------|----------------------------------------------------|\n| `astra-memory init [--no-hook]`            | Interactive wizard — writes config + secrets, runs migrations, installs service, offers the SessionStart memory-pack hook |\n| `astra-memory serve [--port N]`            | Start daemon in foreground (dev/debug)             |\n| `astra-memory service install`             | Register daemon as a user-scope OS service         |\n| `astra-memory service status`              | Show service state                                 |\n| `astra-memory service start`               | Start the service                                  |\n| `astra-memory service stop`                | Stop the service                                   |\n| `astra-memory service uninstall`           | Remove the service unit                            |\n| `astra-memory doctor`                      | Run all health checks, print table                 |\n| `astra-memory doctor --json`               | Machine-readable health check output               |\n| `astra-memory search \"<query>\"`            | Hybrid search, print results table                 |\n| `astra-memory search \"<query>\" --type decision` | Filter by memory type                       |\n| `astra-memory recall \"<question>\"`         | Top-5 semantic recall (alias for search k=5)       |\n| `astra-memory remember \"<text>\" [--type]`  | Direct insert, bypasses distillation pipeline      |\n| `astra-memory queue`                       | Show pending/failed jobs                           |\n| `astra-memory queue --state failed`        | Show only failed jobs                              |\n| `astra-memory rebuild [--reembed]`         | Rebuild derived indexes; --reembed re-vectors all  |\n| `astra-memory providers list`              | List configured providers and their health         |\n| `astra-memory providers test [name]`       | Ping provider, print latency + dim                 |\n| `astra-memory budget`                      | Show today and month spend vs cap                  |\n| `astra-memory budget --reset`              | Clear today's spend counter (override, logged)     |\n| `astra-memory token print`                 | Print the current Bearer token                     |\n| `astra-memory token rotate`                | Generate new token, invalidate the old one         |\n\n---\n\n## Further reading\n\n- [docs/migration-from-saas.md](docs/migration-from-saas.md) — switch the plugin from remote SaaS to local daemon\n- [docs/configuration.md](docs/configuration.md) — full config.yaml reference\n- [docs/providers.md](docs/providers.md) — Ollama and Azure OpenAI setup\n- [docs/capture-protocol.md](docs/capture-protocol.md) — `astramem-capture@1` wire contract (`transcript` + `events` kinds)\n- [docs/hooks/memory-pack.md](docs/hooks/memory-pack.md) — SessionStart memory-pack hook (auto-installed by `init`)\n- [docs/troubleshooting.md](docs/troubleshooting.md) — common issues and fixes\n- [docs/contracts.md](docs/contracts.md) — frozen type interfaces (for contributors)\n- [CHANGELOG.md](CHANGELOG.md) — release history\n\n---\n\n## Development\n\nThis project uses [Bun](https://bun.sh/) as the package manager and script runner.\n\n```bash\nbun install          # install dependencies\nbun run build        # compile TypeScript → dist/\nbun run test         # run the vitest suite\n```\n\n**Publishing** (maintainers only):\n\n```bash\nbun publish          # publishes to GitHub Packages via .npmrc (same token as npm publish)\n```\n\n> **Why Bun?** Faster installs, a single binary, and `bun publish` works natively with the existing\n> `.npmrc` / GH Packages setup. Tests still run through vitest (`bun run test`) because the vitest\n> suite uses `better-sqlite3` native bindings which are not yet compatible with Bun's own test runner.\n\n---\n\n## Status\n\n**v0.1.0** — Waves 1-4 of the implementation plan completed.\n\n- Wave 1: SQLite schema, migration runner, FTS5, sqlite-vec, ingest endpoint, Fastify server, CLI skeleton.\n- Wave 2: Job worker loop, hybrid search, service install adapters, Ollama + Azure providers.\n- Wave 3: 8-stage distillation pipeline, budget tracker, Zod-validated extraction.\n- Wave 4: Install wizard, cross-OS CI matrix, E2E plugin integration test, this documentation.\n\nSpec: [astramemory-plugin/docs/superpowers/specs/2026-06-27-astramemory-local-v1-design.md](../astramemory-plugin/docs/superpowers/specs/2026-06-27-astramemory-local-v1-design.md)\n","readmeFilename":"README.md"}