@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 +51 -47
- package/compose.yaml +24 -3
- package/package.json +11 -5
- package/prisma/migrations/20261001022030_init/migration.sql +119 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +125 -0
- package/src/agents.ts +5 -15
- package/src/cli.ts +239 -180
- package/src/client.ts +79 -51
- package/src/collector.ts +11 -11
- package/src/config.ts +33 -44
- package/src/kingdom.ts +103 -0
- package/src/remote.ts +170 -0
- package/src/schemas.ts +40 -0
- package/src/server.ts +56 -63
- package/src/store.ts +438 -0
- package/src/local.ts +0 -355
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
|
|
8
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
21
|
-
archive
|
|
20
|
+
archive up # creates ~/.local/share/archive/server.token, then docker compose up
|
|
21
|
+
archive down
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
52
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
76
|
+
bun run archive pair --kingdom https://api.your-kingdom.example --name "Work laptop"
|
|
89
77
|
```
|
|
90
78
|
|
|
91
|
-
|
|
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 --
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
-
bun run
|
|
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:-
|
|
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:
|
|
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.
|
|
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.
|
|
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,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
|
-
|
|
43
|
-
|
|
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) =>
|