@inixiative/archive 0.5.1 → 0.7.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/README.md CHANGED
@@ -4,79 +4,65 @@ MIT-licensed, local-first session storage for Claude Code, Codex and Foundry. Ru
4
4
 
5
5
  ## What runs today
6
6
 
7
- - Public transcript extraction, exact-project collection, immutable SQLite revisions, local lexical search and lossless text chunks.
8
- - A standalone authenticated HTTP server, Docker image, persistent Compose deployment, Railway configuration and a Render blueprint.
7
+ - Public transcript extraction, exact-project collection, immutable revisions in Postgres (pgvector and pg_trgm enabled), lexical search and lossless text chunks.
8
+ - An authenticated HTTP server, published image, Compose deployment with its own Postgres, Railway configuration and a Render blueprint.
9
+ - An HTTP client the CLI, collectors and Foundry write through; only the server holds the database.
9
10
  - Multiple explicit destinations, durable publication receipts/outbox, conflict detection and retry after interruption.
10
- - Native Foundry capture and context retrieval; Kingdom's existing Archives browser, ownership and sharing APIs.
11
11
 
12
- This is an initial pilot, not a general multi-user standalone hosting service. Each standalone deployment is one ownership boundary with one access token. Kingdom supplies account-based sharing. Use distinct storage and credentials for personal and organization archives.
12
+ Each deployment is one ownership boundary with one access token. Kingdom supplies account-based sharing. Use distinct deployments and credentials for personal and organization archives.
13
13
 
14
14
  ## Local setup
15
15
 
16
- Install Bun 1.4.2 or later, then install the published CLI:
16
+ Run one Archive per machine with Docker Compose. It runs the published image with its own Postgres, on the `archive` block of the shared port registry (`bunx @inixiative/config ports archive`): HTTP on loopback 4700, Postgres on loopback 6132.
17
17
 
18
18
  ```sh
19
19
  bun add --global @inixiative/archive
20
- archive init
21
- archive serve
20
+ archive up # creates ~/.local/share/archive/server.token, then docker compose up
21
+ archive down
22
22
  ```
23
23
 
24
- For development from source:
24
+ `up` runs the `compose.yaml` that ships in the package (project `archive`) with `ARCHIVE_DATA_DIR` set to the archive home and the server token from its `server.token`. Postgres data lives in `$ARCHIVE_DATA_DIR/postgres` and the server's destinations in `$ARCHIVE_DATA_DIR/config`, so both survive rebuilds and volume prunes. Pin a version with `ARCHIVE_VERSION`; build from a checkout with `docker compose -f compose.yaml -f compose.build.yaml up -d --build`.
25
25
 
26
- ```sh
27
- bun install
28
- bun run archive init
29
- bun run archive serve
30
- ```
31
-
32
- `init` creates a local database and a private `server.token` under `~/.local/share/archive`. It prints the token's file path, never its value. `serve` binds to loopback port 4411. The server requires bearer authentication for data endpoints; `/health` has no session data.
26
+ The server applies its schema migrations on start and requires bearer authentication for data endpoints; `/health` has no session data. Every other command talks to it over HTTP at `--url` (default `http://127.0.0.1:4700`) with the token in `--token-file` (default `~/.local/share/archive/server.token`) or `ARCHIVE_TOKEN`:
33
27
 
34
28
  ```sh
35
- bun run archive preview --directory /path/to/history --source codex
36
- bun run archive import --file /path/to/session.jsonl --source codex --project-id inixiative --tag coding
37
- bun run archive collect --directory /path/to/history --source codex --project-root /exact/session/cwd --project-id inixiative --watch
38
- bun run archive list
39
- bun run archive search --query 'migration'
29
+ archive preview --directory /path/to/history --source codex
30
+ archive import --file /path/to/session.jsonl --source codex --project-id inixiative --tag coding
31
+ archive collect --directory /path/to/history --source codex --project-root /exact/session/cwd --project-id inixiative --watch
32
+ archive list
33
+ archive search --query 'migration'
40
34
  ```
41
35
 
