@ghostpaw/affinity 0.4.1 → 0.6.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 +345 -113
- package/SKILL.md +101 -260
- package/dist/cli.js +4928 -1505
- package/dist/index.cjs +2955 -753
- package/dist/index.d.cts +874 -95
- package/dist/index.d.ts +874 -95
- package/dist/index.js +2955 -753
- package/dist/mcp-server.js +3664 -1235
- package/dist/mcp.cjs +3270 -1067
- package/dist/mcp.d.cts +3 -0
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +3272 -1069
- package/package.json +1 -1
- package/skills/affinity/SKILL.md +101 -260
- package/skills/affinity/references/identify-and-locate-contacts.md +9 -0
- package/skills/affinity/references/import-history-without-faking-evidence.md +25 -0
- package/skills/affinity/references/manage-promises-and-agreements.md +8 -0
- package/skills/affinity/references/manage-recurring-dates-and-reminders.md +8 -0
- package/skills/affinity/references/record-direct-evidence-well.md +18 -0
- package/skills/affinity/references/record-observations-and-referrals.md +10 -0
- package/skills/affinity/references/review-radar-progression-and-graph.md +13 -0
- package/skills/affinity/references/tools.md +489 -0
package/README.md
CHANGED
|
@@ -6,117 +6,111 @@
|
|
|
6
6
|
|
|
7
7
|
**Agent contacts: people, promises and yearly dates, with a radar for who is drifting.**
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
A local social CRM: contacts, identities, relationships, journal events,
|
|
10
|
+
commitments and recurring dates in one SQLite file. One self-contained binary
|
|
11
|
+
per platform, an MCP server, and a zero-dependency TypeScript library.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## Install
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
one coherent model instead of separate systems. It ships as a single prebundled
|
|
18
|
-
blob with zero runtime dependencies, designed for two audiences at once: human
|
|
19
|
-
developers working directly in code, and LLM agents operating through a
|
|
20
|
-
structured `soul` / `tools` / `skills` runtime.
|
|
15
|
+
Each line downloads the latest release for one platform, checks it against
|
|
16
|
+
`SHA256SUMS`, puts `ghostpaw-affinity` on your `PATH` and prints the version.
|
|
21
17
|
|
|
22
|
-
|
|
18
|
+
**Linux x64**
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
```sh
|
|
21
|
+
cd "$(mktemp -d)" && T=linux-x64 && V=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/GhostPawJS/affinity/releases/latest | sed 's|.*/v||') && F=ghostpaw-affinity-$V-$T.tar.gz && U=https://github.com/GhostPawJS/affinity/releases/download/v$V && curl -fsSL -O "$U/$F" -O "$U/SHA256SUMS" && grep " $F\$" SHA256SUMS | sha256sum -c - && tar -xzf "$F" ghostpaw-affinity && mkdir -p ~/.local/bin && mv ghostpaw-affinity ~/.local/bin/ && { case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.profile; export PATH="$HOME/.local/bin:$PATH";; esac; } && ghostpaw-affinity --version
|
|
22
|
+
```
|
|
27
23
|
|
|
28
|
-
**
|
|
24
|
+
**Linux arm64**
|
|
29
25
|
|
|
30
|
-
```
|
|
31
|
-
|
|
26
|
+
```sh
|
|
27
|
+
cd "$(mktemp -d)" && T=linux-arm64 && V=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/GhostPawJS/affinity/releases/latest | sed 's|.*/v||') && F=ghostpaw-affinity-$V-$T.tar.gz && U=https://github.com/GhostPawJS/affinity/releases/download/v$V && curl -fsSL -O "$U/$F" -O "$U/SHA256SUMS" && grep " $F\$" SHA256SUMS | sha256sum -c - && tar -xzf "$F" ghostpaw-affinity && mkdir -p ~/.local/bin && mv ghostpaw-affinity ~/.local/bin/ && { case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.profile; export PATH="$HOME/.local/bin:$PATH";; esac; } && ghostpaw-affinity --version
|
|
32
28
|
```
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
**macOS Apple silicon**
|
|
35
31
|
|
|
36
|
-
```
|
|
37
|
-
/
|
|
38
|
-
/plugin install ghostpaw-affinity@ghostpaw-affinity
|
|
32
|
+
```sh
|
|
33
|
+
cd "$(mktemp -d)" && T=darwin-arm64 && V=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/GhostPawJS/affinity/releases/latest | sed 's|.*/v||') && F=ghostpaw-affinity-$V-$T.tar.gz && U=https://github.com/GhostPawJS/affinity/releases/download/v$V && curl -fsSL -O "$U/$F" -O "$U/SHA256SUMS" && grep " $F\$" SHA256SUMS | shasum -a 256 -c - && tar -xzf "$F" ghostpaw-affinity && sudo mkdir -p /usr/local/bin && sudo mv ghostpaw-affinity /usr/local/bin/ && ghostpaw-affinity --version
|
|
39
34
|
```
|
|
40
35
|
|
|
41
|
-
**
|
|
36
|
+
**macOS Intel**
|
|
42
37
|
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
command = "npx"
|
|
46
|
-
args = ["-y", "@ghostpaw/affinity", "mcp"]
|
|
38
|
+
```sh
|
|
39
|
+
cd "$(mktemp -d)" && T=darwin-x64 && V=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/GhostPawJS/affinity/releases/latest | sed 's|.*/v||') && F=ghostpaw-affinity-$V-$T.tar.gz && U=https://github.com/GhostPawJS/affinity/releases/download/v$V && curl -fsSL -O "$U/$F" -O "$U/SHA256SUMS" && grep " $F\$" SHA256SUMS | shasum -a 256 -c - && tar -xzf "$F" ghostpaw-affinity && sudo mkdir -p /usr/local/bin && sudo mv ghostpaw-affinity /usr/local/bin/ && ghostpaw-affinity --version
|
|
47
40
|
```
|
|
48
41
|
|
|
49
|
-
**
|
|
50
|
-
or a project `.mcp.json`:
|
|
42
|
+
**Windows x64 (PowerShell)**
|
|
51
43
|
|
|
52
|
-
```
|
|
53
|
-
{
|
|
54
|
-
"mcpServers": {
|
|
55
|
-
"ghostpaw-affinity": {
|
|
56
|
-
"command": "npx",
|
|
57
|
-
"args": ["-y", "@ghostpaw/affinity", "mcp"]
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
44
|
+
```powershell
|
|
45
|
+
$r='GhostPawJS/affinity'; $v=(irm "https://api.github.com/repos/$r/releases/latest").tag_name; $f="ghostpaw-affinity-$($v.TrimStart('v'))-windows-x64.zip"; $u="https://github.com/$r/releases/download/$v"; $t=(New-Item -ItemType Directory (Join-Path $env:TEMP ([guid]::NewGuid()))).FullName; iwr "$u/$f" -OutFile "$t\$f" -UseBasicParsing; iwr "$u/SHA256SUMS" -OutFile "$t\SHA256SUMS" -UseBasicParsing; $want=((Get-Content "$t\SHA256SUMS" | Where-Object { $_ -like "* $f" }) -split '\s+')[0]; if (-not $want -or (Get-FileHash "$t\$f" -Algorithm SHA256).Hash -ne $want) { throw "checksum mismatch for $f" }; $d=Join-Path $env:LOCALAPPDATA 'Programs\ghostpaw\bin'; Expand-Archive "$t\$f" $t -Force; New-Item -ItemType Directory -Force $d | Out-Null; Copy-Item "$t\ghostpaw-affinity.exe" $d -Force; $p=[Environment]::GetEnvironmentVariable('Path','User'); if (";$p;" -notlike "*;$d;*") { [Environment]::SetEnvironmentVariable('Path',"$p;$d",'User') }; $env:Path="$d;$env:Path"; ghostpaw-affinity --version
|
|
61
46
|
```
|
|
62
47
|
|
|
63
|
-
|
|
64
|
-
[latest GitHub release](https://github.com/GhostPawJS/affinity/releases/latest).
|
|
65
|
-
A Dockerfile is included for container hosts:
|
|
66
|
-
`docker build -t ghostpaw-affinity . && docker run -i --rm -v affinity:/data ghostpaw-affinity`.
|
|
67
|
-
|
|
68
|
-
The server speaks MCP 2026-07-28 (stateless, `server/discover`) and the
|
|
69
|
-
initialize-based revisions 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05.
|
|
48
|
+
These commands install the latest release in `~/.local/bin` on Linux, `/usr/local/bin` on macOS (asks for sudo), or `%LOCALAPPDATA%\Programs\ghostpaw\bin` on Windows; Linux adds it to PATH via `~/.profile` only if missing, and Windows adds it to the user PATH (open a new terminal afterwards). To pin a version, set `V=0.6.0` instead of resolving latest (`$v='v0.6.0'` in PowerShell).
|
|
70
49
|
|
|
71
|
-
|
|
50
|
+
For a pinned Linux/macOS download to the current directory with no profile or PATH edits, choose the matching `T` (`linux-x64`, `linux-arm64`, `darwin-arm64`, or `darwin-x64`):
|
|
72
51
|
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
52
|
+
```sh
|
|
53
|
+
V=0.6.0
|
|
54
|
+
T=linux-x64
|
|
55
|
+
F=ghostpaw-affinity-$V-$T.tar.gz
|
|
56
|
+
U=https://github.com/GhostPawJS/affinity/releases/download/v$V
|
|
57
|
+
curl -fsSL -O "$U/$F" -O "$U/SHA256SUMS" &&
|
|
58
|
+
{ case "$T" in darwin-*) grep " $F\$" SHA256SUMS | shasum -a 256 -c - ;; *) grep " $F\$" SHA256SUMS | sha256sum -c - ;; esac; } &&
|
|
59
|
+
tar -xzf "$F" ghostpaw-affinity && ./ghostpaw-affinity --version
|
|
77
60
|
```
|
|
78
61
|
|
|
79
|
-
|
|
80
|
-
`ghostpaw-affinity mcp` starts the stdio server.
|
|
62
|
+
Then:
|
|
81
63
|
|
|
82
|
-
|
|
64
|
+
```sh
|
|
65
|
+
ghostpaw-affinity contact create '{"name":"Sam","kind":"human"}'
|
|
66
|
+
ghostpaw-affinity search sam
|
|
67
|
+
ghostpaw-affinity skill # cheat sheet and tool reference
|
|
68
|
+
```
|
|
83
69
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
70
|
+
Contacts live in `~/.ghostpaw/affinity.db` unless `--db <path>` or the
|
|
71
|
+
`GHOSTPAW_AFFINITY_DB` environment variable says otherwise; relative paths
|
|
72
|
+
resolve against the current directory.
|
|
87
73
|
|
|
88
|
-
|
|
89
|
-
npx -y @ghostpaw/affinity skill > SKILL.md
|
|
90
|
-
```
|
|
74
|
+
A session with the linux-x64 binary against a fresh database:
|
|
91
75
|
|
|
92
|
-
|
|
76
|
+

