@ghostpaw/affinity 0.3.0 → 0.4.1

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/dist/index.cjs CHANGED
@@ -788,13 +788,20 @@ function createContact(db, input) {
788
788
  if (!KINDS.has(input.kind)) {
789
789
  throw new AffinityValidationError("invalid contact kind");
790
790
  }
791
+ if (input.isOwner !== void 0) {
792
+ throw new AffinityValidationError(
793
+ "isOwner is read-only; pass bootstrapOwner: true to create the owner contact"
794
+ );
795
+ }
791
796
  const now = resolveNow(input.now);
792
797
  if (input.bootstrapOwner) {
793
798
  const existing = db.prepare(
794
- "SELECT id FROM contacts WHERE is_owner = 1 AND deleted_at IS NULL"
799
+ "SELECT id, name FROM contacts WHERE is_owner = 1 AND deleted_at IS NULL"
795
800
  ).get();
796
801
  if (existing !== void 0) {
797
- throw new AffinityConflictError("owner already exists");
802
+ throw new AffinityConflictError(
803
+ `owner already exists: contact ${existing.id} (${existing.name}); bootstrapOwner can only be used once`
804
+ );
798
805
  }
799
806
  }
800
807
  const isOwner = input.bootstrapOwner ? 1 : 0;
@@ -6453,17 +6460,6 @@ function unsetAttribute(db, target, name, removedAt) {
6453
6460
  });
6454
6461
  }
6455
6462
 
6456
- // src/tools/tool_summary.ts
6457
- function summarizeCount(count, singular) {
6458
- if (count === 0) {
6459
- return `No ${singular}s found.`;
6460
- }
6461
- if (count === 1) {
6462
- return `Found 1 ${singular}.`;
6463
- }
6464
- return `Found ${count} ${singular}s.`;
6465
- }
6466
-
6467
6463
  // src/tools/tool_mutation.ts