42
- Use `--source claude-code` for Claude histories. Collection matches the exact working-directory metadata recorded by the provider. Repeat `--project-root` to map several checkouts to one project; `--worktrees` also accepts every current git worktree of each root, re-read on each scan. Nested checkouts are separate mappings. Unknown directories, symlinks and sessions without usable metadata are skipped. Incomplete or changing files remain at source and retry on the next scan. The initial collector rescans files every 30 seconds; it deduplicates normalized content rather than maintaining byte-offset ingestion cursors. It retains prior manually assigned tags. It does not upload: run sync separately.
36
+ Use `--source claude-code` for Claude histories. Collection matches the exact working-directory metadata recorded by the provider. Repeat `--project-root` to map several checkouts to one project; `--worktrees` also accepts every current git worktree of each root, re-read on each scan. Nested checkouts are separate mappings. Unknown directories, symlinks and sessions without usable metadata are skipped. Incomplete or changing files remain at source and retry on the next scan. The collector rescans files every 30 seconds; it deduplicates normalized content rather than maintaining byte-offset ingestion cursors. A watching collector waits out a server that is still starting.
37
+
38
+ To run the server without Docker, point `DATABASE_URL` at a Postgres with the `vector` and `pg_trgm` extensions available and run `archive serve [--port 4700] [--sync]`. Set `ARCHIVE_DEBUG=1` to see the underlying error when a command fails.
43
39
 
44
40
  ## Connect to BYO hosting
45
41
 
46
- Deploy the included Dockerfile with a persistent volume mounted at `/data`, HTTPS, and a unique `ARCHIVE_SERVER_TOKEN` of at least 32 characters. Compose bind-mounts `./data` (or `ARCHIVE_DATA_DIR`) at `/data`, so the store survives rebuilds, `down -v` and volume prunes, and binds only to loopback on port 4411 (or `ARCHIVE_PORT`); put an HTTPS reverse proxy in front for remote use. Railway needs a `/data` volume. The Render blueprint provisions a dedicated disk and generated token.
42
+ Every deployment runs the published image `ghcr.io/inixiative/archive` (tags: version, `latest`, commit SHA), built from main for amd64 and arm64, with `DATABASE_URL` pointing at Postgres 15 or later with pgvector, HTTPS in front, and a unique `ARCHIVE_SERVER_TOKEN` of at least 32 characters. It needs nothing else. The Render blueprint provisions the database, the image and a generated token together. On Railway, add a pgvector Postgres service and the image as a service with `DATABASE_URL` referencing it (`railway.json` builds the Dockerfile when deploying from the repository instead).
47
43
 
48
- Place the destination's token in a private file (`chmod 600`, owned by you) or an environment variable, then run:
44
+ The local Archive publishes onward to destinations. Place the destination's token in a private file under the server's config directory (`chmod 600`, owned by you) or an environment variable, then run:
49
45
 
50
46
  ```sh
51
- bun run archive connect --url https://your-archive.example --project-id inixiative --token-file ~/.local/share/archive/credentials/inixiative.token
52
- bun run archive routes
53
- bun run archive sync
54
- bun run archive sync --watch
55
- bun run archive search --remote --query 'migration'
47
+ archive connect --home ~/.local/share/archive/config --url https://your-archive.example --project-id inixiative --token-file ~/.local/share/archive/config/credentials/inixiative.token
48
+ archive search --remote --home ~/.local/share/archive/config --query 'migration'
56
49
  ```
57
50
 
58
- `setup` is an alias for `connect`. Setup verifies access before saving configuration and does not upload. The URL locates the server; the token grants access. Configuration stores only the token file path (`tokenFile`) or environment variable name (`--token-env`, `tokenEnv`), never the token. A token file is read on every request, so rotating it needs no restart; symlinks and files readable by group or others are refused. An environment variable must be set in the sync process environment. Redirects are refused; only HTTPS or loopback HTTP is allowed.
59
-
60
- `--home PATH`, `--store FILE` and `--config FILE` support custom paths. Keep the database with its source UUID when moving machines. Back up the entire database; a new store creates a new source identity.
51
+ `setup` is an alias for `connect`. Setup verifies access before saving `destinations.json` and does not upload; the Compose server (`serve --sync`) reads it and publishes every 30 seconds. `sync` and `routes` run the same publication, or preview it, once against `DATABASE_URL`. The URL locates the server; the token grants access. Configuration stores only the token file path (`tokenFile`) or environment variable name (`--token-env`, `tokenEnv`), never the token. A token file is read on every request, so rotating it needs no restart; symlinks and files readable by group or others are refused. Redirects are refused; only HTTPS or loopback HTTP is allowed.
61
52
 
