@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 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
- [![npm](https://img.shields.io/npm/v/@ghostpaw/affinity)](https://www.npmjs.com/package/@ghostpaw/affinity)
4
- [![node](https://img.shields.io/node/v/@ghostpaw/affinity)](https://nodejs.org)
5
- [![license](https://img.shields.io/npm/l/@ghostpaw/affinity)](LICENSE)
6
- [![dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](package.json)
7
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.8-blue?logo=typescript&logoColor=white)](https://www.typescriptlang.org)
8
- [![MCP Registry](https://img.shields.io/badge/MCP_Registry-io.github.GhostPawJS%2Faffinity-06d6a0)](https://registry.modelcontextprotocol.io/v0.1/servers/io.github.GhostPawJS%2Faffinity/versions/latest)
5
+ [![npm](https://img.shields.io/npm/v/@ghostpaw/affinity?style=flat-square&labelColor=0a1119&color=62e6f2&label=npm)](https://www.npmjs.com/package/@ghostpaw/affinity) [![CI](https://img.shields.io/github/actions/workflow/status/GhostPawJS/affinity/ci.yml?branch=main&style=flat-square&labelColor=0a1119&label=CI)](https://github.com/GhostPawJS/affinity/actions/workflows/ci.yml) [![site](https://img.shields.io/badge/site-affinity.ghostpaw.dev-62e6f2?style=flat-square&labelColor=0a1119)](https://affinity.ghostpaw.dev/) [![runtime deps](https://img.shields.io/badge/runtime_deps-0-7af0b4?style=flat-square&labelColor=0a1119)](package.json) [![MCP Registry](https://img.shields.io/badge/MCP_Registry-io.github.GhostPawJS%2Faffinity-b39dff?style=flat-square&labelColor=0a1119)](https://registry.modelcontextprotocol.io/v0.1/servers/io.github.GhostPawJS%2Faffinity/versions/latest) [![node](https://img.shields.io/badge/node-%E2%89%A524-93a5b9?style=flat-square&labelColor=0a1119)](https://nodejs.org) [![license](https://img.shields.io/npm/l/@ghostpaw/affinity?style=flat-square&labelColor=0a1119&color=93a5b9)](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
- ```bash
92
- npm install @ghostpaw/affinity
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
- Requires **Node.js 24+** (uses the built-in `node:sqlite` module).
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
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
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
- // src/surfaces/surface_types.ts
8198
- function isErrorResult(result) {
8199
- return typeof result === "object" && result !== null && "ok" in result && result.ok === false;
8200
- }
8201
- var init_surface_types = __esm({
8202
- "src/surfaces/surface_types.ts"() {
8203
- }
8204
- });
8205
-
8206
- // src/surfaces/validate_input.ts
8207
- function validateInput(schema, input, path = "$") {
8208
- const errors = [];
8209
- const matchesType = (type) => {
8210
- switch (type) {
8211
- case "object":
8212
- return typeof input === "object" && input !== null && !Array.isArray(input);
8213
- case "array":
8214
- return Array.isArray(input);
8215
- case "integer":
8216
- return typeof input === "number" && Number.isInteger(input);
8217
- case "number":
8218
- return typeof input === "number" && Number.isFinite(input);
8219
- case "null":
8220
- return input === null;
8221
- default:
8222
- return typeof input === type;
8223
- }
8224
- };
8225
- if (schema.type && !(Array.isArray(schema.type) ? schema.type : [schema.type]).some(
8226
- matchesType
8227
- )) {
8228
- return [`${path}: expected ${String(schema.type)}`];
8229
- }
8230
- if (schema.enum && !schema.enum.some(
8231
- (value) => JSON.stringify(value) === JSON.stringify(input)
8232
- ))
8233
- errors.push(`${path}: expected one of ${JSON.stringify(schema.enum)}`);
8234
- if (schema.oneOf && !schema.oneOf.some(
8235
- (branch) => validateInput(branch, input, path).length === 0
8236
- ))
8237
- errors.push(`${path}: must match at least one schema branch`);
8238
- if (typeof input === "object" && input !== null && !Array.isArray(input)) {
8239
- const record = input;
8240
- for (const name of schema.required ?? [])
8241
- if (!Object.hasOwn(record, name))
8242
- errors.push(`${path}.${name}: required`);
8243
- for (const [name, property] of Object.entries(schema.properties ?? {}))
8244
- if (Object.hasOwn(record, name))
8245
- errors.push(
8246
- ...validateInput(property, record[name], `${path}.${name}`)
8247
- );
8248
- }
8249
- if (Array.isArray(input) && schema.items)
8250
- input.forEach((value, index) => {
8251
- errors.push(
8252
- ...validateInput(
8253
- schema.items,
8254
- value,
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
- JSON.stringify(tool.inputSchema.properties?.action?.enum ?? [])
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: affinitySurfaceTools,
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/cli.ts
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, options) {
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 = [...argv];
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(({ handler: _handler, ...tool2 }) => tool2),
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
- init_version();
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);
@@ -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
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
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.3.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
- JSON.stringify(tool.inputSchema.properties?.action?.enum ?? [])
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.3.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
- JSON.stringify(tool.inputSchema.properties?.action?.enum ?? [])
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.3.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
- JSON.stringify(tool.inputSchema.properties?.action?.enum ?? [])
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.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://github.com/GhostPawJS/affinity",
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.27.4",
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
  }