@hasna/contacts 0.7.0 → 0.9.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.
Files changed (85) hide show
  1. package/README.md +93 -13
  2. package/dist/browser/cli.d.ts +4 -0
  3. package/dist/browser/cli.d.ts.map +1 -0
  4. package/dist/browser/identity.d.ts +3 -0
  5. package/dist/browser/identity.d.ts.map +1 -0
  6. package/dist/browser/install.d.ts +32 -0
  7. package/dist/browser/install.d.ts.map +1 -0
  8. package/dist/browser/native.d.ts +3 -0
  9. package/dist/browser/native.d.ts.map +1 -0
  10. package/dist/browser/native.js +822 -0
  11. package/dist/browser/protocol.d.ts +26 -0
  12. package/dist/browser/protocol.d.ts.map +1 -0
  13. package/dist/browser/values.d.ts +18 -0
  14. package/dist/browser/values.d.ts.map +1 -0
  15. package/dist/cli/commands/core.d.ts.map +1 -1
  16. package/dist/cli/index.js +479 -149
  17. package/dist/cli/status-domain.preload.d.ts +1 -1
  18. package/dist/cli/status-domain.preload.d.ts.map +1 -1
  19. package/dist/cli/status-fixture.d.ts +17 -0
  20. package/dist/cli/status-fixture.d.ts.map +1 -0
  21. package/dist/cloud/http-storage.d.ts +20 -12
  22. package/dist/cloud/http-storage.d.ts.map +1 -1
  23. package/dist/cloud/resolver-inputs.d.ts +51 -0
  24. package/dist/cloud/resolver-inputs.d.ts.map +1 -0
  25. package/dist/db/paths.d.ts +4 -4
  26. package/dist/db/paths.d.ts.map +1 -1
  27. package/dist/generated/storage-kit/backend.d.ts +19 -0
  28. package/dist/generated/storage-kit/backend.d.ts.map +1 -0
  29. package/dist/generated/storage-kit/index.d.ts +2 -1
  30. package/dist/generated/storage-kit/index.d.ts.map +1 -1
  31. package/dist/generated/storage-kit/migrations.d.ts +21 -0
  32. package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
  33. package/dist/generated/storage-kit/own.d.ts +11 -0
  34. package/dist/generated/storage-kit/own.d.ts.map +1 -0
  35. package/dist/generated/storage-kit/pool.d.ts +5 -17
  36. package/dist/generated/storage-kit/pool.d.ts.map +1 -1
  37. package/dist/generated/storage-kit/query.d.ts +1 -1
  38. package/dist/generated/storage-kit/query.d.ts.map +1 -1
  39. package/dist/generated/storage-kit/tls.d.ts +30 -3
  40. package/dist/generated/storage-kit/tls.d.ts.map +1 -1
  41. package/dist/index.d.ts +2 -2
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +148 -28
  44. package/dist/lib/audience-contract.d.ts +9 -9
  45. package/dist/lib/compact-output.d.ts +67 -0
  46. package/dist/lib/compact-output.d.ts.map +1 -0
  47. package/dist/mcp/handlers/core.d.ts.map +1 -1
  48. package/dist/mcp/index.d.ts +11 -1
  49. package/dist/mcp/index.d.ts.map +1 -1
  50. package/dist/mcp/index.js +490 -212
  51. package/dist/mcp/profile.d.ts +8 -0
  52. package/dist/mcp/profile.d.ts.map +1 -0
  53. package/dist/mcp/register-tools.d.ts +3 -1
  54. package/dist/mcp/register-tools.d.ts.map +1 -1
  55. package/dist/mcp/startup-gate.d.ts +48 -0
  56. package/dist/mcp/startup-gate.d.ts.map +1 -0
  57. package/dist/mcp/storage-tools.d.ts +2 -1
  58. package/dist/mcp/storage-tools.d.ts.map +1 -1
  59. package/dist/mcp/tools.d.ts +7 -45
  60. package/dist/mcp/tools.d.ts.map +1 -1
  61. package/dist/sdk/index.d.ts +70 -3
  62. package/dist/sdk/index.d.ts.map +1 -1
  63. package/dist/sdk/index.js +990 -7
  64. package/dist/server/index.js +231 -59
  65. package/dist/server/pg-store.d.ts +1 -0
  66. package/dist/server/pg-store.d.ts.map +1 -1
  67. package/dist/store/index.d.ts.map +1 -1
  68. package/docs/chrome-autofill.md +45 -0
  69. package/extension/background.js +284 -0
  70. package/extension/content.js +387 -0
  71. package/extension/detect.js +228 -0
  72. package/extension/fill.js +112 -0
  73. package/extension/icons/128.png +0 -0
  74. package/extension/icons/16.png +0 -0
  75. package/extension/icons/32.png +0 -0
  76. package/extension/icons/48.png +0 -0
  77. package/extension/icons/icon.svg +17 -0
  78. package/extension/manifest.json +54 -0
  79. package/extension/popup.css +78 -0
  80. package/extension/popup.html +47 -0
  81. package/extension/popup.js +166 -0
  82. package/hasna.contract.json +20 -6
  83. package/package.json +10 -6
  84. package/dist/lib/config.d.ts +0 -7
  85. package/dist/lib/config.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // @bun