62
53
  ## Always-on local archiving
63
54
 
64
- `archive agents` declares long-running agents in `~/.local/share/archive/agents.json` and installs them as launchd agents (macOS) or systemd user units (Linux):
55
+ `archive agents` declares collectors in `~/.local/share/archive/agents.json` and installs them as launchd agents (macOS) or systemd user units (Linux). They run on the host, where the session histories are, and write to the local Archive server:
65
56
 
66
57
  ```sh
67
- archive init
68
- archive connect --url https://your-archive.example --project-id inixiative --token-file ~/.local/share/archive/credentials/inixiative.token
69
58
  archive agents add-collector --name claude-code.inixiative --source claude-code --project-id inixiative --project-root ~/code/inixiative --worktrees
70
59
  archive agents add-collector --name codex.inixiative --source codex --project-id inixiative --project-root ~/code/inixiative --worktrees
71
- archive agents serve on
72
- archive agents sync on
60
+ archive agents server --url http://127.0.0.1:4700 # only when not the default
73
61
  archive agents install
74
62
  archive agents status
75
63
  ```
76
64
 
77
- `add-collector` upserts by `--name` (lowercase letters, digits, `.` and `-`); `--directory` defaults to `~/.claude/projects` or `~/.codex/sessions`, and `--project-root`, `--worktrees` and `--atlas` behave as for `collect`. `remove-collector --name N`, `serve on|off [--port P]` and `sync on|off` edit the same file. `install` writes one unit per agent: `com.inixiative.archive.local` (serve), `com.inixiative.archive.sync` (`sync --watch`) and `com.inixiative.archive.collect.<name>`. Each runs the current Bun with this package's own `src/cli.ts`, restarts on exit (30 second throttle), works in the archive home and logs to `logs/<label>.{out,err}.log` there. Re-running `install` reloads only changed or stopped units and removes Archive units no longer declared; other launchd agents are untouched. `uninstall` removes every Archive unit; `status` reports each unit's load state, PID and last log entry. One archive home per user account: units are named by agent, not by home. Sync reads destination token files itself, so no wrapper script is needed. Re-run `install` after upgrading the package if its install path changes.
78
-
79
- Docker Compose is the alternative for the server: `docker compose up -d` bind-mounts `./data` (or `ARCHIVE_DATA_DIR`) at `/data` and binds loopback port 4411. It is a separate store, so connect it as a destination and let the `sync` agent publish to it; do not bind-mount the store the host agents are writing, since SQLite locking across the Docker VM boundary is unreliable. Collectors run on the host, where the session histories are.
65
+ `add-collector` upserts by `--name` (lowercase letters, digits, `.` and `-`); `--directory` defaults to `~/.claude/projects` or `~/.codex/sessions`, and `--project-root`, `--worktrees` and `--atlas` behave as for `collect`. `remove-collector --name N` and `server --url URL` edit the same file. `install` writes one unit per collector, `com.inixiative.archive.collect.<name>`. Each runs the current Bun with this package's own `src/cli.ts`, restarts on exit (30 second throttle), works in the archive home and logs to `logs/<label>.{out,err}.log` there. Re-running `install` reloads only changed or stopped units and removes Archive units no longer declared (including the retired `local` and `sync` units); other launchd agents are untouched. `uninstall` removes every Archive unit; `status` reports each unit's load state, PID and last log entry. Re-run `install` after upgrading the package if its install path changes.
80
66
 
81
67
  `@inixiative/archive/agents` exports the pure pieces for integrations: `agentsConfigSchema`, `agentUnits`, `renderPlist`, `renderSystemdUnit` and `planAgents`, plus `installAgents`, `uninstallAgents` and `agentStatus` with an injectable command runner and supervisor directory.
82
68
 
@@ -96,19 +82,43 @@ bun run archive connect --kind kingdom --url https://your-kingdom.example --conn
96
82
 
97
83
  Kingdom takes the owner from the runtime credential; `--owner-model`, `--organization-id` and `--space-id` narrow it to an organization or space that owner manages. A forwarding connection accepts only its bound `projectId`, and the hosted Archive's own token stays in Kingdom's server environment. A Kingdom runtime credential is distinct from a standalone Archive token. Kingdom retains responsibility for memberships, shares, revocation and hosted browsing.
98
84
 