|
|
77
|
+
|
|
78
|
+
The same session as an [animated GIF](https://raw.githubusercontent.com/GhostPawJS/affinity/main/docs/assets/cli.gif).
|
|
93
79
|
|
|
80
|
+
### npx / npm (for npm-based environments)
|
|
94
81
|
|
|
95
|
-
The [npm package](https://www.npmjs.com/package/@ghostpaw/affinity)
|
|
82
|
+
The [npm package](https://www.npmjs.com/package/@ghostpaw/affinity) needs Node 24+
|
|
83
|
+
and has zero runtime dependencies:
|
|
96
84
|
|
|
97
85
|
```sh
|
|
98
|
-
npm install -g @ghostpaw/affinity
|
|
99
86
|
npx -y @ghostpaw/affinity --help
|
|
87
|
+
npm install -g @ghostpaw/affinity # provides ghostpaw-affinity
|
|
100
88
|
```
|
|
101
89
|
|
|
102
|
-
|
|
103
|
-
for linux-x64, linux-arm64, darwin-arm64, darwin-x64 and windows-x64.
|
|
90
|
+
### Library (for harnesses)
|
|
104
91
|
|
|
105
92
|
```sh
|
|
106
|
-
|
|
93
|
+
npm install @ghostpaw/affinity
|
|
107
94
|
```
|
|
108
95
|
|
|
109
|
-
```
|
|
110
|
-
|
|
96
|
+
```ts
|
|
97
|
+
import { DatabaseSync } from "node:sqlite";
|
|
98
|
+
import { initAffinityTables, read, write, tools } from "@ghostpaw/affinity";
|
|
111
99
|
```
|
|
112
100
|
|
|
113
|
-
|
|
101
|
+
See [Quick Start](#quick-start) and [docs/LLM.md](docs/LLM.md) for the tool facade.
|
|
114
102
|
|
|
115
|
-
|
|
103
|
+
### Other installers and verification
|
|
116
104
|
|
|
117
|
-
|
|
105
|
+
Interactive installers (also verify `SHA256SUMS`):
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
curl -fsSL https://raw.githubusercontent.com/GhostPawJS/affinity/main/install.sh | sh
|
|
109
|
+
```
|
|
118
110
|
|
|
119
|
-
|
|
111
|
+
```powershell
|
|
112
|
+
irm https://raw.githubusercontent.com/GhostPawJS/affinity/main/install.ps1 | iex
|
|
113
|
+
```
|
|
120
114
|
|
|
121
115
|
```sh
|
|
122
116
|
mise use -g github:GhostPawJS/affinity
|
|
@@ -155,8 +149,8 @@ packages:
|
|
|
155
149
|
- name: ghostpaw-affinity
|
|
156
150
|
```
|
|
157
151
|
|
|
158
|
-
|
|
159
|
-
[build attestation](https://cli.github.com/manual/gh_attestation_verify):
|
|
152
|
+
`SHA256SUMS` covers every archive and `server.mcpb`. To verify a downloaded
|
|
153
|
+
archive's [build attestation](https://cli.github.com/manual/gh_attestation_verify):
|
|
160
154
|
|
|
161
155
|
```sh
|
|
162
156
|
gh attestation verify <archive> -R GhostPawJS/affinity
|
|
@@ -169,80 +163,229 @@ download on macOS, remove quarantine with
|
|
|
169
163
|
Linux binaries require glibc 2.28+; use npx on Alpine.
|
|
170
164
|
See the [binary build workflow](.github/workflows/release-binaries.yml).
|
|
171
165
|
|
|
172
|
-
|
|
166
|
+
Each archive is roughly 40-46 MB (the binary embeds the Node runtime, 105-150 MB
|
|
167
|
+
unpacked); the npm package is about 2 MB unpacked. Every release, including the
|
|
168
|
+
one-liners above, is installed and run on ubuntu-24.04, macos-15 and
|
|
169
|
+
windows-2025 by the [install check](.github/workflows/install-check.yml).
|
|
173
170
|
|
|
174
|
-
|
|
175
|
-
|---|---|---|
|
|
176
|
-
| linux-x64 | 41.7 MB tar.gz | 117.6 MB |
|
|
177
|
-
| linux-arm64 | 41.2 MB tar.gz | 115.3 MB |
|
|
178
|
-
| darwin-arm64 | 44.9 MB tar.gz | 146.8 MB |
|
|
179
|
-
| darwin-x64 | 46.4 MB tar.gz | 149.7 MB |
|
|
180
|
-
| windows-x64 | 38.4 MB zip | 105.7 MB |
|
|
171
|
+
## MCP server
|
|
181
172
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
[install check](.github/workflows/install-check.yml).
|
|
173
|
+
Every client below starts the same stdio server: `ghostpaw-affinity mcp`.
|
|
174
|
+
Without the binary, use `npx -y @ghostpaw/affinity mcp` as the command instead.
|
|
185
175
|
|
|
186
|
-
|
|
176
|
+
**Claude Code**
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
claude mcp add ghostpaw-affinity -- ghostpaw-affinity mcp
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
or as a plugin, which also installs the skill:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
/plugin marketplace add GhostPawJS/affinity
|
|
186
|
+
/plugin install ghostpaw-affinity@ghostpaw-affinity
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Codex CLI** (`~/.codex/config.toml`)
|
|
190
|
+
|
|
191
|
+
```toml
|
|
192
|
+
[mcp_servers.ghostpaw-affinity]
|
|
193
|
+
command = "ghostpaw-affinity"
|
|
194
|
+
args = ["mcp"]
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Cursor** (`.cursor/mcp.json`), **Claude Desktop** (`claude_desktop_config.json`)
|
|
198
|
+
or a project `.mcp.json`:
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"mcpServers": {
|
|
203
|
+
"ghostpaw-affinity": {
|
|
204
|
+
"command": "ghostpaw-affinity",
|
|
205
|
+
"args": ["mcp"]
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Claude Desktop users can also install `server.mcpb` from the
|
|
212
|
+
[latest GitHub release](https://github.com/GhostPawJS/affinity/releases/latest).
|
|
213
|
+
A Dockerfile is included for container hosts:
|
|
214
|
+
`docker build -t ghostpaw-affinity . && docker run -i --rm -v affinity:/data ghostpaw-affinity`.
|
|
215
|
+
|
|
216
|
+
The server speaks MCP 2026-07-28 (stateless, `server/discover`) and the
|
|
217
|
+
initialize-based revisions 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05.
|
|
218
|
+
|
|
219
|
+
### Agent Skill
|
|
220
|
+
|
|
221
|
+
`SKILL.md` (repo root) and `skills/affinity/SKILL.md` follow the
|
|
222
|
+
[Agent Skills](https://agentskills.io) format and ship in the npm package.
|
|
223
|
+
Copy `skills/affinity/` into your agent's skills folder, or print it:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
ghostpaw-affinity skill > SKILL.md
|
|
227
|
+
ghostpaw-affinity skill --list # workflow guides
|
|
228
|
+
ghostpaw-affinity skill bootstrap-and-cold-start
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Events
|
|
232
|
+
|
|
233
|
+
Record events and commitments with an optional `externalRef: {source, id}`.
|
|
234
|
+
Both values are trimmed and non-empty; source is lowercased. The pair is unique
|
|
235
|
+
across the database. Repeated intake returns the original event and `no_op`,
|
|
236
|
+
without creating rows or relationship effects.
|
|
237
|
+
|
|
238
|
+
`content` preserves text exactly, including whitespace, empty strings and NUL,
|
|
239
|
+
up to 1 MB (1,048,576 UTF-8 bytes). Supply a short `summary`, or omit it to derive
|
|
240
|
+
one from the first non-empty content line (at most 140 characters; empty text
|
|
241
|
+
becomes `(no text)`). Unknown event fields and moment kinds are validation errors.
|
|
242
|
+
Journal/timeline rows omit content by default; use `{includeContent: true}` in
|
|
243
|
+
library/tool options or CLI `--with-content`. Content appears once in review JSON.
|
|
244
|
+
|
|
245
|
+
Events support a free-text `channel` (trimmed, lowercased, at most 40 characters),
|
|
246
|
+
`parentRef`, `parentEventId`, and a root `threadRef`. Journal, timeline, moments,
|
|
247
|
+
and open commitment reviews accept a channel filter. Set event attributes with
|
|
248
|
+
`write.setAttribute(db, {kind: "event", eventId}, name, value)`; unset and replace
|
|
249
|
+
use the same target and validation as contact/link attributes.
|
|
250
|
+
|
|
251
|
+
Read exact events with `read.getEventById(db, eventId)` or
|
|
252
|
+
`read.getEventByExternalRef(db, source, id)`. `read.searchEvents` searches folded
|
|
253
|
+
summary, content, reference and channel text. The search tool's `scope` is
|
|
254
|
+
`contacts` by default, with additive `events` and `all` modes.
|
|
255
|
+
`read.getEventThread(db, source, id)` returns a root and its descendants in time
|
|
256
|
+
order, including parents imported after their children.
|
|
257
|
+
|
|
258
|
+
`write.reviseEvent(db, eventIdOrTarget, patch)` accepts summary, content,
|
|
259
|
+
occurredAt, significance, channel, type within the same event family, and an
|
|
260
|
+
externalRef only when absent. `write.deleteEvent(db, eventIdOrTarget,
|
|
261
|
+
{confirm: true})` removes evidence, participants, attributes and link effects,
|
|
262
|
+
then replays surviving effects and refreshes rollups. Targets can also be
|
|
263
|
+
`{ref: {source, id}}`. Existing 0.5.x databases migrate through
|
|
264
|
+
`initAffinityTables`. Historical affinity/trust/rank baselines are recovered
|
|
265
|
+
from saved deltas; historical cadence and state before evidence were not stored
|
|
266
|
+
and use the default cadence and active state during replay.
|
|
187
267
|
|
|
188
268
|
## CLI
|
|
189
269
|
|
|
270
|
+
```sh
|
|
271
|
+
export GHOSTPAW_AFFINITY_DB=~/crm/affinity.db # or --db <path>
|
|
272
|
+
ghostpaw-affinity contact create '{"name":"Fox","kind":"human","bootstrapOwner":true}'
|
|
273
|
+
ghostpaw-affinity search "jana" # names, identities, tags, attributes, roles, events
|
|
274
|
+
ghostpaw-affinity contact create '{"name":"Jana Petersen","kind":"human"}'
|
|
275
|
+
ghostpaw-affinity identity add --contact '{"contactId":2}' --input '{"type":"email","value":"jana@example.com"}'
|
|
276
|
+
ghostpaw-affinity event record --kind interaction --input '{"type":"conversation","occurredAt":"today","summary":"Call re pilot","significance":6,"participants":[{"contactId":2,"role":"recipient","directionality":"mutual"}]}'
|
|
277
|
+
ghostpaw-affinity commitment record --input '{"commitmentType":"promise","occurredAt":"today","dueAt":"+3d","summary":"Send proposal","significance":6,"participants":[{"contactId":2,"role":"recipient"}]}'
|
|
278
|
+
ghostpaw-affinity attribute set --target '{"kind":"contact","contact":{"contactId":2}}' --name tag:gastro --value null
|
|
279
|
+
ghostpaw-affinity review contacts.list --tag gastro
|
|
280
|
+
ghostpaw-affinity review contacts.list --attribute pipeline_stage=proposal
|
|
281
|
+
ghostpaw-affinity review commitments.open # overdue promises are flagged
|
|
282
|
+
ghostpaw-affinity review links.radar # who is drifting, with reasons
|
|
283
|
+
ghostpaw-affinity show contact_profile --contact '{"contactId":2}'
|
|
284
|
+
ghostpaw-affinity contact export --contact '{"contactId":2}' # omit --contact for the whole database
|
|
285
|
+
ghostpaw-affinity contact delete --contact '{"contactId":2}' --confirm
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`ghostpaw-affinity skill` prints the full cheat sheet; `skill --list` and
|
|
289
|
+
`skill <guide>` print the workflow guides.
|
|
290
|
+
|
|
190
291
|
```text
|
|
191
|
-
ghostpaw-affinity search <query>
|
|
192
|
-
ghostpaw-affinity review <view>
|
|
292
|
+
ghostpaw-affinity search <query> [--tag T] [--attribute name[=value]]
|
|
293
|
+
ghostpaw-affinity review <view> [--tag T] [--attribute name[=value]]
|
|
193
294
|
ghostpaw-affinity show <owner_profile|contact_profile|link> [--contact JSON --link JSON]
|
|
194
|
-
ghostpaw-affinity contact create
|
|
195
|
-
ghostpaw-affinity contact revise <contact-json> --patch JSON
|
|
196
|
-
ghostpaw-affinity contact lifecycle <contact-json> <lifecycleState>
|
|
197
|
-
ghostpaw-affinity contact merge [flags]
|
|
295
|
+
ghostpaw-affinity contact create|revise|lifecycle|export|delete|merge
|
|
198
296
|
ghostpaw-affinity identity add|revise|verify|remove [flags]
|
|
199
297
|
ghostpaw-affinity relationship add|bond|state|tie|untie [flags]
|
|
200
298
|
ghostpaw-affinity commitment record|resolve [flags]
|
|
201
299
|
ghostpaw-affinity attribute set|unset|replace [flags]
|
|
202
300
|
ghostpaw-affinity date add|revise|remove [flags]
|
|
203
301
|
ghostpaw-affinity event record [flags]
|
|
302
|
+
ghostpaw-affinity show event --event-id 12
|
|
303
|
+
ghostpaw-affinity show event --ref-source bluesky --ref-id at://record
|
|
304
|
+
ghostpaw-affinity search --scope events "text or record id"
|
|
305
|
+
ghostpaw-affinity review events.contact_journal --contact '{"contactId":2}' --with-content --channel bluesky
|
|
306
|
+
ghostpaw-affinity review events.thread --ref-source bluesky --ref-id at://root
|
|
307
|
+
ghostpaw-affinity event revise --event-id 12 --patch '{"summary":"Corrected"}'
|
|
308
|
+
ghostpaw-affinity event delete --event-id 12 --confirm
|
|
204
309
|
ghostpaw-affinity duplicate dismiss|undismiss [flags]
|
|
205
310
|
ghostpaw-affinity mcp|skill|tools|describe|call|completions|version|help
|
|
206
311
|
```
|
|
207
312
|
|
|
208
|
-
There are
|
|
313
|
+
There are 29 domain commands. `review` accepts the existing view names,
|
|
209
314
|
including `contacts.list`, `contacts.duplicates` and `dates.upcoming`.
|
|
210
315
|
`show` selects an inspection kind; contact and link locators are JSON objects.
|
|
211
|
-
Use `help contact` or `contact create --help` for schema-derived help
|
|
212
|
-
Nested payloads use JSON or `@path`
|
|
316
|
+
Use `help contact` or `contact create --help` for schema-derived help; a
|
|
317
|
+
mistyped command suggests the closest one. Nested payloads use JSON or `@path`
|
|
318
|
+
to read a file; repeated array flags accept JSON objects
|
|
319
|
+
(`--entries '{"name":"a","value":"x"}'`) or one JSON array.
|
|
320
|
+
|
|
321
|
+
Every time field accepts epoch milliseconds, ISO 8601 with `Z` or an offset,
|
|
322
|
+
`YYYY-MM-DD`, `DD.MM.YYYY`, `today`, `tomorrow`, `yesterday` and signed day offsets
|
|
323
|
+
such as `+3d` or `-2d`, with the same grammar in CLI, MCP and direct tool calls.
|
|
324
|
+
Date-only values preserve their calendar day: timestamps use UTC midnight, or
|
|
325
|
+
23:59:59.999Z for `dueAt`. Keywords first resolve against the process local
|
|
326
|
+
calendar date; this is the only time-zone-dependent parsing step. `occurredOn`
|
|
327
|
+
and `dueOn` retain the input day (the written offset day for ISO inputs; UTC day
|
|
328
|
+
for epoch inputs). Existing rows derive these fields from their UTC timestamps.
|
|
329
|
+
Review rows for dates include `occursOn`, the anchor occurrence day; the library
|
|
330
|
+
keeps its numeric `occursOn` sorting key and adds `occursOnDate`.
|
|
331
|
+
The existing `*Iso` fields remain UTC. Overdue commitments compare the local
|
|
332
|
+
date to `dueOn`. Future events carry a `future_event` warning and do not count
|
|
333
|
+
as contact until their timestamp and day arrive. Commitments and their
|
|
334
|
+
resolutions do not reset recency or cadence. Radar marks `drift` after a gap
|
|
335
|
+
exceeds 1.5 times the expected cadence and explains its reasons in the snippet.
|
|
213
336
|
|
|
214
337
|
Global flags: `--db <path>`, `--json`, `--quiet`, `--compact`,
|
|
215
338
|
`--help`/`-h`, `--version`/`-V`/`-v`, `--no-color`.
|
|
216
|
-
`NO_COLOR` is accepted
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
339
|
+
`NO_COLOR` is accepted. In a terminal, commands print a readable summary with
|
|
340
|
+
names and dates. With `--json` or piped stdout they print the tool result as
|
|
341
|
+
JSON with ISO date siblings and floats rounded to three decimals. Journal review rows
|
|
342
|
+
keep one summary and compact `{id,name,role}` participants. Radar rows keep one link
|
|
343
|
+
id, counterparty name, reasons and rounded scores; upcoming link dates omit radar
|
|
344
|
+
scores. Default piped writes and `--compact` keep primary and changed ids plus
|
|
345
|
+
warnings; explicit `--json` retains full mutation detail. `--compact`
|
|
346
|
+
removes indentation and empty receipt arrays. `--quiet` suppresses stdout.
|
|
347
|
+
Writes list what changed in `data.changes`, and creates warn about likely
|
|
348
|
+
duplicates. Kebab-case flags map to schema properties. `--` ends flags.
|
|
349
|
+
A positional `-`, or piped stdin when no other arguments are given, reads a
|
|
350
|
+
JSON object (`echo '{"name":"Sam","kind":"human"}' | ghostpaw-affinity contact create`).
|
|
351
|
+
Explicit flags override stdin fields.
|
|
223
352
|
Database selection uses `--db`, `GHOSTPAW_AFFINITY_DB`, then
|
|
224
|
-
`~/.ghostpaw/affinity.db
|
|
353
|
+
`~/.ghostpaw/affinity.db`; relative paths resolve against the current
|
|
354
|
+
directory, and `--help` shows the resolved path. A new database is reported on
|
|
355
|
+
stderr. These commands work offline.
|
|
225
356
|
|
|
226
|
-
| Exit |
|
|
357
|
+
| Exit | Result |
|
|
227
358
|
|---|---|
|
|
228
359
|
| 0 | Success, including no_op |
|
|
229
|
-
| 1 |
|
|
230
|
-
| 2 | Usage or
|
|
360
|
+
| 1 | Other tool error |
|
|
361
|
+
| 2 | Usage, schema or invalid input |
|
|
231
362
|
| 3 | Clarification needed or not found |
|
|
232
|
-
| 4 | Conflict or version mismatch |
|
|
363
|
+
| 4 | Conflict, already resolved or version mismatch |
|
|
233
364
|
| 70 | Internal error |
|
|
234
365
|
|
|
235
366
|
Errors go to stderr. With `--json`, errors also print to stdout.
|
|
236
|
-
|
|
237
|
-
|
|
367
|
+
`--version` names the variant: `affinity 0.5.0 (binary, linux-x64)`,
|
|
368
|
+
`(npm)` or `(library)`. Use `--version --short` in scripts for exact semver
|
|
369
|
+
matching. `--version --json` returns `name`, `version`, `variant` and `target`
|
|
370
|
+
(the target is null outside a binary). `version --short` and `version --json`
|
|
371
|
+
are equivalent.
|
|
372
|
+
|
|
373
|
+
Read commands (search, show, review and contact export, including direct tool
|
|
374
|
+
calls) exit 3 on a missing database without creating files:
|
|
375
|
+
`Database not found: <path> (it is created by the first write)`.
|
|
376
|
+
The first write creates the database and reports `created <path>` on stderr.
|
|
377
|
+
The MCP server continues to create its database on startup.
|
|
378
|
+
|
|
379
|
+
String-valued flags such as `--value Hamburg`, `--bond 'Warm lead'` and
|
|
380
|
+
`--reason 'Different people'` accept plain strings or JSON strings/null.
|
|
381
|
+
Object and array flags still require JSON. Tag filters accept both
|
|
382
|
+
`--tag gastro-radar` and `--tag tag:gastro-radar`.
|
|
383
|
+
Per-command `--help` lists the fields used by that action.
|
|
384
|
+
|
|
385
|
+
Legacy `tools [--json]`, `describe <tool>`,
|
|
386
|
+
`call <tool> [JSON|-]` and `<tool_name> --parameter value` remain available.
|
|
238
387
|
`completions bash|zsh|fish` prints shell scripts.
|
|
239
388
|
|
|
240
|
-
```sh
|
|
241
|
-
ghostpaw-affinity contact create '{"name":"Sam","kind":"human"}' --db /tmp/affinity-example.db --json
|
|
242
|
-
ghostpaw-affinity search Sam --db /tmp/affinity-example.db --json
|
|
243
|
-
ghostpaw-affinity review contacts.list --db /tmp/affinity-example.db --json
|
|
244
|
-
```
|
|
245
|
-
|
|
246
389
|
## Quick Start
|
|
247
390
|
|
|
248
391
|
```ts
|
|
@@ -479,3 +622,92 @@ If this package helps your project, consider sponsoring its maintenance:
|
|
|
479
622
|
---
|
|
480
623
|
|
|
481
624
|
**[Anonyfox](https://anonyfox.com) • [MIT License](LICENSE)**
|
|
625
|
+
|
|
626
|
+
### Social imports and batch writes
|
|
627
|
+
|
|
628
|
+
Contact creation accepts `identities` and `ifExists: "error" | "return"` in the
|
|
629
|
+
library and tools. Identity ownership conflicts fail atomically (CLI exit 4),
|
|
630
|
+
including `data.existingContactId` and `error.details.existingContactId` in tool
|
|
631
|
+
results. `contact create '<JSON>' --if-exists return` returns the existing contact
|
|
632
|
+
with `outcome: "no_op"` and `matchedIdentities`; it attaches nothing, including
|
|
633
|
+
other free identities. Name-only duplicates still receive a possible-duplicate
|
|
634
|
+
warning. Links can opt into `input.ifExists: "return"` when seeding repeatedly. Identity
|
|
635
|
+
addition supports the same opt-in policy for an identity already on that contact;
|
|
636
|
+
another contact still conflicts. Importers add changed handles separately after
|
|
637
|
+
contact creation.
|
|
638
|
+
|
|
639
|
+
Handle identities strip a leading `@` and lowercase on add, revise and lookup.
|
|
640
|
+
Bluesky handles must be domain-like, X/Twitter handles have 1–15 letters, digits
|
|
641
|
+
or underscores, and Mastodon handles use `user@host.tld`. Use native `did` (alias
|
|
642
|
+
`atproto_did`) for stable AT Protocol identities. Locators also find legacy
|
|
643
|
+
`custom:atproto-did` rows. Keep former handles as additional identities.
|
|
644
|
+
`search @handle` works because CLI values are literal.
|
|
645
|
+
|
|
646
|
+
Only the JSON payload arguments (`--input`, the positional `<input>`, `--patch`
|
|
647
|
+
and the `call` argument) read files. Their help line ends with
|
|
648
|
+
`(@file, @- stdin, @@ literal @)`: `@path` reads the file as UTF-8 relative to
|
|
649
|
+
the current directory, `@-` reads stdin, and `@@text` passes the literal
|
|
650
|
+
`@text`. An empty path (`@`) or an unreadable file exits 2. Every other value,
|
|
651
|
+
including names, notes, attribute values and search queries, is taken byte for
|
|
652
|
+
byte, even with a leading `@` or `@@`.
|
|
653
|
+
|
|
654
|
+
For large payloads use stdin or `@file` to avoid the operating system's argv
|
|
655
|
+
size limit (`E2BIG`). JSON flag values support `-` too:
|
|
656
|
+
|
|
657
|
+
```sh
|
|
658
|
+
ghostpaw-affinity event record --kind interaction --input - < event.json
|
|
659
|
+
ghostpaw-affinity event record --kind interaction --input @event.json
|
|
660
|
+
ghostpaw-affinity review events.contact_journal --contact '{"contactId":2}' --event-types '["conversation","observation"]'
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
Array flags accept JSON arrays, comma lists, or repeated flags. Unknown event
|
|
664
|
+
types fail with exit 2 and allowed values. SQLite timestamps such as
|
|
665
|
+
`2026-10-27 17:51:33` use UTC; explicit offsets are honored. Sub-millisecond
|
|
666
|
+
fractions are rounded silently to milliseconds, including carry into the next
|
|
667
|
+
second. Keep the original timestamp in the source archive if finer precision
|
|
668
|
+
matters. Null `occurredAt` is invalid.
|
|
669
|
+
|
|
670
|
+
Record likes, reposts and follows as observations with `affectsBond: false`.
|
|
671
|
+
Observations with significance <= 2 default to false; other events default to
|
|
672
|
+
true. These ambient records stay in the journal without changing affinity or
|
|
673
|
+
recency. `provenance.ambient: true` also suppresses bond effects. Explicitly
|
|
674
|
+
outbound-only evidence contributes much less and does not reset radar recency;
|
|
675
|
+
without reciprocal contact it generates no routine check-in or drift prompt.
|
|
676
|
+
Include the owner explicitly with the correct actor/recipient role and use
|
|
677
|
+
`owner_initiated`, `other_initiated`, or `mutual` directionality. Observations and
|
|
678
|
+
follow-like milestones never derive breakthroughs; low-significance milestones
|
|
679
|
+
do not derive moments. An explicit `momentKind` is honored.
|
|
680
|
+
|
|
681
|
+
List reviews default to 50 rows, accept `--limit` (up to 250) and `--offset`,
|
|
682
|
+
and include `total` and `hasMore`. Follow `hasMore` rather than assuming the
|
|
683
|
+
first page is complete.
|
|
684
|
+
|
|
685
|
+
`batch` reads one tool invocation per JSONL line, with one transaction for the
|
|
686
|
+
whole input by default, or one per `--chunk N` lines. Each line has its own
|
|
687
|
+
savepoint: failed lines roll back and later lines continue. `--stop-on-error`
|
|
688
|
+
stops at the first failure. Results are JSONL `{line, ok, outcome, id, error?}`
|
|
689
|
+
plus `{done, created, noOp, failed}`; created counts successful non-no-op lines.
|
|
690
|
+
Exit status is 0 for success, 1 for failures, or 2 if a line is invalid JSON.
|
|
691
|
+
Committed chunks remain committed if a later chunk fails.
|
|
692
|
+
|
|
693
|
+
```jsonl
|
|
694
|
+
{"tool":"manage_contact","args":{"action":"create","input":{"name":"Owner","kind":"human","bootstrapOwner":true,"ifExists":"return","identities":[{"type":"did","value":"did:plc:owner"}]}}}
|
|
695
|
+
{"op":"event","kind":"observation","input":{"occurredAt":"2026-10-27","content":"","significance":1,"affectsBond":false,"externalRef":{"source":"bluesky","id":"at://example/like/1"},"participants":[{"contactId":1,"role":"actor"}]}}
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
```sh
|
|
699
|
+
ghostpaw-affinity batch --chunk 500 < import.jsonl
|
|
700
|
+
node scripts/examples/import_social_jsonl.mjs scripts/examples/fixtures/social_sample.json | ghostpaw-affinity --db /tmp/social-demo.db batch
|
|
701
|
+
node scripts/examples/import_dry_run.mjs
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
The dependency-free example consumes a JSON export shaped as accounts, posts,
|
|
705
|
+
interactions and follows; it never opens the source database. It resolves handle
|
|
706
|
+
aliases to DIDs, deduplicates record URIs, keeps content verbatim, sets channel,
|
|
707
|
+
parent references and directionality, and uses first_seen for undated follows
|
|
708
|
+
or counts a skipped timestamp. The dry run checks two imports, DID uniqueness
|
|
709
|
+
and byte-identical content, and reports journal JSON bytes versus raw text bytes.
|
|
710
|
+
Examples and synthetic fixtures are development files, outside the npm package.
|
|
711
|
+
|
|
712
|
+
`ghostpaw-affinity skill --short` prints the compact command reference without
|
|
713
|
+
frontmatter or an appendix. `skill --list` lists guides; `skill <guide>` reads one.
|