6468
6464
  function mutationToolResult(action, receipt, entitySummary) {
6469
6465
  const changedCount = receipt.created.length + receipt.updated.length + receipt.archived.length + receipt.removed.length;
@@ -6477,7 +6473,7 @@ function mutationToolResult(action, receipt, entitySummary) {
6477
6473
  affectedLinks: receipt.affectedLinks,
6478
6474
  derivedEffects: receipt.derivedEffects
6479
6475
  };
6480
- const summary = changedCount === 0 ? `No ${entitySummary} changes were needed.` : summarizeCount(changedCount, `${entitySummary} change`);
6476
+ const summary = changedCount === 0 ? `No ${entitySummary} changes were needed.` : describeChanges(receipt);
6481
6477
  if (changedCount === 0) {
6482
6478
  return toolNoOp(summary, data, {
6483
6479
  entities: toReceiptEntityRefs(receipt)
@@ -6487,6 +6483,23 @@ function mutationToolResult(action, receipt, entitySummary) {
6487
6483
  entities: toReceiptEntityRefs(receipt)
6488
6484
  });
6489
6485
  }
6486
+ var PLURALS = { identity: "identities" };
6487
+ function describeChanges(receipt) {
6488
+ const parts = [];
6489
+ for (const verb of ["created", "updated", "archived", "removed"]) {
6490
+ const counts = /* @__PURE__ */ new Map();
6491
+ for (const ref of receipt[verb]) {
6492
+ counts.set(ref.kind, (counts.get(ref.kind) ?? 0) + 1);
6493
+ }
6494
+ if (counts.size === 0) continue;
6495
+ const items = [...counts].map(
6496
+ ([kind, n]) => `${n} ${n === 1 ? kind : PLURALS[kind] ?? `${kind}s`}`
6497
+ );
6498
+ parts.push(`${verb} ${items.join(", ")}`);
6499
+ }
6500
+ const text = parts.join("; ");
6501
+ return `${text.charAt(0).toUpperCase()}${text.slice(1)}.`;
6502
+ }
6490
6503
 
6491
6504
  // src/tools/manage_attribute_tool.ts
6492
6505
  function contactLocatorSchema2(description) {
@@ -7868,6 +7881,17 @@ var recordEventTool = defineAffinityTool({
7868
7881
  handler: recordEventToolHandler
7869
7882
  });
7870
7883
 
7884
+ // src/tools/tool_summary.ts
7885
+ function summarizeCount(count, singular) {
7886
+ if (count === 0) {
7887
+ return `No ${singular}s found.`;
7888
+ }
7889
+ if (count === 1) {
7890
+ return `Found 1 ${singular}.`;
7891
+ }
7892
+ return `Found ${count} ${singular}s.`;
7893
+ }
7894
+
7871
7895
  // src/tools/review_affinity_tool.ts
7872
7896
  function buildReadOptions(input) {
7873
7897
  return {
package/dist/index.js CHANGED
@@ -786,13 +786,20 @@ function createContact(db, input) {
786
786
  if (!KINDS.has(input.kind)) {
787
787
  throw new AffinityValidationError("invalid contact kind");
788
788
  }
789
+ if (input.isOwner !== void 0) {
790
+ throw new AffinityValidationError(
791
+ "isOwner is read-only; pass bootstrapOwner: true to create the owner contact"
792
+ );
793
+ }
789
794
  const now = resolveNow(input.now);
790
795
  if (input.bootstrapOwner) {
791
796
  const existing = db.prepare(
792
- "SELECT id FROM contacts WHERE is_owner = 1 AND deleted_at IS NULL"
797
+ "SELECT id, name FROM contacts WHERE is_owner = 1 AND deleted_at IS NULL"
793
798
  ).get();
794
799
  if (existing !== void 0) {
795
- throw new AffinityConflictError("owner already exists");
800
+ throw new AffinityConflictError(
801
+ `owner already exists: contact ${existing.id} (${existing.name}); bootstrapOwner can only be used once`
802
+ );
796
803
  }
797
804
  }
798
805
  const isOwner = input.bootstrapOwner ? 1 : 0;
@@ -6451,17 +6458,6 @@ function unsetAttribute(db, target, name, removedAt) {
6451
6458
  });
6452
6459
  }
6453
6460
 
6454
- // src/tools/tool_summary.ts
6455
- function summarizeCount(count, singular) {
6456
- if (count === 0) {
6457
- return `No ${singular}s found.`;
6458
- }
6459
- if (count === 1) {
6460
- return `Found 1 ${singular}.`;
6461
- }
6462
- return `Found ${count} ${singular}s.`;
6463
- }
6464
-
6465
6461
  // src/tools/tool_mutation.ts
6466
6462
  function mutationToolResult(action, receipt, entitySummary) {
6467
6463
  const changedCount = receipt.created.length + receipt.updated.length + receipt.archived.length + receipt.removed.length;
@@ -6475,7 +6471,7 @@ function mutationToolResult(action, receipt, entitySummary) {
6475
6471
  affectedLinks: receipt.affectedLinks,
6476
6472
  derivedEffects: receipt.derivedEffects
6477
6473
  };
6478
- const summary = changedCount === 0 ? `No ${entitySummary} changes were needed.` : summarizeCount(changedCount, `${entitySummary} change`);
6474
+ const summary = changedCount === 0 ? `No ${entitySummary} changes were needed.` : describeChanges(receipt);
6479
6475
  if (changedCount === 0) {
6480
6476
  return toolNoOp(summary, data, {
6481
6477
  entities: toReceiptEntityRefs(receipt)
@@ -6485,6 +6481,23 @@ function mutationToolResult(action, receipt, entitySummary) {
6485
6481
  entities: toReceiptEntityRefs(receipt)
6486
6482
  });
6487
6483
  }
6484
+ var PLURALS = { identity: "identities" };
6485
+ function describeChanges(receipt) {
6486
+ const parts = [];
6487
+ for (const verb of ["created", "updated", "archived", "removed"]) {
6488
+ const counts = /* @__PURE__ */ new Map();
6489
+ for (const ref of receipt[verb]) {
6490
+ counts.set(ref.kind, (counts.get(ref.kind) ?? 0) + 1);
6491
+ }
6492
+ if (counts.size === 0) continue;
6493
+ const items = [...counts].map(
6494
+ ([kind, n]) => `${n} ${n === 1 ? kind : PLURALS[kind] ?? `${kind}s`}`
6495
+ );
6496
+ parts.push(`${verb} ${items.join(", ")}`);
6497
+ }
6498
+ const text = parts.join("; ");
6499
+ return `${text.charAt(0).toUpperCase()}${text.slice(1)}.`;
6500
+ }
6488
6501
 
6489
6502
  // src/tools/manage_attribute_tool.ts
6490
6503
  function contactLocatorSchema2(description) {
@@ -7866,6 +7879,17 @@ var recordEventTool = defineAffinityTool({
7866
7879
  handler: recordEventToolHandler
7867
7880
  });
7868
7881
 
7882
+ // src/tools/tool_summary.ts
7883
+ function summarizeCount(count, singular) {
7884
+ if (count === 0) {
7885
+ return `No ${singular}s found.`;
7886
+ }
7887
+ if (count === 1) {
7888
+ return `Found 1 ${singular}.`;
7889
+ }
7890
+ return `Found ${count} ${singular}s.`;
7891
+ }
7892
+
7869
7893
  // src/tools/review_affinity_tool.ts
7870
7894
  function buildReadOptions(input) {
7871
7895
  return {
@@ -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.1";
284
289
  }
285
290
  });
286
291
 
@@ -2757,21 +2762,6 @@ var init_unset_attribute = __esm({
2757
2762
  }
2758
2763
  });
2759
2764
 
2760
- // src/tools/tool_summary.ts
2761
- function summarizeCount(count, singular) {
2762
- if (count === 0) {
2763
- return `No ${singular}s found.`;
2764
- }
2765
- if (count === 1) {
2766
- return `Found 1 ${singular}.`;
2767
- }
2768
- return `Found ${count} ${singular}s.`;
2769
- }
2770
- var init_tool_summary = __esm({
2771
- "src/tools/tool_summary.ts"() {
2772
- }
2773
- });
2774
-
2775
2765
  // src/tools/tool_mutation.ts
2776
2766
  function mutationToolResult(action, receipt, entitySummary) {
2777
2767
  const changedCount = receipt.created.length + receipt.updated.length + receipt.archived.length + receipt.removed.length;
@@ -2785,7 +2775,7 @@ function mutationToolResult(action, receipt, entitySummary) {
2785
2775
  affectedLinks: receipt.affectedLinks,
2786
2776
  derivedEffects: receipt.derivedEffects
2787
2777
  };
2788
- const summary = changedCount === 0 ? `No ${entitySummary} changes were needed.` : summarizeCount(changedCount, `${entitySummary} change`);
2778
+ const summary = changedCount === 0 ? `No ${entitySummary} changes were needed.` : describeChanges(receipt);
2789
2779
  if (changedCount === 0) {
2790
2780
  return toolNoOp(summary, data, {
2791
2781
  entities: toReceiptEntityRefs(receipt)
@@ -2795,11 +2785,28 @@ function mutationToolResult(action, receipt, entitySummary) {
2795
2785
  entities: toReceiptEntityRefs(receipt)
2796
2786
  });
2797
2787
  }
2788
+ function describeChanges(receipt) {
2789
+ const parts = [];
2790
+ for (const verb of ["created", "updated", "archived", "removed"]) {
2791
+ const counts = /* @__PURE__ */ new Map();
2792
+ for (const ref of receipt[verb]) {
2793
+ counts.set(ref.kind, (counts.get(ref.kind) ?? 0) + 1);
2794
+ }
2795
+ if (counts.size === 0) continue;
2796
+ const items = [...counts].map(
2797
+ ([kind, n]) => `${n} ${n === 1 ? kind : PLURALS[kind] ?? `${kind}s`}`
2798
+ );
2799
+ parts.push(`${verb} ${items.join(", ")}`);
2800
+ }
2801
+ const text = parts.join("; ");
2802
+ return `${text.charAt(0).toUpperCase()}${text.slice(1)}.`;
2803
+ }
2804
+ var PLURALS;
2798
2805
  var init_tool_mutation = __esm({
2799
2806
  "src/tools/tool_mutation.ts"() {
2800
2807
  init_tool_ref();
2801
- init_tool_summary();
2802
2808
  init_tool_types();
2809
+ PLURALS = { identity: "identities" };
2803
2810
  }
2804
2811
  });
2805
2812
 
@@ -4561,13 +4568,20 @@ function createContact(db, input) {
4561
4568
  if (!KINDS.has(input.kind)) {
4562
4569
  throw new AffinityValidationError("invalid contact kind");
4563
4570
  }
4571
+ if (input.isOwner !== void 0) {
4572
+ throw new AffinityValidationError(
4573
+ "isOwner is read-only; pass bootstrapOwner: true to create the owner contact"
4574
+ );
4575
+ }
4564
4576
  const now = resolveNow(input.now);
4565
4577
  if (input.bootstrapOwner) {
4566
4578
  const existing = db.prepare(
4567
- "SELECT id FROM contacts WHERE is_owner = 1 AND deleted_at IS NULL"
4579
+ "SELECT id, name FROM contacts WHERE is_owner = 1 AND deleted_at IS NULL"
4568
4580
  ).get();
4569
4581
  if (existing !== void 0) {
4570
- throw new AffinityConflictError("owner already exists");
4582
+ throw new AffinityConflictError(
4583
+ `owner already exists: contact ${existing.id} (${existing.name}); bootstrapOwner can only be used once`
4584
+ );
4571
4585
  }
4572
4586
  }
4573
4587
  const isOwner = input.bootstrapOwner ? 1 : 0;
@@ -7670,6 +7684,21 @@ var init_list_dismissed_duplicates = __esm({
7670
7684
  }
7671
7685
  });
7672
7686
 
7687
+ // src/tools/tool_summary.ts
7688
+ function summarizeCount(count, singular) {
7689
+ if (count === 0) {
7690
+ return `No ${singular}s found.`;
7691
+ }
7692
+ if (count === 1) {
7693
+ return `Found 1 ${singular}.`;
7694
+ }
7695
+ return `Found ${count} ${singular}s.`;
7696
+ }
7697
+ var init_tool_summary = __esm({
7698
+ "src/tools/tool_summary.ts"() {
7699
+ }
7700
+ });
7701
+
7673
7702
  // src/tools/review_affinity_tool.ts
7674
7703
  function buildReadOptions(input) {
7675
7704
  return {
@@ -8485,7 +8514,7 @@ function affinityMcpOptions(context) {
8485
8514
  annotations: (tool) => ({
8486
8515
  readOnlyHint: tool.readOnly ?? false,
8487
8516
  destructiveHint: tool.name === "merge_contacts" || /remove|unset|undismiss/.test(
8488
- JSON.stringify(tool.inputSchema.properties?.action?.enum ?? [])
8517
+ String(tool.inputSchema.properties?.action?.enum ?? [])
8489
8518
  ),
8490
8519
  idempotentHint: tool.readOnly ?? false,
8491
8520
  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.1";
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.";
@@ -2160,17 +2160,6 @@ function unsetAttribute(db, target, name, removedAt) {
2160
2160
  });
2161
2161
  }
2162
2162
 
2163
- // src/tools/tool_summary.ts
2164
- function summarizeCount(count, singular) {
2165
- if (count === 0) {
2166
- return `No ${singular}s found.`;
2167
- }
2168
- if (count === 1) {
2169
- return `Found 1 ${singular}.`;
2170
- }
2171
- return `Found ${count} ${singular}s.`;
2172
- }
2173
-
2174
2163
  // src/tools/tool_mutation.ts
2175
2164
  function mutationToolResult(action, receipt, entitySummary) {
2176
2165
  const changedCount = receipt.created.length + receipt.updated.length + receipt.archived.length + receipt.removed.length;
@@ -2184,7 +2173,7 @@ function mutationToolResult(action, receipt, entitySummary) {
2184
2173
  affectedLinks: receipt.affectedLinks,
2185
2174
  derivedEffects: receipt.derivedEffects
2186
2175
  };
2187
- const summary = changedCount === 0 ? `No ${entitySummary} changes were needed.` : summarizeCount(changedCount, `${entitySummary} change`);
2176
+ const summary = changedCount === 0 ? `No ${entitySummary} changes were needed.` : describeChanges(receipt);
2188
2177
  if (changedCount === 0) {
2189
2178
  return toolNoOp(summary, data, {
2190
2179
  entities: toReceiptEntityRefs(receipt)
@@ -2194,6 +2183,23 @@ function mutationToolResult(action, receipt, entitySummary) {
2194
2183
  entities: toReceiptEntityRefs(receipt)
2195
2184
  });
2196
2185
  }
2186
+ var PLURALS = { identity: "identities" };
2187
+ function describeChanges(receipt) {
2188
+ const parts = [];
2189
+ for (const verb of ["created", "updated", "archived", "removed"]) {
2190
+ const counts = /* @__PURE__ */ new Map();
2191
+ for (const ref of receipt[verb]) {
2192
+ counts.set(ref.kind, (counts.get(ref.kind) ?? 0) + 1);
2193
+ }
2194
+ if (counts.size === 0) continue;
2195
+ const items = [...counts].map(
2196
+ ([kind, n]) => `${n} ${n === 1 ? kind : PLURALS[kind] ?? `${kind}s`}`
2197
+ );
2198
+ parts.push(`${verb} ${items.join(", ")}`);
2199
+ }
2200
+ const text = parts.join("; ");
2201
+ return `${text.charAt(0).toUpperCase()}${text.slice(1)}.`;
2202
+ }
2197
2203
 
2198
2204
  // src/tools/manage_attribute_tool.ts
2199
2205
  function contactLocatorSchema2(description) {
@@ -3825,13 +3831,20 @@ function createContact(db, input) {
3825
3831
  if (!KINDS.has(input.kind)) {
3826
3832
  throw new AffinityValidationError("invalid contact kind");
3827
3833
  }
3834
+ if (input.isOwner !== void 0) {
3835
+ throw new AffinityValidationError(
3836
+ "isOwner is read-only; pass bootstrapOwner: true to create the owner contact"
3837
+ );
3838
+ }
3828
3839
  const now = resolveNow(input.now);
3829
3840
  if (input.bootstrapOwner) {
3830
3841
  const existing = db.prepare(
3831
- "SELECT id FROM contacts WHERE is_owner = 1 AND deleted_at IS NULL"
3842
+ "SELECT id, name FROM contacts WHERE is_owner = 1 AND deleted_at IS NULL"
3832
3843
  ).get();
3833
3844
  if (existing !== void 0) {
3834
- throw new AffinityConflictError("owner already exists");
3845
+ throw new AffinityConflictError(
3846
+ `owner already exists: contact ${existing.id} (${existing.name}); bootstrapOwner can only be used once`
3847
+ );
3835
3848
  }
3836
3849
  }
3837
3850
  const isOwner = input.bootstrapOwner ? 1 : 0;
@@ -6451,6 +6464,17 @@ function listDismissedDuplicates(db, options) {
6451
6464
  }));
6452
6465
  }
6453
6466
 
6467
+ // src/tools/tool_summary.ts
6468
+ function summarizeCount(count, singular) {
6469
+ if (count === 0) {
6470
+ return `No ${singular}s found.`;
6471
+ }
6472
+ if (count === 1) {
6473
+ return `Found 1 ${singular}.`;
6474
+ }
6475
+ return `Found ${count} ${singular}s.`;
6476
+ }
6477
+
6454
6478
  // src/tools/review_affinity_tool.ts
6455
6479
  function buildReadOptions(input) {
6456
6480
  return {
@@ -7199,7 +7223,7 @@ function affinityMcpOptions(context) {
7199
7223
  annotations: (tool) => ({
7200
7224
  readOnlyHint: tool.readOnly ?? false,
7201
7225
  destructiveHint: tool.name === "merge_contacts" || /remove|unset|undismiss/.test(
7202
- JSON.stringify(tool.inputSchema.properties?.action?.enum ?? [])
7226
+ String(tool.inputSchema.properties?.action?.enum ?? [])
7203
7227
  ),
7204
7228
  idempotentHint: tool.readOnly ?? false,
7205
7229
  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.1";
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.";
@@ -2158,17 +2158,6 @@ function unsetAttribute(db, target, name, removedAt) {
2158
2158
  });
2159
2159
  }
2160
2160
 
2161
- // src/tools/tool_summary.ts
2162
- function summarizeCount(count, singular) {
2163
- if (count === 0) {
2164
- return `No ${singular}s found.`;
2165
- }
2166
- if (count === 1) {
2167
- return `Found 1 ${singular}.`;
2168
- }
2169
- return `Found ${count} ${singular}s.`;
2170
- }
2171
-
2172
2161
  // src/tools/tool_mutation.ts
2173
2162
  function mutationToolResult(action, receipt, entitySummary) {
2174
2163
  const changedCount = receipt.created.length + receipt.updated.length + receipt.archived.length + receipt.removed.length;
@@ -2182,7 +2171,7 @@ function mutationToolResult(action, receipt, entitySummary) {
2182
2171
  affectedLinks: receipt.affectedLinks,
2183
2172
  derivedEffects: receipt.derivedEffects
2184
2173
  };
2185
- const summary = changedCount === 0 ? `No ${entitySummary} changes were needed.` : summarizeCount(changedCount, `${entitySummary} change`);
2174
+ const summary = changedCount === 0 ? `No ${entitySummary} changes were needed.` : describeChanges(receipt);
2186
2175
  if (changedCount === 0) {
2187
2176
  return toolNoOp(summary, data, {
2188
2177
  entities: toReceiptEntityRefs(receipt)
@@ -2192,6 +2181,23 @@ function mutationToolResult(action, receipt, entitySummary) {
2192
2181
  entities: toReceiptEntityRefs(receipt)
2193
2182
  });
2194
2183
  }
2184
+ var PLURALS = { identity: "identities" };
2185
+ function describeChanges(receipt) {
2186
+ const parts = [];
2187
+ for (const verb of ["created", "updated", "archived", "removed"]) {
2188
+ const counts = /* @__PURE__ */ new Map();
2189
+ for (const ref of receipt[verb]) {
2190
+ counts.set(ref.kind, (counts.get(ref.kind) ?? 0) + 1);
2191
+ }
2192
+ if (counts.size === 0) continue;
2193
+ const items = [...counts].map(
2194
+ ([kind, n]) => `${n} ${n === 1 ? kind : PLURALS[kind] ?? `${kind}s`}`
2195
+ );
2196
+ parts.push(`${verb} ${items.join(", ")}`);
2197
+ }
2198
+ const text = parts.join("; ");
2199
+ return `${text.charAt(0).toUpperCase()}${text.slice(1)}.`;
2200
+ }
2195
2201
 
2196
2202
  // src/tools/manage_attribute_tool.ts
2197
2203
  function contactLocatorSchema2(description) {
@@ -3823,13 +3829,20 @@ function createContact(db, input) {
3823
3829
  if (!KINDS.has(input.kind)) {
3824
3830
  throw new AffinityValidationError("invalid contact kind");
3825
3831
  }
3832
+ if (input.isOwner !== void 0) {
3833
+ throw new AffinityValidationError(
3834
+ "isOwner is read-only; pass bootstrapOwner: true to create the owner contact"
3835
+ );
3836
+ }
3826
3837
  const now = resolveNow(input.now);
3827
3838
  if (input.bootstrapOwner) {
3828
3839
  const existing = db.prepare(
3829
- "SELECT id FROM contacts WHERE is_owner = 1 AND deleted_at IS NULL"
3840
+ "SELECT id, name FROM contacts WHERE is_owner = 1 AND deleted_at IS NULL"
3830
3841
  ).get();
3831
3842
  if (existing !== void 0) {
3832
- throw new AffinityConflictError("owner already exists");
3843
+ throw new AffinityConflictError(
3844
+ `owner already exists: contact ${existing.id} (${existing.name}); bootstrapOwner can only be used once`
3845
+ );
3833
3846
  }
3834
3847
  }
3835
3848
  const isOwner = input.bootstrapOwner ? 1 : 0;
@@ -6449,6 +6462,17 @@ function listDismissedDuplicates(db, options) {
6449
6462
  }));
6450
6463
  }
6451
6464
 
6465
+ // src/tools/tool_summary.ts
6466
+ function summarizeCount(count, singular) {
6467
+ if (count === 0) {
6468
+ return `No ${singular}s found.`;
6469
+ }
6470
+ if (count === 1) {
6471
+ return `Found 1 ${singular}.`;
6472
+ }
6473
+ return `Found ${count} ${singular}s.`;
6474
+ }
6475
+
6452
6476
  // src/tools/review_affinity_tool.ts
6453
6477
  function buildReadOptions(input) {
6454
6478
  return {
@@ -7197,7 +7221,7 @@ function affinityMcpOptions(context) {
7197
7221
  annotations: (tool) => ({
7198
7222
  readOnlyHint: tool.readOnly ?? false,
7199
7223
  destructiveHint: tool.name === "merge_contacts" || /remove|unset|undismiss/.test(
7200
- JSON.stringify(tool.inputSchema.properties?.action?.enum ?? [])
7224
+ String(tool.inputSchema.properties?.action?.enum ?? [])
7201
7225
  ),
7202
7226
  idempotentHint: tool.readOnly ?? false,
7203
7227
  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.1",
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
  }