99
- Foundry exposes the same commands through `bun run archive`. Its defaults remain `.foundry/archives/archives.sqlite` and `.foundry/archives.json`. Its durable journal capture automatically publishes matching projects once destination credentials are in the viewer's environment. Restart the viewer after changing destination configuration.
85
+ Foundry writes its captures to the local Archive server through `@inixiative/archive/remote`.
86
+
87
+ ## Routing
88
+
89
+ Only explicit `projectId` matches authorize publication. An archive with no matching destination stays local. Multiple matches create deliberate separate copies. `routes` previews these destinations before upload. Tags, references and actors never change ownership or routing.
100
90
 
101
- ## Routing, tagging and Jev
91
+ ## Tags, references, actors and retention
102
92
 
103
- Only explicit `projectId` matches authorize publication. An archive with no matching destination stays local. Multiple matches create deliberate separate copies. `routes` previews these destinations before upload.
93
+ The archive owns its tag system. It keeps two different things apart:
94
+
95
+ - **Tags** are concepts: the `atlas:<concept>` of repository files a session's tool calls touched (collectors with `--atlas`, from `atlas graph --json`), collector `--tag`s, and tags people add. Heuristic categories are offered as `suggestedTags`, computed on read and never stored.
96
+ - **References** point at items in an integration. Collectors record the session's GitHub repository and branch (`owner/repo`, `owner/repo/tree/branch`); linked GitHub pull requests, issues and commits (`owner/repo#24`, `owner/repo@sha`) and Linear issues (`KEY-12`) are found in the text and counted. A link is a mention, not proof of work. The archive's settings list the integrations it references: GitHub and Linear are recognized natively, and any other integration names the link prefix of its items (for example `https://acme.atlassian.net/browse/`); the item is what follows. Prefixes are literal, so settings cannot make matching slow.
104
97
 
105
98
  ```sh
106
- bun run archive tag --id ARCHIVE_ID --tag reviewed
99
+ bun run archive tag --id ARCHIVE_ID --tag reviewed --untag debugging
107
100
  ```
108
101
 
109
- Collectors add explicit tags from recorded session metadata: `repo:owner/name` and `branch:name` from git context, and with `--atlas` the `atlas:<concept>` of repository files the session's tool calls touched (from `atlas graph --json` in the session's checkout). Suggestions are computed on read and never stored: heuristic categories, plus `reference` suggestions for linked GitHub pull requests, issues and commits (`github:owner/repo#24`, `github:owner/repo@sha`) and Linear issues (`linear:KEY-12`). A link is a mention, not proof of work, so references stay suggestions.
102
+ Tag edits apply to that archive without a new revision, survive re-capture, and do not sync. Offered tags are defined archive-wide or for one actor. Each session can name its **actor**: a `user`, a `service` such as an API-key run, or a `job` (its id is the job name), with the `integrationId` it ran under when there is one. An organization's archive mostly holds its members' work, filterable per person. Entries the model produced record its `model` and `effort` (from Claude Code assistant records and Codex turn context, per turn); listings summarize them per session as `models` and filter by `model` and `effort`.
103
+
104
+ The **retention** setting (`retentionDays`, off by default) deletes archives whose latest capture is older than that, at server start and hourly. It deletes only from the archive it is set on; deletions never sync.
105
+
106
+ ### HTTP API
107
+
108
+ All data endpoints are `POST /api/v1/archive/<action>` with a JSON body and `Authorization: Bearer <token>`:
110
109
 
111
- Tags and suggestions do not change ownership or routing. Jev integration is not enabled: the next step is labeled, shadow-mode tag/destination proposals, scored for accuracy and cross-organization mistakes before any automatic action. Session content must not be sent to Jev without selecting that service for the relevant ownership boundary.
110
+ | Action | Body | Returns |
111
+ |---|---|---|
112
+ | `ingest` | `snapshot`, `previousDigest` | id, digest, revision |
113
+ | `read` | `archiveId`, `revision?` | snapshot and chunks |
114
+ | `list` | filters, `limit?`, `beforeId?` | metadata, `nextCursor` |
115
+ | `search` | filters, `query`, `budget?`, `limit?`, `beforeId?` | matching chunks within the token budget |
116
+ | `tag` | `archiveId`, `add?`, `remove?` | tags |
117
+ | `delete` | `archiveId` | deleted |
118
+ | `settings/read`, `settings/update` | `integrations?`, `retentionDays?` | settings |
119
+ | `tags/list`, `tags/define`, `tags/remove` | `tag`, `actorId?`, `description?` | offered tags with usage counts |
120
+
121
+ Filters are `projectId`, `source`, `tag`, `actorId`, `model`, `effort` and `reference` (`{integration, ref}`). Jev integration is not enabled. Session content must not be sent to Jev without selecting that service for the relevant ownership boundary.
112
122
 