2
2
  // src/cloud/http-storage.ts
3
3
  import {
4
+ ClientTransportConfigurationError,
4
5
  createHasnaHttpTransport,
5
6
  resolveCredential,
6
7
  resolveClientTransport as resolveSharedClientTransport
@@ -90,6 +91,27 @@ function assertConfigurationUnchanged(env, expected) {
90
91
  invalid();
91
92
  }
92
93
 
94
+ // src/cloud/resolver-inputs.ts
95
+ import {
96
+ clientTransportEnvKeys as clientTransportEnvKeys2,
97
+ credentialOverrideEnvKey,
98
+ credentialPointerEnvKey,
99
+ CREDENTIAL_PROFILE_ENV_KEY
100
+ } from "@hasna/contracts/client";
101
+ var CONTRACTS_AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
102
+ function isAmbientContactsEnv(env) {
103
+ if (typeof process !== "undefined" && env === process.env)
104
+ return true;
105
+ return env[CONTRACTS_AMBIENT_ENVIRONMENT] === true;
106
+ }
107
+ function contactsResolverCredentials(env, credentials = {}) {
108
+ const keychain = { ...credentials.keychain };
109
+ if (keychain.enabled === undefined && keychain.run === undefined) {
110
+ keychain.enabled = isAmbientContactsEnv(env);
111
+ }
112
+ return { ...credentials, keychain };
113
+ }
114
+
93
115
  // src/cloud/http-storage.ts
94
116
  var RETIRED_CLIENT_SELECTOR_KEYS = [
95
117
  "HASNA_CONTACTS_STORAGE_MODE",
@@ -125,37 +147,44 @@ class ContactsClientConfigurationError extends Error {
125
147
  this.name = "ContactsClientConfigurationError";
126
148
  }
127
149
  }
128
- function unconfiguredResolution(resolution, issue) {
150
+ function unconfiguredResolution(issue, resolution, warning = null) {
129
151
  return {
130
152
  transport: "unconfigured",
131
153
  baseUrl: null,
132
- apiUrlSource: resolution.apiUrlSource,
133
- apiKeyPresent: resolution.apiKeyPresent,
134
- apiKeySource: resolution.apiKeySource,
135
- apiKeyTier: resolution.apiKeyTier,
154
+ apiUrlSource: resolution?.apiUrlSource ?? null,
155
+ apiKeyPresent: resolution?.apiKeyPresent ?? false,
156
+ apiKeySource: resolution?.apiKeySource ?? null,
157
+ apiKeyTier: resolution?.apiKeyTier ?? null,
136
158
  configured: false,
137
159
  misconfigured: true,
138
160
  issue,
139
- warning: null
161
+ warning
140
162
  };
141
163
  }
142
- function resolveContactsClientTransport(name, env = process.env) {
164
+ function resolveContactsClientTransport(name, env = process.env, credentials = {}) {
143
165
  if (name !== "contacts") {
144
166
  throw new ContactsClientConfigurationError("CONTACTS_CLIENT_NAME_INVALID", "This resolver only accepts the contacts app slug.");
145
167
  }
146
168
  assertNoRetiredClientSelectors(env);
147
169
  const stamp = clientConfigurationStamp(env);
148
- const resolution = resolveSharedClientTransport(name, env);
149
- assertConfigurationUnchanged(env, stamp);
150
- if (resolution.transport !== "http" || !resolution.baseUrl) {
151
- const issue = resolution.misconfigured ? "The configured contacts API URL or credential is invalid or incomplete." : "HASNA_CONTACTS_API_URL and a resolvable contacts API key are required; no local fallback exists.";
152
- return unconfiguredResolution(resolution, issue);
170
+ let resolution;
171
+ try {
172
+ resolution = resolveSharedClientTransport(name, env, {
173
+ credentials: contactsResolverCredentials(env, credentials)
174
+ });
175
+ } catch (error) {
176
+ assertConfigurationUnchanged(env, stamp);
177
+ if (error instanceof ClientTransportConfigurationError) {
178
+ return unconfiguredResolution(error.message, null);
179
+ }
180
+ throw error;
153
181
  }
182
+ assertConfigurationUnchanged(env, stamp);
154
183
  try {
155
184
  assertHttpsBaseUrl(resolution.baseUrl);
156
185
  } catch (error) {
157
186
  return {
158
- ...unconfiguredResolution(resolution, error instanceof Error ? error.message : String(error)),
187
+ ...unconfiguredResolution(error instanceof Error ? error.message : String(error), resolution),
159
188
  apiKeyPresent: resolution.apiKeyPresent
160
189
  };
161
190
  }
@@ -169,7 +198,7 @@ function resolveContactsClientTransport(name, env = process.env) {
169
198
  configured: true,
170
199
  misconfigured: false,
171
200
  issue: null,
172
- warning: null
201
+ warning: resolution.warning
173
202
  };
174
203
  }
175
204
  function resourcePath(resource) {
@@ -223,20 +252,21 @@ function createStorageClient(name, transport) {
223
252
  }
224
253
  };
225
254
  }
226
- function resolveContactsStorageClient(name, env = process.env) {
227
- const resolution = resolveContactsClientTransport(name, env);
255
+ function resolveContactsStorageClient(name, env = process.env, credentials = {}) {
256
+ const resolution = resolveContactsClientTransport(name, env, credentials);
228
257
  if (!resolution.configured || !resolution.baseUrl) {
229
258
  throw new ContactsClientConfigurationError("CONTACTS_API_NOT_CONFIGURED", `${resolution.issue ?? "The contacts API client is not configured."} ` + "Configure HASNA_CONTACTS_API_URL and a contacts API key; the client will not read or create a local SQLite database.");
230
259
  }
231
260
  const baseUrl = resolution.baseUrl;
261
+ const chainOptions = contactsResolverCredentials(env, credentials);
232
262
  const request = async (method, path, body, opts) => {
233
263
  const stamp = clientConfigurationStamp(env);
234
264
  const snapshot = { ...env };
235
- const current = resolveContactsClientTransport(name, snapshot);
265
+ const current = resolveContactsClientTransport(name, snapshot, chainOptions);
236
266
  if (!current.configured || current.baseUrl !== baseUrl) {
237
267
  throw new ContactsClientConfigurationError("CONTACTS_AUTHORITY_CHANGED", "Client authority changed or disappeared; construct a new client before sending data.");
238
268
  }
239
- const credential = resolveCredential(name, snapshot);
269
+ const credential = resolveCredential(name, snapshot, chainOptions);
240
270
  if (!credential)
241
271
  throw new ContactsClientConfigurationError("CONTACTS_API_NOT_CONFIGURED", "No credential is available.");
242
272
  assertConfigurationUnchanged(env, stamp);
@@ -286,6 +316,39 @@ function stripUndefined(input) {
286
316
  return out;
287
317
  }
288
318
 
319
+ class ContactsApiResponseError extends Error {
320
+ constructor(operation, detail) {
321
+ super(`contacts: malformed /v1 response for ${operation}: ${detail}`);
322
+ this.name = "ContactsApiResponseError";
323
+ }
324
+ }
325
+ function requireAppendedContact(response, contactId, collection, matchKey, value) {
326
+ const operation = collection === "emails" ? "addEmailToContact" : "addPhoneToContact";
327
+ const contact = pick(response, "contact");
328
+ if (!contact || typeof contact !== "object" || Array.isArray(contact)) {
329
+ throw new ContactsApiResponseError(operation, "expected an object at response.contact");
330
+ }
331
+ if (contact.id !== contactId) {
332
+ throw new ContactsApiResponseError(operation, "response.contact.id did not match the requested contact");
333
+ }
334
+ const items = contact[collection];
335
+ if (!Array.isArray(items)) {
336
+ throw new ContactsApiResponseError(operation, `response.contact.${collection} was not an array`);
337
+ }
338
+ const normalize = collection === "emails" ? (input) => input.toLowerCase() : (input) => input;
339
+ const wanted = normalize(value);
340
+ const appended = items.find((item) => {
341
+ if (!item || typeof item !== "object" || Array.isArray(item))
342
+ return false;
343
+ const record = item;
344
+ return typeof record.id === "string" && record.contact_id === contactId && typeof record[matchKey] === "string" && normalize(record[matchKey]) === wanted;
345
+ });
346
+ if (!appended) {
347
+ throw new ContactsApiResponseError(operation, `response.contact.${collection} did not contain the requested value`);
348
+ }
349
+ return contact;
350
+ }
351
+
289
352
  class ApiStore {
290
353
  client;
291
354
  mode = "api";
@@ -355,11 +418,17 @@ class ApiStore {
355
418
  async mergeContacts() {
356
419
  return unavailable("mergeContacts");
357
420
  }
358
- async addEmailToContact() {
359
- return unavailable("addEmailToContact");
421
+ async addEmailToContact(contactId, email) {
422
+ const res = await this.patch(`/contacts/${this.enc(contactId)}`, {
423
+ emails_add: [stripUndefined(email)]
424
+ });
425
+ return requireAppendedContact(res, contactId, "emails", "address", email.address);
360
426
  }
361
- async addPhoneToContact() {
362
- return unavailable("addPhoneToContact");
427
+ async addPhoneToContact(contactId, phone) {
428
+ const res = await this.patch(`/contacts/${this.enc(contactId)}`, {
429
+ phones_add: [stripUndefined(phone)]
430
+ });
431
+ return requireAppendedContact(res, contactId, "phones", "number", phone.number);
363
432
  }
364
433
  async archiveContact() {
365
434
  return unavailable("archiveContact");
@@ -979,6 +1048,9 @@ class InvalidAudienceDefinitionError extends Error {
979
1048
  this.name = "InvalidAudienceDefinitionError";
980
1049
  }
981
1050
  }
1051
+ // src/sdk/index.ts
1052
+ import { resolveCredential as resolveCredential2 } from "@hasna/contracts/client";
1053
+
982
1054
  // src/sdk/v1.generated.ts
983
1055
  class ApiError extends Error {
984
1056
  status;
@@ -1239,15 +1311,11 @@ function validateBaseUrl(raw) {
1239
1311
  throw new Error("ContactsV1Client baseUrl must not contain credentials.");
1240
1312
  if (url.search || url.hash)
1241
1313
  throw new Error("ContactsV1Client baseUrl must not contain a query or fragment.");
1242
- const path = url.pathname.replace(/\/+$/, "");
1243
- if (path && path !== "/v1") {
1244
- throw new Error("ContactsV1Client baseUrl must be an authority root or end in /v1.");
1245
- }
1246
- url.pathname = "";
1314
+ url.pathname = url.pathname.replace(/\/+$/, "").replace(/\/v1$/, "");
1247
1315
  return url.toString().replace(/\/+$/, "");
1248
1316
  }
1249
1317
  function validateApiKey(apiKey) {
1250
- const key = apiKey.trim();
1318
+ const key = (apiKey ?? "").trim();
1251
1319
  if (!key)
1252
1320
  throw new Error("ContactsV1Client requires an API key.");
1253
1321
  if (/[^\t\x20-\x7e]/.test(key)) {
@@ -1267,6 +1335,56 @@ class ContactsV1Client2 extends ContactsV1Client {
1267
1335
  });
1268
1336
  }
1269
1337
  }
1338
+ var CONTACTS_APP_NAME = "contacts";
1339
+ function contactsSdkAuthorityPinMessage() {
1340
+ return "an explicit baseUrl requires an explicit apiKey. The SDK never attaches a credential that " + "resolved for a different authority: pass `apiKey` explicitly, or omit `baseUrl` and let the " + "@hasna/contracts chain resolve both halves together.";
1341
+ }
1342
+ var SDK_HOSTED_ONLY = "The contacts SDK is hosted-only and never falls back to local data.";
1343
+ function resolveSdkCredential(env, chainOptions) {
1344
+ const credential = resolveCredential2(CONTACTS_APP_NAME, env, chainOptions);
1345
+ if (!credential) {
1346
+ const diagnosis = resolveContactsClientTransport(CONTACTS_APP_NAME, env, chainOptions);
1347
+ throw new ContactsClientConfigurationError("CONTACTS_API_NOT_CONFIGURED", `${diagnosis.issue ?? "No contacts credential resolved."} ${SDK_HOSTED_ONLY}`);
1348
+ }
1349
+ if (credential.tier === "pointer") {
1350
+ throw new ContactsClientConfigurationError("CONTACTS_CREDENTIAL_POINTER_UNSUPPORTED", `The contacts SDK resolves credentials synchronously and cannot complete the secrets-vault pointer ` + `${credential.source} per request. Use a literal tier instead: an explicit apiKey, the Keychain item ` + `hasna.credentials.${CONTACTS_APP_NAME}.api-key, ~/.hasna/${CONTACTS_APP_NAME}/config/credentials, ` + `or HASNA_CONTACTS_API_KEY.`);
1351
+ }
1352
+ return credential.apiKey;
1353
+ }
1354
+ function createContactsClient(options = {}) {
1355
+ const { baseUrl, apiKey, env, profile, keychain, ...clientOptions } = options;
1356
+ if (baseUrl !== undefined) {
1357
+ if (!apiKey)
1358
+ throw new ContactsClientConfigurationError("CONTACTS_CREDENTIAL_PINNED", contactsSdkAuthorityPinMessage());
1359
+ return new ContactsV1Client2({ ...clientOptions, baseUrl, apiKey });
1360
+ }
1361
+ const envObject = env ?? (typeof process !== "undefined" ? process.env : {});
1362
+ const requested = {
1363
+ ...apiKey !== undefined ? { apiKey } : {},
1364
+ ...profile !== undefined ? { profile } : {},
1365
+ ...keychain !== undefined ? { keychain } : {}
1366
+ };
1367
+ const chainOptions = contactsResolverCredentials(envObject, requested);
1368
+ const initialKey = resolveSdkCredential(envObject, chainOptions);
1369
+ const resolution = resolveContactsClientTransport(CONTACTS_APP_NAME, envObject, { ...chainOptions, apiKey: initialKey });
1370
+ if (!resolution.configured || !resolution.baseUrl) {
1371
+ throw new ContactsClientConfigurationError("CONTACTS_API_NOT_CONFIGURED", `${resolution.issue ?? "The contacts API client is not configured."} ${SDK_HOSTED_ONLY}`);
1372
+ }
1373
+ const pinnedBaseUrl = resolution.baseUrl;
1374
+ const baseFetch = clientOptions.fetch ?? globalThis.fetch;
1375
+ const chainFetch = async (input, init) => {
1376
+ const snapshot = { ...envObject };
1377
+ const freshKey = resolveSdkCredential(snapshot, chainOptions);
1378
+ const current = resolveContactsClientTransport(CONTACTS_APP_NAME, snapshot, { ...chainOptions, apiKey: freshKey });
1379
+ if (!current.configured || current.baseUrl !== pinnedBaseUrl) {
1380
+ throw new ContactsClientConfigurationError("CONTACTS_AUTHORITY_CHANGED", "Client authority changed or disappeared; construct a new client before sending data.");
1381
+ }
1382
+ const headers = new Headers(init?.headers);
1383
+ headers.set("x-api-key", freshKey);
1384
+ return baseFetch(input, { ...init, headers });
1385
+ };
1386
+ return new ContactsV1Client2({ ...clientOptions, baseUrl: pinnedBaseUrl, apiKey: initialKey, fetch: chainFetch });
1387
+ }
1270
1388
  // src/lib/package-version.ts
1271
1389
  import { existsSync, readFileSync as readFileSync2 } from "fs";
1272
1390
  import { dirname, join } from "path";
@@ -1864,6 +1982,8 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
1864
1982
  export {
1865
1983
  resetStoreCache,
1866
1984
  getStore,
1985
+ createContactsClient,
1986
+ contactsSdkAuthorityPinMessage,
1867
1987
  buildV1OpenApiDocument,
1868
1988
  TagNotFoundError,
1869
1989
  InvalidAudienceDefinitionError,
@@ -19,8 +19,8 @@ export declare const AudiencePredicateContractSchema: z.ZodEffects<z.ZodObject<{
19
19
  values: z.ZodDefault<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>, "many">>;
20
20
  }, "strict", z.ZodTypeAny, {
21
21
  values: (string | number | boolean)[];
22
- kind: "tag" | "attribute" | "group";
23
22
  op: "eq" | "neq" | "in" | "not_in" | "exists" | "not_exists";
23
+ kind: "tag" | "attribute" | "group";
24
24
  key?: string | undefined;
25
25
  value?: string | number | boolean | undefined;
26
26
  }, {
@@ -31,8 +31,8 @@ export declare const AudiencePredicateContractSchema: z.ZodEffects<z.ZodObject<{
31
31
  op?: "eq" | "neq" | "in" | "not_in" | "exists" | "not_exists" | undefined;
32
32
  }>, {
33
33
  values: (string | number | boolean)[];
34
- kind: "tag" | "attribute" | "group";
35
34
  op: "eq" | "neq" | "in" | "not_in" | "exists" | "not_exists";
35
+ kind: "tag" | "attribute" | "group";
36
36
  key?: string | undefined;
37
37
  value?: string | number | boolean | undefined;
38
38
  }, {
@@ -52,8 +52,8 @@ export declare const AudienceDefinitionContractSchema: z.ZodObject<{
52
52
  values: z.ZodDefault<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>, "many">>;
53
53
  }, "strict", z.ZodTypeAny, {
54
54
  values: (string | number | boolean)[];
55
- kind: "tag" | "attribute" | "group";
56
55
  op: "eq" | "neq" | "in" | "not_in" | "exists" | "not_exists";
56
+ kind: "tag" | "attribute" | "group";
57
57
  key?: string | undefined;
58
58
  value?: string | number | boolean | undefined;
59
59
  }, {
@@ -64,8 +64,8 @@ export declare const AudienceDefinitionContractSchema: z.ZodObject<{
64
64
  op?: "eq" | "neq" | "in" | "not_in" | "exists" | "not_exists" | undefined;
65
65
  }>, {
66
66
  values: (string | number | boolean)[];
67
- kind: "tag" | "attribute" | "group";
68
67
  op: "eq" | "neq" | "in" | "not_in" | "exists" | "not_exists";
68
+ kind: "tag" | "attribute" | "group";
69
69
  key?: string | undefined;
70
70
  value?: string | number | boolean | undefined;
71
71
  }, {
@@ -79,8 +79,8 @@ export declare const AudienceDefinitionContractSchema: z.ZodObject<{
79
79
  match: "all" | "any";
80
80
  predicates: {
81
81
  values: (string | number | boolean)[];
82
- kind: "tag" | "attribute" | "group";
83
82
  op: "eq" | "neq" | "in" | "not_in" | "exists" | "not_exists";
83
+ kind: "tag" | "attribute" | "group";
84
84
  key?: string | undefined;
85
85
  value?: string | number | boolean | undefined;
86
86
  }[];
@@ -113,8 +113,8 @@ export declare const AudienceContractSchema: z.ZodObject<{
113
113
  values: z.ZodDefault<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>, "many">>;
114
114
  }, "strict", z.ZodTypeAny, {
115
115
  values: (string | number | boolean)[];
116
- kind: "tag" | "attribute" | "group";
117
116
  op: "eq" | "neq" | "in" | "not_in" | "exists" | "not_exists";
117
+ kind: "tag" | "attribute" | "group";
118
118
  key?: string | undefined;
119
119
  value?: string | number | boolean | undefined;
120
120
  }, {
@@ -125,8 +125,8 @@ export declare const AudienceContractSchema: z.ZodObject<{
125
125
  op?: "eq" | "neq" | "in" | "not_in" | "exists" | "not_exists" | undefined;
126
126
  }>, {
127
127
  values: (string | number | boolean)[];
128
- kind: "tag" | "attribute" | "group";
129
128
  op: "eq" | "neq" | "in" | "not_in" | "exists" | "not_exists";
129
+ kind: "tag" | "attribute" | "group";
130
130
  key?: string | undefined;
131
131
  value?: string | number | boolean | undefined;
132
132
  }, {
@@ -140,8 +140,8 @@ export declare const AudienceContractSchema: z.ZodObject<{
140
140
  match: "all" | "any";
141
141
  predicates: {
142
142
  values: (string | number | boolean)[];
143
- kind: "tag" | "attribute" | "group";
144
143
  op: "eq" | "neq" | "in" | "not_in" | "exists" | "not_exists";
144
+ kind: "tag" | "attribute" | "group";
145
145
  key?: string | undefined;
146
146
  value?: string | number | boolean | undefined;
147
147
  }[];
@@ -167,8 +167,8 @@ export declare const AudienceContractSchema: z.ZodObject<{
167
167
  match: "all" | "any";
168
168
  predicates: {
169
169
  values: (string | number | boolean)[];
170
- kind: "tag" | "attribute" | "group";
171
170
  op: "eq" | "neq" | "in" | "not_in" | "exists" | "not_exists";
171
+ kind: "tag" | "attribute" | "group";
172
172
  key?: string | undefined;
173
173
  value?: string | number | boolean | undefined;
174
174
  }[];
@@ -0,0 +1,67 @@
1
+ import type { ContactWithDetails } from "../types/index.js";
2
+ export declare const CONTACTS_COMPACT_LIMIT = 20;
3
+ export declare const CONTACTS_MAX_LIMIT = 500;
4
+ export declare function normalizeContactsLimit(value: unknown, fallback?: number): number;
5
+ export declare function normalizeContactsOffset(value: unknown): number;
6
+ export declare function summarizeContact(contact: ContactWithDetails): {
7
+ id: string;
8
+ display_name: string;
9
+ job_title: string | null;
10
+ company: {
11
+ id: string;
12
+ name: string;
13
+ } | null;
14
+ primary_email: string;
15
+ primary_phone: string;
16
+ status: import("../index.js").ContactStatus;
17
+ follow_up_at: string | null;
18
+ last_contacted_at: string | null;
19
+ archived: boolean;
20
+ sensitivity: import("../index.js").Sensitivity;
21
+ tags: {
22
+ id: string;
23
+ name: string;
24
+ }[];
25
+ updated_at: string;
26
+ };
27
+ export declare function compareContactsByDisplayNameAndId(left: Pick<ContactWithDetails, "display_name" | "id">, right: Pick<ContactWithDetails, "display_name" | "id">): number;
28
+ export declare function compactContactsPage(contacts: ContactWithDetails[], options: {
29
+ total: number;
30
+ limit: number;
31
+ offset: number;
32
+ }): {
33
+ contacts: {
34
+ id: string;
35
+ display_name: string;
36
+ job_title: string | null;
37
+ company: {
38
+ id: string;
39
+ name: string;
40
+ } | null;
41
+ primary_email: string;
42
+ primary_phone: string;
43
+ status: import("../index.js").ContactStatus;
44
+ follow_up_at: string | null;
45
+ last_contacted_at: string | null;
46
+ archived: boolean;
47
+ sensitivity: import("../index.js").Sensitivity;
48
+ tags: {
49
+ id: string;
50
+ name: string;
51
+ }[];
52
+ updated_at: string;
53
+ }[];
54
+ count: number;
55
+ total: number;
56
+ limit: number;
57
+ cursor: number;
58
+ next_cursor: number | null;
59
+ has_more: boolean;
60
+ compact: true;
61
+ sort: {
62
+ fields: string[];
63
+ direction: "asc";
64
+ };
65
+ hint: string;
66
+ };
67
+ //# sourceMappingURL=compact-output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compact-output.d.ts","sourceRoot":"","sources":["../../src/lib/compact-output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,eAAO,MAAM,sBAAsB,KAAK,CAAC;AACzC,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,SAAyB,GAAG,MAAM,CAIhG;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAI9D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB;;;;;;;;;;;;;;;;;;;;EAgB3D;AAED,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,IAAI,CAAC,kBAAkB,EAAE,cAAc,GAAG,IAAI,CAAC,EACrD,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,cAAc,GAAG,IAAI,CAAC,GACrD,MAAM,CAER;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,kBAAkB,EAAE,EAC9B,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkB1D"}
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../src/mcp/handlers/core.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA2C9C,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CA+YpD,CAAC"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../src/mcp/handlers/core.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA4C9C,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CA0YpD,CAAC"}
@@ -1,5 +1,15 @@
1
1
  #!/usr/bin/env bun
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- export declare function buildServer(): McpServer;
3
+ import { type ContactsMcpProfile } from "./profile.js";
4
+ export declare function buildServer(profile?: ContactsMcpProfile): McpServer;
5
+ /**
6
+ * Classify early-exit arguments before any http-mode parse, server build, or
7
+ * stdio bind. `--help` / `--version` answer with rc=0 and the MCP server never
8
+ * starts: previously `contacts-mcp --version` fell through to the stdio
9
+ * JSON-RPC loop and printed "running on stdio" instead of the version
10
+ * (hasna/apps#1720 validation, the binds-before-version class).
11
+ */
12
+ export declare function handleEarlyArgs(argv: string[]): "help" | "version" | "start";
13
+ export declare function mcpUsage(): string;
4
14
  export declare function isDirectMcpEntry(entry?: string): boolean;
5
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAgBpE,wBAAgB,WAAW,IAAI,SAAS,CAKvC;AAkBD,wBAAgB,gBAAgB,CAAC,KAAK,SAAkB,GAAG,OAAO,CAIjE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAMpE,OAAO,EAA6B,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAYlF,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAA2B,GAAG,SAAS,CAQ3E;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAI5E;AAED,wBAAgB,QAAQ,IAAI,MAAM,CAWjC;AA4CD,wBAAgB,gBAAgB,CAAC,KAAK,SAAkB,GAAG,OAAO,CAIjE"}