@hasna/contacts 0.6.4 → 0.6.6

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 CHANGED
@@ -6,39 +6,60 @@ var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ function __accessProp(key) {
10
+ return this[key];
11
+ }
12
+ var __toESMCache_node;
13
+ var __toESMCache_esm;
9
14
  var __toESM = (mod, isNodeMode, target) => {
15
+ var canCache = mod != null && typeof mod === "object";
16
+ if (canCache) {
17
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
18
+ var cached = cache.get(mod);
19
+ if (cached)
20
+ return cached;
21
+ }
10
22
  target = mod != null ? __create(__getProtoOf(mod)) : {};
11
23
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
12
24
  for (let key of __getOwnPropNames(mod))
13
25
  if (!__hasOwnProp.call(to, key))
14
26
  __defProp(to, key, {
15
- get: () => mod[key],
27
+ get: __accessProp.bind(mod, key),
16
28
  enumerable: true
17
29
  });
30
+ if (canCache)
31
+ cache.set(mod, to);
18
32
  return to;
19
33
  };
20
- var __moduleCache = /* @__PURE__ */ new WeakMap;
21
34
  var __toCommonJS = (from) => {
22
- var entry = __moduleCache.get(from), desc;
35
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
23
36
  if (entry)
24
37
  return entry;
25
38
  entry = __defProp({}, "__esModule", { value: true });
26
- if (from && typeof from === "object" || typeof from === "function")
27
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
28
- get: () => from[key],
29
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
30
- }));
39
+ if (from && typeof from === "object" || typeof from === "function") {
40
+ for (var key of __getOwnPropNames(from))
41
+ if (!__hasOwnProp.call(entry, key))
42
+ __defProp(entry, key, {
43
+ get: __accessProp.bind(from, key),
44
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
45
+ });
46
+ }
31
47
  __moduleCache.set(from, entry);
32
48
  return entry;
33
49
  };
50
+ var __moduleCache;
34
51
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
52
+ var __returnValue = (v) => v;
53
+ function __exportSetter(name, newValue) {
54
+ this[name] = __returnValue.bind(null, newValue);
55
+ }
35
56
  var __export = (target, all) => {
36
57
  for (var name in all)
37
58
  __defProp(target, name, {
38
59
  get: all[name],
39
60
  enumerable: true,
40
61
  configurable: true,
41
- set: (newValue) => all[name] = () => newValue
62
+ set: __exportSetter.bind(all, name)
42
63
  });
43
64
  };
44
65
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -2114,16 +2135,34 @@ __export(exports_database, {
2114
2135
  resetDatabase: () => resetDatabase,
2115
2136
  now: () => now,
2116
2137
  getDbPath: () => getDbPath,
2117
- getDatabase: () => getDatabase
2138
+ getDatabase: () => getDatabase,
2139
+ getDataDir: () => getDataDir
2118
2140
  });
2119
2141
  import { Database } from "bun:sqlite";
2120
- import { existsSync, mkdirSync } from "fs";
2142
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
2121
2143
  import { dirname, join, resolve } from "path";
2144
+ function getDataDir() {
2145
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2146
+ const newDir = join(home, ".hasna", "contacts");
2147
+ const oldDir = join(home, ".contacts");
2148
+ if (existsSync(oldDir) && !existsSync(newDir)) {
2149
+ mkdirSync(newDir, { recursive: true });
2150
+ for (const file of readdirSync(oldDir)) {
2151
+ const oldPath = join(oldDir, file);
2152
+ if (statSync(oldPath).isFile()) {
2153
+ copyFileSync(oldPath, join(newDir, file));
2154
+ }
2155
+ }
2156
+ }
2157
+ mkdirSync(newDir, { recursive: true });
2158
+ return newDir;
2159
+ }
2122
2160
  function getDbPath() {
2161
+ if (process.env["HASNA_CONTACTS_DB_PATH"])
2162
+ return process.env["HASNA_CONTACTS_DB_PATH"];
2123
2163
  if (process.env["CONTACTS_DB_PATH"])
2124
2164
  return process.env["CONTACTS_DB_PATH"];
2125
- const home = process.env["HOME"] || "~";
2126
- return join(home, ".contacts", "contacts.db");
2165
+ return join(getDataDir(), "contacts.db");
2127
2166
  }