113
123
  ## Limits
114
124
 
@@ -121,11 +131,15 @@ Tags and suggestions do not change ownership or routing. Jev integration is not
121
131
 
122
132
  ## Development
123
133
 
134
+ Tests create their own databases (`archive_test_*`) in the machine's Archive Postgres, so run `archive up` (or `docker compose up -d postgres`) first, or point `ARCHIVE_TEST_DATABASE_URL` at another pgvector Postgres:
135
+
124
136
  ```sh
125
- bun test
126
- bun run typecheck
137
+ archive up
138
+ bun run check
127
139
  ```
128
140
 
141
+ Schema changes go in `prisma/schema.prisma` with a migration (`bunx prisma migrate dev --name <change>` against a development database).
142
+
129
143
  See [provenance](docs/PROVENANCE.md), [license](LICENSE), and [goals](tickets/README.md). The historical tickets describe earlier scope; this implementation now includes explicit provider collection and authenticated standalone hosting.
130
144
 
131
145
  ### ChatGPT conversation text
package/compose.yaml ADDED
@@ -0,0 +1,37 @@
1
+ # Ports are the archive block of @inixiative/config ports: HTTP 4700, Postgres 6132.
2
+ services:
3
+ postgres:
4
+ image: pgvector/pgvector:pg17
5
+ environment:
6
+ POSTGRES_USER: archive
7
+ POSTGRES_PASSWORD: ${ARCHIVE_POSTGRES_PASSWORD:-archive}
8
+ POSTGRES_DB: archive
9
+ ports:
10
+ - "127.0.0.1:${ARCHIVE_POSTGRES_PORT:-6132}:5432"
11
+ volumes:
12
+ - ${ARCHIVE_DATA_DIR:-./data}/postgres:/var/lib/postgresql/data
13
+ restart: unless-stopped
14
+ healthcheck:
15
+ test: ["CMD", "pg_isready", "-U", "archive"]
16
+ interval: 5s
17
+ timeout: 5s
18
+ retries: 20
19
+ archive:
20
+ image: ghcr.io/inixiative/archive:${ARCHIVE_VERSION:-latest}
21
+ command: ["bun", "run", "src/cli.ts", "serve", "--home", "/data", "--hostname", "0.0.0.0", "--sync"]
22
+ depends_on:
23
+ postgres:
24
+ condition: service_healthy
25
+ ports:
26
+ - "127.0.0.1:${ARCHIVE_PORT:-4700}:4700"
27
+ environment:
28
+ ARCHIVE_SERVER_TOKEN: ${ARCHIVE_SERVER_TOKEN:?Set a unique random token of at least 32 characters}
29
+ DATABASE_URL: postgresql://archive:${ARCHIVE_POSTGRES_PASSWORD:-archive}@postgres:5432/archive
30
+ volumes:
31
+ - ${ARCHIVE_DATA_DIR:-./data}/config:/data
32
+ restart: unless-stopped
33
+ healthcheck:
34
+ test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:4700/health').then(r=>{if(!r.ok)process.exit(1)})"]
35
+ interval: 30s
36
+ timeout: 5s
37
+ retries: 3
package/package.json CHANGED
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "name": "@inixiative/archive",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
7
- "./local": "./src/local.ts",
8
7
  "./import": "./src/import.ts",
9
8
  "./client": "./src/client.ts",
10
9
  "./config": "./src/config.ts",
@@ -15,10 +14,15 @@
15
14
  "./collector": "./src/collector.ts",
16
15
  "./chatgpt": "./src/chatgpt.ts",
17
16
  "./tags": "./src/tags.ts",
18
- "./agents": "./src/agents.ts"
17
+ "./agents": "./src/agents.ts",
18
+ "./store": "./src/store.ts",
19
+ "./remote": "./src/remote.ts",
20
+ "./schemas": "./src/schemas.ts"
19
21
  },
20
22
  "files": [
21
- "src"
23
+ "src",
24
+ "prisma",
25
+ "compose.yaml"
22
26
  ],
