@ghostpaw/affinity 0.3.0 → 0.4.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 +150 -9
- package/dist/cli.js +563 -118
- package/dist/mcp-server.js +9 -4
- package/dist/mcp.cjs +2 -2
- package/dist/mcp.d.cts +10 -0
- package/dist/mcp.d.ts +10 -0
- package/dist/mcp.js +2 -2
- package/package.json +17 -6
package/README.md
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
<a href="https://affinity.ghostpaw.dev/"><img src="https://raw.githubusercontent.com/GhostPawJS/affinity/main/.github/social-preview.png" alt="GhostPaw affinity: Who have you been meaning to call?" width="100%"></a>
|
|
2
|
+
|
|
1
3
|
# @ghostpaw/affinity
|
|
2
4
|
|
|
3
|
-
[](https://www.npmjs.com/package/@ghostpaw/affinity)
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@ghostpaw/affinity) [](https://github.com/GhostPawJS/affinity/actions/workflows/ci.yml) [](https://affinity.ghostpaw.dev/) [](package.json) [](https://registry.modelcontextprotocol.io/v0.1/servers/io.github.GhostPawJS%2Faffinity/versions/latest) [](https://nodejs.org) [](LICENSE)
|
|
6
|
+
|
|
7
|
+
**Agent contacts: people, promises and yearly dates, with a radar for who is drifting.**
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @ghostpaw/affinity
|
|
11
|
+
```
|
|
9
12
|
|
|
10
13
|
A standalone social CRM core for Node.js, built on SQLite.
|
|
11
14
|
|
|
@@ -88,11 +91,139 @@ npx -y @ghostpaw/affinity skill > SKILL.md
|
|
|
88
91
|
|
|
89
92
|
## Install
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
npm
|
|
94
|
+
|
|
95
|
+
The [npm package](https://www.npmjs.com/package/@ghostpaw/affinity) requires Node 24+:
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
npm install -g @ghostpaw/affinity
|
|
99
|
+
npx -y @ghostpaw/affinity --help
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Standalone binaries are available from [GitHub Releases](https://github.com/GhostPawJS/affinity/releases)
|
|
103
|
+
for linux-x64, linux-arm64, darwin-arm64, darwin-x64 and windows-x64.
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
curl -fsSL https://raw.githubusercontent.com/GhostPawJS/affinity/main/install.sh | sh
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```powershell
|
|
110
|
+
irm https://raw.githubusercontent.com/GhostPawJS/affinity/main/install.ps1 | iex
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Other release installers:
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
mise use -g github:GhostPawJS/affinity
|
|
117
|
+
ubi --project GhostPawJS/affinity --exe ghostpaw-affinity --in ~/.local/bin
|
|
118
|
+
eget GhostPawJS/affinity
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
For [aqua](https://aquaproj.github.io/docs/reference/registry-config/), define a
|
|
122
|
+
`github_release` package in `aqua.yaml`:
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
registries:
|
|
126
|
+
- name: affinity
|
|
127
|
+
type: local
|
|
128
|
+
path: affinity-registry.yaml
|
|
129
|
+
packages:
|
|
130
|
+
- name: GhostPawJS/affinity@v<VERSION>
|
|
131
|
+
registry: affinity
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`affinity-registry.yaml`:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
packages:
|
|
138
|
+
- type: github_release
|
|
139
|
+
repo_owner: GhostPawJS
|
|
140
|
+
repo_name: affinity
|
|
141
|
+
asset: ghostpaw-affinity-{{trimV .Version}}-{{.OS}}-{{.Arch}}.tar.gz
|
|
142
|
+
format: tar.gz
|
|
143
|
+
replacements: { amd64: x64 }
|
|
144
|
+
overrides:
|
|
145
|
+
- goos: windows
|
|
146
|
+
asset: ghostpaw-affinity-{{trimV .Version}}-windows-{{.Arch}}.zip
|
|
147
|
+
format: zip
|
|
148
|
+
files:
|
|
149
|
+
- name: ghostpaw-affinity
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Install scripts verify SHA256SUMS. To verify a downloaded archive's
|
|
153
|
+
[build attestation](https://cli.github.com/manual/gh_attestation_verify):
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
gh attestation verify <archive> -R GhostPawJS/affinity
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Set `GHOSTPAW_VERIFY=attestation` for attestation verification in `install.sh`.
|
|
160
|
+
Binaries are unsigned; macOS binaries carry an ad-hoc signature. For a browser
|
|
161
|
+
download on macOS, remove quarantine with
|
|
162
|
+
`xattr -d com.apple.quarantine /path/to/ghostpaw-affinity`.
|
|
163
|
+
Linux binaries require glibc 2.28+; use npx on Alpine.
|
|
164
|
+
See the [binary build workflow](.github/workflows/release-binaries.yml).
|
|
165
|
+
|
|
166
|
+
<!-- sizes -->
|
|
167
|
+
|
|
168
|
+
The install check runs on ubuntu-24.04, macos-15 and windows-2025.
|
|
169
|
+
|
|
170
|
+
## CLI
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
ghostpaw-affinity search <query>
|
|
174
|
+
ghostpaw-affinity review <view>
|
|
175
|
+
ghostpaw-affinity show <owner_profile|contact_profile|link> [--contact JSON --link JSON]
|
|
176
|
+
ghostpaw-affinity contact create <input-json>
|
|
177
|
+
ghostpaw-affinity contact revise <contact-json> --patch JSON
|
|
178
|
+
ghostpaw-affinity contact lifecycle <contact-json> <lifecycleState>
|
|
179
|
+
ghostpaw-affinity contact merge [flags]
|
|
180
|
+
ghostpaw-affinity identity add|revise|verify|remove [flags]
|
|
181
|
+
ghostpaw-affinity relationship add|bond|state|tie|untie [flags]
|
|
182
|
+
ghostpaw-affinity commitment record|resolve [flags]
|
|
183
|
+
ghostpaw-affinity attribute set|unset|replace [flags]
|
|
184
|
+
ghostpaw-affinity date add|revise|remove [flags]
|
|
185
|
+
ghostpaw-affinity event record [flags]
|
|
186
|
+
ghostpaw-affinity duplicate dismiss|undismiss [flags]
|
|
187
|
+
ghostpaw-affinity mcp|skill|tools|describe|call|completions|version|help
|
|
93
188
|
```
|
|
94
189
|
|
|
95
|
-
|
|
190
|
+
There are 27 domain commands. `review` accepts the existing view names,
|
|
191
|
+
including `contacts.list`, `contacts.duplicates` and `dates.upcoming`.
|
|
192
|
+
`show` selects an inspection kind; contact and link locators are JSON objects.
|
|
193
|
+
Use `help contact` or `contact create --help` for schema-derived help.
|
|
194
|
+
Nested payloads use JSON or `@path` to read a file.
|
|
195
|
+
|
|
196
|
+
Global flags: `--db <path>`, `--json`, `--quiet`, `--compact`,
|
|
197
|
+
`--help`/`-h`, `--version`/`-V`/`-v`, `--no-color`.
|
|
198
|
+
`NO_COLOR` is accepted; output uses no terminal colors.
|
|
199
|
+
Noun-verb commands print the full tool result as JSON with `--json` or piped
|
|
200
|
+
stdout. Terminal output shows the summary and payload. `--quiet` suppresses
|
|
201
|
+
stdout. `--compact` removes JSON indentation.
|
|
202
|
+
Array flags repeat; kebab-case flags map to schema properties.
|
|
203
|
+
`--` ends flags. A positional `-`, or piped stdin with no input flags, reads a
|
|
204
|
+
JSON object. Explicit flags override stdin fields.
|
|
205
|
+
Database selection uses `--db`, `GHOSTPAW_AFFINITY_DB`, then
|
|
206
|
+
`~/.ghostpaw/affinity.db`. These commands work offline.
|
|
207
|
+
|
|
208
|
+
| Exit | Noun-verb result |
|
|
209
|
+
|---|---|
|
|
210
|
+
| 0 | Success, including no_op |
|
|
211
|
+
| 1 | Tool error |
|
|
212
|
+
| 2 | Usage or schema error |
|
|
213
|
+
| 3 | Clarification needed or not found |
|
|
214
|
+
| 4 | Conflict or version mismatch |
|
|
215
|
+
| 70 | Internal error |
|
|
216
|
+
|
|
217
|
+
Errors go to stderr. With `--json`, errors also print to stdout.
|
|
218
|
+
Legacy `tools [--json]`, `describe <tool>`, `call <tool> [JSON|-]` and
|
|
219
|
+
`<tool_name> --parameter value` retain their output and exit codes.
|
|
220
|
+
`completions bash|zsh|fish` prints shell scripts.
|
|
221
|
+
|
|
222
|
+
```sh
|
|
223
|
+
ghostpaw-affinity contact create '{"name":"Sam","kind":"human"}' --db /tmp/affinity-example.db --json
|
|
224
|
+
ghostpaw-affinity search Sam --db /tmp/affinity-example.db --json
|
|
225
|
+
ghostpaw-affinity review contacts.list --db /tmp/affinity-example.db --json
|
|
226
|
+
```
|
|
96
227
|
|
|
97
228
|
## Quick Start
|
|
98
229
|
|
|
@@ -135,6 +266,16 @@ const profile = read.getContactProfile(db, ada.id);
|
|
|
135
266
|
const radar = read.listRadar(db);
|
|
136
267
|
```
|
|
137
268
|
|
|
269
|
+
## Docs
|
|
270
|
+
|
|
271
|
+
The live demo runs the library in your browser against an in-memory SQLite database. Each view below opens a different part of it:
|
|
272
|
+
|
|
273
|
+
- [Dashboard](https://affinity.ghostpaw.dev/docs/): overview and session
|
|
274
|
+
- [Contacts](https://affinity.ghostpaw.dev/docs/#/contacts): people, companies, groups and pets you track
|
|
275
|
+
- [Relationships](https://affinity.ghostpaw.dev/docs/#/relationships): links, rank and bonds
|
|
276
|
+
- [Timeline](https://affinity.ghostpaw.dev/docs/#/timeline): events, commitments and date anchors
|
|
277
|
+
- [Insights](https://affinity.ghostpaw.dev/docs/#/insights): radar and analytics
|
|
278
|
+
|
|
138
279
|
## The Model
|
|
139
280
|
|
|
140
281
|
Eight concepts, strict separation of concerns:
|
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { readFileSync, mkdirSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
|
+
import { Writable } from 'node:stream';
|
|
5
6
|
|
|
6
7
|
var __defProp = Object.defineProperty;
|
|
7
8
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
-
var __esm = (fn, res) => function __init() {
|
|
9
|
-
|
|
9
|
+
var __esm = (fn, res, err) => function __init() {
|
|
10
|
+
if (err) throw err[0];
|
|
11
|
+
try {
|
|
12
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
throw err = [e], e;
|
|
15
|
+
}
|
|
10
16
|
};
|
|
11
17
|
var __export = (target, all) => {
|
|
12
18
|
for (var name in all)
|
|
13
19
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
20
|
};
|
|
15
21
|
|
|
22
|
+
// src/version.ts
|
|
23
|
+
var affinityVersion;
|
|
24
|
+
var init_version = __esm({
|
|
25
|
+
"src/version.ts"() {
|
|
26
|
+
affinityVersion = "0.4.0";
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
16
30
|
// src/lib/errors/affinity_error.ts
|
|
17
31
|
var AffinityError;
|
|
18
32
|
var init_affinity_error = __esm({
|
|
@@ -8177,7 +8191,7 @@ var init_tool_registry = __esm({
|
|
|
8177
8191
|
});
|
|
8178
8192
|
|
|
8179
8193
|
// src/surface/affinity_surface_tools.ts
|
|
8180
|
-
var affinitySurfaceTools;
|
|
8194
|
+
var affinitySurfaceTools, mapping, affinityCliSurfaceTools;
|
|
8181
8195
|
var init_affinity_surface_tools = __esm({
|
|
8182
8196
|
"src/surface/affinity_surface_tools.ts"() {
|
|
8183
8197
|
init_tool_registry();
|
|
@@ -8191,83 +8205,67 @@ var init_affinity_surface_tools = __esm({
|
|
|
8191
8205
|
inputSchema: { ...tool.inputSchema, type: "object" },
|
|
8192
8206
|
handler: tool.handler
|
|
8193
8207
|
}));
|
|
8194
|
-
|
|
8195
|
-
}
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
`${path}[${index}]`
|
|
8256
|
-
)
|
|
8257
|
-
);
|
|
8258
|
-
});
|
|
8259
|
-
return errors;
|
|
8260
|
-
}
|
|
8261
|
-
var init_validate_input = __esm({
|
|
8262
|
-
"src/surfaces/validate_input.ts"() {
|
|
8263
|
-
}
|
|
8264
|
-
});
|
|
8265
|
-
|
|
8266
|
-
// src/version.ts
|
|
8267
|
-
var affinityVersion;
|
|
8268
|
-
var init_version = __esm({
|
|
8269
|
-
"src/version.ts"() {
|
|
8270
|
-
affinityVersion = "0.3.0";
|
|
8208
|
+
mapping = {
|
|
8209
|
+
search_affinity: { verb: "search", positionals: ["query"] },
|
|
8210
|
+
review_affinity: { verb: "review", positionals: ["view"] },
|
|
8211
|
+
inspect_affinity_item: { verb: "show", positionals: ["kind"] },
|
|
8212
|
+
manage_contact: {
|
|
8213
|
+
noun: "contact",
|
|
8214
|
+
verb: "manage",
|
|
8215
|
+
action: {
|
|
8216
|
+
property: "action",
|
|
8217
|
+
verbs: { set_lifecycle: "lifecycle" },
|
|
8218
|
+
positionals: {
|
|
8219
|
+
create: ["input"],
|
|
8220
|
+
revise: ["contact"],
|
|
8221
|
+
set_lifecycle: ["contact", "lifecycleState"]
|
|
8222
|
+
}
|
|
8223
|
+
}
|
|
8224
|
+
},
|
|
8225
|
+
merge_contacts: { noun: "contact", verb: "merge" },
|
|
8226
|
+
manage_duplicate_dismissal: { noun: "duplicate", verb: "manage" },
|
|
8227
|
+
manage_identity: {
|
|
8228
|
+
noun: "identity",
|
|
8229
|
+
verb: "manage",
|
|
8230
|
+
action: {
|
|
8231
|
+
property: "action",
|
|
8232
|
+
positionals: {
|
|
8233
|
+
revise: ["identityId"],
|
|
8234
|
+
verify: ["identityId"],
|
|
8235
|
+
remove: ["identityId"]
|
|
8236
|
+
}
|
|
8237
|
+
}
|
|
8238
|
+
},
|
|
8239
|
+
manage_relationship: {
|
|
8240
|
+
noun: "relationship",
|
|
8241
|
+
verb: "manage",
|
|
8242
|
+
action: {
|
|
8243
|
+
property: "action",
|
|
8244
|
+
verbs: {
|
|
8245
|
+
seed_social_link: "add",
|
|
8246
|
+
revise_bond: "bond",
|
|
8247
|
+
override_state: "state",
|
|
8248
|
+
set_structural_tie: "tie",
|
|
8249
|
+
remove_structural_tie: "untie"
|
|
8250
|
+
}
|
|
8251
|
+
}
|
|
8252
|
+
},
|
|
8253
|
+
record_event: { noun: "event", verb: "record" },
|
|
8254
|
+
manage_commitment: { noun: "commitment", verb: "manage" },
|
|
8255
|
+
manage_date_anchor: {
|
|
8256
|
+
noun: "date",
|
|
8257
|
+
verb: "manage",
|
|
8258
|
+
action: {
|
|
8259
|
+
property: "action",
|
|
8260
|
+
positionals: { revise: ["anchorEventId"], remove: ["anchorEventId"] }
|
|
8261
|
+
}
|
|
8262
|
+
},
|
|
8263
|
+
manage_attribute: { noun: "attribute", verb: "manage" }
|
|
8264
|
+
};
|
|
8265
|
+
affinityCliSurfaceTools = /* @__PURE__ */ affinitySurfaceTools.map((tool) => ({
|
|
8266
|
+
...tool,
|
|
8267
|
+
...mapping[tool.name] ? { cli: mapping[tool.name] } : {}
|
|
8268
|
+
}));
|
|
8271
8269
|
}
|
|
8272
8270
|
});
|
|
8273
8271
|
|
|
@@ -8812,6 +8810,75 @@ var init_resolve_affinity_db_path = __esm({
|
|
|
8812
8810
|
}
|
|
8813
8811
|
});
|
|
8814
8812
|
|
|
8813
|
+
// src/surfaces/surface_types.ts
|
|
8814
|
+
function isErrorResult(result) {
|
|
8815
|
+
return typeof result === "object" && result !== null && "ok" in result && result.ok === false;
|
|
8816
|
+
}
|
|
8817
|
+
var init_surface_types = __esm({
|
|
8818
|
+
"src/surfaces/surface_types.ts"() {
|
|
8819
|
+
}
|
|
8820
|
+
});
|
|
8821
|
+
|
|
8822
|
+
// src/surfaces/validate_input.ts
|
|
8823
|
+
function validateInput(schema, input, path = "$") {
|
|
8824
|
+
const errors = [];
|
|
8825
|
+
const matchesType = (type) => {
|
|
8826
|
+
switch (type) {
|
|
8827
|
+
case "object":
|
|
8828
|
+
return typeof input === "object" && input !== null && !Array.isArray(input);
|
|
8829
|
+
case "array":
|
|
8830
|
+
return Array.isArray(input);
|
|
8831
|
+
case "integer":
|
|
8832
|
+
return typeof input === "number" && Number.isInteger(input);
|
|
8833
|
+
case "number":
|
|
8834
|
+
return typeof input === "number" && Number.isFinite(input);
|
|
8835
|
+
case "null":
|
|
8836
|
+
return input === null;
|
|
8837
|
+
default:
|
|
8838
|
+
return typeof input === type;
|
|
8839
|
+
}
|
|
8840
|
+
};
|
|
8841
|
+
if (schema.type && !(Array.isArray(schema.type) ? schema.type : [schema.type]).some(
|
|
8842
|
+
matchesType
|
|
8843
|
+
)) {
|
|
8844
|
+
return [`${path}: expected ${String(schema.type)}`];
|
|
8845
|
+
}
|
|
8846
|
+
if (schema.enum && !schema.enum.some(
|
|
8847
|
+
(value) => JSON.stringify(value) === JSON.stringify(input)
|
|
8848
|
+
))
|
|
8849
|
+
errors.push(`${path}: expected one of ${JSON.stringify(schema.enum)}`);
|
|
8850
|
+
if (schema.oneOf && !schema.oneOf.some(
|
|
8851
|
+
(branch) => validateInput(branch, input, path).length === 0
|
|
8852
|
+
))
|
|
8853
|
+
errors.push(`${path}: must match at least one schema branch`);
|
|
8854
|
+
if (typeof input === "object" && input !== null && !Array.isArray(input)) {
|
|
8855
|
+
const record = input;
|
|
8856
|
+
for (const name of schema.required ?? [])
|
|
8857
|
+
if (!Object.hasOwn(record, name))
|
|
8858
|
+
errors.push(`${path}.${name}: required`);
|
|
8859
|
+
for (const [name, property] of Object.entries(schema.properties ?? {}))
|
|
8860
|
+
if (Object.hasOwn(record, name))
|
|
8861
|
+
errors.push(
|
|
8862
|
+
...validateInput(property, record[name], `${path}.${name}`)
|
|
8863
|
+
);
|
|
8864
|
+
}
|
|
8865
|
+
if (Array.isArray(input) && schema.items)
|
|
8866
|
+
input.forEach((value, index) => {
|
|
8867
|
+
errors.push(
|
|
8868
|
+
...validateInput(
|
|
8869
|
+
schema.items,
|
|
8870
|
+
value,
|
|
8871
|
+
`${path}[${index}]`
|
|
8872
|
+
)
|
|
8873
|
+
);
|
|
8874
|
+
});
|
|
8875
|
+
return errors;
|
|
8876
|
+
}
|
|
8877
|
+
var init_validate_input = __esm({
|
|
8878
|
+
"src/surfaces/validate_input.ts"() {
|
|
8879
|
+
}
|
|
8880
|
+
});
|
|
8881
|
+
|
|
8815
8882
|
// src/surfaces/mcp_server.ts
|
|
8816
8883
|
function createMcpServer(options) {
|
|
8817
8884
|
let initialized = false;
|
|
@@ -9026,7 +9093,7 @@ function affinityMcpOptions(context) {
|
|
|
9026
9093
|
annotations: (tool) => ({
|
|
9027
9094
|
readOnlyHint: tool.readOnly ?? false,
|
|
9028
9095
|
destructiveHint: tool.name === "merge_contacts" || /remove|unset|undismiss/.test(
|
|
9029
|
-
|
|
9096
|
+
String(tool.inputSchema.properties?.action?.enum ?? [])
|
|
9030
9097
|
),
|
|
9031
9098
|
idempotentHint: tool.readOnly ?? false,
|
|
9032
9099
|
openWorldHint: false
|
|
@@ -9929,29 +9996,434 @@ var init_affinity_skill_spec = __esm({
|
|
|
9929
9996
|
packageName: "@ghostpaw/affinity",
|
|
9930
9997
|
server: "ghostpaw-affinity",
|
|
9931
9998
|
about: affinityMcpInstructions,
|
|
9932
|
-
tools:
|
|
9999
|
+
tools: affinityCliSurfaceTools,
|
|
9933
10000
|
workflows: affinitySkills,
|
|
9934
10001
|
principles: affinitySoulTraits.map((trait) => trait.principle)
|
|
9935
10002
|
};
|
|
9936
10003
|
}
|
|
9937
10004
|
});
|
|
9938
10005
|
|
|
9939
|
-
// src/
|
|
10006
|
+
// src/surface/affinity_cli_options.ts
|
|
10007
|
+
init_version();
|
|
9940
10008
|
init_affinity_surface_tools();
|
|
10009
|
+
var affinityCliOptions = {
|
|
10010
|
+
bin: "ghostpaw-affinity",
|
|
10011
|
+
version: affinityVersion,
|
|
10012
|
+
description: "Local relationship model and social CRM",
|
|
10013
|
+
tools: affinityCliSurfaceTools,
|
|
10014
|
+
openContext: async (options) => {
|
|
10015
|
+
const { openAffinityDb: openAffinityDb2 } = await Promise.resolve().then(() => (init_open_affinity_db(), open_affinity_db_exports));
|
|
10016
|
+
const { resolveAffinityDbPath: resolveAffinityDbPath2 } = await Promise.resolve().then(() => (init_resolve_affinity_db_path(), resolve_affinity_db_path_exports));
|
|
10017
|
+
return openAffinityDb2(
|
|
10018
|
+
resolveAffinityDbPath2(
|
|
10019
|
+
options.db === void 0 ? {} : { flag: options.db }
|
|
10020
|
+
)
|
|
10021
|
+
);
|
|
10022
|
+
},
|
|
10023
|
+
closeContext: (db) => db.close(),
|
|
10024
|
+
commands: {
|
|
10025
|
+
mcp: async (options) => {
|
|
10026
|
+
const { serveAffinityMcp: serveAffinityMcp2 } = await Promise.resolve().then(() => (init_serve_affinity_mcp(), serve_affinity_mcp_exports));
|
|
10027
|
+
await serveAffinityMcp2(
|
|
10028
|
+
options.db === void 0 ? {} : { dbPath: options.db }
|
|
10029
|
+
);
|
|
10030
|
+
},
|
|
10031
|
+
skill: async () => {
|
|
10032
|
+
const { renderSkillMarkdown: renderSkillMarkdown2 } = await Promise.resolve().then(() => (init_skill_markdown(), skill_markdown_exports));
|
|
10033
|
+
const { affinitySkillSpec: affinitySkillSpec2 } = await Promise.resolve().then(() => (init_affinity_skill_spec(), affinity_skill_spec_exports));
|
|
10034
|
+
return renderSkillMarkdown2(affinitySkillSpec2);
|
|
10035
|
+
}
|
|
10036
|
+
}
|
|
10037
|
+
};
|
|
10038
|
+
var CliUsageError = class extends Error {
|
|
10039
|
+
exitCode = 2;
|
|
10040
|
+
};
|
|
10041
|
+
var keyOf = (name) => name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
10042
|
+
function parseCliArgs(argv, spec) {
|
|
10043
|
+
const values = {};
|
|
10044
|
+
const positions = [];
|
|
10045
|
+
const flags = spec.flags ?? [];
|
|
10046
|
+
const convert = (raw, arg) => {
|
|
10047
|
+
let value = raw;
|
|
10048
|
+
if (raw.startsWith("@")) {
|
|
10049
|
+
try {
|
|
10050
|
+
value = readFileSync(raw.slice(1), "utf8");
|
|
10051
|
+
} catch {
|
|
10052
|
+
throw new CliUsageError(`Cannot read value file: ${raw.slice(1)}`);
|
|
10053
|
+
}
|
|
10054
|
+
}
|
|
10055
|
+
let result = value;
|
|
10056
|
+
if (arg.type === "number" || arg.type === "integer") {
|
|
10057
|
+
result = Number(value);
|
|
10058
|
+
if (!value.trim() || !Number.isFinite(result) || arg.type === "integer" && !Number.isInteger(result))
|
|
10059
|
+
throw new CliUsageError(
|
|
10060
|
+
`Expected ${arg.type} for ${arg.name}: ${value}`
|
|
10061
|
+
);
|
|
10062
|
+
} else if (arg.type === "boolean") {
|
|
10063
|
+
if (!["true", "false"].includes(value))
|
|
10064
|
+
throw new CliUsageError(`Expected boolean for ${arg.name}`);
|
|
10065
|
+
result = value === "true";
|
|
10066
|
+
} else if (arg.type === "json") {
|
|
10067
|
+
try {
|
|
10068
|
+
result = JSON.parse(value);
|
|
10069
|
+
} catch {
|
|
10070
|
+
throw new CliUsageError(`Invalid JSON for ${arg.name}`);
|
|
10071
|
+
}
|
|
10072
|
+
}
|
|
10073
|
+
if (arg.enum && !arg.enum.includes(result))
|
|
10074
|
+
throw new CliUsageError(
|
|
10075
|
+
`${arg.name} must be one of: ${arg.enum.join(", ")}`
|
|
10076
|
+
);
|
|
10077
|
+
return result;
|
|
10078
|
+
};
|
|
10079
|
+
let ended = false;
|
|
10080
|
+
for (let i = 0; i < argv.length; i++) {
|
|
10081
|
+
const token = argv[i];
|
|
10082
|
+
if (!ended && token === "--") {
|
|
10083
|
+
ended = true;
|
|
10084
|
+
continue;
|
|
10085
|
+
}
|
|
10086
|
+
if (ended || token === "-" || !token.startsWith("-")) {
|
|
10087
|
+
positions.push(token);
|
|
10088
|
+
continue;
|
|
10089
|
+
}
|
|
10090
|
+
const [flag, inline] = token.split(/=(.*)/s);
|
|
10091
|
+
const negative = flag?.startsWith("--no-");
|
|
10092
|
+
const name = flag?.replace(/^--?/, "").replace(/^no-/, "");
|
|
10093
|
+
const arg = flags.find(
|
|
10094
|
+
(entry) => entry.name === name || keyOf(entry.name) === name || entry.short === name
|
|
10095
|
+
);
|
|
10096
|
+
if (!arg) {
|
|
10097
|
+
const prefix = (name ?? "").slice(0, 3);
|
|
10098
|
+
const suggestion = prefix ? flags.find((entry) => entry.name.startsWith(prefix)) : void 0;
|
|
10099
|
+
throw new CliUsageError(
|
|
10100
|
+
`Unknown flag: ${flag}${suggestion ? `; did you mean --${suggestion.name}?` : ""}`
|
|
10101
|
+
);
|
|
10102
|
+
}
|
|
10103
|
+
if (negative && arg.type !== "boolean")
|
|
10104
|
+
throw new CliUsageError(`--no-${arg.name} requires a boolean flag`);
|
|
10105
|
+
let raw = inline;
|
|
10106
|
+
if (arg.type === "boolean" && raw === void 0)
|
|
10107
|
+
raw = negative ? "false" : "true";
|
|
10108
|
+
if (raw === void 0) {
|
|
10109
|
+
const next = argv[i + 1];
|
|
10110
|
+
if (next === void 0 || next.startsWith("--"))
|
|
10111
|
+
throw new CliUsageError(`Missing value: --${arg.name}`);
|
|
10112
|
+
raw = next;
|
|
10113
|
+
i++;
|
|
10114
|
+
}
|
|
10115
|
+
const key = keyOf(arg.name);
|
|
10116
|
+
if (arg.type === "array") {
|
|
10117
|
+
const item = convert(raw, { ...arg, type: arg.itemType ?? "string" });
|
|
10118
|
+
values[key] = [...values[key] ?? [], item];
|
|
10119
|
+
} else values[key] = convert(raw, arg);
|
|
10120
|
+
}
|
|
10121
|
+
let index = 0;
|
|
10122
|
+
for (const arg of spec.positionals ?? []) {
|
|
10123
|
+
if (index >= positions.length) continue;
|
|
10124
|
+
values[keyOf(arg.name)] = arg.variadic ? positions.slice(index).map(
|
|
10125
|
+
(value) => convert(value, { ...arg, type: arg.itemType ?? arg.type })
|
|
10126
|
+
) : convert(positions[index], arg);
|
|
10127
|
+
index = arg.variadic ? positions.length : index + 1;
|
|
10128
|
+
}
|
|
10129
|
+
if (index < positions.length)
|
|
10130
|
+
throw new CliUsageError(`Unexpected positional: ${positions[index]}`);
|
|
10131
|
+
for (const arg of [...flags, ...spec.positionals ?? []]) {
|
|
10132
|
+
if (arg.required && values[keyOf(arg.name)] === void 0)
|
|
10133
|
+
throw new CliUsageError(`Missing required argument: ${arg.name}`);
|
|
10134
|
+
}
|
|
10135
|
+
return values;
|
|
10136
|
+
}
|
|
10137
|
+
function renderCliHelp(spec) {
|
|
10138
|
+
const describe = (arg) => `${arg.type}${arg.enum ? ` (${arg.enum.join("|")})` : ""}${arg.required ? " required" : ""} ${arg.description ?? ""}`;
|
|
10139
|
+
return `Usage: ${spec.command} ${(spec.positionals ?? []).map((arg) => `<${arg.name}${arg.variadic ? "..." : ""}>`).join(" ")} [flags]
|
|
10140
|
+
${spec.description ?? ""}
|
|
10141
|
+
|
|
10142
|
+
Positionals:
|
|
10143
|
+
${(spec.positionals ?? []).map((arg) => ` ${arg.name} ${describe(arg)}`).join("\n")}
|
|
10144
|
+
Flags:
|
|
10145
|
+
${(spec.flags ?? []).map((arg) => ` --${arg.name}${arg.short ? `, -${arg.short}` : ""} ${describe(arg)}`).join("\n")}
|
|
10146
|
+
`;
|
|
10147
|
+
}
|
|
10148
|
+
|
|
10149
|
+
// src/surfaces/cli_commands.ts
|
|
10150
|
+
init_validate_input();
|
|
10151
|
+
function cliCommands(tools) {
|
|
10152
|
+
return tools.flatMap((tool) => {
|
|
10153
|
+
const split = tool.name.indexOf("_");
|
|
10154
|
+
const cli = tool.cli ?? {
|
|
10155
|
+
noun: tool.name.slice(split + 1),
|
|
10156
|
+
verb: tool.name.slice(0, split)
|
|
10157
|
+
};
|
|
10158
|
+
const action = cli.action ?? (tool.inputSchema.properties?.action?.enum ? { property: "action" } : void 0);
|
|
10159
|
+
const actions = action ? tool.inputSchema.properties?.[action.property]?.enum ?? [] : [void 0];
|
|
10160
|
+
return actions.map((value) => {
|
|
10161
|
+
const verb = value === void 0 ? cli.verb : action?.verbs?.[String(value)] ?? String(value);
|
|
10162
|
+
return {
|
|
10163
|
+
tool,
|
|
10164
|
+
path: [cli.noun, verb].filter(Boolean).join(" "),
|
|
10165
|
+
fixed: action && value !== void 0 ? { [action.property]: value } : {},
|
|
10166
|
+
positionals: action?.positionals?.[String(value)] ?? cli.positionals ?? []
|
|
10167
|
+
};
|
|
10168
|
+
});
|
|
10169
|
+
});
|
|
10170
|
+
}
|
|
10171
|
+
async function runCliCommand(argv, options) {
|
|
10172
|
+
const stdout = options.stdout ?? process.stdout;
|
|
10173
|
+
const stderr = options.stderr ?? process.stderr;
|
|
10174
|
+
const stdin = options.stdin ?? process.stdin;
|
|
10175
|
+
const args = [...argv];
|
|
10176
|
+
const tree = cliCommands(options.tools);
|
|
10177
|
+
let json = argv.includes("--json");
|
|
10178
|
+
let quiet = argv.includes("--quiet");
|
|
10179
|
+
let compact = false;
|
|
10180
|
+
let help = false;
|
|
10181
|
+
let db;
|
|
10182
|
+
const fail = (message, code) => {
|
|
10183
|
+
stderr.write(`${message}
|
|
10184
|
+
`);
|
|
10185
|
+
if (json && !quiet)
|
|
10186
|
+
stdout.write(
|
|
10187
|
+
`${JSON.stringify({ ok: false, error: { code: code === 2 ? "usage_error" : "internal_error", message } })}
|
|
10188
|
+
`
|
|
10189
|
+
);
|
|
10190
|
+
return code;
|
|
10191
|
+
};
|
|
10192
|
+
const legacy = ["tools", "describe", "call", "mcp", "skill"];
|
|
10193
|
+
let first;
|
|
10194
|
+
for (let i = 0; i < argv.length; i++) {
|
|
10195
|
+
const token = argv[i];
|
|
10196
|
+
if (token === "--db") {
|
|
10197
|
+
i++;
|
|
10198
|
+
continue;
|
|
10199
|
+
}
|
|
10200
|
+
if (token.startsWith("-")) continue;
|
|
10201
|
+
first = token;
|
|
10202
|
+
break;
|
|
10203
|
+
}
|
|
10204
|
+
if (legacy.includes(first ?? "") || options.tools.some(
|
|
10205
|
+
(tool) => tool.name.replace(/[-_]/g, "") === first?.replace(/[-_]/g, "")
|
|
10206
|
+
))
|
|
10207
|
+
return void 0;
|
|
10208
|
+
try {
|
|
10209
|
+
for (let i = 0; i < args.length; i++) {
|
|
10210
|
+
const flag = args[i];
|
|
10211
|
+
if (flag === "--") break;
|
|
10212
|
+
if (flag === "--db" || flag?.startsWith("--db=")) {
|
|
10213
|
+
db = flag === "--db" ? args.splice(i + 1, 1)[0] : flag.slice(5);
|
|
10214
|
+
if (!db || db.startsWith("--"))
|
|
10215
|
+
throw new CliUsageError("--db requires a path");
|
|
10216
|
+
} else if (flag === "--json") json = true;
|
|
10217
|
+
else if (flag === "--quiet") quiet = true;
|
|
10218
|
+
else if (flag === "--compact") compact = true;
|
|
10219
|
+
else if (flag === "--help" || flag === "-h") help = true;
|
|
10220
|
+
else if (["--version", "-V", "-v"].includes(flag ?? "")) {
|
|
10221
|
+
if (!quiet) stdout.write(`${options.version}
|
|
10222
|
+
`);
|
|
10223
|
+
return 0;
|
|
10224
|
+
} else if (flag !== "--no-color") continue;
|
|
10225
|
+
args.splice(i--, 1);
|
|
10226
|
+
}
|
|
10227
|
+
if (args[0] === "version") {
|
|
10228
|
+
if (!quiet) stdout.write(`${options.version}
|
|
10229
|
+
`);
|
|
10230
|
+
return 0;
|
|
10231
|
+
}
|
|
10232
|
+
if (args[0] === "help") {
|
|
10233
|
+
help = true;
|
|
10234
|
+
args.shift();
|
|
10235
|
+
}
|
|
10236
|
+
if (!args.length) help = true;
|
|
10237
|
+
const entry = tree.find(
|
|
10238
|
+
(command) => command.path.split(" ").every((part, i) => args[i] === part)
|
|
10239
|
+
);
|
|
10240
|
+
const specFor = (command) => {
|
|
10241
|
+
const argument = (name, schema) => ({
|
|
10242
|
+
name,
|
|
10243
|
+
type: schema.type === "object" || !schema.type ? "json" : schema.type,
|
|
10244
|
+
description: schema.description,
|
|
10245
|
+
enum: schema.enum,
|
|
10246
|
+
...schema.items?.type ? { itemType: schema.items.type } : {}
|
|
10247
|
+
});
|
|
10248
|
+
const properties = command.tool.inputSchema.properties ?? {};
|
|
10249
|
+
return {
|
|
10250
|
+
command: `${options.bin} ${command.path}`,
|
|
10251
|
+
description: command.tool.description,
|
|
10252
|
+
positionals: command.positionals.map((name) => ({
|
|
10253
|
+
...argument(name, properties[name] ?? {}),
|
|
10254
|
+
...properties[name]?.type === "array" ? {
|
|
10255
|
+
type: properties[name]?.items?.type ?? "string",
|
|
10256
|
+
variadic: true
|
|
10257
|
+
} : {}
|
|
10258
|
+
})),
|
|
10259
|
+
flags: Object.entries(properties).filter(([name]) => !(name in command.fixed)).map(([name, schema]) => ({
|
|
10260
|
+
...argument(
|
|
10261
|
+
name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`),
|
|
10262
|
+
schema
|
|
10263
|
+
),
|
|
10264
|
+
required: false,
|
|
10265
|
+
description: `${schema.description ?? ""}${command.tool.inputSchema.required?.includes(name) ? " (required)" : ""}`
|
|
10266
|
+
}))
|
|
10267
|
+
};
|
|
10268
|
+
};
|
|
10269
|
+
if (help) {
|
|
10270
|
+
const matches = tree.filter(
|
|
10271
|
+
(command) => command.path.startsWith(args.join(" "))
|
|
10272
|
+
);
|
|
10273
|
+
if (!matches.length)
|
|
10274
|
+
throw new CliUsageError(`Unknown command: ${args.join(" ")}`);
|
|
10275
|
+
if (!quiet)
|
|
10276
|
+
stdout.write(
|
|
10277
|
+
entry ? renderCliHelp(specFor(entry)) : `${options.bin} ${options.version}
|
|
10278
|
+
${options.description}
|
|
10279
|
+
|
|
10280
|
+
Commands:
|
|
10281
|
+
${matches.map((command) => ` ${command.path}`).join("\n")}
|
|
10282
|
+
mcp, skill, tools [--json], describe <tool>, call <tool> [JSON|-], completions <bash|zsh|fish>, version
|
|
10283
|
+
Global flags: --db <path>, --json, --quiet, --compact, --help/-h, --version/-V/-v, --no-color
|
|
10284
|
+
`
|
|
10285
|
+
);
|
|
10286
|
+
return 0;
|
|
10287
|
+
}
|
|
10288
|
+
if (args[0] === "completions") {
|
|
10289
|
+
const shell = args[1];
|
|
10290
|
+
if (!["bash", "zsh", "fish"].includes(shell ?? ""))
|
|
10291
|
+
throw new CliUsageError("Expected completion shell: bash, zsh or fish");
|
|
10292
|
+
if (quiet) return 0;
|
|
10293
|
+
const words = [
|
|
10294
|
+
...new Set(tree.flatMap((command) => command.path.split(" ")))
|
|
10295
|
+
].concat([
|
|
10296
|
+
"mcp",
|
|
10297
|
+
"skill",
|
|
10298
|
+
"tools",
|
|
10299
|
+
"describe",
|
|
10300
|
+
"call",
|
|
10301
|
+
"completions",
|
|
10302
|
+
"version"
|
|
10303
|
+
]);
|
|
10304
|
+
if (shell === "bash")
|
|
10305
|
+
stdout.write(`complete -W '${words.join(" ")}' ${options.bin}
|
|
10306
|
+
`);
|
|
10307
|
+
else if (shell === "zsh")
|
|
10308
|
+
stdout.write(
|
|
10309
|
+
`#compdef ${options.bin}
|
|
10310
|
+
_arguments '*:command:(${words.join(" ")})'
|
|
10311
|
+
`
|
|
10312
|
+
);
|
|
10313
|
+
else if (shell === "fish")
|
|
10314
|
+
stdout.write(`complete -c ${options.bin} -f -a '${words.join(" ")}'
|
|
10315
|
+
`);
|
|
10316
|
+
else
|
|
10317
|
+
throw new CliUsageError("Expected completion shell: bash, zsh or fish");
|
|
10318
|
+
return 0;
|
|
10319
|
+
}
|
|
10320
|
+
if (!entry) throw new CliUsageError(`Unknown command: ${args.join(" ")}`);
|
|
10321
|
+
const rest = args.slice(entry.path.split(" ").length);
|
|
10322
|
+
let base = {};
|
|
10323
|
+
const spec = specFor(entry);
|
|
10324
|
+
const positionalIndexes = [];
|
|
10325
|
+
let ended = false;
|
|
10326
|
+
for (let i = 0; i < rest.length; i++) {
|
|
10327
|
+
const token = rest[i];
|
|
10328
|
+
if (!ended && token === "--") {
|
|
10329
|
+
ended = true;
|
|
10330
|
+
continue;
|
|
10331
|
+
}
|
|
10332
|
+
if (!ended && token.startsWith("--")) {
|
|
10333
|
+
const flag = spec.flags?.find((arg) => `--${arg.name}` === token);
|
|
10334
|
+
if (!token.includes("=") && flag && flag.type !== "boolean") i++;
|
|
10335
|
+
} else positionalIndexes.push(i);
|
|
10336
|
+
}
|
|
10337
|
+
const stdinIndex = positionalIndexes.length === 1 && rest[positionalIndexes[0]] === "-" ? positionalIndexes[0] : void 0;
|
|
10338
|
+
if (stdinIndex !== void 0 || !rest.length && !stdin.isTTY) {
|
|
10339
|
+
if (stdinIndex !== void 0) rest.splice(stdinIndex, 1);
|
|
10340
|
+
let raw = "";
|
|
10341
|
+
for await (const chunk of stdin) raw += String(chunk);
|
|
10342
|
+
try {
|
|
10343
|
+
base = raw.trim() ? JSON.parse(raw) : {};
|
|
10344
|
+
} catch {
|
|
10345
|
+
throw new CliUsageError("Invalid stdin JSON");
|
|
10346
|
+
}
|
|
10347
|
+
if (!base || typeof base !== "object" || Array.isArray(base))
|
|
10348
|
+
throw new CliUsageError("Expected stdin JSON object");
|
|
10349
|
+
}
|
|
10350
|
+
const input = {
|
|
10351
|
+
...base,
|
|
10352
|
+
...parseCliArgs(rest, spec),
|
|
10353
|
+
...entry.fixed
|
|
10354
|
+
};
|
|
10355
|
+
const errors = validateInput(entry.tool.inputSchema, input);
|
|
10356
|
+
if (errors.length) throw new CliUsageError(errors.join("\n"));
|
|
10357
|
+
const context = await options.openContext(db === void 0 ? {} : { db });
|
|
10358
|
+
try {
|
|
10359
|
+
const result = await entry.tool.handler(context, input);
|
|
10360
|
+
const record = result;
|
|
10361
|
+
const code = record.outcome === "needs_clarification" || record.error?.code === "not_found" ? 3 : ["conflict", "version_mismatch"].includes(record.error?.code ?? "") ? 4 : record.ok === false ? 1 : 0;
|
|
10362
|
+
if (code)
|
|
10363
|
+
stderr.write(
|
|
10364
|
+
`${record.error?.message ?? record.summary ?? "Tool failed"}
|
|
10365
|
+
`
|
|
10366
|
+
);
|
|
10367
|
+
if (!quiet)
|
|
10368
|
+
stdout.write(
|
|
10369
|
+
json || !stdout.isTTY ? `${JSON.stringify(result, null, compact ? void 0 : 2)}
|
|
10370
|
+
` : `${record.summary ?? ""}
|
|
10371
|
+
${JSON.stringify(record.data ?? {}, null, 2)}
|
|
10372
|
+
`
|
|
10373
|
+
);
|
|
10374
|
+
return code;
|
|
10375
|
+
} finally {
|
|
10376
|
+
await options.closeContext?.(context);
|
|
10377
|
+
}
|
|
10378
|
+
} catch (error) {
|
|
10379
|
+
return fail(
|
|
10380
|
+
error instanceof Error ? error.message : String(error),
|
|
10381
|
+
error instanceof CliUsageError ? 2 : 70
|
|
10382
|
+
);
|
|
10383
|
+
}
|
|
10384
|
+
}
|
|
9941
10385
|
|
|
9942
10386
|
// src/surfaces/cli.ts
|
|
9943
10387
|
init_surface_types();
|
|
9944
10388
|
init_validate_input();
|
|
9945
|
-
async function runToolCli(argv,
|
|
10389
|
+
async function runToolCli(argv, providedOptions) {
|
|
10390
|
+
let options = providedOptions;
|
|
10391
|
+
const end = argv.indexOf("--");
|
|
10392
|
+
if (argv.slice(0, end < 0 ? argv.length : end).some((arg) => ["--version", "-V", "-v"].includes(arg))) {
|
|
10393
|
+
if (!argv.includes("--quiet"))
|
|
10394
|
+
(options.stdout ?? process.stdout).write(`${options.version}
|
|
10395
|
+
`);
|
|
10396
|
+
return 0;
|
|
10397
|
+
}
|
|
10398
|
+
const modern = await runCliCommand(argv, options);
|
|
10399
|
+
if (modern !== void 0) return modern;
|
|
10400
|
+
const quiet = argv.includes("--quiet");
|
|
10401
|
+
const explicitJson = argv.includes("--json") && !argv.includes("tools");
|
|
10402
|
+
if (quiet)
|
|
10403
|
+
options = {
|
|
10404
|
+
...options,
|
|
10405
|
+
stdout: new Writable({
|
|
10406
|
+
write(_chunk, _encoding, cb) {
|
|
10407
|
+
cb();
|
|
10408
|
+
}
|
|
10409
|
+
})
|
|
10410
|
+
};
|
|
9946
10411
|
const stdout = options.stdout ?? process.stdout;
|
|
9947
10412
|
const stderr = options.stderr ?? process.stderr;
|
|
9948
10413
|
const stdin = options.stdin ?? process.stdin;
|
|
9949
|
-
const args =
|
|
10414
|
+
const args = argv.filter(
|
|
10415
|
+
(arg) => arg !== "--quiet" && arg !== "--no-color" && !(explicitJson && arg === "--json")
|
|
10416
|
+
);
|
|
9950
10417
|
let db;
|
|
9951
10418
|
let compact = false;
|
|
9952
10419
|
const fail = (message, code = 2) => {
|
|
9953
10420
|
stderr.write(`${message}
|
|
9954
10421
|
`);
|
|
10422
|
+
if (explicitJson)
|
|
10423
|
+
stdout.write(
|
|
10424
|
+
`${JSON.stringify({ ok: false, error: { code: "cli_error", message } })}
|
|
10425
|
+
`
|
|
10426
|
+
);
|
|
9955
10427
|
return code;
|
|
9956
10428
|
};
|
|
9957
10429
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -9984,7 +10456,7 @@ ${options.tools.map((tool2) => `${tool2.name} ${tool2.description}`).join("\n")
|
|
|
9984
10456
|
);
|
|
9985
10457
|
return 0;
|
|
9986
10458
|
}
|
|
9987
|
-
if (["--version", "-v"].includes(command)) {
|
|
10459
|
+
if (["--version", "-V", "-v", "version"].includes(command)) {
|
|
9988
10460
|
stdout.write(`${options.version}
|
|
9989
10461
|
`);
|
|
9990
10462
|
return 0;
|
|
@@ -9992,7 +10464,9 @@ ${options.tools.map((tool2) => `${tool2.name} ${tool2.description}`).join("\n")
|
|
|
9992
10464
|
if (command === "tools") {
|
|
9993
10465
|
stdout.write(
|
|
9994
10466
|
args.includes("--json") ? `${JSON.stringify(
|
|
9995
|
-
options.tools.map(
|
|
10467
|
+
options.tools.map(
|
|
10468
|
+
({ handler: _handler, cli: _cli, ...tool2 }) => tool2
|
|
10469
|
+
),
|
|
9996
10470
|
null,
|
|
9997
10471
|
2
|
|
9998
10472
|
)}
|
|
@@ -10127,33 +10601,4 @@ ${JSON.stringify(tool.inputSchema, null, 2)}
|
|
|
10127
10601
|
}
|
|
10128
10602
|
|
|
10129
10603
|
// src/cli.ts
|
|
10130
|
-
|
|
10131
|
-
process.exitCode = await runToolCli(process.argv.slice(2), {
|
|
10132
|
-
bin: "ghostpaw-affinity",
|
|
10133
|
-
version: affinityVersion,
|
|
10134
|
-
description: "Local relationship model and social CRM",
|
|
10135
|
-
tools: affinitySurfaceTools,
|
|
10136
|
-
openContext: async (options) => {
|
|
10137
|
-
const { openAffinityDb: openAffinityDb2 } = await Promise.resolve().then(() => (init_open_affinity_db(), open_affinity_db_exports));
|
|
10138
|
-
const { resolveAffinityDbPath: resolveAffinityDbPath2 } = await Promise.resolve().then(() => (init_resolve_affinity_db_path(), resolve_affinity_db_path_exports));
|
|
10139
|
-
return openAffinityDb2(
|
|
10140
|
-
resolveAffinityDbPath2(
|
|
10141
|
-
options.db === void 0 ? {} : { flag: options.db }
|
|
10142
|
-
)
|
|
10143
|
-
);
|
|
10144
|
-
},
|
|
10145
|
-
closeContext: (db) => db.close(),
|
|
10146
|
-
commands: {
|
|
10147
|
-
mcp: async (options) => {
|
|
10148
|
-
const { serveAffinityMcp: serveAffinityMcp2 } = await Promise.resolve().then(() => (init_serve_affinity_mcp(), serve_affinity_mcp_exports));
|
|
10149
|
-
await serveAffinityMcp2(
|
|
10150
|
-
options.db === void 0 ? {} : { dbPath: options.db }
|
|
10151
|
-
);
|
|
10152
|
-
},
|
|
10153
|
-
skill: async () => {
|
|
10154
|
-
const { renderSkillMarkdown: renderSkillMarkdown2 } = await Promise.resolve().then(() => (init_skill_markdown(), skill_markdown_exports));
|
|
10155
|
-
const { affinitySkillSpec: affinitySkillSpec2 } = await Promise.resolve().then(() => (init_affinity_skill_spec(), affinity_skill_spec_exports));
|
|
10156
|
-
return renderSkillMarkdown2(affinitySkillSpec2);
|
|
10157
|
-
}
|
|
10158
|
-
}
|
|
10159
|
-
});
|
|
10604
|
+
process.exitCode = await runToolCli(process.argv.slice(2), affinityCliOptions);
|
package/dist/mcp-server.js
CHANGED
|
@@ -5,8 +5,13 @@ import { homedir } from 'node:os';
|
|
|
5
5
|
|
|
6
6
|
var __defProp = Object.defineProperty;
|
|
7
7
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
-
var __esm = (fn, res) => function __init() {
|
|
9
|
-
|
|
8
|
+
var __esm = (fn, res, err) => function __init() {
|
|
9
|
+
if (err) throw err[0];
|
|
10
|
+
try {
|
|
11
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
|
+
} catch (e) {
|
|
13
|
+
throw err = [e], e;
|
|
14
|
+
}
|
|
10
15
|
};
|
|
11
16
|
var __export = (target, all) => {
|
|
12
17
|
for (var name in all)
|
|
@@ -280,7 +285,7 @@ var init_mcp_stdio = __esm({
|
|
|
280
285
|
var affinityVersion;
|
|
281
286
|
var init_version = __esm({
|
|
282
287
|
"src/version.ts"() {
|
|
283
|
-
affinityVersion = "0.
|
|
288
|
+
affinityVersion = "0.4.0";
|
|
284
289
|
}
|
|
285
290
|
});
|
|
286
291
|
|
|
@@ -8485,7 +8490,7 @@ function affinityMcpOptions(context) {
|
|
|
8485
8490
|
annotations: (tool) => ({
|
|
8486
8491
|
readOnlyHint: tool.readOnly ?? false,
|
|
8487
8492
|
destructiveHint: tool.name === "merge_contacts" || /remove|unset|undismiss/.test(
|
|
8488
|
-
|
|
8493
|
+
String(tool.inputSchema.properties?.action?.enum ?? [])
|
|
8489
8494
|
),
|
|
8490
8495
|
idempotentHint: tool.readOnly ?? false,
|
|
8491
8496
|
openWorldHint: false
|
package/dist/mcp.cjs
CHANGED
|
@@ -5,7 +5,7 @@ var node_path = require('node:path');
|
|
|
5
5
|
var node_os = require('node:os');
|
|
6
6
|
|
|
7
7
|
// src/version.ts
|
|
8
|
-
var affinityVersion = "0.
|
|
8
|
+
var affinityVersion = "0.4.0";
|
|
9
9
|
|
|
10
10
|
// src/surface/affinity_mcp_instructions.ts
|
|
11
11
|
var affinityMcpInstructions = "Affinity is a local relationship model and social CRM. Search before creating contacts. Inspect before revising or merging. Record direct evidence honestly, keep identities and structural ties distinct from social links, and ask for clarification when a target is ambiguous. Review commitments and recurring dates regularly. Derived scores guide attention and do not prove relationship quality.";
|
|
@@ -7199,7 +7199,7 @@ function affinityMcpOptions(context) {
|
|
|
7199
7199
|
annotations: (tool) => ({
|
|
7200
7200
|
readOnlyHint: tool.readOnly ?? false,
|
|
7201
7201
|
destructiveHint: tool.name === "merge_contacts" || /remove|unset|undismiss/.test(
|
|
7202
|
-
|
|
7202
|
+
String(tool.inputSchema.properties?.action?.enum ?? [])
|
|
7203
7203
|
),
|
|
7204
7204
|
idempotentHint: tool.readOnly ?? false,
|
|
7205
7205
|
openWorldHint: false
|
package/dist/mcp.d.cts
CHANGED
|
@@ -19,6 +19,16 @@ interface JsonSchema {
|
|
|
19
19
|
}
|
|
20
20
|
interface SurfaceTool<TCtx> {
|
|
21
21
|
name: string;
|
|
22
|
+
cli?: {
|
|
23
|
+
noun?: string;
|
|
24
|
+
verb: string;
|
|
25
|
+
positionals?: string[];
|
|
26
|
+
action?: {
|
|
27
|
+
property: string;
|
|
28
|
+
verbs?: Record<string, string>;
|
|
29
|
+
positionals?: Record<string, string[]>;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
22
32
|
title?: string;
|
|
23
33
|
description: string;
|
|
24
34
|
inputSchema: JsonSchema & {
|
package/dist/mcp.d.ts
CHANGED
|
@@ -19,6 +19,16 @@ interface JsonSchema {
|
|
|
19
19
|
}
|
|
20
20
|
interface SurfaceTool<TCtx> {
|
|
21
21
|
name: string;
|
|
22
|
+
cli?: {
|
|
23
|
+
noun?: string;
|
|
24
|
+
verb: string;
|
|
25
|
+
positionals?: string[];
|
|
26
|
+
action?: {
|
|
27
|
+
property: string;
|
|
28
|
+
verbs?: Record<string, string>;
|
|
29
|
+
positionals?: Record<string, string[]>;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
22
32
|
title?: string;
|
|
23
33
|
description: string;
|
|
24
34
|
inputSchema: JsonSchema & {
|
package/dist/mcp.js
CHANGED
|
@@ -3,7 +3,7 @@ import { dirname, join } from 'node:path';
|
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
|
|
5
5
|
// src/version.ts
|
|
6
|
-
var affinityVersion = "0.
|
|
6
|
+
var affinityVersion = "0.4.0";
|
|
7
7
|
|
|
8
8
|
// src/surface/affinity_mcp_instructions.ts
|
|
9
9
|
var affinityMcpInstructions = "Affinity is a local relationship model and social CRM. Search before creating contacts. Inspect before revising or merging. Record direct evidence honestly, keep identities and structural ties distinct from social links, and ask for clarification when a target is ambiguous. Review commitments and recurring dates regularly. Derived scores guide attention and do not prove relationship quality.";
|
|
@@ -7197,7 +7197,7 @@ function affinityMcpOptions(context) {
|
|
|
7197
7197
|
annotations: (tool) => ({
|
|
7198
7198
|
readOnlyHint: tool.readOnly ?? false,
|
|
7199
7199
|
destructiveHint: tool.name === "merge_contacts" || /remove|unset|undismiss/.test(
|
|
7200
|
-
|
|
7200
|
+
String(tool.inputSchema.properties?.action?.enum ?? [])
|
|
7201
7201
|
),
|
|
7202
7202
|
idempotentHint: tool.readOnly ?? false,
|
|
7203
7203
|
openWorldHint: false
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghostpaw/affinity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Relationship intelligence and social CRM model for Node.js — SQLite, zero dependencies, with a built-in LLM tool facade",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"bugs": {
|
|
16
16
|
"url": "https://github.com/GhostPawJS/affinity/issues"
|
|
17
17
|
},
|
|
18
|
-
"homepage": "https://
|
|
18
|
+
"homepage": "https://affinity.ghostpaw.dev/",
|
|
19
19
|
"funding": {
|
|
20
20
|
"type": "github",
|
|
21
21
|
"url": "https://github.com/sponsors/Anonyfox"
|
|
@@ -50,7 +50,12 @@
|
|
|
50
50
|
"mcp-server",
|
|
51
51
|
"model-context-protocol",
|
|
52
52
|
"agent-skills",
|
|
53
|
-
"cli"
|
|
53
|
+
"cli",
|
|
54
|
+
"ghostpaw",
|
|
55
|
+
"ai-agents",
|
|
56
|
+
"llm-tools",
|
|
57
|
+
"node-sqlite",
|
|
58
|
+
"local-first"
|
|
54
59
|
],
|
|
55
60
|
"files": ["dist", "skills", "SKILL.md"],
|
|
56
61
|
"main": "./dist/index.cjs",
|
|
@@ -109,13 +114,16 @@
|
|
|
109
114
|
"check:api": "node scripts/guard/api.mjs",
|
|
110
115
|
"check:consumer": "node scripts/guard/consumer.mjs",
|
|
111
116
|
"check:startup": "node scripts/guard/startup.mjs",
|
|
112
|
-
"mcpb": "node scripts/mcpb/build.mjs"
|
|
117
|
+
"mcpb": "node scripts/mcpb/build.mjs",
|
|
118
|
+
"sea": "node scripts/sea/build.mjs",
|
|
119
|
+
"sea:smoke": "node scripts/sea/smoke.mjs",
|
|
120
|
+
"sea:bench": "node scripts/sea/bench.mjs"
|
|
113
121
|
},
|
|
114
122
|
"devDependencies": {
|
|
115
123
|
"@biomejs/biome": "^1.9.4",
|
|
116
124
|
"@types/node": "^24.12.0",
|
|
117
125
|
"@types/sql.js": "^1.4.10",
|
|
118
|
-
"esbuild": "^0.
|
|
126
|
+
"esbuild": "^0.28.1",
|
|
119
127
|
"fflate": "0.8.3",
|
|
120
128
|
"preact": "^10.29.0",
|
|
121
129
|
"sql.js": "^1.14.1",
|
|
@@ -125,5 +133,8 @@
|
|
|
125
133
|
"bin": {
|
|
126
134
|
"ghostpaw-affinity": "./dist/cli.js"
|
|
127
135
|
},
|
|
128
|
-
"mcpName": "io.github.GhostPawJS/affinity"
|
|
136
|
+
"mcpName": "io.github.GhostPawJS/affinity",
|
|
137
|
+
"overrides": {
|
|
138
|
+
"esbuild": "$esbuild"
|
|
139
|
+
}
|
|
129
140
|
}
|