@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/index.js
CHANGED
|
@@ -30,13 +30,13 @@ import { join, relative } from "path";
|
|
|
30
30
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
31
31
|
import { homedir as homedir2 } from "os";
|
|
32
32
|
import { join as join2 } from "path";
|
|
33
|
-
import { readdirSync as
|
|
34
|
-
import { join as
|
|
33
|
+
import { readdirSync as readdirSync3, existsSync as existsSync6 } from "fs";
|
|
34
|
+
import { join as join6 } from "path";
|
|
35
|
+
import { homedir as homedir5 } from "os";
|
|
35
36
|
import { homedir as homedir3 } from "os";
|
|
36
|
-
import {
|
|
37
|
-
import { join as
|
|
38
|
-
import {
|
|
39
|
-
import { homedir as homedir5, platform } from "os";
|
|
37
|
+
import { join as join3 } from "path";
|
|
38
|
+
import { join as join5, dirname } from "path";
|
|
39
|
+
import { homedir as homedir4, platform } from "os";
|
|
40
40
|
function __accessProp(key) {
|
|
41
41
|
return this[key];
|
|
42
42
|
}
|
|
@@ -907,11 +907,11 @@ function isSyncExcludedTable(table) {
|
|
|
907
907
|
return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
|
|
908
908
|
}
|
|
909
909
|
function discoverServices() {
|
|
910
|
-
const dataDir =
|
|
911
|
-
if (!
|
|
910
|
+
const dataDir = join6(homedir5(), ".hasna");
|
|
911
|
+
if (!existsSync6(dataDir))
|
|
912
912
|
return [];
|
|
913
913
|
try {
|
|
914
|
-
const entries =
|
|
914
|
+
const entries = readdirSync3(dataDir, { withFileTypes: true });
|
|
915
915
|
return entries.filter((e) => {
|
|
916
916
|
if (!e.isDirectory())
|
|
917
917
|
return false;
|
|
@@ -923,30 +923,30 @@ function discoverServices() {
|
|
|
923
923
|
return [];
|
|
924
924
|
}
|
|
925
925
|
}
|
|
926
|
-
function
|
|
926
|
+
function discoverSyncableServices2() {
|
|
927
927
|
const local = discoverServices();
|
|
928
928
|
const pgSet = new Set(KNOWN_PG_SERVICES);
|
|
929
929
|
return local.filter((s) => pgSet.has(s));
|
|
930
930
|
}
|
|
931
931
|
function getServiceDbPath(service) {
|
|
932
|
-
const dataDir =
|
|
933
|
-
if (!
|
|
932
|
+
const dataDir = join6(homedir5(), ".hasna", service);
|
|
933
|
+
if (!existsSync6(dataDir))
|
|
934
934
|
return null;
|
|
935
935
|
const candidates = [
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
936
|
+
join6(dataDir, `${service}.db`),
|
|
937
|
+
join6(dataDir, "data.db"),
|
|
938
|
+
join6(dataDir, "database.db")
|
|
939
939
|
];
|
|
940
940
|
try {
|
|
941
|
-
const files =
|
|
941
|
+
const files = readdirSync3(dataDir);
|
|
942
942
|
for (const f of files) {
|
|
943
943
|
if (f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm")) {
|
|
944
|
-
candidates.push(
|
|
944
|
+
candidates.push(join6(dataDir, f));
|
|
945
945
|
}
|
|
946
946
|
}
|
|
947
947
|
} catch {}
|
|
948
948
|
for (const p of candidates) {
|
|
949
|
-
if (
|
|
949
|
+
if (existsSync6(p))
|
|
950
950
|
return p;
|
|
951
951
|
}
|
|
952
952
|
return null;
|
|
@@ -9323,7 +9323,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
|
|
|
9323
9323
|
__export2(exports_discover, {
|
|
9324
9324
|
isSyncExcludedTable: () => isSyncExcludedTable,
|
|
9325
9325
|
getServiceDbPath: () => getServiceDbPath,
|
|
9326
|
-
discoverSyncableServices: () =>
|
|
9326
|
+
discoverSyncableServices: () => discoverSyncableServices2,
|
|
9327
9327
|
discoverServices: () => discoverServices,
|
|
9328
9328
|
SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS,
|
|
9329
9329
|
KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
|
|
@@ -9378,15 +9378,13 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
|
|
|
9378
9378
|
init_config();
|
|
9379
9379
|
init_config();
|
|
9380
9380
|
init_dotfile();
|
|
9381
|
-
init_adapter();
|
|
9382
9381
|
init_config();
|
|
9383
|
-
|
|
9384
|
-
AUTO_SYNC_CONFIG_PATH = join4(homedir4(), ".hasna", "cloud", "config.json");
|
|
9382
|
+
AUTO_SYNC_CONFIG_PATH = join3(homedir3(), ".hasna", "cloud", "config.json");
|
|
9385
9383
|
init_config();
|
|
9386
9384
|
init_adapter();
|
|
9387
9385
|
init_dotfile();
|
|
9388
9386
|
init_config();
|
|
9389
|
-
CONFIG_DIR2 =
|
|
9387
|
+
CONFIG_DIR2 = join5(homedir4(), ".hasna", "cloud");
|
|
9390
9388
|
init_adapter();
|
|
9391
9389
|
init_config();
|
|
9392
9390
|
init_discover();
|
|
@@ -9400,18 +9398,18 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
|
|
|
9400
9398
|
});
|
|
9401
9399
|
|
|
9402
9400
|
// src/db/database.ts
|
|
9403
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
9404
|
-
import { dirname as dirname2, join as
|
|
9401
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, statSync } from "fs";
|
|
9402
|
+
import { dirname as dirname2, join as join4, resolve } from "path";
|
|
9405
9403
|
function getDataDir2() {
|
|
9406
9404
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
9407
|
-
const newDir =
|
|
9408
|
-
const oldDir =
|
|
9409
|
-
if (
|
|
9405
|
+
const newDir = join4(home, ".hasna", "contacts");
|
|
9406
|
+
const oldDir = join4(home, ".contacts");
|
|
9407
|
+
if (existsSync3(oldDir) && !existsSync3(newDir)) {
|
|
9410
9408
|
mkdirSync3(newDir, { recursive: true });
|
|
9411
|
-
for (const file of
|
|
9412
|
-
const oldPath =
|
|
9409
|
+
for (const file of readdirSync2(oldDir)) {
|
|
9410
|
+
const oldPath = join4(oldDir, file);
|
|
9413
9411
|
if (statSync(oldPath).isFile()) {
|
|
9414
|
-
copyFileSync2(oldPath,
|
|
9412
|
+
copyFileSync2(oldPath, join4(newDir, file));
|
|
9415
9413
|
}
|
|
9416
9414
|
}
|
|
9417
9415
|
}
|
|
@@ -9423,13 +9421,13 @@ function getDbPath2() {
|
|
|
9423
9421
|
return process.env["HASNA_CONTACTS_DB_PATH"];
|
|
9424
9422
|
if (process.env["CONTACTS_DB_PATH"])
|
|
9425
9423
|
return process.env["CONTACTS_DB_PATH"];
|
|
9426
|
-
return
|
|
9424
|
+
return join4(getDataDir2(), "contacts.db");
|
|
9427
9425
|
}
|
|
9428
9426
|
function ensureDir(filePath) {
|
|
9429
9427
|
if (filePath === ":memory:")
|
|
9430
9428
|
return;
|
|
9431
9429
|
const dir = dirname2(resolve(filePath));
|
|
9432
|
-
if (!
|
|
9430
|
+
if (!existsSync3(dir))
|
|
9433
9431
|
mkdirSync3(dir, { recursive: true });
|
|
9434
9432
|
}
|
|
9435
9433
|
function getDatabase(path) {
|
|
@@ -12315,7 +12313,7 @@ async function importToApple(vcfData) {
|
|
|
12315
12313
|
}
|
|
12316
12314
|
// src/lib/connector.ts
|
|
12317
12315
|
import { join as join7 } from "path";
|
|
12318
|
-
import { existsSync as
|
|
12316
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
|
|
12319
12317
|
import { homedir as homedir6 } from "os";
|
|
12320
12318
|
|
|
12321
12319
|
class ConnectorNotInstalledError extends Error {
|
|
@@ -12389,7 +12387,7 @@ function getConnectorTokenPath(name, profile = "default") {
|
|
|
12389
12387
|
join7(homedir6(), ".connect", `connect-${name}`, "tokens.json")
|
|
12390
12388
|
];
|
|
12391
12389
|
for (const p of bases) {
|
|
12392
|
-
if (
|
|
12390
|
+
if (existsSync5(p))
|
|
12393
12391
|
return p;
|
|
12394
12392
|
}
|
|
12395
12393
|
return null;
|
|
@@ -13845,9 +13843,9 @@ init_contacts();
|
|
|
13845
13843
|
|
|
13846
13844
|
// src/lib/images.ts
|
|
13847
13845
|
init_database();
|
|
13848
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync5, copyFileSync as copyFileSync3, unlinkSync, readdirSync as
|
|
13849
|
-
import { join as
|
|
13850
|
-
var IMAGES_DIR =
|
|
13846
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, copyFileSync as copyFileSync3, unlinkSync, readdirSync as readdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
13847
|
+
import { join as join9, extname, basename } from "path";
|
|
13848
|
+
var IMAGES_DIR = join9(getDataDir2(), "images");
|
|
13851
13849
|
function ensureImagesDir() {
|
|
13852
13850
|
if (!existsSync7(IMAGES_DIR))
|
|
13853
13851
|
mkdirSync5(IMAGES_DIR, { recursive: true });
|
|
@@ -13864,14 +13862,14 @@ function saveImage(entityId, source, options) {
|
|
|
13864
13862
|
const ext2 = base64Match[1] === "jpeg" ? "jpg" : base64Match[1];
|
|
13865
13863
|
const data = Buffer.from(base64Match[2], "base64");
|
|
13866
13864
|
const filename2 = `${entityId}.${ext2}`;
|
|
13867
|
-
writeFileSync2(
|
|
13865
|
+
writeFileSync2(join9(IMAGES_DIR, filename2), data);
|
|
13868
13866
|
return filename2;
|
|
13869
13867
|
}
|
|
13870
13868
|
if (!existsSync7(source) && /^[A-Za-z0-9+/=\n\r]+$/.test(source.trim()) && source.length > 100) {
|
|
13871
13869
|
const ext2 = options?.format || "jpg";
|
|
13872
13870
|
const data = Buffer.from(source.trim(), "base64");
|
|
13873
13871
|
const filename2 = `${entityId}.${ext2}`;
|
|
13874
|
-
writeFileSync2(
|
|
13872
|
+
writeFileSync2(join9(IMAGES_DIR, filename2), data);
|
|
13875
13873
|
return filename2;
|
|
13876
13874
|
}
|
|
13877
13875
|
if (!existsSync7(source)) {
|
|
@@ -13883,14 +13881,14 @@ function saveImage(entityId, source, options) {
|
|
|
13883
13881
|
throw new Error(`Unsupported image format: ${ext}. Supported: ${validExts.join(", ")}`);
|
|
13884
13882
|
}
|
|
13885
13883
|
const filename = `${entityId}.${ext === "jpeg" ? "jpg" : ext}`;
|
|
13886
|
-
copyFileSync3(source,
|
|
13884
|
+
copyFileSync3(source, join9(IMAGES_DIR, filename));
|
|
13887
13885
|
return filename;
|
|
13888
13886
|
}
|
|
13889
13887
|
function getImagePath(entityId) {
|
|
13890
13888
|
ensureImagesDir();
|
|
13891
|
-
const files =
|
|
13889
|
+
const files = readdirSync4(IMAGES_DIR);
|
|
13892
13890
|
const match = files.find((f) => f.startsWith(`${entityId}.`));
|
|
13893
|
-
return match ?
|
|
13891
|
+
return match ? join9(IMAGES_DIR, match) : null;
|
|
13894
13892
|
}
|
|
13895
13893
|
function getImageAsBase64(entityId) {
|
|
13896
13894
|
const path = getImagePath(entityId);
|
|
@@ -13903,11 +13901,11 @@ function getImageAsBase64(entityId) {
|
|
|
13903
13901
|
}
|
|
13904
13902
|
function deleteImage(entityId) {
|
|
13905
13903
|
ensureImagesDir();
|
|
13906
|
-
const files =
|
|
13904
|
+
const files = readdirSync4(IMAGES_DIR);
|
|
13907
13905
|
let deleted = false;
|
|
13908
13906
|
for (const f of files) {
|
|
13909
13907
|
if (f.startsWith(`${entityId}.`)) {
|
|
13910
|
-
unlinkSync(
|
|
13908
|
+
unlinkSync(join9(IMAGES_DIR, f));
|
|
13911
13909
|
deleted = true;
|
|
13912
13910
|
}
|
|
13913
13911
|
}
|
|
@@ -13915,22 +13913,22 @@ function deleteImage(entityId) {
|
|
|
13915
13913
|
}
|
|
13916
13914
|
function listImages() {
|
|
13917
13915
|
ensureImagesDir();
|
|
13918
|
-
const files =
|
|
13916
|
+
const files = readdirSync4(IMAGES_DIR).filter((f) => !f.startsWith("."));
|
|
13919
13917
|
return files.map((f) => ({
|
|
13920
13918
|
entity_id: basename(f, extname(f)),
|
|
13921
13919
|
filename: f,
|
|
13922
|
-
path:
|
|
13920
|
+
path: join9(IMAGES_DIR, f)
|
|
13923
13921
|
}));
|
|
13924
13922
|
}
|
|
13925
13923
|
// src/lib/vault.ts
|
|
13926
13924
|
init_database();
|
|
13927
|
-
import { existsSync as
|
|
13928
|
-
import { join as
|
|
13925
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6, unlinkSync as unlinkSync2 } from "fs";
|
|
13926
|
+
import { join as join10 } from "path";
|
|
13929
13927
|
import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync, createHash } from "crypto";
|
|
13930
13928
|
var VAULT_DIR = getDataDir2();
|
|
13931
|
-
var VAULT_CONFIG =
|
|
13932
|
-
var VAULT_SESSION =
|
|
13933
|
-
var DOCUMENTS_DIR =
|
|
13929
|
+
var VAULT_CONFIG = join10(VAULT_DIR, "vault.json");
|
|
13930
|
+
var VAULT_SESSION = join10(VAULT_DIR, ".vault-session");
|
|
13931
|
+
var DOCUMENTS_DIR = join10(VAULT_DIR, "documents");
|
|
13934
13932
|
var SESSION_TTL_MS = 30 * 60 * 1000;
|
|
13935
13933
|
var _derivedKey = null;
|
|
13936
13934
|
function deriveKey(passphrase, salt) {
|
|
@@ -13944,7 +13942,7 @@ function saveSession(key) {
|
|
|
13944
13942
|
writeFileSync3(VAULT_SESSION, JSON.stringify(session), { mode: 384 });
|
|
13945
13943
|
}
|
|
13946
13944
|
function loadSession() {
|
|
13947
|
-
if (!
|
|
13945
|
+
if (!existsSync9(VAULT_SESSION))
|
|
13948
13946
|
return null;
|
|
13949
13947
|
try {
|
|
13950
13948
|
const session = JSON.parse(readFileSync4(VAULT_SESSION, "utf-8"));
|
|
@@ -13961,14 +13959,14 @@ function loadSession() {
|
|
|
13961
13959
|
}
|
|
13962
13960
|
function clearSession() {
|
|
13963
13961
|
try {
|
|
13964
|
-
if (
|
|
13962
|
+
if (existsSync9(VAULT_SESSION))
|
|
13965
13963
|
unlinkSync2(VAULT_SESSION);
|
|
13966
13964
|
} catch {}
|
|
13967
13965
|
}
|
|
13968
13966
|
function initVault(passphrase) {
|
|
13969
|
-
if (!
|
|
13967
|
+
if (!existsSync9(VAULT_DIR))
|
|
13970
13968
|
mkdirSync6(VAULT_DIR, { recursive: true });
|
|
13971
|
-
if (!
|
|
13969
|
+
if (!existsSync9(DOCUMENTS_DIR))
|
|
13972
13970
|
mkdirSync6(DOCUMENTS_DIR, { recursive: true });
|
|
13973
13971
|
const salt = randomBytes(32);
|
|
13974
13972
|
const key = deriveKey(passphrase, salt);
|
|
@@ -13979,10 +13977,10 @@ function initVault(passphrase) {
|
|
|
13979
13977
|
saveSession(key);
|
|
13980
13978
|
}
|
|
13981
13979
|
function isVaultInitialized() {
|
|
13982
|
-
return
|
|
13980
|
+
return existsSync9(VAULT_CONFIG);
|
|
13983
13981
|
}
|
|
13984
13982
|
function unlockVault(passphrase) {
|
|
13985
|
-
if (!
|
|
13983
|
+
if (!existsSync9(VAULT_CONFIG))
|
|
13986
13984
|
throw new Error("Vault not initialized. Run 'contacts vault init' first.");
|
|
13987
13985
|
const config = JSON.parse(readFileSync4(VAULT_CONFIG, "utf-8"));
|
|
13988
13986
|
const salt = Buffer.from(config.salt, "hex");
|
|
@@ -14039,21 +14037,21 @@ function decrypt(ciphertext, iv) {
|
|
|
14039
14037
|
return decrypted;
|
|
14040
14038
|
}
|
|
14041
14039
|
function storeFile(sourcePath, entityId) {
|
|
14042
|
-
if (!
|
|
14040
|
+
if (!existsSync9(DOCUMENTS_DIR))
|
|
14043
14041
|
mkdirSync6(DOCUMENTS_DIR, { recursive: true });
|
|
14044
14042
|
const ext = sourcePath.split(".").pop() || "bin";
|
|
14045
|
-
const destPath =
|
|
14043
|
+
const destPath = join10(DOCUMENTS_DIR, `${entityId}.${ext}`);
|
|
14046
14044
|
const data = readFileSync4(sourcePath);
|
|
14047
14045
|
writeFileSync3(destPath, data);
|
|
14048
14046
|
return destPath;
|
|
14049
14047
|
}
|
|
14050
14048
|
function getDocumentFilePath(entityId) {
|
|
14051
|
-
if (!
|
|
14049
|
+
if (!existsSync9(DOCUMENTS_DIR))
|
|
14052
14050
|
return null;
|
|
14053
|
-
const { readdirSync:
|
|
14054
|
-
const files =
|
|
14051
|
+
const { readdirSync: readdirSync5 } = __require("fs");
|
|
14052
|
+
const files = readdirSync5(DOCUMENTS_DIR);
|
|
14055
14053
|
const match = files.find((f) => f.startsWith(`${entityId}.`) && !f.endsWith(".enc"));
|
|
14056
|
-
return match ?
|
|
14054
|
+
return match ? join10(DOCUMENTS_DIR, match) : null;
|
|
14057
14055
|
}
|
|
14058
14056
|
function decryptFile(encPath) {
|
|
14059
14057
|
const key = requireVault();
|
|
@@ -14066,13 +14064,13 @@ function decryptFile(encPath) {
|
|
|
14066
14064
|
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
14067
14065
|
}
|
|
14068
14066
|
function getDocumentsDir() {
|
|
14069
|
-
if (!
|
|
14067
|
+
if (!existsSync9(DOCUMENTS_DIR))
|
|
14070
14068
|
mkdirSync6(DOCUMENTS_DIR, { recursive: true });
|
|
14071
14069
|
return DOCUMENTS_DIR;
|
|
14072
14070
|
}
|
|
14073
14071
|
// src/db/documents.ts
|
|
14074
14072
|
init_database();
|
|
14075
|
-
import { existsSync as
|
|
14073
|
+
import { existsSync as existsSync10, unlinkSync as unlinkSync3 } from "fs";
|
|
14076
14074
|
var DOCUMENT_TYPES = [
|
|
14077
14075
|
"passport",
|
|
14078
14076
|
"national_id",
|
|
@@ -14129,7 +14127,7 @@ function listDocuments(contactId, db) {
|
|
|
14129
14127
|
function deleteDocument(id, db) {
|
|
14130
14128
|
const _db2 = db || getDatabase();
|
|
14131
14129
|
const row = _db2.query(`SELECT encrypted_file_path FROM contact_documents WHERE id = ?`).get(id);
|
|
14132
|
-
if (row?.encrypted_file_path &&
|
|
14130
|
+
if (row?.encrypted_file_path && existsSync10(row.encrypted_file_path)) {
|
|
14133
14131
|
try {
|
|
14134
14132
|
unlinkSync3(row.encrypted_file_path);
|
|
14135
14133
|
} catch {}
|
|
@@ -14245,7 +14243,7 @@ function deleteHealthData(contactId, db) {
|
|
|
14245
14243
|
_db2.query(`DELETE FROM contact_health WHERE contact_id = ?`).run(contactId);
|
|
14246
14244
|
}
|
|
14247
14245
|
// src/lib/document-scanner.ts
|
|
14248
|
-
import { readFileSync as readFileSync5, existsSync as
|
|
14246
|
+
import { readFileSync as readFileSync5, existsSync as existsSync11 } from "fs";
|
|
14249
14247
|
import { extname as extname2 } from "path";
|
|
14250
14248
|
async function scanDocument(imageSource, docType) {
|
|
14251
14249
|
const apiKey = process.env["OPENAI_API_KEY"];
|
|
@@ -14255,7 +14253,7 @@ async function scanDocument(imageSource, docType) {
|
|
|
14255
14253
|
let imageData;
|
|
14256
14254
|
if (imageSource.startsWith("data:image/")) {
|
|
14257
14255
|
imageData = imageSource;
|
|
14258
|
-
} else if (
|
|
14256
|
+
} else if (existsSync11(imageSource)) {
|
|
14259
14257
|
const buffer = readFileSync5(imageSource);
|
|
14260
14258
|
const ext = extname2(imageSource).slice(1).toLowerCase();
|
|
14261
14259
|
const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "png" ? "image/png" : ext === "webp" ? "image/webp" : ext === "gif" ? "image/gif" : "image/jpeg";
|