23
27
  "scripts": {
24
28
  "test": "bun test tests",
@@ -28,15 +32,17 @@
28
32
  "check": "bun run typecheck && bun run lint && bun run test",
29
33
  "prepublishOnly": "bun run check",
30
34
  "lint": "biome check .",
31
- "prepare": "lefthook install"
35
+ "prepare": "lefthook install && prisma generate"
32
36
  },
33
37
  "dependencies": {
38
+ "@prisma/client": "^6.6.0",
34
39
  "js-tiktoken": "1.0.21",
40
+ "prisma": "^6.6.0",
35
41
  "zod": "^4.3.6"
36
42
  },
37
43
  "devDependencies": {
38
44
  "@biomejs/biome": "2.5.2",
39
- "@inixiative/config": "^0.5.39",
45
+ "@inixiative/config": "^0.5.44",
40
46
  "@types/bun": "1.4.2",
41
47
  "lefthook": "2.1.9",
42
48
  "typescript": "6.0.3"
@@ -0,0 +1,119 @@
1
+ -- CreateExtension
2
+ CREATE EXTENSION IF NOT EXISTS "pg_trgm";
3
+
4
+ -- CreateExtension
5
+ CREATE EXTENSION IF NOT EXISTS "vector";
6
+
7
+ -- CreateTable
8
+ CREATE TABLE "settings" (
9
+ "key" TEXT NOT NULL,
10
+ "value" JSONB NOT NULL,
11
+
12
+ CONSTRAINT "settings_pkey" PRIMARY KEY ("key")
13
+ );
14
+
15
+ -- CreateTable
16
+ CREATE TABLE "archives" (
17
+ "id" TEXT NOT NULL,
18
+ "seq" BIGSERIAL NOT NULL,
19
+ "digest" TEXT NOT NULL,
20
+ "revision" INTEGER NOT NULL,
21
+ "source" TEXT NOT NULL,
22
+ "projectId" TEXT,
23
+ "actorId" TEXT,
24
+ "capturedAt" DOUBLE PRECISION NOT NULL,
25
+ "summary" JSONB NOT NULL,
26
+
27
+ CONSTRAINT "archives_pkey" PRIMARY KEY ("id")
28
+ );
29
+
30
+ -- CreateTable
31
+ CREATE TABLE "revisions" (
32
+ "archiveId" TEXT NOT NULL,
33
+ "revision" INTEGER NOT NULL,
34
+ "digest" TEXT NOT NULL,
35
+ "snapshot" JSONB NOT NULL,
36
+
37
+ CONSTRAINT "revisions_pkey" PRIMARY KEY ("archiveId","revision")
38
+ );
39
+
40
+ -- CreateTable
41
+ CREATE TABLE "chunks" (
42
+ "archiveId" TEXT NOT NULL,
43
+ "revision" INTEGER NOT NULL,
44
+ "position" INTEGER NOT NULL,
45
+ "id" TEXT NOT NULL,
46
+ "entryId" TEXT NOT NULL,
47
+ "sourceRef" TEXT NOT NULL,
48
+ "kind" TEXT NOT NULL,
49
+ "start" INTEGER NOT NULL,
50
+ "end" INTEGER NOT NULL,
51
+ "text" TEXT NOT NULL,
52
+ "tokenCount" INTEGER NOT NULL,
53
+ "embedding" vector(1536),
54
+
55
+ CONSTRAINT "chunks_pkey" PRIMARY KEY ("archiveId","revision","position")
56
+ );
57
+
58
+ -- CreateTable
59
+ CREATE TABLE "tag_edits" (
60
+ "archiveId" TEXT NOT NULL,
61
+ "tag" TEXT NOT NULL,
62
+ "added" BOOLEAN NOT NULL,
63
+
64
+ CONSTRAINT "tag_edits_pkey" PRIMARY KEY ("archiveId","tag")
65
+ );
66
+
67
+ -- CreateTable
68
+ CREATE TABLE "tag_definitions" (
69
+ "actorId" TEXT NOT NULL,
70
+ "tag" TEXT NOT NULL,
71
+ "description" TEXT,
72
+
73
+ CONSTRAINT "tag_definitions_pkey" PRIMARY KEY ("actorId","tag")
74
+ );
75
+
76
+ -- CreateTable
77
+ CREATE TABLE "receipts" (
78
+ "archiveId" TEXT NOT NULL,
79
+ "destination" TEXT NOT NULL,
80
+ "digest" TEXT NOT NULL,
81
+
82
+ CONSTRAINT "receipts_pkey" PRIMARY KEY ("archiveId","destination")
83
+ );
84
+
85
+ -- CreateTable
86
+ CREATE TABLE "outbox" (
87
+ "archiveId" TEXT NOT NULL,
88
+ "destination" TEXT NOT NULL,
89
+ "revision" INTEGER NOT NULL,
90
+
91
+ CONSTRAINT "outbox_pkey" PRIMARY KEY ("archiveId","destination")
92
+ );
93
+
94
+ -- CreateIndex
95
+ CREATE UNIQUE INDEX "archives_seq_key" ON "archives"("seq");
96
+
97
+ -- CreateIndex
98
+ CREATE INDEX "archives_projectId_idx" ON "archives"("projectId");
99
+
100
+ -- CreateIndex
101
+ CREATE INDEX "archives_actorId_idx" ON "archives"("actorId");
102
+
103
+ -- CreateIndex
104
+ CREATE INDEX "chunks_text_idx" ON "chunks" USING GIN ("text" gin_trgm_ops);
105
+
106
+ -- AddForeignKey
107
+ ALTER TABLE "revisions" ADD CONSTRAINT "revisions_archiveId_fkey" FOREIGN KEY ("archiveId") REFERENCES "archives"("id") ON DELETE CASCADE ON UPDATE CASCADE;
108
+
109
+ -- AddForeignKey
110
+ ALTER TABLE "chunks" ADD CONSTRAINT "chunks_archiveId_revision_fkey" FOREIGN KEY ("archiveId", "revision") REFERENCES "revisions"("archiveId", "revision") ON DELETE CASCADE ON UPDATE CASCADE;
111
+
112
+ -- AddForeignKey
113
+ ALTER TABLE "tag_edits" ADD CONSTRAINT "tag_edits_archiveId_fkey" FOREIGN KEY ("archiveId") REFERENCES "archives"("id") ON DELETE CASCADE ON UPDATE CASCADE;
114
+
115
+ -- AddForeignKey
116
+ ALTER TABLE "receipts" ADD CONSTRAINT "receipts_archiveId_fkey" FOREIGN KEY ("archiveId") REFERENCES "archives"("id") ON DELETE CASCADE ON UPDATE CASCADE;
117
+
118
+ -- AddForeignKey
119
+ ALTER TABLE "outbox" ADD CONSTRAINT "outbox_archiveId_fkey" FOREIGN KEY ("archiveId") REFERENCES "archives"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,3 @@
1
+ # Please do not edit this file manually
2
+ # It should be added in your version-control system (e.g., Git)
3
+ provider = "postgresql"
@@ -0,0 +1,125 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ previewFeatures = ["postgresqlExtensions"]
4
+ }
5
+
6
+ datasource db {
7
+ provider = "postgresql"
8
+ url = env("DATABASE_URL")
9
+ extensions = [vector, pg_trgm]
10
+ }
11
+
12
+ /// Archive-wide values: the source identity, and the settings (integrations, retention).
13
+ model Setting {
14
+ key String @id
15
+ value Json
16
+
17
+ @@map("settings")
18
+ }
19
+
20
+ /// One session. `summary` is its listing (snapshot metadata, tags as captured, models,
21
+ /// references, suggestions), recomputed on capture and when settings change.
22
+ model Archive {
23
+ id String @id
24
+ /// Insertion order: listings are newest first.
25
+ seq BigInt @unique @default(autoincrement())
26
+ digest String
27
+ revision Int
28
+ source String
29
+ projectId String?
30
+ actorId String?
31
+ capturedAt Float
32
+ summary Json
33
+
34
+ revisions Revision[]
35
+ tagEdits TagEdit[]
36
+ receipts Receipt[]
37
+ outbox Outbox[]
38
+
39
+ @@index([projectId])
40
+ @@index([actorId])
41
+ @@map("archives")
42
+ }
43
+
44
+ /// An immutable revision of a session.
45
+ model Revision {
46
+ archiveId String
47
+ revision Int
48
+ digest String
49
+ snapshot Json
50
+
51
+ archive Archive @relation(fields: [archiveId], references: [id], onDelete: Cascade)
52
+ chunks Chunk[]
53
+
54
+ @@id([archiveId, revision])
55
+ @@map("revisions")
56
+ }
57
+
58
+ /// Lossless text chunks of a revision, in order, for search.
59
+ model Chunk {
60
+ archiveId String
61
+ revision Int
62
+ position Int
63
+ id String
64
+ entryId String
65
+ sourceRef String
66
+ kind String
67
+ start Int
68
+ end Int
69
+ text String
70
+ tokenCount Int
71
+ /// Reserved for semantic search; nothing writes it yet.
72
+ embedding Unsupported("vector(1536)")?
73
+
74
+ revisionRef Revision @relation(fields: [archiveId, revision], references: [archiveId, revision], onDelete: Cascade)
75
+
76
+ @@id([archiveId, revision, position])
77
+ @@index([text(ops: raw("gin_trgm_ops"))], type: Gin)
78
+ @@map("chunks")
79
+ }
80
+
81
+ /// A tag added (added = true) or removed from what the session was captured with.
82
+ model TagEdit {
83
+ archiveId String
84
+ tag String
85
+ added Boolean
86
+
87
+ archive Archive @relation(fields: [archiveId], references: [id], onDelete: Cascade)
88
+
89
+ @@id([archiveId, tag])
90
+ @@map("tag_edits")
91
+ }
92
+
93
+ /// A tag this archive offers: archive-wide (actorId "") or for one actor.
94
+ model TagDefinition {
95
+ actorId String
96
+ tag String
97
+ description String?
98
+
99
+ @@id([actorId, tag])
100
+ @@map("tag_definitions")
101
+ }
102
+
103
+ /// The digest a destination last acknowledged.
104
+ model Receipt {
105
+ archiveId String
106
+ destination String
107
+ digest String
108
+
109
+ archive Archive @relation(fields: [archiveId], references: [id], onDelete: Cascade)
110
+
111
+ @@id([archiveId, destination])
112
+ @@map("receipts")
113
+ }
114
+
115
+ /// A revision queued for a destination.
116
+ model Outbox {
117
+ archiveId String
118
+ destination String
119
+ revision Int
120
+
121
+ archive Archive @relation(fields: [archiveId], references: [id], onDelete: Cascade)
122
+
123
+ @@id([archiveId, destination])
124
+ @@map("outbox")
125
+ }
package/src/agents.ts CHANGED
@@ -39,8 +39,8 @@ export const collectorAgentSchema = z.strictObject({
39
39
  });
