@hasna/contacts 0.8.1 → 0.9.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.
Files changed (56) hide show
  1. package/README.md +10 -0
  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 +408 -127
  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/index.js +43 -4
  22. package/dist/lib/audience-contract.d.ts +9 -9
  23. package/dist/lib/compact-output.d.ts +67 -0
  24. package/dist/lib/compact-output.d.ts.map +1 -0
  25. package/dist/mcp/handlers/core.d.ts.map +1 -1
  26. package/dist/mcp/index.d.ts +2 -1
  27. package/dist/mcp/index.d.ts.map +1 -1
  28. package/dist/mcp/index.js +378 -194
  29. package/dist/mcp/profile.d.ts +8 -0
  30. package/dist/mcp/profile.d.ts.map +1 -0
  31. package/dist/mcp/register-tools.d.ts +3 -1
  32. package/dist/mcp/register-tools.d.ts.map +1 -1
  33. package/dist/mcp/storage-tools.d.ts +2 -1
  34. package/dist/mcp/storage-tools.d.ts.map +1 -1
  35. package/dist/mcp/tools.d.ts +7 -45
  36. package/dist/mcp/tools.d.ts.map +1 -1
  37. package/dist/sdk/index.js +1 -1
  38. package/dist/server/index.js +30 -16
  39. package/dist/server/pg-store.d.ts +1 -0
  40. package/dist/server/pg-store.d.ts.map +1 -1
  41. package/dist/store/index.d.ts.map +1 -1
  42. package/docs/chrome-autofill.md +45 -0
  43. package/extension/background.js +284 -0
  44. package/extension/content.js +392 -0
  45. package/extension/detect.js +228 -0
  46. package/extension/fill.js +112 -0
  47. package/extension/icons/128.png +0 -0
  48. package/extension/icons/16.png +0 -0
  49. package/extension/icons/32.png +0 -0
  50. package/extension/icons/48.png +0 -0
  51. package/extension/icons/icon.svg +7 -0
  52. package/extension/manifest.json +54 -0
  53. package/extension/popup.css +71 -0
  54. package/extension/popup.html +50 -0
  55. package/extension/popup.js +166 -0
  56. package/package.json +9 -5
