@hasna/contacts 0.6.18 → 0.6.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +831 -155
- package/dist/index.js +67 -69
- package/dist/mcp/index.js +70 -79
- package/dist/server/index.js +80 -89
- package/package.json +3 -2
package/dist/server/index.js
CHANGED
|
@@ -61,14 +61,14 @@ import { join, relative } from "path";
|
|
|
61
61
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
62
62
|
import { homedir as homedir2 } from "os";
|
|
63
63
|
import { join as join2 } from "path";
|
|
64
|
-
import { readdirSync as
|
|
65
|
-
import { join as
|
|
66
|
-
import { homedir as
|
|
64
|
+
import { readdirSync as readdirSync3, existsSync as existsSync6 } from "fs";
|
|
65
|
+
import { join as join6 } from "path";
|
|
66
|
+
import { homedir as homedir5 } from "os";
|
|
67
67
|
import { hostname } from "os";
|
|
68
|
-
import { homedir as
|
|
69
|
-
import { join as
|
|
70
|
-
import { join as
|
|
71
|
-
import { homedir as
|
|
68
|
+
import { homedir as homedir3 } from "os";
|
|
69
|
+
import { join as join3 } from "path";
|
|
70
|
+
import { join as join5, dirname } from "path";
|
|
71
|
+
import { homedir as homedir4, platform } from "os";
|
|
72
72
|
function __accessProp2(key) {
|
|
73
73
|
return this[key];
|
|
74
74
|
}
|
|
@@ -996,11 +996,11 @@ function isSyncExcludedTable(table) {
|
|
|
996
996
|
return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
|
|
997
997
|
}
|
|
998
998
|
function discoverServices() {
|
|
999
|
-
const dataDir =
|
|
1000
|
-
if (!
|
|
999
|
+
const dataDir = join6(homedir5(), ".hasna");
|
|
1000
|
+
if (!existsSync6(dataDir))
|
|
1001
1001
|
return [];
|
|
1002
1002
|
try {
|
|
1003
|
-
const entries =
|
|
1003
|
+
const entries = readdirSync3(dataDir, { withFileTypes: true });
|
|
1004
1004
|
return entries.filter((e) => {
|
|
1005
1005
|
if (!e.isDirectory())
|
|
1006
1006
|
return false;
|
|
@@ -1012,30 +1012,30 @@ function discoverServices() {
|
|
|
1012
1012
|
return [];
|
|
1013
1013
|
}
|
|
1014
1014
|
}
|
|
1015
|
-
function
|
|
1015
|
+
function discoverSyncableServices2() {
|
|
1016
1016
|
const local = discoverServices();
|
|
1017
1017
|
const pgSet = new Set(KNOWN_PG_SERVICES);
|
|
1018
1018
|
return local.filter((s) => pgSet.has(s));
|
|
1019
1019
|
}
|
|
1020
1020
|
function getServiceDbPath(service) {
|
|
1021
|
-
const dataDir =
|
|
1022
|
-
if (!
|
|
1021
|
+
const dataDir = join6(homedir5(), ".hasna", service);
|
|
1022
|
+
if (!existsSync6(dataDir))
|
|
1023
1023
|
return null;
|
|
1024
1024
|
const candidates = [
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1025
|
+
join6(dataDir, `${service}.db`),
|
|
1026
|
+
join6(dataDir, "data.db"),
|
|
1027
|
+
join6(dataDir, "database.db")
|
|
1028
1028
|
];
|
|
1029
1029
|
try {
|
|
1030
|
-
const files =
|
|
1030
|
+
const files = readdirSync3(dataDir);
|
|
1031
1031
|
for (const f of files) {
|
|
1032
1032
|
if (f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm")) {
|
|
1033
|
-
candidates.push(
|
|
1033
|
+
candidates.push(join6(dataDir, f));
|
|
1034
1034
|
}
|
|
1035
1035
|
}
|
|
1036
1036
|
} catch {}
|
|
1037
1037
|
for (const p of candidates) {
|
|
1038
|
-
if (
|
|
1038
|
+
if (existsSync6(p))
|
|
1039
1039
|
return p;
|
|
1040
1040
|
}
|
|
1041
1041
|
return null;
|
|
@@ -1342,9 +1342,9 @@ async function syncTransfer(source, target, options, _direction) {
|
|
|
1342
1342
|
const batch = rows.slice(offset, offset + batchSize);
|
|
1343
1343
|
try {
|
|
1344
1344
|
if (isAsyncAdapter(target)) {
|
|
1345
|
-
await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch
|
|
1345
|
+
await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
|
|
1346
1346
|
} else {
|
|
1347
|
-
batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch
|
|
1347
|
+
batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
|
|
1348
1348
|
}
|
|
1349
1349
|
result.rowsWritten += batch.length;
|
|
1350
1350
|
} catch (err) {
|
|
@@ -1391,7 +1391,7 @@ async function syncTransfer(source, target, options, _direction) {
|
|
|
1391
1391
|
}
|
|
1392
1392
|
return results;
|
|
1393
1393
|
}
|
|
1394
|
-
async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch
|
|
1394
|
+
async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch) {
|
|
1395
1395
|
if (batch.length === 0)
|
|
1396
1396
|
return;
|
|
1397
1397
|
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
@@ -1401,22 +1401,20 @@ async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, ba
|
|
|
1401
1401
|
}).join(", ");
|
|
1402
1402
|
const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
|
|
1403
1403
|
const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
|
|
1404
|
-
const whereClause = conflictColumn && updateCols.includes(conflictColumn) ? ` WHERE "${table}"."${conflictColumn}" IS NULL OR EXCLUDED."${conflictColumn}" >= "${table}"."${conflictColumn}"` : "";
|
|
1405
1404
|
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
|
|
1406
|
-
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}
|
|
1405
|
+
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
|
|
1407
1406
|
const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
|
|
1408
1407
|
await target.run(sql, ...params);
|
|
1409
1408
|
}
|
|
1410
|
-
function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch
|
|
1409
|
+
function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch) {
|
|
1411
1410
|
if (batch.length === 0)
|
|
1412
1411
|
return;
|
|
1413
1412
|
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
1414
1413
|
const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
|
|
1415
1414
|
const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
|
|
1416
1415
|
const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
|
|
1417
|
-
const whereClause = conflictColumn && updateCols.includes(conflictColumn) ? ` WHERE "${table}"."${conflictColumn}" IS NULL OR EXCLUDED."${conflictColumn}" >= "${table}"."${conflictColumn}"` : "";
|
|
1418
1416
|
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
|
|
1419
|
-
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}
|
|
1417
|
+
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
|
|
1420
1418
|
const params = batch.flatMap((row) => columns.map((c) => coerceForSqlite(row[c])));
|
|
1421
1419
|
target.run(sql, ...params);
|
|
1422
1420
|
}
|
|
@@ -1641,7 +1639,7 @@ class SyncProgressTracker {
|
|
|
1641
1639
|
}
|
|
1642
1640
|
}
|
|
1643
1641
|
}
|
|
1644
|
-
function registerCloudTools(server, serviceName
|
|
1642
|
+
function registerCloudTools(server, serviceName) {
|
|
1645
1643
|
server.tool(`${serviceName}_cloud_status`, "Show cloud configuration and connection health", {}, async () => {
|
|
1646
1644
|
const config = getCloudConfig();
|
|
1647
1645
|
const lines = [
|
|
@@ -1674,13 +1672,8 @@ function registerCloudTools(server, serviceName, opts = {}) {
|
|
|
1674
1672
|
isError: true
|
|
1675
1673
|
};
|
|
1676
1674
|
}
|
|
1677
|
-
const local = new SqliteAdapter(
|
|
1675
|
+
const local = new SqliteAdapter(getDbPath(serviceName));
|
|
1678
1676
|
const cloud = new PgAdapterAsync(getConnectionString(serviceName));
|
|
1679
|
-
if (opts.migrations?.length) {
|
|
1680
|
-
for (const sql of opts.migrations) {
|
|
1681
|
-
await cloud.run(sql);
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
1677
|
const tableList = tablesStr ? tablesStr.split(",").map((t) => t.trim()) : listSqliteTables(local);
|
|
1685
1678
|
const results = await syncPush(local, cloud, { tables: tableList });
|
|
1686
1679
|
local.close();
|
|
@@ -1702,7 +1695,7 @@ function registerCloudTools(server, serviceName, opts = {}) {
|
|
|
1702
1695
|
isError: true
|
|
1703
1696
|
};
|
|
1704
1697
|
}
|
|
1705
|
-
const local = new SqliteAdapter(
|
|
1698
|
+
const local = new SqliteAdapter(getDbPath(serviceName));
|
|
1706
1699
|
const cloud = new PgAdapterAsync(getConnectionString(serviceName));
|
|
1707
1700
|
let tableList;
|
|
1708
1701
|
if (tablesStr) {
|
|
@@ -10007,7 +10000,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
|
|
|
10007
10000
|
__export2(exports_discover, {
|
|
10008
10001
|
isSyncExcludedTable: () => isSyncExcludedTable,
|
|
10009
10002
|
getServiceDbPath: () => getServiceDbPath,
|
|
10010
|
-
discoverSyncableServices: () =>
|
|
10003
|
+
discoverSyncableServices: () => discoverSyncableServices2,
|
|
10011
10004
|
discoverServices: () => discoverServices,
|
|
10012
10005
|
SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS,
|
|
10013
10006
|
KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
|
|
@@ -10062,15 +10055,13 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
|
|
|
10062
10055
|
init_config();
|
|
10063
10056
|
init_config();
|
|
10064
10057
|
init_dotfile();
|
|
10065
|
-
init_adapter();
|
|
10066
10058
|
init_config();
|
|
10067
|
-
|
|
10068
|
-
AUTO_SYNC_CONFIG_PATH = join4(homedir4(), ".hasna", "cloud", "config.json");
|
|
10059
|
+
AUTO_SYNC_CONFIG_PATH = join3(homedir3(), ".hasna", "cloud", "config.json");
|
|
10069
10060
|
init_config();
|
|
10070
10061
|
init_adapter();
|
|
10071
10062
|
init_dotfile();
|
|
10072
10063
|
init_config();
|
|
10073
|
-
CONFIG_DIR2 =
|
|
10064
|
+
CONFIG_DIR2 = join5(homedir4(), ".hasna", "cloud");
|
|
10074
10065
|
init_adapter();
|
|
10075
10066
|
init_config();
|
|
10076
10067
|
init_discover();
|
|
@@ -10084,18 +10075,18 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
|
|
|
10084
10075
|
});
|
|
10085
10076
|
|
|
10086
10077
|
// src/db/database.ts
|
|
10087
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
10088
|
-
import { dirname as dirname2, join as
|
|
10078
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, statSync } from "fs";
|
|
10079
|
+
import { dirname as dirname2, join as join4, resolve } from "path";
|
|
10089
10080
|
function getDataDir2() {
|
|
10090
10081
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
10091
|
-
const newDir =
|
|
10092
|
-
const oldDir =
|
|
10093
|
-
if (
|
|
10082
|
+
const newDir = join4(home, ".hasna", "contacts");
|
|
10083
|
+
const oldDir = join4(home, ".contacts");
|
|
10084
|
+
if (existsSync3(oldDir) && !existsSync3(newDir)) {
|
|
10094
10085
|
mkdirSync3(newDir, { recursive: true });
|
|
10095
|
-
for (const file of
|
|
10096
|
-
const oldPath =
|
|
10086
|
+
for (const file of readdirSync2(oldDir)) {
|
|
10087
|
+
const oldPath = join4(oldDir, file);
|
|
10097
10088
|
if (statSync(oldPath).isFile()) {
|
|
10098
|
-
copyFileSync2(oldPath,
|
|
10089
|
+
copyFileSync2(oldPath, join4(newDir, file));
|
|
10099
10090
|
}
|
|
10100
10091
|
}
|
|
10101
10092
|
}
|
|
@@ -10107,13 +10098,13 @@ function getDbPath2() {
|
|
|
10107
10098
|
return process.env["HASNA_CONTACTS_DB_PATH"];
|
|
10108
10099
|
if (process.env["CONTACTS_DB_PATH"])
|
|
10109
10100
|
return process.env["CONTACTS_DB_PATH"];
|
|
10110
|
-
return
|
|
10101
|
+
return join4(getDataDir2(), "contacts.db");
|
|
10111
10102
|
}
|
|
10112
10103
|
function ensureDir(filePath) {
|
|
10113
10104
|
if (filePath === ":memory:")
|
|
10114
10105
|
return;
|
|
10115
10106
|
const dir = dirname2(resolve(filePath));
|
|
10116
|
-
if (!
|
|
10107
|
+
if (!existsSync3(dir))
|
|
10117
10108
|
mkdirSync3(dir, { recursive: true });
|
|
10118
10109
|
}
|
|
10119
10110
|
function getDatabase(path) {
|
|
@@ -17933,8 +17924,8 @@ var init_events = __esm(() => {
|
|
|
17933
17924
|
// src/server/serve.ts
|
|
17934
17925
|
init_database();
|
|
17935
17926
|
init_contacts();
|
|
17936
|
-
import { existsSync as
|
|
17937
|
-
import { join as
|
|
17927
|
+
import { existsSync as existsSync12 } from "fs";
|
|
17928
|
+
import { join as join12 } from "path";
|
|
17938
17929
|
|
|
17939
17930
|
// src/db/companies.ts
|
|
17940
17931
|
init_types2();
|
|
@@ -18755,11 +18746,11 @@ async function exportContacts(format, contacts) {
|
|
|
18755
18746
|
|
|
18756
18747
|
// src/lib/images.ts
|
|
18757
18748
|
init_database();
|
|
18758
|
-
import { existsSync as
|
|
18749
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, copyFileSync as copyFileSync3, unlinkSync, readdirSync as readdirSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
18759
18750
|
import { join as join7, extname, basename } from "path";
|
|
18760
18751
|
var IMAGES_DIR = join7(getDataDir2(), "images");
|
|
18761
18752
|
function ensureImagesDir() {
|
|
18762
|
-
if (!
|
|
18753
|
+
if (!existsSync5(IMAGES_DIR))
|
|
18763
18754
|
mkdirSync5(IMAGES_DIR, { recursive: true });
|
|
18764
18755
|
}
|
|
18765
18756
|
function getImagesDir() {
|
|
@@ -18777,14 +18768,14 @@ function saveImage(entityId, source, options) {
|
|
|
18777
18768
|
writeFileSync2(join7(IMAGES_DIR, filename2), data);
|
|
18778
18769
|
return filename2;
|
|
18779
18770
|
}
|
|
18780
|
-
if (!
|
|
18771
|
+
if (!existsSync5(source) && /^[A-Za-z0-9+/=\n\r]+$/.test(source.trim()) && source.length > 100) {
|
|
18781
18772
|
const ext2 = options?.format || "jpg";
|
|
18782
18773
|
const data = Buffer.from(source.trim(), "base64");
|
|
18783
18774
|
const filename2 = `${entityId}.${ext2}`;
|
|
18784
18775
|
writeFileSync2(join7(IMAGES_DIR, filename2), data);
|
|
18785
18776
|
return filename2;
|
|
18786
18777
|
}
|
|
18787
|
-
if (!
|
|
18778
|
+
if (!existsSync5(source)) {
|
|
18788
18779
|
throw new Error(`Image file not found: ${source}`);
|
|
18789
18780
|
}
|
|
18790
18781
|
const ext = extname(source).slice(1).toLowerCase() || "jpg";
|
|
@@ -18798,7 +18789,7 @@ function saveImage(entityId, source, options) {
|
|
|
18798
18789
|
}
|
|
18799
18790
|
function getImagePath(entityId) {
|
|
18800
18791
|
ensureImagesDir();
|
|
18801
|
-
const files =
|
|
18792
|
+
const files = readdirSync4(IMAGES_DIR);
|
|
18802
18793
|
const match = files.find((f) => f.startsWith(`${entityId}.`));
|
|
18803
18794
|
return match ? join7(IMAGES_DIR, match) : null;
|
|
18804
18795
|
}
|
|
@@ -18813,7 +18804,7 @@ function getImageAsBase64(entityId) {
|
|
|
18813
18804
|
}
|
|
18814
18805
|
function deleteImage(entityId) {
|
|
18815
18806
|
ensureImagesDir();
|
|
18816
|
-
const files =
|
|
18807
|
+
const files = readdirSync4(IMAGES_DIR);
|
|
18817
18808
|
let deleted = false;
|
|
18818
18809
|
for (const f of files) {
|
|
18819
18810
|
if (f.startsWith(`${entityId}.`)) {
|
|
@@ -22149,7 +22140,7 @@ class JSONSchemaGenerator {
|
|
|
22149
22140
|
if (val === undefined) {
|
|
22150
22141
|
if (this.unrepresentable === "throw") {
|
|
22151
22142
|
throw new Error("Literal `undefined` cannot be represented in JSON Schema");
|
|
22152
|
-
}
|
|
22143
|
+
}
|
|
22153
22144
|
} else if (typeof val === "bigint") {
|
|
22154
22145
|
if (this.unrepresentable === "throw") {
|
|
22155
22146
|
throw new Error("BigInt literals cannot be represented in JSON Schema");
|
|
@@ -24705,7 +24696,7 @@ function startMcpHttpServer(options) {
|
|
|
24705
24696
|
|
|
24706
24697
|
// src/mcp/index.ts
|
|
24707
24698
|
import { readFileSync as readFileSync6 } from "fs";
|
|
24708
|
-
import { join as
|
|
24699
|
+
import { join as join11 } from "path";
|
|
24709
24700
|
|
|
24710
24701
|
// node_modules/zod/v3/external.js
|
|
24711
24702
|
var exports_external2 = {};
|
|
@@ -32371,7 +32362,7 @@ class StdioServerTransport {
|
|
|
32371
32362
|
init_dist();
|
|
32372
32363
|
|
|
32373
32364
|
// src/lib/connector.ts
|
|
32374
|
-
import { join as
|
|
32365
|
+
import { join as join9 } from "path";
|
|
32375
32366
|
import { existsSync as existsSync7, readFileSync as readFileSync3 } from "fs";
|
|
32376
32367
|
import { homedir as homedir6 } from "os";
|
|
32377
32368
|
|
|
@@ -32441,9 +32432,9 @@ async function runConnector(name, args, opts = {}) {
|
|
|
32441
32432
|
}
|
|
32442
32433
|
function getConnectorTokenPath(name, profile = "default") {
|
|
32443
32434
|
const bases = [
|
|
32444
|
-
|
|
32445
|
-
|
|
32446
|
-
|
|
32435
|
+
join9(homedir6(), ".connectors", `connect-${name}`, "profiles", profile, "tokens.json"),
|
|
32436
|
+
join9(homedir6(), ".connect", `connect-${name}`, "profiles", profile, "tokens.json"),
|
|
32437
|
+
join9(homedir6(), ".connect", `connect-${name}`, "tokens.json")
|
|
32447
32438
|
];
|
|
32448
32439
|
for (const p of bases) {
|
|
32449
32440
|
if (existsSync7(p))
|
|
@@ -35440,13 +35431,13 @@ function getCoverageGaps(companyId, db) {
|
|
|
35440
35431
|
|
|
35441
35432
|
// src/lib/vault.ts
|
|
35442
35433
|
init_database();
|
|
35443
|
-
import { existsSync as
|
|
35444
|
-
import { join as
|
|
35434
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6, unlinkSync as unlinkSync2 } from "fs";
|
|
35435
|
+
import { join as join10 } from "path";
|
|
35445
35436
|
import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync, createHash } from "crypto";
|
|
35446
35437
|
var VAULT_DIR = getDataDir2();
|
|
35447
|
-
var VAULT_CONFIG =
|
|
35448
|
-
var VAULT_SESSION =
|
|
35449
|
-
var DOCUMENTS_DIR =
|
|
35438
|
+
var VAULT_CONFIG = join10(VAULT_DIR, "vault.json");
|
|
35439
|
+
var VAULT_SESSION = join10(VAULT_DIR, ".vault-session");
|
|
35440
|
+
var DOCUMENTS_DIR = join10(VAULT_DIR, "documents");
|
|
35450
35441
|
var SESSION_TTL_MS = 30 * 60 * 1000;
|
|
35451
35442
|
var _derivedKey = null;
|
|
35452
35443
|
function deriveKey(passphrase, salt) {
|
|
@@ -35460,7 +35451,7 @@ function saveSession(key) {
|
|
|
35460
35451
|
writeFileSync3(VAULT_SESSION, JSON.stringify(session), { mode: 384 });
|
|
35461
35452
|
}
|
|
35462
35453
|
function loadSession() {
|
|
35463
|
-
if (!
|
|
35454
|
+
if (!existsSync9(VAULT_SESSION))
|
|
35464
35455
|
return null;
|
|
35465
35456
|
try {
|
|
35466
35457
|
const session = JSON.parse(readFileSync4(VAULT_SESSION, "utf-8"));
|
|
@@ -35477,14 +35468,14 @@ function loadSession() {
|
|
|
35477
35468
|
}
|
|
35478
35469
|
function clearSession() {
|
|
35479
35470
|
try {
|
|
35480
|
-
if (
|
|
35471
|
+
if (existsSync9(VAULT_SESSION))
|
|
35481
35472
|
unlinkSync2(VAULT_SESSION);
|
|
35482
35473
|
} catch {}
|
|
35483
35474
|
}
|
|
35484
35475
|
function initVault(passphrase) {
|
|
35485
|
-
if (!
|
|
35476
|
+
if (!existsSync9(VAULT_DIR))
|
|
35486
35477
|
mkdirSync6(VAULT_DIR, { recursive: true });
|
|
35487
|
-
if (!
|
|
35478
|
+
if (!existsSync9(DOCUMENTS_DIR))
|
|
35488
35479
|
mkdirSync6(DOCUMENTS_DIR, { recursive: true });
|
|
35489
35480
|
const salt = randomBytes(32);
|
|
35490
35481
|
const key = deriveKey(passphrase, salt);
|
|
@@ -35495,10 +35486,10 @@ function initVault(passphrase) {
|
|
|
35495
35486
|
saveSession(key);
|
|
35496
35487
|
}
|
|
35497
35488
|
function isVaultInitialized() {
|
|
35498
|
-
return
|
|
35489
|
+
return existsSync9(VAULT_CONFIG);
|
|
35499
35490
|
}
|
|
35500
35491
|
function unlockVault(passphrase) {
|
|
35501
|
-
if (!
|
|
35492
|
+
if (!existsSync9(VAULT_CONFIG))
|
|
35502
35493
|
throw new Error("Vault not initialized. Run 'contacts vault init' first.");
|
|
35503
35494
|
const config2 = JSON.parse(readFileSync4(VAULT_CONFIG, "utf-8"));
|
|
35504
35495
|
const salt = Buffer.from(config2.salt, "hex");
|
|
@@ -35555,10 +35546,10 @@ function decrypt(ciphertext, iv) {
|
|
|
35555
35546
|
return decrypted;
|
|
35556
35547
|
}
|
|
35557
35548
|
function storeFile(sourcePath, entityId) {
|
|
35558
|
-
if (!
|
|
35549
|
+
if (!existsSync9(DOCUMENTS_DIR))
|
|
35559
35550
|
mkdirSync6(DOCUMENTS_DIR, { recursive: true });
|
|
35560
35551
|
const ext = sourcePath.split(".").pop() || "bin";
|
|
35561
|
-
const destPath =
|
|
35552
|
+
const destPath = join10(DOCUMENTS_DIR, `${entityId}.${ext}`);
|
|
35562
35553
|
const data = readFileSync4(sourcePath);
|
|
35563
35554
|
writeFileSync3(destPath, data);
|
|
35564
35555
|
return destPath;
|
|
@@ -35566,7 +35557,7 @@ function storeFile(sourcePath, entityId) {
|
|
|
35566
35557
|
|
|
35567
35558
|
// src/db/documents.ts
|
|
35568
35559
|
init_database();
|
|
35569
|
-
import { existsSync as
|
|
35560
|
+
import { existsSync as existsSync10, unlinkSync as unlinkSync3 } from "fs";
|
|
35570
35561
|
var DOCUMENT_TYPES = [
|
|
35571
35562
|
"passport",
|
|
35572
35563
|
"national_id",
|
|
@@ -35623,7 +35614,7 @@ function listDocuments(contactId, db) {
|
|
|
35623
35614
|
function deleteDocument(id, db) {
|
|
35624
35615
|
const _db2 = db || getDatabase();
|
|
35625
35616
|
const row = _db2.query(`SELECT encrypted_file_path FROM contact_documents WHERE id = ?`).get(id);
|
|
35626
|
-
if (row?.encrypted_file_path &&
|
|
35617
|
+
if (row?.encrypted_file_path && existsSync10(row.encrypted_file_path)) {
|
|
35627
35618
|
try {
|
|
35628
35619
|
unlinkSync3(row.encrypted_file_path);
|
|
35629
35620
|
} catch {}
|
|
@@ -35741,7 +35732,7 @@ function deleteHealthData(contactId, db) {
|
|
|
35741
35732
|
}
|
|
35742
35733
|
|
|
35743
35734
|
// src/lib/document-scanner.ts
|
|
35744
|
-
import { readFileSync as readFileSync5, existsSync as
|
|
35735
|
+
import { readFileSync as readFileSync5, existsSync as existsSync11 } from "fs";
|
|
35745
35736
|
import { extname as extname2 } from "path";
|
|
35746
35737
|
async function scanDocument(imageSource, docType) {
|
|
35747
35738
|
const apiKey = process.env["OPENAI_API_KEY"];
|
|
@@ -35751,7 +35742,7 @@ async function scanDocument(imageSource, docType) {
|
|
|
35751
35742
|
let imageData;
|
|
35752
35743
|
if (imageSource.startsWith("data:image/")) {
|
|
35753
35744
|
imageData = imageSource;
|
|
35754
|
-
} else if (
|
|
35745
|
+
} else if (existsSync11(imageSource)) {
|
|
35755
35746
|
const buffer = readFileSync5(imageSource);
|
|
35756
35747
|
const ext = extname2(imageSource).slice(1).toLowerCase();
|
|
35757
35748
|
const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "png" ? "image/png" : ext === "webp" ? "image/webp" : ext === "gif" ? "image/gif" : "image/jpeg";
|
|
@@ -38012,7 +38003,7 @@ function registerContactsTools(server) {
|
|
|
38012
38003
|
// src/mcp/index.ts
|
|
38013
38004
|
function getServerVersion() {
|
|
38014
38005
|
try {
|
|
38015
|
-
const packageJsonPath =
|
|
38006
|
+
const packageJsonPath = join11(import.meta.dir, "..", "..", "package.json");
|
|
38016
38007
|
const pkg = JSON.parse(readFileSync6(packageJsonPath, "utf8"));
|
|
38017
38008
|
return pkg.version ?? "0.0.0";
|
|
38018
38009
|
} catch {
|
|
@@ -38048,7 +38039,7 @@ if (isDirectRun) {
|
|
|
38048
38039
|
}
|
|
38049
38040
|
|
|
38050
38041
|
// src/server/serve.ts
|
|
38051
|
-
var DASHBOARD_DIST =
|
|
38042
|
+
var DASHBOARD_DIST = join12(import.meta.dir, "../../dashboard/dist");
|
|
38052
38043
|
function json3(data, status = 200) {
|
|
38053
38044
|
return new Response(JSON.stringify(data), {
|
|
38054
38045
|
status,
|
|
@@ -38263,7 +38254,7 @@ function handleDocumentFiles(req, segments) {
|
|
|
38263
38254
|
if (sub === "file") {
|
|
38264
38255
|
const db = getDatabase();
|
|
38265
38256
|
const row = db.query(`SELECT encrypted_file_path FROM contact_documents WHERE id = ?`).get(docId);
|
|
38266
|
-
if (!row?.encrypted_file_path || !
|
|
38257
|
+
if (!row?.encrypted_file_path || !existsSync12(row.encrypted_file_path)) {
|
|
38267
38258
|
return new Response("No file attachment", { status: 404 });
|
|
38268
38259
|
}
|
|
38269
38260
|
return new Response(Bun.file(row.encrypted_file_path), {
|
|
@@ -38278,7 +38269,7 @@ async function handleImages(req, _url2, segments) {
|
|
|
38278
38269
|
return apiError("Entity ID required");
|
|
38279
38270
|
if (req.method === "GET") {
|
|
38280
38271
|
const imagePath = getImagePath(entityId);
|
|
38281
|
-
if (!imagePath || !
|
|
38272
|
+
if (!imagePath || !existsSync12(imagePath)) {
|
|
38282
38273
|
return new Response(null, { status: 404, headers: { "Content-Type": "text/plain" } });
|
|
38283
38274
|
}
|
|
38284
38275
|
return new Response(Bun.file(imagePath), {
|
|
@@ -38294,7 +38285,7 @@ async function handleImages(req, _url2, segments) {
|
|
|
38294
38285
|
return apiError("No image file in form data");
|
|
38295
38286
|
const ext = file.name?.split(".").pop() || "jpg";
|
|
38296
38287
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
38297
|
-
const tmpPath =
|
|
38288
|
+
const tmpPath = join12(getImagesDir(), `_upload_${entityId}.${ext}`);
|
|
38298
38289
|
const { writeFileSync: wfs } = await import("fs");
|
|
38299
38290
|
wfs(tmpPath, buffer);
|
|
38300
38291
|
try {
|
|
@@ -38325,7 +38316,7 @@ async function handleImages(req, _url2, segments) {
|
|
|
38325
38316
|
return apiError("Method not allowed", 405);
|
|
38326
38317
|
}
|
|
38327
38318
|
function serveStaticFile(filePath) {
|
|
38328
|
-
if (!
|
|
38319
|
+
if (!existsSync12(filePath))
|
|
38329
38320
|
return null;
|
|
38330
38321
|
return new Response(Bun.file(filePath));
|
|
38331
38322
|
}
|
|
@@ -38381,8 +38372,8 @@ function startServer(port) {
|
|
|
38381
38372
|
response = apiError("Not found", 404);
|
|
38382
38373
|
}
|
|
38383
38374
|
} else {
|
|
38384
|
-
const filePath =
|
|
38385
|
-
response = serveStaticFile(filePath) ?? serveStaticFile(
|
|
38375
|
+
const filePath = join12(DASHBOARD_DIST, url.pathname === "/" ? "index.html" : url.pathname);
|
|
38376
|
+
response = serveStaticFile(filePath) ?? serveStaticFile(join12(DASHBOARD_DIST, "index.html")) ?? new Response("Not Found", { status: 404 });
|
|
38386
38377
|
}
|
|
38387
38378
|
} catch (err) {
|
|
38388
38379
|
console.error("Request error:", err);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/contacts",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.20",
|
|
4
4
|
"description": "Contact management for AI coding agents — CLI + MCP + Web",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -61,7 +61,8 @@
|
|
|
61
61
|
"author": "Andrei Hasna <andrei@hasna.com>",
|
|
62
62
|
"license": "Apache-2.0",
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@hasna/cloud": "
|
|
64
|
+
"@hasna/cloud": "0.1.24",
|
|
65
|
+
"@hasna/events": "^0.1.6",
|
|
65
66
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
66
67
|
"chalk": "^5.4.1",
|
|
67
68
|
"commander": "^13.1.0",
|