2128
2167
  function ensureDir(filePath) {
2129
2168
  if (filePath === ":memory:")
@@ -4521,7 +4560,7 @@ __export(exports_images, {
4521
4560
  getImageAsBase64: () => getImageAsBase64,
4522
4561
  deleteImage: () => deleteImage
4523
4562
  });
4524
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, copyFileSync, unlinkSync, readdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
4563
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, copyFileSync as copyFileSync2, unlinkSync, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
4525
4564
  import { join as join3, extname, basename } from "path";
4526
4565
  function ensureImagesDir() {
4527
4566
  if (!existsSync3(IMAGES_DIR))
@@ -4558,12 +4597,12 @@ function saveImage(entityId, source, options) {
4558
4597
  throw new Error(`Unsupported image format: ${ext}. Supported: ${validExts.join(", ")}`);
4559
4598
  }
4560
4599
  const filename = `${entityId}.${ext === "jpeg" ? "jpg" : ext}`;
4561
- copyFileSync(source, join3(IMAGES_DIR, filename));
4600
+ copyFileSync2(source, join3(IMAGES_DIR, filename));
4562
4601
  return filename;
4563
4602
  }
4564
4603
  function getImagePath(entityId) {
4565
4604
  ensureImagesDir();
4566
- const files = readdirSync(IMAGES_DIR);
4605
+ const files = readdirSync2(IMAGES_DIR);
4567
4606
  const match = files.find((f) => f.startsWith(`${entityId}.`));
4568
4607
  return match ? join3(IMAGES_DIR, match) : null;
4569
4608
  }
@@ -4578,7 +4617,7 @@ function getImageAsBase64(entityId) {
4578
4617
  }
4579
4618
  function deleteImage(entityId) {
4580
4619
  ensureImagesDir();
4581
- const files = readdirSync(IMAGES_DIR);
4620
+ const files = readdirSync2(IMAGES_DIR);
4582
4621
  let deleted = false;
4583
4622
  for (const f of files) {
4584
4623
  if (f.startsWith(`${entityId}.`)) {
@@ -4590,7 +4629,7 @@ function deleteImage(entityId) {
4590
4629
  }
4591
4630
  function listImages() {
4592
4631
  ensureImagesDir();
4593
- const files = readdirSync(IMAGES_DIR).filter((f) => !f.startsWith("."));
4632
+ const files = readdirSync2(IMAGES_DIR).filter((f) => !f.startsWith("."));
4594
4633
  return files.map((f) => ({
4595
4634
  entity_id: basename(f, extname(f)),
4596
4635
  filename: f,
@@ -4599,7 +4638,8 @@ function listImages() {
4599
4638
  }
4600
4639
  var IMAGES_DIR;
4601
4640
  var init_images = __esm(() => {
4602
- IMAGES_DIR = join3(process.env["HOME"] || "~", ".contacts", "images");
4641
+ init_database();
4642
+ IMAGES_DIR = join3(getDataDir(), "images");
4603
4643
  });
4604
4644
 
4605
4645
  // src/server/serve.ts
@@ -6206,8 +6246,8 @@ function storeFile(sourcePath, entityId) {
6206
6246
  function getDocumentFilePath(entityId) {
6207
6247
  if (!existsSync5(DOCUMENTS_DIR))
6208
6248
  return null;
6209
- const { readdirSync: readdirSync2 } = __require("fs");
6210
- const files = readdirSync2(DOCUMENTS_DIR);
6249
+ const { readdirSync: readdirSync3 } = __require("fs");
6250
+ const files = readdirSync3(DOCUMENTS_DIR);
6211
6251
  const match = files.find((f) => f.startsWith(`${entityId}.`) && !f.endsWith(".enc"));
6212
6252
  return match ? join5(DOCUMENTS_DIR, match) : null;
6213
6253
  }
@@ -6228,7 +6268,8 @@ function getDocumentsDir() {
6228
6268
  }
6229
6269
  var VAULT_DIR, VAULT_CONFIG, VAULT_SESSION, DOCUMENTS_DIR, SESSION_TTL_MS, _derivedKey = null;
6230
6270
  var init_vault = __esm(() => {
6231
- VAULT_DIR = join5(process.env["HOME"] || "~", ".contacts");
6271
+ init_database();
6272
+ VAULT_DIR = getDataDir();
6232
6273
  VAULT_CONFIG = join5(VAULT_DIR, "vault.json");
6233
6274
  VAULT_SESSION = join5(VAULT_DIR, ".vault-session");
6234
6275
  DOCUMENTS_DIR = join5(VAULT_DIR, "documents");
@@ -6818,9 +6859,10 @@ function findNameDuplicates(db) {
6818
6859
  }
6819
6860
 
6820
6861
  // src/lib/config.ts
6862
+ init_database();
6821
6863
  import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
6822
6864
  import { join as join2 } from "path";
6823
- var CONFIG_DIR = join2(process.env["HOME"] || "~", ".contacts");
6865
+ var CONFIG_DIR = getDataDir();
6824
6866
  var CONFIG_FILE = join2(CONFIG_DIR, "config.json");
6825
6867
  function readConfig() {
6826
6868
  if (!existsSync2(CONFIG_FILE))
@@ -6833,7 +6875,7 @@ function readConfig() {
6833
6875
  }
6834
6876
 
6835
6877
  // src/cli/index.tsx
6836
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync8, copyFileSync as copyFileSync2, statSync, mkdirSync as mkdirSync5, readdirSync as readdirSync2 } from "fs";
6878
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync8, copyFileSync as copyFileSync3, statSync as statSync2, mkdirSync as mkdirSync5, readdirSync as readdirSync3 } from "fs";
6837
6879
  import { extname as extname3, join as join6 } from "path";
6838
6880
  function renderTable(headers, rows) {
6839
6881
  const colWidths = headers.map((h) => h.length);
@@ -7569,7 +7611,8 @@ program.command("init").description("Show setup info, stats, and configuration")
7569
7611
  console.log();
7570
7612
  });
7571
7613
  program.command("backup").description("Backup the contacts database").option("--output <path>", "Output path").option("--list", "List existing backups").action((opts) => {
7572
- const backupDir = join6(process.env["HOME"] || "~", ".contacts", "backups");
7614
+ const { getDataDir: getDataDir2 } = (init_database(), __toCommonJS(exports_database));
7615
+ const backupDir = join6(getDataDir2(), "backups");
7573
7616
  if (opts.list) {
7574
7617
  if (!existsSync8(backupDir)) {
7575
7618
  console.log(chalk.gray(`
@@ -7577,7 +7620,7 @@ No backups found.
7577
7620
  `));
7578
7621
  return;
7579
7622
  }
7580
- const files = readdirSync2(backupDir).filter((f) => f.endsWith(".db")).sort().reverse();
7623
+ const files = readdirSync3(backupDir).filter((f) => f.endsWith(".db")).sort().reverse();
7581
7624
  if (files.length === 0) {
7582
7625
  console.log(chalk.gray(`
7583
7626
  No backups found.
@@ -7589,8 +7632,8 @@ Existing Backups:
7589
7632
  `));
7590
7633
  for (const f of files) {
7591
7634
  const filePath = join6(backupDir, f);
7592
- const size2 = statSync(filePath).size;
7593
- const mtime = statSync(filePath).mtime.toISOString().slice(0, 19).replace("T", " ");
7635
+ const size2 = statSync2(filePath).size;
7636
+ const mtime = statSync2(filePath).mtime.toISOString().slice(0, 19).replace("T", " ");
7594
7637
  console.log(` ${chalk.cyan(f)} ${chalk.gray(`${(size2 / 1024).toFixed(1)} KB ${mtime}`)}`);
7595
7638
  }
7596
7639
  console.log();
@@ -7606,8 +7649,8 @@ Database not found: ${src}
7606
7649
  mkdirSync5(backupDir, { recursive: true });
7607
7650
  const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
7608
7651
  const dest = opts.output || join6(backupDir, `contacts-${ts}.db`);
7609
- copyFileSync2(src, dest);
7610
- const size = statSync(dest).size;
7652
+ copyFileSync3(src, dest);
7653
+ const size = statSync2(dest).size;
7611
7654
  console.log(chalk.green(`
7612
7655
  \u2713 Backed up to ${dest} (${(size / 1024).toFixed(1)} KB)
7613
7656
  `));
@@ -8866,8 +8909,8 @@ photoCmd.command("set <contact-id> <image-path>").description("Set a contact's p
8866
8909
  try {
8867
8910
  const contact = getContact(contactId);
8868
8911
  const filename = saveImage2(contactId, imagePath);
8869
- updateContact(contactId, { avatar_url: `~/.contacts/images/${filename}` });
8870
- console.log(chalk.green(`Photo set for ${contact.display_name}: ~/.contacts/images/${filename}`));
8912
+ updateContact(contactId, { avatar_url: `~/.hasna/contacts/images/${filename}` });
8913
+ console.log(chalk.green(`Photo set for ${contact.display_name}: ~/.hasna/contacts/images/${filename}`));
8871
8914
  } catch (e) {
8872
8915
  console.error(chalk.red(e instanceof Error ? e.message : String(e)));
8873
8916
  }
@@ -8920,8 +8963,8 @@ logoCmd.command("set <company-id> <image-path>").description("Set a company's lo
8920
8963
  }
8921
8964
  const filename = saveImage2(companyId, imagePath);
8922
8965
  const { updateCompany: updateCompany2 } = (init_companies(), __toCommonJS(exports_companies));
8923
- updateCompany2(companyId, { logo_url: `~/.contacts/images/${filename}` });
8924
- console.log(chalk.green(`Logo set for ${company.name}: ~/.contacts/images/${filename}`));
8966
+ updateCompany2(companyId, { logo_url: `~/.hasna/contacts/images/${filename}` });
8967
+ console.log(chalk.green(`Logo set for ${company.name}: ~/.hasna/contacts/images/${filename}`));
8925
8968
  } catch (e) {
8926
8969
  console.error(chalk.red(e instanceof Error ? e.message : String(e)));
8927
8970
  }
@@ -1,4 +1,5 @@
1
1
  import { Database } from "bun:sqlite";
2
+ export declare function getDataDir(): string;
2
3
  export declare function getDbPath(): string;
3
4
  export declare function getDatabase(path?: string): Database;
4
5
  export declare function resetDatabase(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC,wBAAgB,SAAS,IAAI,MAAM,CAIlC;AAkhBD,wBAAgB,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,CAUnD;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,IAAI,IAAI,MAAM,CAE7B;AAED,wBAAgB,GAAG,IAAI,MAAM,CAE5B"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC,wBAAgB,UAAU,IAAI,MAAM,CAkBnC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAIlC;AAkhBD,wBAAgB,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,CAUnD;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,IAAI,IAAI,MAAM,CAE7B;AAED,wBAAgB,GAAG,IAAI,MAAM,CAE5B"}
package/dist/index.js CHANGED
@@ -1,12 +1,16 @@
1
1
  // @bun
2
2
  var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
3
7
  var __export = (target, all) => {
4
8
  for (var name in all)
5
9
  __defProp(target, name, {
6
10
  get: all[name],
7
11
  enumerable: true,
8
12
  configurable: true,
9
- set: (newValue) => all[name] = () => newValue
13
+ set: __exportSetter.bind(all, name)
10
14
  });
11
15
  };
12
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -14,13 +18,30 @@ var __require = import.meta.require;
14
18
 
15
19
  // src/db/database.ts
16
20
  import { Database } from "bun:sqlite";
17
- import { existsSync, mkdirSync } from "fs";
21
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
18
22
  import { dirname, join, resolve } from "path";
23
+ function getDataDir() {
24
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
25
+ const newDir = join(home, ".hasna", "contacts");
26
+ const oldDir = join(home, ".contacts");
27
+ if (existsSync(oldDir) && !existsSync(newDir)) {
28
+ mkdirSync(newDir, { recursive: true });
29
+ for (const file of readdirSync(oldDir)) {
30
+ const oldPath = join(oldDir, file);
31
+ if (statSync(oldPath).isFile()) {
32
+ copyFileSync(oldPath, join(newDir, file));
33
+ }
34
+ }
35
+ }
36
+ mkdirSync(newDir, { recursive: true });
37
+ return newDir;
38
+ }
19
39
  function getDbPath() {
40
+ if (process.env["HASNA_CONTACTS_DB_PATH"])
41
+ return process.env["HASNA_CONTACTS_DB_PATH"];
20
42
  if (process.env["CONTACTS_DB_PATH"])
21
43
  return process.env["CONTACTS_DB_PATH"];
22
- const home = process.env["HOME"] || "~";
23
- return join(home, ".contacts", "contacts.db");
44
+ return join(getDataDir(), "contacts.db");
24
45
  }
25
46
  function ensureDir(filePath) {
26
47
  if (filePath === ":memory:")
@@ -4426,9 +4447,10 @@ async function ingestMeetingParticipants(event, db) {
4426
4447
  init_contacts();
4427
4448
 
4428
4449
  // src/lib/images.ts
4429
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, copyFileSync, unlinkSync, readdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
4450
+ init_database();
4451
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, copyFileSync as copyFileSync2, unlinkSync, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
4430
4452
  import { join as join3, extname, basename } from "path";
4431
- var IMAGES_DIR = join3(process.env["HOME"] || "~", ".contacts", "images");
4453
+ var IMAGES_DIR = join3(getDataDir(), "images");
4432
4454
  function ensureImagesDir() {
4433
4455
  if (!existsSync3(IMAGES_DIR))
4434
4456
  mkdirSync2(IMAGES_DIR, { recursive: true });
@@ -4464,12 +4486,12 @@ function saveImage(entityId, source, options) {
4464
4486
  throw new Error(`Unsupported image format: ${ext}. Supported: ${validExts.join(", ")}`);
4465
4487
  }
4466
4488
  const filename = `${entityId}.${ext === "jpeg" ? "jpg" : ext}`;
4467
- copyFileSync(source, join3(IMAGES_DIR, filename));
4489
+ copyFileSync2(source, join3(IMAGES_DIR, filename));
4468
4490
  return filename;
4469
4491
  }
4470
4492
  function getImagePath(entityId) {
4471
4493
  ensureImagesDir();
4472
- const files = readdirSync(IMAGES_DIR);
4494
+ const files = readdirSync2(IMAGES_DIR);
4473
4495
  const match = files.find((f) => f.startsWith(`${entityId}.`));
4474
4496
  return match ? join3(IMAGES_DIR, match) : null;
4475
4497
  }
@@ -4484,7 +4506,7 @@ function getImageAsBase64(entityId) {
4484
4506
  }
4485
4507
  function deleteImage(entityId) {
4486
4508
  ensureImagesDir();
4487
- const files = readdirSync(IMAGES_DIR);
4509
+ const files = readdirSync2(IMAGES_DIR);
4488
4510
  let deleted = false;
4489
4511
  for (const f of files) {
4490
4512
  if (f.startsWith(`${entityId}.`)) {
@@ -4496,7 +4518,7 @@ function deleteImage(entityId) {
4496
4518
  }
4497
4519
  function listImages() {
4498
4520
  ensureImagesDir();
4499
- const files = readdirSync(IMAGES_DIR).filter((f) => !f.startsWith("."));
4521
+ const files = readdirSync2(IMAGES_DIR).filter((f) => !f.startsWith("."));
4500
4522
  return files.map((f) => ({
4501
4523
  entity_id: basename(f, extname(f)),
4502
4524
  filename: f,
@@ -4504,10 +4526,11 @@ function listImages() {
4504
4526
  }));
4505
4527
  }
4506
4528
  // src/lib/vault.ts
4529
+ init_database();
4507
4530
  import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2 } from "fs";
4508
4531
  import { join as join4 } from "path";
4509
4532
  import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync, createHash } from "crypto";
4510
- var VAULT_DIR = join4(process.env["HOME"] || "~", ".contacts");
4533
+ var VAULT_DIR = getDataDir();
4511
4534
  var VAULT_CONFIG = join4(VAULT_DIR, "vault.json");
4512
4535
  var VAULT_SESSION = join4(VAULT_DIR, ".vault-session");
4513
4536
  var DOCUMENTS_DIR = join4(VAULT_DIR, "documents");
@@ -4630,8 +4653,8 @@ function storeFile(sourcePath, entityId) {
4630
4653
  function getDocumentFilePath(entityId) {
4631
4654
  if (!existsSync4(DOCUMENTS_DIR))
4632
4655
  return null;
4633
- const { readdirSync: readdirSync2 } = __require("fs");
4634
- const files = readdirSync2(DOCUMENTS_DIR);
4656
+ const { readdirSync: readdirSync3 } = __require("fs");
4657
+ const files = readdirSync3(DOCUMENTS_DIR);
4635
4658
  const match = files.find((f) => f.startsWith(`${entityId}.`) && !f.endsWith(".enc"));
4636
4659
  return match ? join4(DOCUMENTS_DIR, match) : null;
4637
4660
  }
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAMA,UAAU,cAAc;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,UAAU,IAAI,cAAc,CAG3C;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAGxD"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAOA,UAAU,cAAc;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,UAAU,IAAI,cAAc,CAG3C;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAGxD"}
@@ -1 +1 @@
1
- {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/lib/images.ts"],"names":[],"mappings":"AAaA,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5B,MAAM,CAuCR;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK5D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOhE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAWrD;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,KAAK,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAQzF"}
1
+ {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/lib/images.ts"],"names":[],"mappings":"AAcA,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5B,MAAM,CAuCR;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK5D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOhE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAWrD;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,KAAK,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAQzF"}
@@ -1 +1 @@
1
- {"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["../../src/lib/vault.ts"],"names":[],"mappings":"AAgDA,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAUlD;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAUvD;AAED,wBAAgB,SAAS,IAAI,IAAI,CAGhC;AAED,wBAAgB,eAAe,IAAI,OAAO,CASzC;AAED,wBAAgB,YAAY,IAAI,MAAM,CASrC;AAED,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAQ7E;AAED,wBAAgB,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAS9D;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOtE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMnE;AAGD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASnD;AAED,wBAAgB,eAAe,IAAI,MAAM,CAGxC"}
1
+ {"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["../../src/lib/vault.ts"],"names":[],"mappings":"AAiDA,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAUlD;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAUvD;AAED,wBAAgB,SAAS,IAAI,IAAI,CAGhC;AAED,wBAAgB,eAAe,IAAI,OAAO,CASzC;AAED,wBAAgB,YAAY,IAAI,MAAM,CASrC;AAED,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAQ7E;AAED,wBAAgB,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAS9D;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOtE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMnE;AAGD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASnD;AAED,wBAAgB,eAAe,IAAI,MAAM,CAGxC"}
package/dist/mcp/index.js CHANGED
@@ -1,28 +1,17 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- var __create = Object.create;
4
- var __getProtoOf = Object.getPrototypeOf;
5
3
  var __defProp = Object.defineProperty;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __toESM = (mod, isNodeMode, target) => {
9
- target = mod != null ? __create(__getProtoOf(mod)) : {};
10
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
- for (let key of __getOwnPropNames(mod))
12
- if (!__hasOwnProp.call(to, key))
13
- __defProp(to, key, {
14
- get: () => mod[key],
15
- enumerable: true
16
- });
17
- return to;
18
- };
4
+ var __returnValue = (v) => v;
5
+ function __exportSetter(name, newValue) {
6
+ this[name] = __returnValue.bind(null, newValue);
7
+ }
19
8
  var __export = (target, all) => {
20
9
  for (var name in all)
21
10
  __defProp(target, name, {
22
11
  get: all[name],
23
12
  enumerable: true,
24
13
  configurable: true,
25
- set: (newValue) => all[name] = () => newValue
14
+ set: __exportSetter.bind(all, name)
26
15
  });
27
16
  };
28
17
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -30,13 +19,30 @@ var __require = import.meta.require;
30
19
 
31
20
  // src/db/database.ts
32
21
  import { Database } from "bun:sqlite";
33
- import { existsSync, mkdirSync } from "fs";
22
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
34
23
  import { dirname, join, resolve } from "path";
24
+ function getDataDir() {
25
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
26
+ const newDir = join(home, ".hasna", "contacts");
27
+ const oldDir = join(home, ".contacts");
28
+ if (existsSync(oldDir) && !existsSync(newDir)) {
29
+ mkdirSync(newDir, { recursive: true });
30
+ for (const file of readdirSync(oldDir)) {
31
+ const oldPath = join(oldDir, file);
32
+ if (statSync(oldPath).isFile()) {
33
+ copyFileSync(oldPath, join(newDir, file));
34
+ }
35
+ }
36
+ }
37
+ mkdirSync(newDir, { recursive: true });
38
+ return newDir;
39
+ }
35
40
  function getDbPath() {
41
+ if (process.env["HASNA_CONTACTS_DB_PATH"])
42
+ return process.env["HASNA_CONTACTS_DB_PATH"];
36
43
  if (process.env["CONTACTS_DB_PATH"])
37
44
  return process.env["CONTACTS_DB_PATH"];
38
- const home = process.env["HOME"] || "~";
39
- return join(home, ".contacts", "contacts.db");
45
+ return join(getDataDir(), "contacts.db");
40
46
  }
41
47
  function ensureDir(filePath) {
42
48
  if (filePath === ":memory:")
@@ -4251,9 +4257,10 @@ function getCoverageGaps(companyId, db) {
4251
4257
  }
4252
4258
 
4253
4259
  // src/lib/images.ts
4254
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, copyFileSync, unlinkSync, readdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
4260
+ init_database();
4261
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, copyFileSync as copyFileSync2, unlinkSync, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
4255
4262
  import { join as join3, extname, basename } from "path";
4256
- var IMAGES_DIR = join3(process.env["HOME"] || "~", ".contacts", "images");
4263
+ var IMAGES_DIR = join3(getDataDir(), "images");
4257
4264
  function ensureImagesDir() {
4258
4265
  if (!existsSync3(IMAGES_DIR))
4259
4266
  mkdirSync2(IMAGES_DIR, { recursive: true });
@@ -4285,12 +4292,12 @@ function saveImage(entityId, source, options) {
4285
4292
  throw new Error(`Unsupported image format: ${ext}. Supported: ${validExts.join(", ")}`);
4286
4293
  }
4287
4294
  const filename = `${entityId}.${ext === "jpeg" ? "jpg" : ext}`;
4288
- copyFileSync(source, join3(IMAGES_DIR, filename));
4295
+ copyFileSync2(source, join3(IMAGES_DIR, filename));
4289
4296
  return filename;
4290
4297
  }
4291
4298
  function getImagePath(entityId) {
4292
4299
  ensureImagesDir();
4293
- const files = readdirSync(IMAGES_DIR);
4300
+ const files = readdirSync2(IMAGES_DIR);
4294
4301
  const match = files.find((f) => f.startsWith(`${entityId}.`));
4295
4302
  return match ? join3(IMAGES_DIR, match) : null;
4296
4303
  }
@@ -4305,7 +4312,7 @@ function getImageAsBase64(entityId) {
4305
4312
  }
4306
4313
  function deleteImage(entityId) {
4307
4314
  ensureImagesDir();
4308
- const files = readdirSync(IMAGES_DIR);
4315
+ const files = readdirSync2(IMAGES_DIR);
4309
4316
  let deleted = false;
4310
4317
  for (const f of files) {
4311
4318
  if (f.startsWith(`${entityId}.`)) {
@@ -4317,10 +4324,11 @@ function deleteImage(entityId) {
4317
4324
  }
4318
4325
 
4319
4326
  // src/lib/vault.ts
4327
+ init_database();
4320
4328
  import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2 } from "fs";
4321
4329
  import { join as join4 } from "path";
4322
4330
  import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync, createHash } from "crypto";
4323
- var VAULT_DIR = join4(process.env["HOME"] || "~", ".contacts");
4331
+ var VAULT_DIR = getDataDir();
4324
4332
  var VAULT_CONFIG = join4(VAULT_DIR, "vault.json");
4325
4333
  var VAULT_SESSION = join4(VAULT_DIR, ".vault-session");
4326
4334
  var DOCUMENTS_DIR = join4(VAULT_DIR, "documents");
@@ -6177,10 +6185,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
6177
6185
  { name: "get_deal_team", description: "Get the full buying committee for a deal with contact names and roles.", inputSchema: { type: "object", properties: { deal_id: { type: "string" } }, required: ["deal_id"] } },
6178
6186
  { name: "get_coverage_gaps", description: "Identify coverage gaps in a company account \u2014 missing economic buyer, technical evaluator, or org chart relationships.", inputSchema: { type: "object", properties: { company_id: { type: "string" } }, required: ["company_id"] } },
6179
6187
  { name: "get_recent_contact_events", description: "Polling fallback for change events \u2014 returns recent activity log entries, optionally filtered by event type or date.", inputSchema: { type: "object", properties: { since: { type: "string", description: "ISO 8601 datetime \u2014 only events after this date" }, event_types: { type: "array", items: { type: "string" } } } } },
6180
- { name: "set_contact_photo", description: "Set a contact's profile photo. Provide either a local file path or base64-encoded image data (with or without data URI prefix). Stores image in ~/.contacts/images/ and updates avatar_url. Supported formats: jpg, png, gif, webp, svg, avif.", inputSchema: { type: "object", properties: { contact_id: { type: "string" }, image: { type: "string", description: "File path (e.g. /tmp/photo.jpg) OR base64 data (e.g. data:image/png;base64,...) OR raw base64 string" }, format: { type: "string", description: "Image format hint when using raw base64 (jpg, png, webp). Not needed for file paths or data URIs." } }, required: ["contact_id", "image"] } },
6188
+ { name: "set_contact_photo", description: "Set a contact's profile photo. Provide either a local file path or base64-encoded image data (with or without data URI prefix). Stores image in ~/.hasna/contacts/images/ and updates avatar_url. Supported formats: jpg, png, gif, webp, svg, avif.", inputSchema: { type: "object", properties: { contact_id: { type: "string" }, image: { type: "string", description: "File path (e.g. /tmp/photo.jpg) OR base64 data (e.g. data:image/png;base64,...) OR raw base64 string" }, format: { type: "string", description: "Image format hint when using raw base64 (jpg, png, webp). Not needed for file paths or data URIs." } }, required: ["contact_id", "image"] } },
6181
6189
  { name: "get_contact_photo", description: "Get a contact's profile photo as base64 data URI. Returns null if no photo is set.", inputSchema: { type: "object", properties: { contact_id: { type: "string" } }, required: ["contact_id"] } },
6182
6190
  { name: "delete_contact_photo", description: "Remove a contact's profile photo.", inputSchema: { type: "object", properties: { contact_id: { type: "string" } }, required: ["contact_id"] } },
6183
- { name: "set_company_logo", description: "Set a company's logo image. Provide either a local file path or base64-encoded image data. Stores image in ~/.contacts/images/ and updates logo_url.", inputSchema: { type: "object", properties: { company_id: { type: "string" }, image: { type: "string", description: "File path or base64 data" }, format: { type: "string", description: "Image format hint for raw base64" } }, required: ["company_id", "image"] } },
6191
+ { name: "set_company_logo", description: "Set a company's logo image. Provide either a local file path or base64-encoded image data. Stores image in ~/.hasna/contacts/images/ and updates logo_url.", inputSchema: { type: "object", properties: { company_id: { type: "string" }, image: { type: "string", description: "File path or base64 data" }, format: { type: "string", description: "Image format hint for raw base64" } }, required: ["company_id", "image"] } },
6184
6192
  { name: "get_company_logo", description: "Get a company's logo as base64 data URI.", inputSchema: { type: "object", properties: { company_id: { type: "string" } }, required: ["company_id"] } },
6185
6193
  { name: "delete_company_logo", description: "Remove a company's logo image.", inputSchema: { type: "object", properties: { company_id: { type: "string" } }, required: ["company_id"] } },
6186
6194
  { name: "set_sensitivity", description: "Set a contact's sensitivity level (normal, confidential, restricted). Restricted contacts are hidden from list/search unless explicitly requested.", inputSchema: { type: "object", properties: { contact_id: { type: "string" }, sensitivity: { type: "string", enum: ["normal", "confidential", "restricted"] } }, required: ["contact_id", "sensitivity"] } },
@@ -6188,7 +6196,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
6188
6196
  { name: "vault_unlock", description: "Unlock the vault for this session with a passphrase.", inputSchema: { type: "object", properties: { passphrase: { type: "string" } }, required: ["passphrase"] } },
6189
6197
  { name: "vault_lock", description: "Lock the vault, clearing the encryption key from memory.", inputSchema: { type: "object", properties: {} } },
6190
6198
  { name: "vault_status", description: "Check vault initialization and lock status.", inputSchema: { type: "object", properties: {} } },
6191
- { name: "add_document", description: "Store a document for a contact (passport, tax_id, medical_record, etc.). Text values are encrypted; file attachments are stored plain so agents can read them. Vault must be unlocked.", inputSchema: { type: "object", properties: { contact_id: { type: "string" }, doc_type: { type: "string", enum: [...DOCUMENT_TYPES] }, label: { type: "string" }, value: { type: "string", description: "Plaintext value (will be encrypted in DB)" }, file_path: { type: "string", description: "File to attach \u2014 stored PLAIN in ~/.contacts/documents/ for agent access" }, metadata: { type: "object" }, expires_at: { type: "string" } }, required: ["contact_id", "doc_type", "value"] } },
6199
+ { name: "add_document", description: "Store a document for a contact (passport, tax_id, medical_record, etc.). Text values are encrypted; file attachments are stored plain so agents can read them. Vault must be unlocked.", inputSchema: { type: "object", properties: { contact_id: { type: "string" }, doc_type: { type: "string", enum: [...DOCUMENT_TYPES] }, label: { type: "string" }, value: { type: "string", description: "Plaintext value (will be encrypted in DB)" }, file_path: { type: "string", description: "File to attach \u2014 stored PLAIN in ~/.hasna/contacts/documents/ for agent access" }, metadata: { type: "object" }, expires_at: { type: "string" } }, required: ["contact_id", "doc_type", "value"] } },
6192
6200
  { name: "list_documents", description: "List documents for a contact (metadata only \u2014 no decryption needed). Returns file_path for attachments so agents can read them directly.", inputSchema: { type: "object", properties: { contact_id: { type: "string" } }, required: ["contact_id"] } },
6193
6201
  { name: "get_document", description: "Get a document with decrypted value and file_path. Vault must be unlocked for the text value; file is always accessible.", inputSchema: { type: "object", properties: { document_id: { type: "string" } }, required: ["document_id"] } },
6194
6202
  { name: "get_document_file", description: "Get the plain file path for a document attachment. Agents can read this file directly \u2014 it is NOT encrypted. Returns null if no file attached.", inputSchema: { type: "object", properties: { document_id: { type: "string" } }, required: ["document_id"] } },
@@ -7694,8 +7702,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
7694
7702
  const { contact_id, image, format } = a;
7695
7703
  const contact = getContact(contact_id);
7696
7704
  const filename = saveImage(contact_id, image, { format });
7697
- updateContact(contact_id, { avatar_url: `~/.contacts/images/${filename}` });
7698
- return { content: [{ type: "text", text: JSON.stringify({ ok: true, contact_id, filename, avatar_url: `~/.contacts/images/${filename}` }) }] };
7705
+ updateContact(contact_id, { avatar_url: `~/.hasna/contacts/images/${filename}` });
7706
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true, contact_id, filename, avatar_url: `~/.hasna/contacts/images/${filename}` }) }] };
7699
7707
  }
7700
7708
  case "get_contact_photo": {
7701
7709
  const { contact_id } = a;
@@ -7715,8 +7723,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
7715
7723
  const { company_id, image, format } = a;
7716
7724
  const co = getCompany(company_id);
7717
7725
  const filename = saveImage(company_id, image, { format });
7718
- updateCompany(company_id, { logo_url: `~/.contacts/images/${filename}` });
7719
- return { content: [{ type: "text", text: JSON.stringify({ ok: true, company_id, filename, logo_url: `~/.contacts/images/${filename}` }) }] };
7726
+ updateCompany(company_id, { logo_url: `~/.hasna/contacts/images/${filename}` });
7727
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true, company_id, filename, logo_url: `~/.hasna/contacts/images/${filename}` }) }] };
7720
7728
  }
7721
7729
  case "get_company_logo": {
7722
7730
  const { company_id } = a;
@@ -1,21 +1,5 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- var __create = Object.create;
4
- var __getProtoOf = Object.getPrototypeOf;
5
- var __defProp = Object.defineProperty;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __toESM = (mod, isNodeMode, target) => {
9
- target = mod != null ? __create(__getProtoOf(mod)) : {};
10
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
- for (let key of __getOwnPropNames(mod))
12
- if (!__hasOwnProp.call(to, key))
13
- __defProp(to, key, {
14
- get: () => mod[key],
15
- enumerable: true
16
- });
17
- return to;
18
- };
19
3
  var __require = import.meta.require;
20
4
 
21
5
  // src/server/serve.ts
@@ -24,13 +8,30 @@ import { join as join3 } from "path";
24
8
 
25
9
  // src/db/database.ts
26
10
  import { Database } from "bun:sqlite";
27
- import { existsSync, mkdirSync } from "fs";
11
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
28
12
  import { dirname, join, resolve } from "path";
13
+ function getDataDir() {
14
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
15
+ const newDir = join(home, ".hasna", "contacts");
16
+ const oldDir = join(home, ".contacts");
17
+ if (existsSync(oldDir) && !existsSync(newDir)) {
18
+ mkdirSync(newDir, { recursive: true });
19
+ for (const file of readdirSync(oldDir)) {
20
+ const oldPath = join(oldDir, file);
21
+ if (statSync(oldPath).isFile()) {
22
+ copyFileSync(oldPath, join(newDir, file));
23
+ }
24
+ }
25
+ }
26
+ mkdirSync(newDir, { recursive: true });
27
+ return newDir;
28
+ }
29
29
  function getDbPath() {
30
+ if (process.env["HASNA_CONTACTS_DB_PATH"])
31
+ return process.env["HASNA_CONTACTS_DB_PATH"];
30
32
  if (process.env["CONTACTS_DB_PATH"])
31
33
  return process.env["CONTACTS_DB_PATH"];
32
- const home = process.env["HOME"] || "~";
33
- return join(home, ".contacts", "contacts.db");
34
+ return join(getDataDir(), "contacts.db");
34
35
  }
35
36
  function ensureDir(filePath) {
36
37
  if (filePath === ":memory:")
@@ -1769,9 +1770,9 @@ async function exportContacts(format, contacts) {
1769
1770
  }
1770
1771
 
1771
1772
  // src/lib/images.ts
1772
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, copyFileSync, unlinkSync, readdirSync, readFileSync, writeFileSync } from "fs";
1773
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, copyFileSync as copyFileSync2, unlinkSync, readdirSync as readdirSync2, readFileSync, writeFileSync } from "fs";
1773
1774
  import { join as join2, extname, basename } from "path";
1774
- var IMAGES_DIR = join2(process.env["HOME"] || "~", ".contacts", "images");
1775
+ var IMAGES_DIR = join2(getDataDir(), "images");
1775
1776
  function ensureImagesDir() {
1776
1777
  if (!existsSync2(IMAGES_DIR))
1777
1778
  mkdirSync2(IMAGES_DIR, { recursive: true });
@@ -1807,18 +1808,18 @@ function saveImage(entityId, source, options) {
1807
1808
  throw new Error(`Unsupported image format: ${ext}. Supported: ${validExts.join(", ")}`);
1808
1809
  }
1809
1810
  const filename = `${entityId}.${ext === "jpeg" ? "jpg" : ext}`;
1810
- copyFileSync(source, join2(IMAGES_DIR, filename));
1811
+ copyFileSync2(source, join2(IMAGES_DIR, filename));
1811
1812
  return filename;
1812
1813
  }
1813
1814
  function getImagePath(entityId) {
1814
1815
  ensureImagesDir();
1815
- const files = readdirSync(IMAGES_DIR);
1816
+ const files = readdirSync2(IMAGES_DIR);
1816
1817
  const match = files.find((f) => f.startsWith(`${entityId}.`));
1817
1818
  return match ? join2(IMAGES_DIR, match) : null;
1818
1819
  }
1819
1820
  function deleteImage(entityId) {
1820
1821
  ensureImagesDir();
1821
- const files = readdirSync(IMAGES_DIR);
1822
+ const files = readdirSync2(IMAGES_DIR);
1822
1823
  let deleted = false;
1823
1824
  for (const f of files) {
1824
1825
  if (f.startsWith(`${entityId}.`)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/contacts",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "description": "Contact management for AI coding agents — CLI + MCP + Web",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -43,6 +43,7 @@
43
43
  "address-book"
44
44
  ],
45
45
  "publishConfig": {
46
+ "registry": "https://registry.npmjs.org",
46
47
  "access": "public"
47
48
  },
48
49
  "repository": {
@@ -59,6 +60,7 @@
59
60
  "author": "Andrei Hasna <andrei@hasna.com>",
60
61
  "license": "Apache-2.0",
61
62
  "dependencies": {
63
+ "@hasna/cloud": "^0.1.0",
62
64
  "@modelcontextprotocol/sdk": "^1.12.1",
63
65
  "chalk": "^5.4.1",
64
66
  "commander": "^13.1.0",