package/dist/cli/index.js CHANGED
@@ -47,7 +47,7 @@ var __export = (target, all) => {
47
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
  var __require = import.meta.require;
49
49
 
50
- // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/error.js
50
+ // node_modules/commander/lib/error.js
51
51
  var require_error = __commonJS((exports) => {
52
52
  class CommanderError extends Error {
53
53
  constructor(exitCode, code, message) {
@@ -71,7 +71,7 @@ var require_error = __commonJS((exports) => {
71
71
  exports.InvalidArgumentError = InvalidArgumentError;
72
72
  });
73
73
 
74
- // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/argument.js
74
+ // node_modules/commander/lib/argument.js
75
75
  var require_argument = __commonJS((exports) => {
76
76
  var { InvalidArgumentError } = require_error();
77
77
 
@@ -150,7 +150,7 @@ var require_argument = __commonJS((exports) => {
150
150
  exports.humanReadableArgName = humanReadableArgName;
151
151
  });
152
152
 
153
- // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/help.js
153
+ // node_modules/commander/lib/help.js
154
154
  var require_help = __commonJS((exports) => {
155
155
  var { humanReadableArgName } = require_argument();
156
156
 
@@ -500,7 +500,7 @@ ${itemIndentStr}`);
500
500
  exports.stripColor = stripColor;
501
501
  });
502
502
 
503
- // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/option.js
503
+ // node_modules/commander/lib/option.js
504
504
  var require_option = __commonJS((exports) => {
505
505
  var { InvalidArgumentError } = require_error();
506
506
 
@@ -678,7 +678,7 @@ var require_option = __commonJS((exports) => {
678
678
  exports.DualOptions = DualOptions;
679
679
  });
680
680
 
681
- // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/suggestSimilar.js
681
+ // node_modules/commander/lib/suggestSimilar.js
682
682
  var require_suggestSimilar = __commonJS((exports) => {
683
683
  var maxDistance = 3;
684
684
  function editDistance(a, b) {
@@ -751,7 +751,7 @@ var require_suggestSimilar = __commonJS((exports) => {
751
751
  exports.suggestSimilar = suggestSimilar;
752
752
  });
753
753
 
754
- // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/command.js
754
+ // node_modules/commander/lib/command.js
755
755
  var require_command = __commonJS((exports) => {
756
756
  var EventEmitter = __require("events").EventEmitter;
757
757
  var childProcess = __require("child_process");
@@ -1290,8 +1290,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
1290
1290
  args = args.slice();
1291
1291
  let launchWithNode = false;
1292
1292
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1293
- function findFile(baseDir, baseName) {
1294
- const localBin = path.resolve(baseDir, baseName);
1293
+ function findFile(baseDir2, baseName) {
1294
+ const localBin = path.resolve(baseDir2, baseName);
1295
1295
  if (fs.existsSync(localBin))
1296
1296
  return localBin;
1297
1297
  if (sourceExt.includes(path.extname(baseName)))
@@ -2061,7 +2061,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2061
2061
  exports.useColor = useColor;
2062
2062
  });
2063
2063
 
2064
- // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/index.js
2064
+ // node_modules/commander/index.js
2065
2065
  var require_commander = __commonJS((exports) => {
2066
2066
  var { Argument } = require_argument();
2067
2067
  var { Command } = require_command();
@@ -2182,7 +2182,7 @@ Only include fields that are actually visible in the document. Return valid JSON
2182
2182
  }
2183
2183
  var init_document_scanner = () => {};
2184
2184
 
2185
- // ../events/dist/commander.js
2185
+ // node_modules/@hasna/events/dist/commander.js
2186
2186
  var exports_commander = {};
2187
2187
  __export(exports_commander, {
2188
2188
  registerEventsCommands: () => registerEventsCommands,
@@ -2193,12 +2193,12 @@ __export(exports_commander, {
2193
2193
  import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
2194
2194
  import { Buffer as Buffer2 } from "buffer";
2195
2195
  import { existsSync as existsSync2 } from "fs";
2196
- import { join as join2 } from "path";
2196
+ import { join as join3 } from "path";
2197
2197
  import { existsSync } from "fs";
2198
+ import { homedir as homedir2 } from "os";
2199
+ import { join as join2, resolve } from "path";
2198
2200
  import { homedir } from "os";
2199
- import { join, resolve } from "path";
2200
- import { homedir as pathsResolverHomedir } from "os";
2201
- import { join as pathsResolverJoin } from "path";
2201
+ import { join } from "path";
2202
2202
  import { createHmac, timingSafeEqual } from "crypto";
2203
2203
  import { lookup as dnsLookup } from "dns/promises";
2204
2204
  import { isIP } from "net";
@@ -2302,72 +2302,75 @@ function channelMatchesEvent(channel, event) {
2302
2302
  return true;
2303
2303
  return channel.filters.some((filter) => eventMatchesFilter(event, filter));
2304
2304
  }
2305
- var PATHS_RESOLVER_KIND_ENV = {
2305
+ var KIND_ENV = {
2306
2306
  config: "HASNA_CONFIG_HOME",
2307
2307
  data: "HASNA_DATA_HOME",
2308
2308
  state: "HASNA_STATE_HOME",
2309
2309
  cache: "HASNA_CACHE_HOME"
2310
2310
  };
2311
- var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2312
- function pathsResolverAssertApp(app) {
2311
+ var APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2312
+ function assertApp(app) {
2313
2313
  if (typeof app !== "string" || app.length === 0) {
2314
2314
  throw new TypeError("paths: app must be a non-empty string");
2315
2315
  }
2316
- if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
2316
+ if (!APP_SLUG_RE.test(app)) {
2317
2317
  throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
2318
2318
  }
2319
2319
  }
2320
- function pathsResolverAssertKind(kind) {
2321
- if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
2322
- throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
2323
- }
2320
+ function envOf(options) {
2321
+ return options.env ?? process.env;
2324
2322
  }
2325
- function pathsResolverBaseDir(kind, options) {
2326
- pathsResolverAssertKind(kind);
2327
- const env = options.env ?? process.env;
2328
- const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
2329
- if (typeof override === "string" && override.length > 0)
2323
+ function envValue(options, kind) {
2324
+ const value = envOf(options)[KIND_ENV[kind]];
2325
+ return typeof value === "string" && value.length > 0 ? value : undefined;
2326
+ }
2327
+ function isMacOS(platform) {
2328
+ return platform === "darwin";
2329
+ }
2330
+ function baseDir(kind, options) {
2331
+ const override = envValue(options, kind);
2332
+ if (override)
2330
2333
  return override;
2331
- const home = options.home ?? pathsResolverHomedir();
2334
+ const home = options.home ?? homedir();
2332
2335
  const platform = options.platform ?? process.platform;
2333
- if (platform === "darwin") {
2336
+ if (isMacOS(platform)) {
2334
2337
  switch (kind) {
2335
2338
  case "config":
2336
2339
  case "data":
2337
- return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
2340
+ return join(home, "Library", "Application Support", "Hasna");
2338
2341
  case "cache":
2339
- return pathsResolverJoin(home, "Library", "Caches", "Hasna");
2342
+ return join(home, "Library", "Caches", "Hasna");
2340
2343
  case "state":
2341
- return pathsResolverJoin(home, "Library", "Logs", "Hasna");
2344
+ return join(home, "Library", "Logs", "Hasna");
2342
2345
  }
2343
2346
  }
2344
2347
  switch (kind) {
2345
2348
  case "config":
2346
- return pathsResolverJoin(home, ".config", "hasna");
2349
+ return join(home, ".config", "hasna");
2347
2350
  case "data":
2348
- return pathsResolverJoin(home, ".local", "share", "hasna");
2351
+ return join(home, ".local", "share", "hasna");
2349
2352
  case "state":
2350
- return pathsResolverJoin(home, ".local", "state", "hasna");
2353
+ return join(home, ".local", "state", "hasna");
2351
2354
  case "cache":
2352
- return pathsResolverJoin(home, ".cache", "hasna");
2355
+ return join(home, ".cache", "hasna");
2353
2356
  }
2354
2357
  }
2355
- function pathsResolverResolve(kind, options) {
2356
- pathsResolverAssertApp(options.app);
2357
- const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
2358
- return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
2358
+ function resolvePath(kind, options) {
2359
+ assertApp(options.app);
2360
+ const appSegment = options.internal === true ? join("internal", options.app) : options.app;
2361
+ return join(baseDir(kind, options), appSegment);
2359
2362
  }
2360
2363
  function dataDir(options) {
2361
- return pathsResolverResolve("data", options);
2364
+ return resolvePath("data", options);
2362
2365
  }
2363
2366
  var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
2364
2367
  var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
2365
2368
  var EVENTS_STORE_SENTINEL_FILE = "events.json";
2366
2369
  function effectiveHome() {
2367
- return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
2370
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir2();
2368
2371
  }
2369
2372
  function legacyHomeDir() {
2370
- return join(effectiveHome(), ".hasna", "events");
2373
+ return join2(effectiveHome(), ".hasna", "events");
2371
2374
  }
2372
2375
  function resolverHome() {
2373
2376
  return dataDir({ app: "events", home: effectiveHome() || undefined });
@@ -2376,7 +2379,7 @@ function adoptResolverHome(resolved, env = process.env) {
2376
2379
  const dataOverride = env.HASNA_DATA_HOME;
2377
2380
  if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
2378
2381
  return true;
2379
- return existsSync(join(resolved, EVENTS_STORE_SENTINEL_FILE));
2382
+ return existsSync(join2(resolved, EVENTS_STORE_SENTINEL_FILE));
2380
2383
  }
2381
2384
  function exactEventsHome() {
2382
2385
  const dir = process.env[HASNA_EVENTS_DIR_ENV];
@@ -2417,9 +2420,9 @@ class JsonEventsStore {
2417
2420
  constructor(dataDir2 = getEventsDataDir()) {
2418
2421
  this.dataDir = dataDir2;
2419
2422
  this.runtime = localJsonRuntime(dataDir2);
2420
- this.channelsPath = join2(dataDir2, "channels.json");
2421
- this.eventsPath = join2(dataDir2, "events.json");
2422
- this.deliveriesPath = join2(dataDir2, "deliveries.json");
2423
+ this.channelsPath = join3(dataDir2, "channels.json");
2424
+ this.eventsPath = join3(dataDir2, "events.json");
2425
+ this.deliveriesPath = join3(dataDir2, "deliveries.json");
2423
2426
  }
2424
2427
  async init() {
2425
2428
  await mkdir(this.dataDir, { recursive: true, mode: 448 });
@@ -2687,7 +2690,7 @@ async function getEventsStatus(dataDir2) {
2687
2690
  };
2688
2691
  }
2689
2692
  function statusFile(dataDir2, fileName, records) {
2690
- const path = join2(dataDir2, fileName);
2693
+ const path = join3(dataDir2, fileName);
2691
2694
  return { path, exists: existsSync2(path), records };
2692
2695
  }
2693
2696
  var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
@@ -3621,13 +3624,6 @@ function parseMatcherExpression(value, label) {
3621
3624
  negated: false
3622
3625
  };
3623
3626
  }
3624
- function webhookTargetPolicyFromEnv() {
3625
- const value = process.env.HASNA_EVENTS_ALLOW_PRIVATE_WEBHOOK_TARGETS;
3626
- if (!value)
3627
- return;
3628
- const hosts = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
3629
- return hosts.length > 0 ? { allowPrivateHosts: hosts } : undefined;
3630
- }
3631
3627
  var DEFAULT_EVENT_LIST_LIMIT = 100;
3632
3628
  function parseJsonObject(value, fallback) {
3633
3629
  if (!value)
@@ -3653,7 +3649,7 @@ function parseHeaders(values) {
3653
3649
  function createClient(options) {
3654
3650
  if (options.createClient)
3655
3651
  return options.createClient();
3656
- return new EventsClient({ store: new JsonEventsStore(options.dataDir), webhookTargetPolicy: webhookTargetPolicyFromEnv() });
3652
+ return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
3657
3653
  }
3658
3654
  function print(value, json, text) {
3659
3655
  if (json)
@@ -3734,8 +3730,6 @@ function registerChannelCommands(program, options) {
3734
3730
  metadata: parseJsonObject(actionOptions.metadata, {})
3735
3731
  }, { honorFilters: actionOptions.honorFilters });
3736
3732
  print(result, json, `${result.status}: ${result.channelId}`);
3737
- if (result.status === "failed")
3738
- process.exitCode = 1;
3739
3733
  } catch (error) {
3740
3734
  fail(error, json);
3741
3735
  }
@@ -3825,7 +3819,7 @@ function replaySummary(events, deliveries, nextCursor) {
3825
3819
  return `Replayed ${events} event(s), ${deliveries} delivery result(s)${suffix}`;
3826
3820
  }
3827
3821
 
3828
- // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/esm.mjs
3822
+ // node_modules/commander/esm.mjs
3829
3823
  var import__ = __toESM(require_commander(), 1);
3830
3824
  var {
3831
3825
  program,
@@ -4164,6 +4158,39 @@ function stripUndefined(input) {
4164
4158
  return out;
4165
4159
  }
4166
4160
 
4161
+ class ContactsApiResponseError extends Error {
4162
+ constructor(operation, detail) {
4163
+ super(`contacts: malformed /v1 response for ${operation}: ${detail}`);
4164
+ this.name = "ContactsApiResponseError";
4165
+ }
4166
+ }
4167
+ function requireAppendedContact(response, contactId, collection, matchKey, value) {
4168
+ const operation = collection === "emails" ? "addEmailToContact" : "addPhoneToContact";
4169
+ const contact = pick(response, "contact");
4170
+ if (!contact || typeof contact !== "object" || Array.isArray(contact)) {
4171
+ throw new ContactsApiResponseError(operation, "expected an object at response.contact");
4172
+ }
4173
+ if (contact.id !== contactId) {
4174
+ throw new ContactsApiResponseError(operation, "response.contact.id did not match the requested contact");
4175
+ }
4176
+ const items = contact[collection];
4177
+ if (!Array.isArray(items)) {
4178
+ throw new ContactsApiResponseError(operation, `response.contact.${collection} was not an array`);
4179
+ }
4180
+ const normalize = collection === "emails" ? (input) => input.toLowerCase() : (input) => input;
4181
+ const wanted = normalize(value);
4182
+ const appended = items.find((item) => {
4183
+ if (!item || typeof item !== "object" || Array.isArray(item))
4184
+ return false;
4185
+ const record = item;
4186
+ return typeof record.id === "string" && record.contact_id === contactId && typeof record[matchKey] === "string" && normalize(record[matchKey]) === wanted;
4187
+ });
4188
+ if (!appended) {
4189
+ throw new ContactsApiResponseError(operation, `response.contact.${collection} did not contain the requested value`);
4190
+ }
4191
+ return contact;
4192
+ }
4193
+
4167
4194
  class ApiStore {
4168
4195
  client;
4169
4196
  mode = "api";
@@ -4233,11 +4260,17 @@ class ApiStore {
4233
4260
  async mergeContacts() {
4234
4261
  return unavailable("mergeContacts");
4235
4262
  }
4236
- async addEmailToContact() {
4237
- return unavailable("addEmailToContact");
4263
+ async addEmailToContact(contactId, email) {
4264
+ const res = await this.patch(`/contacts/${this.enc(contactId)}`, {
4265
+ emails_add: [stripUndefined(email)]
4266
+ });
4267
+ return requireAppendedContact(res, contactId, "emails", "address", email.address);
4238
4268
  }
4239
- async addPhoneToContact() {
4240
- return unavailable("addPhoneToContact");
4269
+ async addPhoneToContact(contactId, phone) {
4270
+ const res = await this.patch(`/contacts/${this.enc(contactId)}`, {
4271
+ phones_add: [stripUndefined(phone)]
4272
+ });
4273
+ return requireAppendedContact(res, contactId, "phones", "number", phone.number);
4241
4274
  }
4242
4275
  async archiveContact() {
4243
4276
  return unavailable("archiveContact");
@@ -5408,6 +5441,95 @@ async function confirmUser(question) {
5408
5441
  return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
5409
5442
  }
5410
5443
 
5444
+ // src/lib/compact-output.ts
5445
+ var CONTACTS_COMPACT_LIMIT = 20;
5446
+ var CONTACTS_MAX_LIMIT = 500;
5447
+ function normalizeContactsLimit(value, fallback = CONTACTS_COMPACT_LIMIT) {
5448
+ const parsed = typeof value === "number" ? value : Number(value ?? fallback);
5449
+ if (!Number.isFinite(parsed))
5450
+ return fallback;
5451
+ return Math.max(1, Math.min(CONTACTS_MAX_LIMIT, Math.trunc(parsed)));
5452
+ }
5453
+ function normalizeContactsOffset(value) {
5454
+ const parsed = typeof value === "number" ? value : Number(value ?? 0);
5455
+ if (!Number.isFinite(parsed))
5456
+ return 0;
5457
+ return Math.max(0, Math.trunc(parsed));
5458
+ }
5459
+ function summarizeContact(contact) {
5460
+ return {
5461
+ id: contact.id,
5462
+ display_name: contact.display_name,
5463
+ job_title: contact.job_title,
5464
+ company: contact.company ? { id: contact.company.id, name: contact.company.name } : null,
5465
+ primary_email: contact.emails?.find((email) => email.is_primary)?.address ?? contact.emails?.[0]?.address ?? null,
5466
+ primary_phone: contact.phones?.find((phone) => phone.is_primary)?.number ?? contact.phones?.[0]?.number ?? null,
5467
+ status: contact.status,
5468
+ follow_up_at: contact.follow_up_at,
5469
+ last_contacted_at: contact.last_contacted_at,
5470
+ archived: contact.archived,
5471
+ sensitivity: contact.sensitivity,
5472
+ tags: (contact.tags ?? []).map((tag) => ({ id: tag.id, name: tag.name })),
5473
+ updated_at: contact.updated_at
5474
+ };
5475
+ }
5476
+ function compareContactsByDisplayNameAndId(left, right) {
5477
+ return left.display_name.localeCompare(right.display_name) || left.id.localeCompare(right.id);
5478
+ }
5479
+ function compactContactsPage(contacts, options) {
5480
+ const ordered = [...contacts].sort(compareContactsByDisplayNameAndId);
5481
+ const nextCursor = options.offset + ordered.length < options.total ? options.offset + contacts.length : null;
5482
+ return {
5483
+ contacts: ordered.map(summarizeContact),
5484
+ count: ordered.length,
5485
+ total: options.total,
5486
+ limit: options.limit,
5487
+ cursor: options.offset,
5488
+ next_cursor: nextCursor,
5489
+ has_more: nextCursor !== null,
5490
+ compact: true,
5491
+ sort: { fields: ["display_name", "id"], direction: "asc" },
5492
+ hint: "Use contacts show <id> for one full record; pass --full for the legacy list payload."
5493
+ };
5494
+ }
5495
+
5496
+ // src/mcp/profile.ts
5497
+ var CONTACTS_FULL_TOOL_COUNT = 190;
5498
+ var CONTACTS_CORE_TOOL_NAMES = [
5499
+ "create_contact",
5500
+ "get_contact",
5501
+ "update_contact",
5502
+ "delete_contact",
5503
+ "list_contacts",
5504
+ "search_contacts",
5505
+ "find_or_create_contact",
5506
+ "upsert_contact",
5507
+ "get_contact_by_email",
5508
+ "add_email_to_contact",
5509
+ "add_phone_to_contact",
5510
+ "create_company",
5511
+ "get_company",
5512
+ "update_company",
5513
+ "delete_company",
5514
+ "list_companies",
5515
+ "search_companies",
5516
+ "create_tag",
5517
+ "list_tags",
5518
+ "add_tag_to_contact",
5519
+ "remove_tag_from_contact",
5520
+ "add_relationship",
5521
+ "list_relationships",
5522
+ "delete_relationship",
5523
+ "get_contact_brief",
5524
+ "get_contact_timeline",
5525
+ "get_recent_contact_events",
5526
+ "send_feedback",
5527
+ "contacts_connection_status",
5528
+ "search_tools",
5529
+ "describe_tools"
5530
+ ];
5531
+ var CORE_TOOLS = new Set(CONTACTS_CORE_TOOL_NAMES);
5532
+
5411
5533
  // src/cli/commands/core.tsx
5412
5534
  function collect(val, prev) {
5413
5535
  return [...prev, val];
@@ -5541,19 +5663,19 @@ Add New Contact
5541
5663
  \u2713 Contact created: ${contact.display_name} (${contact.id})
5542
5664
  `));
5543
5665
  });
5544
- program2.command("list").description("List contacts").option("--tag <tag_id>", "Filter by tag ID").option("--company <id>", "Filter by company ID").option("--include-restricted", "Include restricted-sensitivity contacts").option("-l, --limit <n>", "Max results", "50").option("-o, --offset <n>", "Skip first N results", "0").option("--order-by <field>", "Sort field: display_name|created_at|updated_at|last_contacted_at|follow_up_at", "display_name").option("--order-dir <dir>", "Sort direction: asc|desc", "asc").option("-j, --json", "Output JSON").action(async (opts) => {
5666
+ program2.command("list").description("List contacts").option("--tag <tag_id>", "Filter by tag ID").option("--company <id>", "Filter by company ID").option("-l, --limit <n>", "Max results (default 20; --full defaults to 50)").option("-o, --offset <n>", "Skip first N results", "0").option("--status <status>", "Filter by hosted lifecycle status").option("-j, --json", "Output compact paged JSON").option("--full", "Return the legacy full-record list payload").action(async (opts) => {
5545
5667
  const store = getStore();
5668
+ const limit = normalizeContactsLimit(opts.limit, opts.full ? 50 : 20);
5669
+ const offset = normalizeContactsOffset(opts.offset);
5546
5670
  const result = await store.listContacts({
5547
5671
  tag_id: opts.tag,
5548
5672
  company_id: opts.company,
5549
- include_restricted: opts.includeRestricted,
5550
- limit: parseInt(opts.limit, 10),
5551
- offset: parseInt(opts.offset, 10),
5552
- order_by: opts.orderBy,
5553
- order_dir: opts.orderDir === "desc" ? "desc" : "asc"
5673
+ status: opts.status,
5674
+ limit,
5675
+ offset
5554
5676
  });
5555
5677
  if (opts.json) {
5556
- console.log(JSON.stringify(result, null, 2));
5678
+ console.log(JSON.stringify(opts.full ? result : compactContactsPage(result.contacts, { total: result.total, limit, offset }), null, 2));
5557
5679
  return;
5558
5680
  }
5559
5681
  if (result.contacts.length === 0) {
@@ -5894,33 +6016,22 @@ Invalid format: ${format}. Use csv, vcf, or json
5894
6016
  process.stdout.write(output);
5895
6017
  }
5896
6018
  });
5897
- program2.command("mcp").description("Print MCP server setup instructions").action(() => {
6019
+ program2.command("mcp").description("Print truthful MCP server setup instructions").action(() => {
5898
6020
  const config = JSON.stringify({ contacts: { command: "contacts-mcp", args: [], env: {} } }, null, 4);
6021
+ const coreCount = CONTACTS_CORE_TOOL_NAMES.length;
6022
+ const fullCount = CONTACTS_FULL_TOOL_COUNT;
5899
6023
  console.log(`
5900
6024
  ${chalk2.bold.blue("\u2501\u2501\u2501 Contacts MCP Server Setup \u2501\u2501\u2501")}
5901
6025
 
5902
- ${chalk2.bold("1. Install the package:")}
5903
- ${chalk2.cyan("npm install -g @hasna/contacts")}
5904
- ${chalk2.gray("or:")} ${chalk2.cyan("bun add -g @hasna/contacts")}
5905
-
5906
- ${chalk2.bold("2. Add to Claude Code (recommended):")}
5907
- ${chalk2.cyan("claude mcp add --transport stdio --scope user contacts -- contacts-mcp")}
5908
-
5909
- ${chalk2.bold("3. Or add manually to ~/.claude.json:")}
5910
- ${chalk2.yellow(config)}
6026
+ ${chalk2.bold("Install:")} ${chalk2.cyan("npm install -g @hasna/contacts")}
6027
+ ${chalk2.bold("Claude Code:")} ${chalk2.cyan("claude mcp add --transport stdio --scope user contacts -- contacts-mcp")}
6028
+ ${chalk2.bold("Manual config:")} ${chalk2.yellow(config)}
5911
6029
 
5912
- ${chalk2.bold("4. Restart Claude Code and verify with")} ${chalk2.cyan("/mcp")}
6030
+ Default profile: core (${coreCount} tools). It includes routine contact/company/tag/relationship reads and writes plus search_tools and describe_tools for complete discovery across the ${fullCount}-tool full inventory.
6031
+ Full compatibility profile: set HASNA_CONTACTS_MCP_PROFILE=full or run contacts-mcp --mcp-profile full.
6032
+ List ordering: display_name asc, then id asc. Hosted list filters: company_id, tag_id, status.
5913
6033
 
5914
- ${chalk2.bold("Available tools (24 total):")}
5915
- ${chalk2.gray("Contacts: ")}${chalk2.white("create_contact get_contact update_contact delete_contact")}
5916
- ${chalk2.gray(" ")}${chalk2.white("list_contacts search_contacts merge_contacts")}
5917
- ${chalk2.gray("Companies:")}${chalk2.white("create_company get_company update_company delete_company")}
5918
- ${chalk2.gray(" ")}${chalk2.white("list_companies search_companies")}
5919
- ${chalk2.gray("Tags: ")}${chalk2.white("create_tag list_tags delete_tag")}
5920
- ${chalk2.gray(" ")}${chalk2.white("add_tag_to_contact remove_tag_from_contact")}
5921
- ${chalk2.gray("Rels: ")}${chalk2.white("add_relationship list_relationships delete_relationship")}
5922
- ${chalk2.gray("I/O: ")}${chalk2.white("import_contacts export_contacts get_stats")}
5923
- `);
6034
+ Restart the client and verify the active profile in the MCP initialize instructions.`);
5924
6035
  });
5925
6036
  program2.command("recent").description("Show recently added or modified contacts").option("-l, --limit <n>", "Number to show", "10").option("-j, --json", "Output JSON").action(async (opts) => {
5926
6037
  const store = getStore();
@@ -7925,7 +8036,7 @@ Health data cleared for ${contact?.display_name ?? id}
7925
8036
  // src/cli/commands/audience.tsx
7926
8037
  import chalk5 from "chalk";
7927
8038
 
7928
- // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
8039
+ // node_modules/zod/v3/external.js
7929
8040
  var exports_external = {};
7930
8041
  __export(exports_external, {
7931
8042
  void: () => voidType,
@@ -8037,7 +8148,7 @@ __export(exports_external, {
8037
8148
  BRAND: () => BRAND
8038
8149
  });
8039
8150
 
8040
- // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
8151
+ // node_modules/zod/v3/helpers/util.js
8041
8152
  var util;
8042
8153
  (function(util2) {
8043
8154
  util2.assertEqual = (_) => {};
@@ -8168,7 +8279,7 @@ var getParsedType = (data) => {
8168
8279
  }
8169
8280
  };
8170
8281
 
8171
- // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
8282
+ // node_modules/zod/v3/ZodError.js
8172
8283
  var ZodIssueCode = util.arrayToEnum([
8173
8284
  "invalid_type",
8174
8285
  "invalid_literal",
@@ -8287,7 +8398,7 @@ ZodError.create = (issues) => {
8287
8398
  return error;
8288
8399
  };
8289
8400
 
8290
- // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
8401
+ // node_modules/zod/v3/locales/en.js
8291
8402
  var errorMap = (issue, _ctx) => {
8292
8403
  let message;
8293
8404
  switch (issue.code) {
@@ -8390,7 +8501,7 @@ var errorMap = (issue, _ctx) => {
8390
8501
  };
8391
8502
  var en_default = errorMap;
8392
8503
 
8393
- // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
8504
+ // node_modules/zod/v3/errors.js
8394
8505
  var overrideErrorMap = en_default;
8395
8506
  function setErrorMap(map) {
8396
8507
  overrideErrorMap = map;
@@ -8398,7 +8509,7 @@ function setErrorMap(map) {
8398
8509
  function getErrorMap() {
8399
8510
  return overrideErrorMap;
8400
8511
  }
8401
- // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
8512
+ // node_modules/zod/v3/helpers/parseUtil.js
8402
8513
  var makeIssue = (params) => {
8403
8514
  const { data, path, errorMaps, issueData } = params;
8404
8515
  const fullPath = [...path, ...issueData.path || []];
@@ -8504,14 +8615,14 @@ var isAborted = (x) => x.status === "aborted";
8504
8615
  var isDirty = (x) => x.status === "dirty";
8505
8616
  var isValid = (x) => x.status === "valid";
8506
8617
  var isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
8507
- // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
8618
+ // node_modules/zod/v3/helpers/errorUtil.js
8508
8619
  var errorUtil;
8509
8620
  (function(errorUtil2) {
8510
8621
  errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {};
8511
8622
  errorUtil2.toString = (message) => typeof message === "string" ? message : message?.message;
8512
8623
  })(errorUtil || (errorUtil = {}));
8513
8624
 
8514
- // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
8625
+ // node_modules/zod/v3/types.js
8515
8626
  class ParseInputLazyPath {
8516
8627
  constructor(parent, value, path, key) {
8517
8628
  this._cachedPath = [];
@@ -12228,73 +12339,73 @@ function registerStorageCommands(program2) {
12228
12339
  // src/cli/legacy.ts
12229
12340
  import { createHash as createHash2 } from "crypto";
12230
12341
  import fs, { constants, closeSync, existsSync as existsSync6, fstatSync, lstatSync, readSync, realpathSync, statSync as statSync2, writeSync } from "fs";
12231
- import { homedir as homedir2 } from "os";
12232
- import { basename, dirname, join as join3, resolve as resolve2 } from "path";
12342
+ import { homedir as homedir3 } from "os";
12343
+ import { basename, dirname, join as join4, resolve as resolve2 } from "path";
12233
12344
  import chalk7 from "chalk";
12234
12345
 
12235
12346
  // src/db/paths.ts
12236
- import { homedir as pathsResolverHomedir2 } from "os";
12237
- import { join as pathsResolverJoin2 } from "path";
12238
- var PATHS_RESOLVER_KIND_ENV2 = {
12347
+ import { homedir as pathsResolverHomedir } from "os";
12348
+ import { join as pathsResolverJoin } from "path";
12349
+ var PATHS_RESOLVER_KIND_ENV = {
12239
12350
  data: "HASNA_DATA_HOME",
12240
12351
  state: "HASNA_STATE_HOME"
12241
12352
  };
12242
- var PATHS_RESOLVER_APP_SLUG_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
12243
- function pathsResolverAssertApp2(app) {
12353
+ var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
12354
+ function pathsResolverAssertApp(app) {
12244
12355
  if (typeof app !== "string" || app.length === 0) {
12245
12356
  throw new TypeError("paths: app must be a non-empty string");
12246
12357
  }
12247
- if (!PATHS_RESOLVER_APP_SLUG_RE2.test(app)) {
12358
+ if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
12248
12359
  throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
12249
12360
  }
12250
12361
  }
12251
- function pathsResolverAssertKind2(kind) {
12252
- if (!Object.keys(PATHS_RESOLVER_KIND_ENV2).includes(kind)) {
12253
- throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV2).join(", ")}`);
12362
+ function pathsResolverAssertKind(kind) {
12363
+ if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
12364
+ throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
12254
12365
  }
12255
12366
  }
12256
- function pathsResolverBaseDir2(kind, options) {
12257
- pathsResolverAssertKind2(kind);
12367
+ function pathsResolverBaseDir(kind, options) {
12368
+ pathsResolverAssertKind(kind);
12258
12369
  const env = options.env ?? process.env;
12259
- const override = env[PATHS_RESOLVER_KIND_ENV2[kind]];
12370
+ const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
12260
12371
  if (typeof override === "string" && override.length > 0)
12261
12372
  return override;
12262
- const home = options.home ?? pathsResolverHomedir2();
12373
+ const home = options.home ?? pathsResolverHomedir();
12263
12374
  const platform = options.platform ?? process.platform;
12264
12375
  if (platform === "darwin") {
12265
12376
  switch (kind) {
12266
12377
  case "data":
12267
- return pathsResolverJoin2(home, "Library", "Application Support", "Hasna");
12378
+ return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
12268
12379
  case "state":
12269
- return pathsResolverJoin2(home, "Library", "Logs", "Hasna");
12380
+ return pathsResolverJoin(home, "Library", "Logs", "Hasna");
12270
12381
  }
12271
12382
  }
12272
12383
  switch (kind) {
12273
12384
  case "data":
12274
- return pathsResolverJoin2(home, ".local", "share", "hasna");
12385
+ return pathsResolverJoin(home, ".local", "share", "hasna");
12275
12386
  case "state":
12276
- return pathsResolverJoin2(home, ".local", "state", "hasna");
12387
+ return pathsResolverJoin(home, ".local", "state", "hasna");
12277
12388
  }
12278
12389
  }
12279
- function pathsResolverResolve2(kind, options) {
12280
- pathsResolverAssertApp2(options.app);
12281
- const appSegment = options.internal === true ? pathsResolverJoin2("internal", options.app) : options.app;
12282
- return pathsResolverJoin2(pathsResolverBaseDir2(kind, options), appSegment);
12390
+ function pathsResolverResolve(kind, options) {
12391
+ pathsResolverAssertApp(options.app);
12392
+ const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
12393
+ return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
12283
12394
  }
12284
12395
  function dataDir2(options) {
12285
- return pathsResolverResolve2("data", options);
12396
+ return pathsResolverResolve("data", options);
12286
12397
  }
12287
12398
 
12288
12399
  // src/cli/legacy.ts
12289
12400
  function home(env = process.env) {
12290
- return env.HOME || env.USERPROFILE || homedir2();
12401
+ return env.HOME || env.USERPROFILE || homedir3();
12291
12402
  }
12292
12403
  function legacyDatabaseCandidates(env = process.env) {
12293
12404
  const base = home(env);
12294
12405
  const paths = [
12295
- { source: "xdg", path: join3(dataDir2({ app: "contacts", home: base, env }), "contacts.db") },
12296
- { source: "legacy-hasna", path: join3(base, ".hasna", "contacts", "contacts.db") },
12297
- { source: "ancient", path: join3(base, ".contacts", "contacts.db") }
12406
+ { source: "xdg", path: join4(dataDir2({ app: "contacts", home: base, env }), "contacts.db") },
12407
+ { source: "legacy-hasna", path: join4(base, ".hasna", "contacts", "contacts.db") },
12408
+ { source: "ancient", path: join4(base, ".contacts", "contacts.db") }
12298
12409
  ];
12299
12410
  return paths.map(({ source, path }) => {
12300
12411
  const exists = existsSync6(path) && lstatSync(path).isFile();
@@ -12441,6 +12552,175 @@ function registerLegacyCommands(program2) {
12441
12552
  });
12442
12553
  }
12443
12554
 
12555
+ // src/browser/install.ts
12556
+ import { chmodSync, cpSync, existsSync as existsSync7, mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
12557
+ import { homedir as homedir5 } from "os";
12558
+ import { dirname as dirname2, join as join6, resolve as resolve3 } from "path";
12559
+ import { fileURLToPath } from "url";
12560
+
12561
+ // src/browser/identity.ts
12562
+ var EXTENSION_ID = "ceegpmbcoiomonopbhpifdalooccfgjj";
12563
+ var HOST_NAME = "com.hasna.contacts";
12564
+
12565
+ // src/browser/protocol.ts
12566
+ class BrowserError extends Error {
12567
+ code;
12568
+ constructor(code) {
12569
+ super(code);
12570
+ this.code = code;
12571
+ this.name = "BrowserError";
12572
+ }
12573
+ }
12574
+ function safeError(error) {
12575
+ return error instanceof BrowserError ? error.code : "CONTACTS_UNAVAILABLE";
12576
+ }
12577
+
12578
+ class NativeProtocol {
12579
+ handlers;
12580
+ buffer = Buffer.alloc(0);
12581
+ queued = 0;
12582
+ chain = Promise.resolve();
12583
+ constructor(handlers) {
12584
+ this.handlers = handlers;
12585
+ }
12586
+ accept(chunk) {
12587
+ this.buffer = Buffer.concat([this.buffer, chunk]);
12588
+ if (this.buffer.length > 1048576)
12589
+ return this.handlers.fail();
12590
+ while (this.buffer.length >= 4) {
12591
+ const length = this.buffer.readUInt32LE(0);
12592
+ if (!length || length > 65536)
12593
+ return this.handlers.fail();
12594
+ if (this.buffer.length < 4 + length)
12595
+ break;
12596
+ let message;
12597
+ try {
12598
+ message = JSON.parse(this.buffer.subarray(4, 4 + length).toString());
12599
+ } catch {
12600
+ return this.handlers.fail();
12601
+ }
12602
+ this.buffer = this.buffer.subarray(4 + length);
12603
+ if (!message || typeof message.id !== "string" || !/^[0-9a-f-]{36}$/.test(message.id))
12604
+ return this.handlers.fail();
12605
+ if (++this.queued > 32)
12606
+ return this.handlers.fail();
12607
+ this.chain = this.chain.then(async () => {
12608
+ try {
12609
+ const result = await this.handlers.handle(message);
12610
+ this.handlers.send({ id: message.id, ok: true, result });
12611
+ } catch (e) {
12612
+ this.handlers.send({ id: message.id, ok: false, error: safeError(e) });
12613
+ } finally {
12614
+ this.queued--;
12615
+ }
12616
+ });
12617
+ }
12618
+ }
12619
+ async idle() {
12620
+ await this.chain;
12621
+ }
12622
+ }
12623
+
12624
+ // src/browser/install.ts
12625
+ function packageRoot() {
12626
+ let dir = dirname2(fileURLToPath(import.meta.url));
12627
+ for (let i = 0;i < 5; i++, dir = dirname2(dir)) {
12628
+ const p = join6(dir, "package.json");
12629
+ if (existsSync7(p) && JSON.parse(readFileSync4(p, "utf8")).name === "@hasna/contacts")
12630
+ return dir;
12631
+ }
12632
+ throw new BrowserError("PACKAGE_ROOT_NOT_FOUND");
12633
+ }
12634
+ function hostDirectories(home2 = homedir5(), userDataDir) {
12635
+ const dirs = process.platform === "darwin" ? [
12636
+ "Library/Application Support/Google/Chrome/NativeMessagingHosts",
12637
+ "Library/Application Support/Chromium/NativeMessagingHosts",
12638
+ "Library/Application Support/Google/Chrome for Testing/NativeMessagingHosts"
12639
+ ] : [".config/google-chrome/NativeMessagingHosts", ".config/chromium/NativeMessagingHosts"];
12640
+ const out = dirs.map((d) => join6(home2, d));
12641
+ if (userDataDir)
12642
+ out.push(join6(resolve3(userDataDir), "NativeMessagingHosts"));
12643
+ return [...new Set(out)];
12644
+ }
12645
+ function installBrowser(options = {}) {
12646
+ if (!["darwin", "linux"].includes(process.platform))
12647
+ throw new BrowserError("UNSUPPORTED_PLATFORM");
12648
+ const root = options.root ?? packageRoot();
12649
+ const home2 = options.home ?? homedir5();
12650
+ const install = join6(home2, ".hasna", "contacts", "chrome");
12651
+ mkdirSync(install, { recursive: true, mode: 448 });
12652
+ const extension = join6(install, "extension");
12653
+ cpSync(join6(root, "extension"), extension, { recursive: true });
12654
+ const host = join6(install, "native-host");
12655
+ const entry = resolve3(root, "dist/browser/native.js");
12656
+ if (!existsSync7(entry))
12657
+ throw new BrowserError("BUILD_REQUIRED");
12658
+ writeFileSync2(host, `#!${options.execPath ?? process.execPath}
12659
+ import ${JSON.stringify(entry)};
12660
+ `, { mode: 448 });
12661
+ chmodSync(host, 448);
12662
+ const manifests = hostDirectories(home2, options.userDataDir).map((dir) => {
12663
+ mkdirSync(dir, { recursive: true, mode: 448 });
12664
+ const path = join6(dir, `${HOST_NAME}.json`);
12665
+ const manifest = {
12666
+ name: HOST_NAME,
12667
+ description: "Hasna Contacts autofill",
12668
+ path: host,
12669
+ type: "stdio",
12670
+ allowed_origins: [`chrome-extension://${EXTENSION_ID}/`]
12671
+ };
12672
+ if (existsSync7(path)) {
12673
+ const old = JSON.parse(readFileSync4(path, "utf8"));
12674
+ if (old.name !== HOST_NAME || old.path !== host)
12675
+ throw new BrowserError("EXISTING_HOST_CONFLICT");
12676
+ }
12677
+ writeFileSync2(path, `${JSON.stringify(manifest, null, 2)}
12678
+ `, { mode: 384 });
12679
+ return path;
12680
+ });
12681
+ return {
12682
+ ok: true,
12683
+ extensionId: EXTENSION_ID,
12684
+ extensionPath: extension,
12685
+ nativeHost: host,
12686
+ nativeManifests: manifests,
12687
+ next: "In the desired Chrome profile, open chrome://extensions, enable Developer mode, choose Load unpacked, and select extensionPath. Pin Contacts."
12688
+ };
12689
+ }
12690
+ function browserStatus(options = {}) {
12691
+ const home2 = options.home ?? homedir5();
12692
+ const host = join6(home2, ".hasna", "contacts", "chrome", "native-host");
12693
+ const extension = join6(home2, ".hasna", "contacts", "chrome", "extension");
12694
+ return {
12695
+ ok: true,
12696
+ extensionId: EXTENSION_ID,
12697
+ extensionInstalled: existsSync7(join6(extension, "manifest.json")),
12698
+ nativeHostInstalled: existsSync7(host),
12699
+ manifests: hostDirectories(home2, options.userDataDir).map((dir) => ({
12700
+ path: join6(dir, `${HOST_NAME}.json`),
12701
+ present: existsSync7(join6(dir, `${HOST_NAME}.json`))
12702
+ }))
12703
+ };
12704
+ }
12705
+
12706
+ // src/browser/cli.ts
12707
+ function registerBrowserCommands(program2) {
12708
+ const output = (fn) => {
12709
+ try {
12710
+ const r = fn();
12711
+ console.log(JSON.stringify(r));
12712
+ if (r?.ok === false)
12713
+ process.exitCode = 1;
12714
+ } catch (e) {
12715
+ console.log(JSON.stringify({ ok: false, error: safeError(e) }));
12716
+ process.exitCode = 1;
12717
+ }
12718
+ };
12719
+ const cmd = program2.command("browser").description("Chrome extension: inline contact autofill (hosted Contacts API)");
12720
+ cmd.command("install").description("Install the native host and unpacked extension assets; no browser restart").option("--user-data-dir <path>", "Also register the native host in an automated Chrome user-data directory").action((opts) => output(() => installBrowser({ userDataDir: opts.userDataDir })));
12721
+ cmd.command("status").description("Report where the native host manifests and extension assets are installed").option("--user-data-dir <path>", "Also check an automated Chrome user-data directory").action((opts) => output(() => browserStatus({ userDataDir: opts.userDataDir })));
12722
+ }
12723
+
12444
12724
  // src/cli/index.tsx
12445
12725
  import { createRequire } from "module";
12446
12726
  var { registerEventCommands: registerEventCommands2 } = exports_commander;
@@ -12454,6 +12734,7 @@ registerAdvancedCommands(program);
12454
12734
  registerAudienceCommands(program);
12455
12735
  registerStorageCommands(program);
12456
12736
  registerLegacyCommands(program);
12737
+ registerBrowserCommands(program);
12457
12738
  registerWebhookCommands2?.(program, { source: "contacts" });
12458
12739
  registerEventCommands2(program, { source: "contacts", eventsCommandName: "hasna-events" });
12459
12740
  program.parseAsync(process.argv).catch((err) => {