@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.
- package/README.md +93 -13
- 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 +479 -149
- 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/cloud/http-storage.d.ts +20 -12
- package/dist/cloud/http-storage.d.ts.map +1 -1
- package/dist/cloud/resolver-inputs.d.ts +51 -0
- package/dist/cloud/resolver-inputs.d.ts.map +1 -0
- package/dist/db/paths.d.ts +4 -4
- package/dist/db/paths.d.ts.map +1 -1
- package/dist/generated/storage-kit/backend.d.ts +19 -0
- package/dist/generated/storage-kit/backend.d.ts.map +1 -0
- package/dist/generated/storage-kit/index.d.ts +2 -1
- package/dist/generated/storage-kit/index.d.ts.map +1 -1
- package/dist/generated/storage-kit/migrations.d.ts +21 -0
- package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
- package/dist/generated/storage-kit/own.d.ts +11 -0
- package/dist/generated/storage-kit/own.d.ts.map +1 -0
- package/dist/generated/storage-kit/pool.d.ts +5 -17
- package/dist/generated/storage-kit/pool.d.ts.map +1 -1
- package/dist/generated/storage-kit/query.d.ts +1 -1
- package/dist/generated/storage-kit/query.d.ts.map +1 -1
- package/dist/generated/storage-kit/tls.d.ts +30 -3
- package/dist/generated/storage-kit/tls.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +148 -28
- 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 +11 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +490 -212
- 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/startup-gate.d.ts +48 -0
- package/dist/mcp/startup-gate.d.ts.map +1 -0
- 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.d.ts +70 -3
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +990 -7
- package/dist/server/index.js +231 -59
- 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/hasna.contract.json +20 -6
- package/package.json +10 -6
- package/dist/lib/config.d.ts +0 -7
- package/dist/lib/config.d.ts.map +0 -1
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;
|
|
@@ -3816,7 +3819,7 @@ function replaySummary(events, deliveries, nextCursor) {
|
|
|
3816
3819
|
return `Replayed ${events} event(s), ${deliveries} delivery result(s)${suffix}`;
|
|
3817
3820
|
}
|
|
3818
3821
|
|
|
3819
|
-
//
|
|
3822
|
+
// node_modules/commander/esm.mjs
|
|
3820
3823
|
var import__ = __toESM(require_commander(), 1);
|
|
3821
3824
|
var {
|
|
3822
3825
|
program,
|
|
@@ -3840,6 +3843,7 @@ import chalk2 from "chalk";
|
|
|
3840
3843
|
|
|
3841
3844
|
// src/cloud/http-storage.ts
|
|
3842
3845
|
import {
|
|
3846
|
+
ClientTransportConfigurationError,
|
|
3843
3847
|
createHasnaHttpTransport,
|
|
3844
3848
|
resolveCredential,
|
|
3845
3849
|
resolveClientTransport as resolveSharedClientTransport
|
|
@@ -3929,6 +3933,27 @@ function assertConfigurationUnchanged(env, expected) {
|
|
|
3929
3933
|
invalid();
|
|
3930
3934
|
}
|
|
3931
3935
|
|
|
3936
|
+
// src/cloud/resolver-inputs.ts
|
|
3937
|
+
import {
|
|
3938
|
+
clientTransportEnvKeys as clientTransportEnvKeys2,
|
|
3939
|
+
credentialOverrideEnvKey,
|
|
3940
|
+
credentialPointerEnvKey,
|
|
3941
|
+
CREDENTIAL_PROFILE_ENV_KEY
|
|
3942
|
+
} from "@hasna/contracts/client";
|
|
3943
|
+
var CONTRACTS_AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
3944
|
+
function isAmbientContactsEnv(env) {
|
|
3945
|
+
if (typeof process !== "undefined" && env === process.env)
|
|
3946
|
+
return true;
|
|
3947
|
+
return env[CONTRACTS_AMBIENT_ENVIRONMENT] === true;
|
|
3948
|
+
}
|
|
3949
|
+
function contactsResolverCredentials(env, credentials = {}) {
|
|
3950
|
+
const keychain = { ...credentials.keychain };
|
|
3951
|
+
if (keychain.enabled === undefined && keychain.run === undefined) {
|
|
3952
|
+
keychain.enabled = isAmbientContactsEnv(env);
|
|
3953
|
+
}
|
|
3954
|
+
return { ...credentials, keychain };
|
|
3955
|
+
}
|
|
3956
|
+
|
|
3932
3957
|
// src/cloud/http-storage.ts
|
|
3933
3958
|
var RETIRED_CLIENT_SELECTOR_KEYS = [
|
|
3934
3959
|
"HASNA_CONTACTS_STORAGE_MODE",
|
|
@@ -3964,37 +3989,44 @@ class ContactsClientConfigurationError extends Error {
|
|
|
3964
3989
|
this.name = "ContactsClientConfigurationError";
|
|
3965
3990
|
}
|
|
3966
3991
|
}
|
|
3967
|
-
function unconfiguredResolution(resolution,
|
|
3992
|
+
function unconfiguredResolution(issue, resolution, warning = null) {
|
|
3968
3993
|
return {
|
|
3969
3994
|
transport: "unconfigured",
|
|
3970
3995
|
baseUrl: null,
|
|
3971
|
-
apiUrlSource: resolution
|
|
3972
|
-
apiKeyPresent: resolution
|
|
3973
|
-
apiKeySource: resolution
|
|
3974
|
-
apiKeyTier: resolution
|
|
3996
|
+
apiUrlSource: resolution?.apiUrlSource ?? null,
|
|
3997
|
+
apiKeyPresent: resolution?.apiKeyPresent ?? false,
|
|
3998
|
+
apiKeySource: resolution?.apiKeySource ?? null,
|
|
3999
|
+
apiKeyTier: resolution?.apiKeyTier ?? null,
|
|
3975
4000
|
configured: false,
|
|
3976
4001
|
misconfigured: true,
|
|
3977
4002
|
issue,
|
|
3978
|
-
warning
|
|
4003
|
+
warning
|
|
3979
4004
|
};
|
|
3980
4005
|
}
|
|
3981
|
-
function resolveContactsClientTransport(name, env = process.env) {
|
|
4006
|
+
function resolveContactsClientTransport(name, env = process.env, credentials = {}) {
|
|
3982
4007
|
if (name !== "contacts") {
|
|
3983
4008
|
throw new ContactsClientConfigurationError("CONTACTS_CLIENT_NAME_INVALID", "This resolver only accepts the contacts app slug.");
|
|
3984
4009
|
}
|
|
3985
4010
|
assertNoRetiredClientSelectors(env);
|
|
3986
4011
|
const stamp = clientConfigurationStamp(env);
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
4012
|
+
let resolution;
|
|
4013
|
+
try {
|
|
4014
|
+
resolution = resolveSharedClientTransport(name, env, {
|
|
4015
|
+
credentials: contactsResolverCredentials(env, credentials)
|
|
4016
|
+
});
|
|
4017
|
+
} catch (error) {
|
|
4018
|
+
assertConfigurationUnchanged(env, stamp);
|
|
4019
|
+
if (error instanceof ClientTransportConfigurationError) {
|
|
4020
|
+
return unconfiguredResolution(error.message, null);
|
|
4021
|
+
}
|
|
4022
|
+
throw error;
|
|
3992
4023
|
}
|
|
4024
|
+
assertConfigurationUnchanged(env, stamp);
|
|
3993
4025
|
try {
|
|
3994
4026
|
assertHttpsBaseUrl(resolution.baseUrl);
|
|
3995
4027
|
} catch (error) {
|
|
3996
4028
|
return {
|
|
3997
|
-
...unconfiguredResolution(
|
|
4029
|
+
...unconfiguredResolution(error instanceof Error ? error.message : String(error), resolution),
|
|
3998
4030
|
apiKeyPresent: resolution.apiKeyPresent
|
|
3999
4031
|
};
|
|
4000
4032
|
}
|
|
@@ -4008,7 +4040,7 @@ function resolveContactsClientTransport(name, env = process.env) {
|
|
|
4008
4040
|
configured: true,
|
|
4009
4041
|
misconfigured: false,
|
|
4010
4042
|
issue: null,
|
|
4011
|
-
warning:
|
|
4043
|
+
warning: resolution.warning
|
|
4012
4044
|
};
|
|
4013
4045
|
}
|
|
4014
4046
|
function resourcePath(resource) {
|
|
@@ -4062,20 +4094,21 @@ function createStorageClient(name, transport) {
|
|
|
4062
4094
|
}
|
|
4063
4095
|
};
|
|
4064
4096
|
}
|
|
4065
|
-
function resolveContactsStorageClient(name, env = process.env) {
|
|
4066
|
-
const resolution = resolveContactsClientTransport(name, env);
|
|
4097
|
+
function resolveContactsStorageClient(name, env = process.env, credentials = {}) {
|
|
4098
|
+
const resolution = resolveContactsClientTransport(name, env, credentials);
|
|
4067
4099
|
if (!resolution.configured || !resolution.baseUrl) {
|
|
4068
4100
|
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.");
|
|
4069
4101
|
}
|
|
4070
4102
|
const baseUrl = resolution.baseUrl;
|
|
4103
|
+
const chainOptions = contactsResolverCredentials(env, credentials);
|
|
4071
4104
|
const request = async (method, path, body, opts) => {
|
|
4072
4105
|
const stamp = clientConfigurationStamp(env);
|
|
4073
4106
|
const snapshot = { ...env };
|
|
4074
|
-
const current = resolveContactsClientTransport(name, snapshot);
|
|
4107
|
+
const current = resolveContactsClientTransport(name, snapshot, chainOptions);
|
|
4075
4108
|
if (!current.configured || current.baseUrl !== baseUrl) {
|
|
4076
4109
|
throw new ContactsClientConfigurationError("CONTACTS_AUTHORITY_CHANGED", "Client authority changed or disappeared; construct a new client before sending data.");
|
|
4077
4110
|
}
|
|
4078
|
-
const credential = resolveCredential(name, snapshot);
|
|
4111
|
+
const credential = resolveCredential(name, snapshot, chainOptions);
|
|
4079
4112
|
if (!credential)
|
|
4080
4113
|
throw new ContactsClientConfigurationError("CONTACTS_API_NOT_CONFIGURED", "No credential is available.");
|
|
4081
4114
|
assertConfigurationUnchanged(env, stamp);
|
|
@@ -4125,6 +4158,39 @@ function stripUndefined(input) {
|
|
|
4125
4158
|
return out;
|
|
4126
4159
|
}
|
|
4127
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
|
+
|
|
4128
4194
|
class ApiStore {
|
|
4129
4195
|
client;
|
|
4130
4196
|
mode = "api";
|
|
@@ -4194,11 +4260,17 @@ class ApiStore {
|
|
|
4194
4260
|
async mergeContacts() {
|
|
4195
4261
|
return unavailable("mergeContacts");
|
|
4196
4262
|
}
|
|
4197
|
-
async addEmailToContact() {
|
|
4198
|
-
|
|
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);
|
|
4199
4268
|
}
|
|
4200
|
-
async addPhoneToContact() {
|
|
4201
|
-
|
|
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);
|
|
4202
4274
|
}
|
|
4203
4275
|
async archiveContact() {
|
|
4204
4276
|
return unavailable("archiveContact");
|
|
@@ -5369,18 +5441,117 @@ async function confirmUser(question) {
|
|
|
5369
5441
|
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
5370
5442
|
}
|
|
5371
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
|
+
|
|
5372
5533
|
// src/cli/commands/core.tsx
|
|
5373
5534
|
function collect(val, prev) {
|
|
5374
5535
|
return [...prev, val];
|
|
5375
5536
|
}
|
|
5376
5537
|
function registerCoreCommands(program2, version) {
|
|
5377
5538
|
program2.command("status").description("Show CLI version, API endpoint, storage mode, and record counts").option("--json", "Output as JSON").action(async (opts) => {
|
|
5378
|
-
|
|
5379
|
-
|
|
5539
|
+
let apiLine = "(not configured \u2014 set HASNA_CONTACTS_API_URL plus a contacts API key)";
|
|
5540
|
+
let apiUrlSource = null;
|
|
5541
|
+
let apiKeySource = null;
|
|
5542
|
+
let apiKeyTier = null;
|
|
5543
|
+
let issue = null;
|
|
5380
5544
|
let counts;
|
|
5381
5545
|
let storage;
|
|
5382
5546
|
let failure;
|
|
5383
5547
|
try {
|
|
5548
|
+
const resolution = resolveContactsClientTransport("contacts");
|
|
5549
|
+
apiUrlSource = resolution.apiUrlSource;
|
|
5550
|
+
apiKeySource = resolution.apiKeySource;
|
|
5551
|
+
apiKeyTier = resolution.apiKeyTier;
|
|
5552
|
+
issue = resolution.issue;
|
|
5553
|
+
if (resolution.configured && resolution.baseUrl)
|
|
5554
|
+
apiLine = resolution.baseUrl;
|
|
5384
5555
|
const store = getStore();
|
|
5385
5556
|
const [contacts, companies] = await Promise.all([
|
|
5386
5557
|
store.listContacts({ limit: 1 }),
|
|
@@ -5400,7 +5571,11 @@ function registerCoreCommands(program2, version) {
|
|
|
5400
5571
|
service: "contacts",
|
|
5401
5572
|
version,
|
|
5402
5573
|
api: apiLine,
|
|
5574
|
+
api_url_source: apiUrlSource,
|
|
5575
|
+
api_key_source: apiKeySource,
|
|
5576
|
+
api_key_tier: apiKeyTier,
|
|
5403
5577
|
storage,
|
|
5578
|
+
...issue !== null ? { issue } : {},
|
|
5404
5579
|
...failure !== undefined ? { error: failure } : {},
|
|
5405
5580
|
...counts ? { counts } : {}
|
|
5406
5581
|
};
|
|
@@ -5410,7 +5585,13 @@ function registerCoreCommands(program2, version) {
|
|
|
5410
5585
|
}
|
|
5411
5586
|
console.log(chalk2.bold(`contacts v${version}`));
|
|
5412
5587
|
console.log(`API: ${apiLine}`);
|
|
5588
|
+
if (apiUrlSource)
|
|
5589
|
+
console.log(`API URL source: ${apiUrlSource}`);
|
|
5590
|
+
if (apiKeySource)
|
|
5591
|
+
console.log(`API key source: ${apiKeySource}${apiKeyTier ? ` (${apiKeyTier})` : ""}`);
|
|
5413
5592
|
console.log(`Storage: ${storage}`);
|
|
5593
|
+
if (issue !== null)
|
|
5594
|
+
console.log(chalk2.yellow(`Issue: ${issue}`));
|
|
5414
5595
|
if (failure !== undefined)
|
|
5415
5596
|
console.log(chalk2.red(`Error: ${failure}`));
|
|
5416
5597
|
if (counts) {
|
|
@@ -5482,19 +5663,19 @@ Add New Contact
|
|
|
5482
5663
|
\u2713 Contact created: ${contact.display_name} (${contact.id})
|
|
5483
5664
|
`));
|
|
5484
5665
|
});
|
|
5485
|
-
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) => {
|
|
5486
5667
|
const store = getStore();
|
|
5668
|
+
const limit = normalizeContactsLimit(opts.limit, opts.full ? 50 : 20);
|
|
5669
|
+
const offset = normalizeContactsOffset(opts.offset);
|
|
5487
5670
|
const result = await store.listContacts({
|
|
5488
5671
|
tag_id: opts.tag,
|
|
5489
5672
|
company_id: opts.company,
|
|
5490
|
-
|
|
5491
|
-
limit
|
|
5492
|
-
offset
|
|
5493
|
-
order_by: opts.orderBy,
|
|
5494
|
-
order_dir: opts.orderDir === "desc" ? "desc" : "asc"
|
|
5673
|
+
status: opts.status,
|
|
5674
|
+
limit,
|
|
5675
|
+
offset
|
|
5495
5676
|
});
|
|
5496
5677
|
if (opts.json) {
|
|
5497
|
-
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));
|
|
5498
5679
|
return;
|
|
5499
5680
|
}
|
|
5500
5681
|
if (result.contacts.length === 0) {
|
|
@@ -5835,33 +6016,22 @@ Invalid format: ${format}. Use csv, vcf, or json
|
|
|
5835
6016
|
process.stdout.write(output);
|
|
5836
6017
|
}
|
|
5837
6018
|
});
|
|
5838
|
-
program2.command("mcp").description("Print MCP server setup instructions").action(() => {
|
|
6019
|
+
program2.command("mcp").description("Print truthful MCP server setup instructions").action(() => {
|
|
5839
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;
|
|
5840
6023
|
console.log(`
|
|
5841
6024
|
${chalk2.bold.blue("\u2501\u2501\u2501 Contacts MCP Server Setup \u2501\u2501\u2501")}
|
|
5842
6025
|
|
|
5843
|
-
${chalk2.bold("
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
${chalk2.bold("2. Add to Claude Code (recommended):")}
|
|
5848
|
-
${chalk2.cyan("claude mcp add --transport stdio --scope user contacts -- contacts-mcp")}
|
|
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)}
|
|
5849
6029
|
|
|
5850
|
-
${
|
|
5851
|
-
|
|
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.
|
|
5852
6033
|
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
${chalk2.bold("Available tools (24 total):")}
|
|
5856
|
-
${chalk2.gray("Contacts: ")}${chalk2.white("create_contact get_contact update_contact delete_contact")}
|
|
5857
|
-
${chalk2.gray(" ")}${chalk2.white("list_contacts search_contacts merge_contacts")}
|
|
5858
|
-
${chalk2.gray("Companies:")}${chalk2.white("create_company get_company update_company delete_company")}
|
|
5859
|
-
${chalk2.gray(" ")}${chalk2.white("list_companies search_companies")}
|
|
5860
|
-
${chalk2.gray("Tags: ")}${chalk2.white("create_tag list_tags delete_tag")}
|
|
5861
|
-
${chalk2.gray(" ")}${chalk2.white("add_tag_to_contact remove_tag_from_contact")}
|
|
5862
|
-
${chalk2.gray("Rels: ")}${chalk2.white("add_relationship list_relationships delete_relationship")}
|
|
5863
|
-
${chalk2.gray("I/O: ")}${chalk2.white("import_contacts export_contacts get_stats")}
|
|
5864
|
-
`);
|
|
6034
|
+
Restart the client and verify the active profile in the MCP initialize instructions.`);
|
|
5865
6035
|
});
|
|
5866
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) => {
|
|
5867
6037
|
const store = getStore();
|
|
@@ -7866,7 +8036,7 @@ Health data cleared for ${contact?.display_name ?? id}
|
|
|
7866
8036
|
// src/cli/commands/audience.tsx
|
|
7867
8037
|
import chalk5 from "chalk";
|
|
7868
8038
|
|
|
7869
|
-
//
|
|
8039
|
+
// node_modules/zod/v3/external.js
|
|
7870
8040
|
var exports_external = {};
|
|
7871
8041
|
__export(exports_external, {
|
|
7872
8042
|
void: () => voidType,
|
|
@@ -7978,7 +8148,7 @@ __export(exports_external, {
|
|
|
7978
8148
|
BRAND: () => BRAND
|
|
7979
8149
|
});
|
|
7980
8150
|
|
|
7981
|
-
//
|
|
8151
|
+
// node_modules/zod/v3/helpers/util.js
|
|
7982
8152
|
var util;
|
|
7983
8153
|
(function(util2) {
|
|
7984
8154
|
util2.assertEqual = (_) => {};
|
|
@@ -8109,7 +8279,7 @@ var getParsedType = (data) => {
|
|
|
8109
8279
|
}
|
|
8110
8280
|
};
|
|
8111
8281
|
|
|
8112
|
-
//
|
|
8282
|
+
// node_modules/zod/v3/ZodError.js
|
|
8113
8283
|
var ZodIssueCode = util.arrayToEnum([
|
|
8114
8284
|
"invalid_type",
|
|
8115
8285
|
"invalid_literal",
|
|
@@ -8228,7 +8398,7 @@ ZodError.create = (issues) => {
|
|
|
8228
8398
|
return error;
|
|
8229
8399
|
};
|
|
8230
8400
|
|
|
8231
|
-
//
|
|
8401
|
+
// node_modules/zod/v3/locales/en.js
|
|
8232
8402
|
var errorMap = (issue, _ctx) => {
|
|
8233
8403
|
let message;
|
|
8234
8404
|
switch (issue.code) {
|
|
@@ -8331,7 +8501,7 @@ var errorMap = (issue, _ctx) => {
|
|
|
8331
8501
|
};
|
|
8332
8502
|
var en_default = errorMap;
|
|
8333
8503
|
|
|
8334
|
-
//
|
|
8504
|
+
// node_modules/zod/v3/errors.js
|
|
8335
8505
|
var overrideErrorMap = en_default;
|
|
8336
8506
|
function setErrorMap(map) {
|
|
8337
8507
|
overrideErrorMap = map;
|
|
@@ -8339,7 +8509,7 @@ function setErrorMap(map) {
|
|
|
8339
8509
|
function getErrorMap() {
|
|
8340
8510
|
return overrideErrorMap;
|
|
8341
8511
|
}
|
|
8342
|
-
//
|
|
8512
|
+
// node_modules/zod/v3/helpers/parseUtil.js
|
|
8343
8513
|
var makeIssue = (params) => {
|
|
8344
8514
|
const { data, path, errorMaps, issueData } = params;
|
|
8345
8515
|
const fullPath = [...path, ...issueData.path || []];
|
|
@@ -8445,14 +8615,14 @@ var isAborted = (x) => x.status === "aborted";
|
|
|
8445
8615
|
var isDirty = (x) => x.status === "dirty";
|
|
8446
8616
|
var isValid = (x) => x.status === "valid";
|
|
8447
8617
|
var isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
|
|
8448
|
-
//
|
|
8618
|
+
// node_modules/zod/v3/helpers/errorUtil.js
|
|
8449
8619
|
var errorUtil;
|
|
8450
8620
|
(function(errorUtil2) {
|
|
8451
8621
|
errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {};
|
|
8452
8622
|
errorUtil2.toString = (message) => typeof message === "string" ? message : message?.message;
|
|
8453
8623
|
})(errorUtil || (errorUtil = {}));
|
|
8454
8624
|
|
|
8455
|
-
//
|
|
8625
|
+
// node_modules/zod/v3/types.js
|
|
8456
8626
|
class ParseInputLazyPath {
|
|
8457
8627
|
constructor(parent, value, path, key) {
|
|
8458
8628
|
this._cachedPath = [];
|
|
@@ -12169,82 +12339,73 @@ function registerStorageCommands(program2) {
|
|
|
12169
12339
|
// src/cli/legacy.ts
|
|
12170
12340
|
import { createHash as createHash2 } from "crypto";
|
|
12171
12341
|
import fs, { constants, closeSync, existsSync as existsSync6, fstatSync, lstatSync, readSync, realpathSync, statSync as statSync2, writeSync } from "fs";
|
|
12172
|
-
import { homedir as
|
|
12173
|
-
import { basename, dirname, join as
|
|
12342
|
+
import { homedir as homedir3 } from "os";
|
|
12343
|
+
import { basename, dirname, join as join4, resolve as resolve2 } from "path";
|
|
12174
12344
|
import chalk7 from "chalk";
|
|
12175
12345
|
|
|
12176
12346
|
// src/db/paths.ts
|
|
12177
|
-
import { homedir as
|
|
12178
|
-
import { join as
|
|
12179
|
-
var
|
|
12180
|
-
config: "HASNA_CONFIG_HOME",
|
|
12347
|
+
import { homedir as pathsResolverHomedir } from "os";
|
|
12348
|
+
import { join as pathsResolverJoin } from "path";
|
|
12349
|
+
var PATHS_RESOLVER_KIND_ENV = {
|
|
12181
12350
|
data: "HASNA_DATA_HOME",
|
|
12182
|
-
state: "HASNA_STATE_HOME"
|
|
12183
|
-
cache: "HASNA_CACHE_HOME"
|
|
12351
|
+
state: "HASNA_STATE_HOME"
|
|
12184
12352
|
};
|
|
12185
|
-
var
|
|
12186
|
-
function
|
|
12353
|
+
var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
12354
|
+
function pathsResolverAssertApp(app) {
|
|
12187
12355
|
if (typeof app !== "string" || app.length === 0) {
|
|
12188
12356
|
throw new TypeError("paths: app must be a non-empty string");
|
|
12189
12357
|
}
|
|
12190
|
-
if (!
|
|
12358
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
|
|
12191
12359
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
12192
12360
|
}
|
|
12193
12361
|
}
|
|
12194
|
-
function
|
|
12195
|
-
if (!Object.keys(
|
|
12196
|
-
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(", ")}`);
|
|
12197
12365
|
}
|
|
12198
12366
|
}
|
|
12199
|
-
function
|
|
12200
|
-
|
|
12367
|
+
function pathsResolverBaseDir(kind, options) {
|
|
12368
|
+
pathsResolverAssertKind(kind);
|
|
12201
12369
|
const env = options.env ?? process.env;
|
|
12202
|
-
const override = env[
|
|
12370
|
+
const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
|
|
12203
12371
|
if (typeof override === "string" && override.length > 0)
|
|
12204
12372
|
return override;
|
|
12205
|
-
const home = options.home ??
|
|
12373
|
+
const home = options.home ?? pathsResolverHomedir();
|
|
12206
12374
|
const platform = options.platform ?? process.platform;
|
|
12207
12375
|
if (platform === "darwin") {
|
|
12208
12376
|
switch (kind) {
|
|
12209
|
-
case "config":
|
|
12210
12377
|
case "data":
|
|
12211
|
-
return
|
|
12212
|
-
case "cache":
|
|
12213
|
-
return pathsResolverJoin2(home, "Library", "Caches", "Hasna");
|
|
12378
|
+
return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
|
|
12214
12379
|
case "state":
|
|
12215
|
-
return
|
|
12380
|
+
return pathsResolverJoin(home, "Library", "Logs", "Hasna");
|
|
12216
12381
|
}
|
|
12217
12382
|
}
|
|
12218
12383
|
switch (kind) {
|
|
12219
|
-
case "config":
|
|
12220
|
-
return pathsResolverJoin2(home, ".config", "hasna");
|
|
12221
12384
|
case "data":
|
|
12222
|
-
return
|
|
12385
|
+
return pathsResolverJoin(home, ".local", "share", "hasna");
|
|
12223
12386
|
case "state":
|
|
12224
|
-
return
|
|
12225
|
-
case "cache":
|
|
12226
|
-
return pathsResolverJoin2(home, ".cache", "hasna");
|
|
12387
|
+
return pathsResolverJoin(home, ".local", "state", "hasna");
|
|
12227
12388
|
}
|
|
12228
12389
|
}
|
|
12229
|
-
function
|
|
12230
|
-
|
|
12231
|
-
const appSegment = options.internal === true ?
|
|
12232
|
-
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);
|
|
12233
12394
|
}
|
|
12234
12395
|
function dataDir2(options) {
|
|
12235
|
-
return
|
|
12396
|
+
return pathsResolverResolve("data", options);
|
|
12236
12397
|
}
|
|
12237
12398
|
|
|
12238
12399
|
// src/cli/legacy.ts
|
|
12239
12400
|
function home(env = process.env) {
|
|
12240
|
-
return env.HOME || env.USERPROFILE ||
|
|
12401
|
+
return env.HOME || env.USERPROFILE || homedir3();
|
|
12241
12402
|
}
|
|
12242
12403
|
function legacyDatabaseCandidates(env = process.env) {
|
|
12243
12404
|
const base = home(env);
|
|
12244
12405
|
const paths = [
|
|
12245
|
-
{ source: "xdg", path:
|
|
12246
|
-
{ source: "legacy-hasna", path:
|
|
12247
|
-
{ 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") }
|
|
12248
12409
|
];
|
|
12249
12410
|
return paths.map(({ source, path }) => {
|
|
12250
12411
|
const exists = existsSync6(path) && lstatSync(path).isFile();
|
|
@@ -12391,6 +12552,175 @@ function registerLegacyCommands(program2) {
|
|
|
12391
12552
|
});
|
|
12392
12553
|
}
|
|
12393
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
|
+
|
|
12394
12724
|
// src/cli/index.tsx
|
|
12395
12725
|
import { createRequire } from "module";
|
|
12396
12726
|
var { registerEventCommands: registerEventCommands2 } = exports_commander;
|
|
@@ -12404,13 +12734,13 @@ registerAdvancedCommands(program);
|
|
|
12404
12734
|
registerAudienceCommands(program);
|
|
12405
12735
|
registerStorageCommands(program);
|
|
12406
12736
|
registerLegacyCommands(program);
|
|
12737
|
+
registerBrowserCommands(program);
|
|
12407
12738
|
registerWebhookCommands2?.(program, { source: "contacts" });
|
|
12408
12739
|
registerEventCommands2(program, { source: "contacts", eventsCommandName: "hasna-events" });
|
|
12409
12740
|
program.parseAsync(process.argv).catch((err) => {
|
|
12410
12741
|
const message = err instanceof Error ? err.message : String(err);
|
|
12411
12742
|
const isApiUnavailable = err instanceof Error && err.name === "ApiUnavailableError";
|
|
12412
|
-
console.error(`
|
|
12413
|
-
` + (isApiUnavailable ? chalk8.yellow(message) : chalk8.red(message)) + `
|
|
12743
|
+
console.error((isApiUnavailable ? chalk8.yellow(message) : chalk8.red(message)) + `
|
|
12414
12744
|
`);
|
|
12415
12745
|
process.exit(1);
|
|
12416
12746
|
});
|