40
40
  export const agentsConfigSchema = z
41
41
  .strictObject({
42
- serve: z.strictObject({ port: z.number().int().min(1).max(65535).optional() }).optional(),
43
- sync: z.boolean().optional(),
42
+ /** The Archive server collectors write to; the token is the home's `server.token`. */
43
+ server: z.strictObject({ url: z.url() }).optional(),
44
44
  collectors: z.array(collectorAgentSchema).max(100),
45
45
  })
46
46
  .refine(
@@ -82,9 +82,10 @@ export interface AgentUnitOptions {
82
82
  }
83
83
  export const packageCliPath = () => fileURLToPath(new URL('./cli.ts', import.meta.url));
84
84
 
85
- export function collectorArgs(c: CollectorAgent) {
85
+ export function collectorArgs(c: CollectorAgent, server?: { url: string }) {
86
86
  return [
87
87
  'collect',
88
+ ...(server ? ['--url', server.url] : []),
88
89
  '--directory',
89
90
  c.directory,
90
91
  '--source',
@@ -113,18 +114,7 @@ export function agentUnits(config: AgentsConfig, options: AgentUnitOptions): Age
113
114
  stderr: join(home, 'logs', `${label}.err.log`),
114
115
  };
115
116
  };
116
- return [
117
- ...(config.serve
118
- ? [
119
- unit('local', [
120
- 'serve',
121
- ...(config.serve.port ? ['--port', String(config.serve.port)] : []),
122
- ]),
123
- ]
124
- : []),
125
- ...(config.sync ? [unit('sync', ['sync', '--watch'])] : []),
126
- ...config.collectors.map((c) => unit(`collect.${c.name}`, collectorArgs(c))),
127
- ];
117
+ return config.collectors.map((c) => unit(`collect.${c.name}`, collectorArgs(c, config.server)));
128
118
  }
129
119
 
130
120
  const xml = (value: string) =>