@inixiative/archive 0.6.0 → 0.8.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,99 +4,99 @@ 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; with `--sync` it also publishes to its destinations every 30 seconds from the same process. 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 keeps tags captured earlier. 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
- Every deployment runs the published image `ghcr.io/inixiative/archive` (tags: version, `latest`, commit SHA), built from main for amd64 and arm64. Give it a persistent volume at `/data`, HTTPS, and a unique `ARCHIVE_SERVER_TOKEN` of at least 32 characters; it needs nothing else and creates its store on first start. Compose (`compose.yaml`, also shipped in the npm package) 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. Pin a version with `ARCHIVE_VERSION`; build from a checkout with `docker compose -f compose.yaml -f compose.build.yaml up -d --build`. The Render blueprint runs the image with a dedicated disk and generated token. Railway deploys the image as a service with a `/data` volume (`railway.json` builds the Dockerfile when deploying from the repository instead).
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` runs `serve --sync`, so the container publishes to the destinations in its own `/data/destinations.json` (token files must live under `/data` too). It is a separate store from the host agents': 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
 
83
69
  ## Connect through Kingdom
84
70
 
85
- Pair a runtime with Kingdom (Foundry: Settings → Kingdom) and expose its `kingdom_runtime_` credential in an environment variable, then either let Kingdom store the archives:
71
+ Kingdom is the permission hub. Your machine's Archive talks directly only to local pieces, such as your local Foundry. It reaches a hosted Archive through Kingdom, by presenting a Signet.
72
+
73
+ First, pair this Archive with Kingdom:
86
74
 
87
75
  ```sh
88
- bun run archive connect --kind kingdom --url https://your-kingdom.example --project-id inixiative --token-env KINGDOM_ARCHIVE_TOKEN
76
+ bun run archive pair --kingdom https://api.your-kingdom.example --name "Work laptop"
89
77
  ```
90
78
 
91
- or forward them through Kingdom to a hosted Archive it has bound for that project:
79
+ `pair` creates a device key in `<home>/kingdom/` and asks Kingdom to pair this Archive, identified by its `sourceId`. It prints a review code and a link. Open the link, choose the owner and the hosted Archives this machine may write to, and approve. The command waits for the approval, then saves the delivered Signet credential (0600) and lists the libraries it may write to.
80
+
81
+ Next, route a project to one of those libraries:
92
82
 
93
83
  ```sh
94
- bun run archive connect --kind kingdom --url https://your-kingdom.example --connection-id inixiative --project-id inixiative --token-env KINGDOM_ARCHIVE_TOKEN
84
+ bun run archive connect --kind kingdom --url https://api.your-kingdom.example --credential-file ~/.archive/kingdom/signet-SIGNET.json --integration-id HOSTED_ARCHIVE_INTEGRATION --resource-id LIBRARY --project-id inixiative
95
85
  ```
96
86
 
97
- 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.
87
+ `connect` checks that the Signet grants `sessions.write` on that library before it saves anything. `serve --sync` then sends each changed session through `POST /api/v1/access/execute`. Every call carries the Signet's access token and a fresh DPoP proof (`@inixiative/signet` renews the token).
88
+
89
+ Kingdom enforces the grant on every write:
90
+
91
+ - It accepts only snapshots whose `sourceId` is this Archive's.
92
+ - It records the paired integration on the session's actor.
93
+ - It resolves a revision conflict from this source by writing over the hosted head. Older captures are still refused.
94
+
95
+ Each write is usage on the Signet. Revoke the Signet, or pause the local Archive integration in Kingdom, to stop it. `search --remote` uses `documents.search` when the Signet grants it.
98
96
 
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.
97
+ Kingdom keeps no archive store. It forwards each write to the hosted Archive server, whose own token stays in Kingdom as an encrypted credential.
98
+
99
+ Foundry writes its captures to the local Archive server through `@inixiative/archive/remote`.
100
100
 
101
101
  ## Routing
102
102
 
@@ -145,11 +145,15 @@ Filters are `projectId`, `source`, `tag`, `actorId`, `model`, `effort` and `refe
145
145
 
146
146
  ## Development
147
147
 
148
+ 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:
149
+
148
150
  ```sh
149
- bun test
150
- bun run typecheck
151
+ archive up
152
+ bun run check
151
153
  ```
152
154
 
155
+ Schema changes go in `prisma/schema.prisma` with a migration (`bunx prisma migrate dev --name <change>` against a development database).
156
+
153
157
  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.
154
158
 
155
159
  ### ChatGPT conversation text
package/compose.yaml CHANGED
@@ -1,16 +1,37 @@
1
+ # Ports are the archive block of @inixiative/config ports: HTTP 4700, Postgres 6132.
1
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
2
19
  archive:
3
20
  image: ghcr.io/inixiative/archive:${ARCHIVE_VERSION:-latest}
4
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
5
25
  ports:
6
- - "127.0.0.1:${ARCHIVE_PORT:-4411}:4411"
26
+ - "127.0.0.1:${ARCHIVE_PORT:-4700}:4700"
7
27
  environment:
8
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
9
30
  volumes:
10
- - ${ARCHIVE_DATA_DIR:-./data}:/data
31
+ - ${ARCHIVE_DATA_DIR:-./data}/config:/data
11
32
  restart: unless-stopped
12
33
  healthcheck:
13
- test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:4411/health').then(r=>{if(!r.ok)process.exit(1)})"]
34
+ test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:4700/health').then(r=>{if(!r.ok)process.exit(1)})"]
14
35
  interval: 30s
15
36
  timeout: 5s
16
37
  retries: 3
package/package.json CHANGED
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "name": "@inixiative/archive",
3
- "version": "0.6.0",
3
+ "version": "0.8.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,14 @@
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
23
  "src",
24
+ "prisma",
22
25
  "compose.yaml"
23
26
  ],
24
27
  "scripts": {
@@ -29,15 +32,18 @@
29
32
  "check": "bun run typecheck && bun run lint && bun run test",
30
33
  "prepublishOnly": "bun run check",
31
34
  "lint": "biome check .",
32
- "prepare": "lefthook install"
35
+ "prepare": "if git rev-parse --git-dir >/dev/null 2>&1; then lefthook install; fi && prisma generate"
33
36
  },
34
37
  "dependencies": {
38
+ "@inixiative/signet": "^0.1.0",
39
+ "@prisma/client": "^6.6.0",
35
40
  "js-tiktoken": "1.0.21",
41
+ "prisma": "^6.6.0",
36
42
  "zod": "^4.3.6"
37
43
  },
38
44
  "devDependencies": {
39
45
  "@biomejs/biome": "2.5.2",
40
- "@inixiative/config": "^0.5.42",
46
+ "@inixiative/config": "^0.5.53",
41
47
  "@types/bun": "1.4.2",
42
48
  "lefthook": "2.1.9",
43
49
  "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) =>