@hasna/contacts 0.8.1 → 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.
- package/README.md +10 -0
- package/dist/browser/cli.d.ts +4 -0
- package/dist/browser/cli.d.ts.map +1 -0
- package/dist/browser/identity.d.ts +3 -0
- package/dist/browser/identity.d.ts.map +1 -0
- package/dist/browser/install.d.ts +32 -0
- package/dist/browser/install.d.ts.map +1 -0
- package/dist/browser/native.d.ts +3 -0
- package/dist/browser/native.d.ts.map +1 -0
- package/dist/browser/native.js +822 -0
- package/dist/browser/protocol.d.ts +26 -0
- package/dist/browser/protocol.d.ts.map +1 -0
- package/dist/browser/values.d.ts +18 -0
- package/dist/browser/values.d.ts.map +1 -0
- package/dist/cli/commands/core.d.ts.map +1 -1
- package/dist/cli/index.js +408 -127
- package/dist/cli/status-domain.preload.d.ts +1 -1
- package/dist/cli/status-domain.preload.d.ts.map +1 -1
- package/dist/cli/status-fixture.d.ts +17 -0
- package/dist/cli/status-fixture.d.ts.map +1 -0
- package/dist/index.js +43 -4
- package/dist/lib/audience-contract.d.ts +9 -9
- package/dist/lib/compact-output.d.ts +67 -0
- package/dist/lib/compact-output.d.ts.map +1 -0
- package/dist/mcp/handlers/core.d.ts.map +1 -1
- package/dist/mcp/index.d.ts +2 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +378 -194
- package/dist/mcp/profile.d.ts +8 -0
- package/dist/mcp/profile.d.ts.map +1 -0
- package/dist/mcp/register-tools.d.ts +3 -1
- package/dist/mcp/register-tools.d.ts.map +1 -1
- package/dist/mcp/storage-tools.d.ts +2 -1
- package/dist/mcp/storage-tools.d.ts.map +1 -1
- package/dist/mcp/tools.d.ts +7 -45
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/sdk/index.js +1 -1
- package/dist/server/index.js +30 -16
- package/dist/server/pg-store.d.ts +1 -0
- package/dist/server/pg-store.d.ts.map +1 -1
- package/dist/store/index.d.ts.map +1 -1
- package/docs/chrome-autofill.md +45 -0
- package/extension/background.js +284 -0
- package/extension/content.js +387 -0
- package/extension/detect.js +228 -0
- package/extension/fill.js +112 -0
- package/extension/icons/128.png +0 -0
- package/extension/icons/16.png +0 -0
- package/extension/icons/32.png +0 -0
- package/extension/icons/48.png +0 -0
- package/extension/icons/icon.svg +17 -0
- package/extension/manifest.json +54 -0
- package/extension/popup.css +78 -0
- package/extension/popup.html +47 -0
- package/extension/popup.js +166 -0
- 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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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(
|
|
1294
|
-
const localBin = path.resolve(
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2312
|
-
function
|
|
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 (!
|
|
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
|
|
2321
|
-
|
|
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
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
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 ??
|
|
2334
|
+
const home = options.home ?? homedir();
|
|
2332
2335
|
const platform = options.platform ?? process.platform;
|
|
2333
|
-
if (platform
|
|
2336
|
+
if (isMacOS(platform)) {
|
|
2334
2337
|
switch (kind) {
|
|
2335
2338
|
case "config":
|
|
2336
2339
|
case "data":
|
|
2337
|
-
return
|
|
2340
|
+
return join(home, "Library", "Application Support", "Hasna");
|
|
2338
2341
|
case "cache":
|
|
2339
|
-
return
|
|
2342
|
+
return join(home, "Library", "Caches", "Hasna");
|
|
2340
2343
|
case "state":
|
|
2341
|
-
return
|
|
2344
|
+
return join(home, "Library", "Logs", "Hasna");
|
|
2342
2345
|
}
|
|
2343
2346
|
}
|
|
2344
2347
|
switch (kind) {
|
|
2345
2348
|
case "config":
|
|
2346
|
-
return
|
|
2349
|
+
return join(home, ".config", "hasna");
|
|
2347
2350
|
case "data":
|
|
2348
|
-
return
|
|
2351
|
+
return join(home, ".local", "share", "hasna");
|
|
2349
2352
|
case "state":
|
|
2350
|
-
return
|
|
2353
|
+
return join(home, ".local", "state", "hasna");
|
|
2351
2354
|
case "cache":
|
|
2352
|
-
return
|
|
2355
|
+
return join(home, ".cache", "hasna");
|
|
2353
2356
|
}
|
|
2354
2357
|
}
|
|
2355
|
-
function
|
|
2356
|
-
|
|
2357
|
-
const appSegment = options.internal === true ?
|
|
2358
|
-
return
|
|
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
|
|
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"] ||
|
|
2370
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir2();
|
|
2368
2371
|
}
|
|
2369
2372
|
function legacyHomeDir() {
|
|
2370
|
-
return
|
|
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(
|
|
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 =
|
|
2421
|
-
this.eventsPath =
|
|
2422
|
-
this.deliveriesPath =
|
|
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 =
|
|
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)
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
5550
|
-
limit
|
|
5551
|
-
offset
|
|
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("
|
|
5903
|
-
|
|
5904
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
12232
|
-
import { basename, dirname, join as
|
|
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
|
|
12237
|
-
import { join as
|
|
12238
|
-
var
|
|
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
|
|
12243
|
-
function
|
|
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 (!
|
|
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
|
|
12252
|
-
if (!Object.keys(
|
|
12253
|
-
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(
|
|
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
|
|
12257
|
-
|
|
12367
|
+
function pathsResolverBaseDir(kind, options) {
|
|
12368
|
+
pathsResolverAssertKind(kind);
|
|
12258
12369
|
const env = options.env ?? process.env;
|
|
12259
|
-
const override = env[
|
|
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 ??
|
|
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
|
|
12378
|
+
return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
|
|
12268
12379
|
case "state":
|
|
12269
|
-
return
|
|
12380
|
+
return pathsResolverJoin(home, "Library", "Logs", "Hasna");
|
|
12270
12381
|
}
|
|
12271
12382
|
}
|
|
12272
12383
|
switch (kind) {
|
|
12273
12384
|
case "data":
|
|
12274
|
-
return
|
|
12385
|
+
return pathsResolverJoin(home, ".local", "share", "hasna");
|
|
12275
12386
|
case "state":
|
|
12276
|
-
return
|
|
12387
|
+
return pathsResolverJoin(home, ".local", "state", "hasna");
|
|
12277
12388
|
}
|
|
12278
12389
|
}
|
|
12279
|
-
function
|
|
12280
|
-
|
|
12281
|
-
const appSegment = options.internal === true ?
|
|
12282
|
-
return
|
|
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
|
|
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 ||
|
|
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:
|
|
12296
|
-
{ source: "legacy-hasna", path:
|
|
12297
|
-
{ source: "ancient", path:
|
|
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) => {
|