@hasna/contacts 0.5.2 → 0.5.3

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
@@ -4,6 +4,7 @@ var __create = Object.create;
4
4
  var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
9
  var __toESM = (mod, isNodeMode, target) => {
9
10
  target = mod != null ? __create(__getProtoOf(mod)) : {};
@@ -16,6 +17,20 @@ var __toESM = (mod, isNodeMode, target) => {
16
17
  });
17
18
  return to;
18
19
  };
20
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
21
+ var __toCommonJS = (from) => {
22
+ var entry = __moduleCache.get(from), desc;
23
+ if (entry)
24
+ return entry;
25
+ 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
+ }));
31
+ __moduleCache.set(from, entry);
32
+ return entry;
33
+ };
19
34
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
20
35
  var __export = (target, all) => {
21
36
  for (var name in all)
@@ -3263,6 +3278,19 @@ var init_contacts = __esm(() => {
3263
3278
  });
3264
3279
 
3265
3280
  // src/db/companies.ts
3281
+ var exports_companies = {};
3282
+ __export(exports_companies, {
3283
+ updateCompany: () => updateCompany,
3284
+ unarchiveCompany: () => unarchiveCompany,
3285
+ searchCompanies: () => searchCompanies,
3286
+ listOwnedEntities: () => listOwnedEntities,
3287
+ listCompanyEmployees: () => listCompanyEmployees,
3288
+ listCompanies: () => listCompanies,
3289
+ getCompany: () => getCompany,
3290
+ deleteCompany: () => deleteCompany,
3291
+ createCompany: () => createCompany,
3292
+ archiveCompany: () => archiveCompany
3293
+ });
3266
3294
  function rowToCompany2(row) {
3267
3295
  return {
3268
3296
  ...row,
@@ -3485,6 +3513,58 @@ function deleteCompany(id, db) {
3485
3513
  logActivity(d, { company_id: id, action: "company.deleted", details: `Deleted company: ${row.name}` });
3486
3514
  d.run(`DELETE FROM companies WHERE id = ?`, [id]);
3487
3515
  }
3516
+ function searchCompanies(query, db) {
3517
+ const d = db || getDatabase();
3518
+ const rows = d.query(`
3519
+ SELECT * FROM companies
3520
+ WHERE name LIKE ? OR domain LIKE ? OR description LIKE ? OR industry LIKE ?
3521
+ LIMIT 50
3522
+ `).all(`%${query}%`, `%${query}%`, `%${query}%`, `%${query}%`);
3523
+ return rows.map((row) => loadCompanyDetails(d, rowToCompany2(row)));
3524
+ }
3525
+ function listCompanyEmployees(companyId, db) {
3526
+ const d = db || getDatabase();
3527
+ const row = d.query(`SELECT id FROM companies WHERE id = ?`).get(companyId);
3528
+ if (!row)
3529
+ throw new CompanyNotFoundError(companyId);
3530
+ const rows = d.query(`SELECT * FROM contacts WHERE company_id = ? ORDER BY display_name ASC`).all(companyId);
3531
+ return rows.map((r) => ({
3532
+ ...r,
3533
+ source: r.source,
3534
+ custom_fields: JSON.parse(r.custom_fields || "{}"),
3535
+ preferred_contact_method: r.preferred_contact_method ?? null,
3536
+ status: r.status ?? "active",
3537
+ follow_up_at: r.follow_up_at ?? null,
3538
+ archived: !!r.archived,
3539
+ project_id: r.project_id ?? null,
3540
+ do_not_contact: !!r.do_not_contact,
3541
+ priority: r.priority ?? 3,
3542
+ timezone: r.timezone ?? null
3543
+ }));
3544
+ }
3545
+ function archiveCompany(id, db) {
3546
+ const d = db || getDatabase();
3547
+ const row = d.query(`SELECT * FROM companies WHERE id = ?`).get(id);
3548
+ if (!row)
3549
+ throw new CompanyNotFoundError(id);
3550
+ d.run(`UPDATE companies SET archived = 1, updated_at = ? WHERE id = ?`, [now(), id]);
3551
+ logActivity(d, { company_id: id, action: "company.archived", details: `Archived company: ${row.name}` });
3552
+ const updated = d.query(`SELECT * FROM companies WHERE id = ?`).get(id);
3553
+ return loadCompanyDetails(d, rowToCompany2(updated));
3554
+ }
3555
+ function unarchiveCompany(id, db) {
3556
+ const d = db || getDatabase();
3557
+ const row = d.query(`SELECT * FROM companies WHERE id = ?`).get(id);
3558
+ if (!row)
3559
+ throw new CompanyNotFoundError(id);
3560
+ d.run(`UPDATE companies SET archived = 0, updated_at = ? WHERE id = ?`, [now(), id]);
3561
+ logActivity(d, { company_id: id, action: "company.unarchived", details: `Unarchived company: ${row.name}` });
3562
+ const updated = d.query(`SELECT * FROM companies WHERE id = ?`).get(id);
3563
+ return loadCompanyDetails(d, rowToCompany2(updated));
3564
+ }
3565
+ function listOwnedEntities(db) {
3566
+ return listCompanies({ is_owned_entity: true }, db);
3567
+ }
3488
3568
  var init_companies = __esm(() => {
3489
3569
  init_types();
3490
3570
  init_database();
@@ -4384,13 +4464,104 @@ async function exportContacts(format, contacts) {
4384
4464
  }
4385
4465
  }
4386
4466
 
4467
+ // src/lib/images.ts
4468
+ var exports_images = {};
4469
+ __export(exports_images, {
4470
+ saveImage: () => saveImage,
4471
+ listImages: () => listImages,
4472
+ getImagesDir: () => getImagesDir,
4473
+ getImagePath: () => getImagePath,
4474
+ getImageAsBase64: () => getImageAsBase64,
4475
+ deleteImage: () => deleteImage
4476
+ });
4477
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, copyFileSync, unlinkSync, readdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
4478
+ import { join as join3, extname, basename } from "path";
4479
+ function ensureImagesDir() {
4480
+ if (!existsSync3(IMAGES_DIR))
4481
+ mkdirSync3(IMAGES_DIR, { recursive: true });
4482
+ }
4483
+ function getImagesDir() {
4484
+ ensureImagesDir();
4485
+ return IMAGES_DIR;
4486
+ }
4487
+ function saveImage(entityId, source, options) {
4488
+ ensureImagesDir();
4489
+ deleteImage(entityId);
4490
+ const base64Match = source.match(/^data:image\/(\w+);base64,(.+)$/s);
4491
+ if (base64Match) {
4492
+ const ext2 = base64Match[1] === "jpeg" ? "jpg" : base64Match[1];
4493
+ const data = Buffer.from(base64Match[2], "base64");
4494
+ const filename2 = `${entityId}.${ext2}`;
4495
+ writeFileSync2(join3(IMAGES_DIR, filename2), data);
4496
+ return filename2;
4497
+ }
4498
+ if (!existsSync3(source) && /^[A-Za-z0-9+/=\n\r]+$/.test(source.trim()) && source.length > 100) {
4499
+ const ext2 = options?.format || "jpg";
4500
+ const data = Buffer.from(source.trim(), "base64");
4501
+ const filename2 = `${entityId}.${ext2}`;
4502
+ writeFileSync2(join3(IMAGES_DIR, filename2), data);
4503
+ return filename2;
4504
+ }
4505
+ if (!existsSync3(source)) {
4506
+ throw new Error(`Image file not found: ${source}`);
4507
+ }
4508
+ const ext = extname(source).slice(1).toLowerCase() || "jpg";
4509
+ const validExts = ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif", "ico"];
4510
+ if (!validExts.includes(ext)) {
4511
+ throw new Error(`Unsupported image format: ${ext}. Supported: ${validExts.join(", ")}`);
4512
+ }
4513
+ const filename = `${entityId}.${ext === "jpeg" ? "jpg" : ext}`;
4514
+ copyFileSync(source, join3(IMAGES_DIR, filename));
4515
+ return filename;
4516
+ }
4517
+ function getImagePath(entityId) {
4518
+ ensureImagesDir();
4519
+ const files = readdirSync(IMAGES_DIR);
4520
+ const match = files.find((f) => f.startsWith(`${entityId}.`));
4521
+ return match ? join3(IMAGES_DIR, match) : null;
4522
+ }
4523
+ function getImageAsBase64(entityId) {
4524
+ const path = getImagePath(entityId);
4525
+ if (!path)
4526
+ return null;
4527
+ const ext = extname(path).slice(1);
4528
+ const mime = ext === "jpg" ? "image/jpeg" : ext === "svg" ? "image/svg+xml" : `image/${ext}`;
4529
+ const data = readFileSync2(path);
4530
+ return `data:${mime};base64,${data.toString("base64")}`;
4531
+ }
4532
+ function deleteImage(entityId) {
4533
+ ensureImagesDir();
4534
+ const files = readdirSync(IMAGES_DIR);
4535
+ let deleted = false;
4536
+ for (const f of files) {
4537
+ if (f.startsWith(`${entityId}.`)) {
4538
+ unlinkSync(join3(IMAGES_DIR, f));
4539
+ deleted = true;
4540
+ }
4541
+ }
4542
+ return deleted;
4543
+ }
4544
+ function listImages() {
4545
+ ensureImagesDir();
4546
+ const files = readdirSync(IMAGES_DIR).filter((f) => !f.startsWith("."));
4547
+ return files.map((f) => ({
4548
+ entity_id: basename(f, extname(f)),
4549
+ filename: f,
4550
+ path: join3(IMAGES_DIR, f)
4551
+ }));
4552
+ }
4553
+ var IMAGES_DIR;
4554
+ var init_images = __esm(() => {
4555
+ IMAGES_DIR = join3(process.env["HOME"] || "~", ".contacts", "images");
4556
+ });
4557
+
4387
4558
  // src/server/serve.ts
4388
4559
  var exports_serve = {};
4389
4560
  __export(exports_serve, {
4390
4561
  startServer: () => startServer
4391
4562
  });
4392
- import { existsSync as existsSync3 } from "fs";
4393
- import { join as join3 } from "path";
4563
+ import { existsSync as existsSync4 } from "fs";
4564
+ import { join as join4 } from "path";
4394
4565
  function json(data, status = 200) {
4395
4566
  return new Response(JSON.stringify(data), {
4396
4567
  status,
@@ -4595,8 +4766,60 @@ async function handleExport(req) {
4595
4766
  }
4596
4767
  });
4597
4768
  }
4769
+ async function handleImages(req, _url, segments) {
4770
+ const entityId = segments[2];
4771
+ if (!entityId)
4772
+ return apiError("Entity ID required");
4773
+ if (req.method === "GET") {
4774
+ const imagePath = getImagePath(entityId);
4775
+ if (!imagePath || !existsSync4(imagePath)) {
4776
+ return new Response(null, { status: 404, headers: { "Content-Type": "text/plain" } });
4777
+ }
4778
+ return new Response(Bun.file(imagePath), {
4779
+ headers: { "Cache-Control": "public, max-age=3600" }
4780
+ });
4781
+ }
4782
+ if (req.method === "POST") {
4783
+ const contentType = req.headers.get("content-type") || "";
4784
+ if (contentType.includes("multipart/form-data")) {
4785
+ const formData = await req.formData();
4786
+ const file = formData.get("image");
4787
+ if (!file)
4788
+ return apiError("No image file in form data");
4789
+ const ext = file.name?.split(".").pop() || "jpg";
4790
+ const buffer = Buffer.from(await file.arrayBuffer());
4791
+ const tmpPath = join4(getImagesDir(), `_upload_${entityId}.${ext}`);
4792
+ const { writeFileSync: wfs } = await import("fs");
4793
+ wfs(tmpPath, buffer);
4794
+ try {
4795
+ const filename = saveImage(entityId, tmpPath);
4796
+ const { unlinkSync: unlinkSync2 } = await import("fs");
4797
+ try {
4798
+ unlinkSync2(tmpPath);
4799
+ } catch {}
4800
+ return json({ ok: true, entity_id: entityId, filename });
4801
+ } catch (e) {
4802
+ return apiError(e instanceof Error ? e.message : "Upload failed");
4803
+ }
4804
+ }
4805
+ const body = await parseJson(req);
4806
+ if (!body?.image)
4807
+ return apiError("Provide image as base64 string or file upload");
4808
+ try {
4809
+ const filename = saveImage(entityId, body.image, { format: body.format });
4810
+ return json({ ok: true, entity_id: entityId, filename });
4811
+ } catch (e) {
4812
+ return apiError(e instanceof Error ? e.message : "Upload failed");
4813
+ }
4814
+ }
4815
+ if (req.method === "DELETE") {
4816
+ const deleted = deleteImage(entityId);
4817
+ return json({ ok: true, deleted });
4818
+ }
4819
+ return apiError("Method not allowed", 405);
4820
+ }
4598
4821
  function serveStaticFile(filePath) {
4599
- if (!existsSync3(filePath))
4822
+ if (!existsSync4(filePath))
4600
4823
  return null;
4601
4824
  return new Response(Bun.file(filePath));
4602
4825
  }
@@ -4636,12 +4859,15 @@ function startServer(port) {
4636
4859
  case "export":
4637
4860
  response = req.method === "GET" ? await handleExport(req) : apiError("Method not allowed", 405);
4638
4861
  break;
4862
+ case "images":
4863
+ response = await handleImages(req, url, segments);
4864
+ break;
4639
4865
  default:
4640
4866
  response = apiError("Not found", 404);
4641
4867
  }
4642
4868
  } else {
4643
- const filePath = join3(DASHBOARD_DIST, url.pathname === "/" ? "index.html" : url.pathname);
4644
- response = serveStaticFile(filePath) ?? serveStaticFile(join3(DASHBOARD_DIST, "index.html")) ?? new Response("Not Found", { status: 404 });
4869
+ const filePath = join4(DASHBOARD_DIST, url.pathname === "/" ? "index.html" : url.pathname);
4870
+ response = serveStaticFile(filePath) ?? serveStaticFile(join4(DASHBOARD_DIST, "index.html")) ?? new Response("Not Found", { status: 404 });
4645
4871
  }
4646
4872
  } catch (err) {
4647
4873
  console.error("Request error:", err);
@@ -4662,7 +4888,8 @@ var init_serve = __esm(() => {
4662
4888
  init_contacts();
4663
4889
  init_companies();
4664
4890
  init_tags();
4665
- DASHBOARD_DIST = join3(import.meta.dir, "../../dashboard/dist");
4891
+ init_images();
4892
+ DASHBOARD_DIST = join4(import.meta.dir, "../../dashboard/dist");
4666
4893
  });
4667
4894
 
4668
4895
  // src/db/notes.ts
@@ -6074,8 +6301,8 @@ function readConfig() {
6074
6301
  }
6075
6302
 
6076
6303
  // src/cli/index.tsx
6077
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync4, copyFileSync, statSync, mkdirSync as mkdirSync3, readdirSync } from "fs";
6078
- import { extname, join as join4 } from "path";
6304
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync5, copyFileSync as copyFileSync2, statSync, mkdirSync as mkdirSync4, readdirSync as readdirSync2 } from "fs";
6305
+ import { extname as extname2, join as join5 } from "path";
6079
6306
  function renderTable(headers, rows) {
6080
6307
  const colWidths = headers.map((h) => h.length);
6081
6308
  for (const row of rows) {
@@ -6486,13 +6713,13 @@ Add New Tag
6486
6713
  `));
6487
6714
  });
6488
6715
  program.command("import <file>").description("Import contacts from CSV, vCard (.vcf), or JSON file").action(async (file) => {
6489
- if (!existsSync4(file)) {
6716
+ if (!existsSync5(file)) {
6490
6717
  console.error(chalk.red(`
6491
6718
  File not found: ${file}
6492
6719
  `));
6493
6720
  process.exit(1);
6494
6721
  }
6495
- const ext = extname(file).toLowerCase();
6722
+ const ext = extname2(file).toLowerCase();
6496
6723
  const formatMap = {
6497
6724
  ".csv": "csv",
6498
6725
  ".vcf": "vcf",
@@ -6506,7 +6733,7 @@ Unsupported file type: ${ext}. Use .csv, .vcf, or .json
6506
6733
  `));
6507
6734
  process.exit(1);
6508
6735
  }
6509
- const data = readFileSync2(file, "utf8");
6736
+ const data = readFileSync3(file, "utf8");
6510
6737
  console.log(chalk.blue(`
6511
6738
  Importing ${format.toUpperCase()} from ${file}...
6512
6739
  `));
@@ -6537,7 +6764,7 @@ Invalid format: ${format}. Use csv, vcf, or json
6537
6764
  const { contacts } = listContacts({ limit: 1e5 });
6538
6765
  const output = await exportContacts(format, contacts);
6539
6766
  if (opts.output) {
6540
- writeFileSync2(opts.output, output, "utf8");
6767
+ writeFileSync3(opts.output, output, "utf8");
6541
6768
  console.log(chalk.green(`
6542
6769
  \u2713 Exported ${contacts.length} contact(s) to ${opts.output}
6543
6770
  `));
@@ -6809,15 +7036,15 @@ program.command("init").description("Show setup info, stats, and configuration")
6809
7036
  console.log();
6810
7037
  });
6811
7038
  program.command("backup").description("Backup the contacts database").option("--output <path>", "Output path").option("--list", "List existing backups").action((opts) => {
6812
- const backupDir = join4(process.env["HOME"] || "~", ".contacts", "backups");
7039
+ const backupDir = join5(process.env["HOME"] || "~", ".contacts", "backups");
6813
7040
  if (opts.list) {
6814
- if (!existsSync4(backupDir)) {
7041
+ if (!existsSync5(backupDir)) {
6815
7042
  console.log(chalk.gray(`
6816
7043
  No backups found.
6817
7044
  `));
6818
7045
  return;
6819
7046
  }
6820
- const files = readdirSync(backupDir).filter((f) => f.endsWith(".db")).sort().reverse();
7047
+ const files = readdirSync2(backupDir).filter((f) => f.endsWith(".db")).sort().reverse();
6821
7048
  if (files.length === 0) {
6822
7049
  console.log(chalk.gray(`
6823
7050
  No backups found.
@@ -6828,7 +7055,7 @@ No backups found.
6828
7055
  Existing Backups:
6829
7056
  `));
6830
7057
  for (const f of files) {
6831
- const filePath = join4(backupDir, f);
7058
+ const filePath = join5(backupDir, f);
6832
7059
  const size2 = statSync(filePath).size;
6833
7060
  const mtime = statSync(filePath).mtime.toISOString().slice(0, 19).replace("T", " ");
6834
7061
  console.log(` ${chalk.cyan(f)} ${chalk.gray(`${(size2 / 1024).toFixed(1)} KB ${mtime}`)}`);
@@ -6837,16 +7064,16 @@ Existing Backups:
6837
7064
  return;
6838
7065
  }
6839
7066
  const src = getDbPath();
6840
- if (!existsSync4(src)) {
7067
+ if (!existsSync5(src)) {
6841
7068
  console.error(chalk.red(`
6842
7069
  Database not found: ${src}
6843
7070
  `));
6844
7071
  process.exit(1);
6845
7072
  }
6846
- mkdirSync3(backupDir, { recursive: true });
7073
+ mkdirSync4(backupDir, { recursive: true });
6847
7074
  const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
6848
- const dest = opts.output || join4(backupDir, `contacts-${ts}.db`);
6849
- copyFileSync(src, dest);
7075
+ const dest = opts.output || join5(backupDir, `contacts-${ts}.db`);
7076
+ copyFileSync2(src, dest);
6850
7077
  const size = statSync(dest).size;
6851
7078
  console.log(chalk.green(`
6852
7079
  \u2713 Backed up to ${dest} (${(size / 1024).toFixed(1)} KB)
@@ -8100,4 +8327,84 @@ dealsTeamCmd.command("assign <deal-id> <contact-id>").description("Assign a cont
8100
8327
  \u2713 ${contact.display_name} assigned as ${opts.role || "other"} in deal ${dealId}
8101
8328
  `));
8102
8329
  });
8330
+ var photoCmd = program.command("photo").description("Manage contact profile photos");
8331
+ photoCmd.command("set <contact-id> <image-path>").description("Set a contact's profile photo from a local file").action((contactId, imagePath) => {
8332
+ const { saveImage: saveImage2 } = (init_images(), __toCommonJS(exports_images));
8333
+ try {
8334
+ const contact = getContact(contactId);
8335
+ const filename = saveImage2(contactId, imagePath);
8336
+ updateContact(contactId, { avatar_url: `~/.contacts/images/${filename}` });
8337
+ console.log(chalk.green(`Photo set for ${contact.display_name}: ~/.contacts/images/${filename}`));
8338
+ } catch (e) {
8339
+ console.error(chalk.red(e instanceof Error ? e.message : String(e)));
8340
+ }
8341
+ });
8342
+ photoCmd.command("show <contact-id>").description("Show the path to a contact's profile photo").action((contactId) => {
8343
+ const { getImagePath: getImagePath2 } = (init_images(), __toCommonJS(exports_images));
8344
+ const contact = getContact(contactId);
8345
+ const path = getImagePath2(contactId);
8346
+ if (path) {
8347
+ console.log(`${chalk.bold(contact.display_name)}: ${chalk.cyan(path)}`);
8348
+ } else {
8349
+ console.log(chalk.yellow(`No photo set for ${contact.display_name}`));
8350
+ }
8351
+ });
8352
+ photoCmd.command("remove <contact-id>").description("Remove a contact's profile photo").action((contactId) => {
8353
+ const { deleteImage: deleteImage2 } = (init_images(), __toCommonJS(exports_images));
8354
+ const contact = getContact(contactId);
8355
+ const deleted = deleteImage2(contactId);
8356
+ if (deleted) {
8357
+ updateContact(contactId, { avatar_url: null });
8358
+ console.log(chalk.green(`Photo removed for ${contact.display_name}`));
8359
+ } else {
8360
+ console.log(chalk.yellow(`No photo found for ${contact.display_name}`));
8361
+ }
8362
+ });
8363
+ photoCmd.command("list").description("List all stored photos").action(() => {
8364
+ const { listImages: listImages2 } = (init_images(), __toCommonJS(exports_images));
8365
+ const images = listImages2();
8366
+ if (!images.length) {
8367
+ console.log(chalk.yellow("No photos stored"));
8368
+ return;
8369
+ }
8370
+ for (const img of images) {
8371
+ try {
8372
+ const contact = getContact(img.entity_id);
8373
+ console.log(`${chalk.bold(contact.display_name)}: ${chalk.cyan(img.path)}`);
8374
+ } catch {
8375
+ console.log(`${chalk.gray(img.entity_id)}: ${chalk.cyan(img.path)}`);
8376
+ }
8377
+ }
8378
+ });
8379
+ var logoCmd = program.command("logo").description("Manage company logos");
8380
+ logoCmd.command("set <company-id> <image-path>").description("Set a company's logo from a local file").action((companyId, imagePath) => {
8381
+ const { saveImage: saveImage2 } = (init_images(), __toCommonJS(exports_images));
8382
+ try {
8383
+ const company = getCompany(companyId);
8384
+ if (!company) {
8385
+ console.error(chalk.red("Company not found"));
8386
+ return;
8387
+ }
8388
+ const filename = saveImage2(companyId, imagePath);
8389
+ const { updateCompany: updateCompany2 } = (init_companies(), __toCommonJS(exports_companies));
8390
+ updateCompany2(companyId, { logo_url: `~/.contacts/images/${filename}` });
8391
+ console.log(chalk.green(`Logo set for ${company.name}: ~/.contacts/images/${filename}`));
8392
+ } catch (e) {
8393
+ console.error(chalk.red(e instanceof Error ? e.message : String(e)));
8394
+ }
8395
+ });
8396
+ logoCmd.command("show <company-id>").description("Show the path to a company's logo").action((companyId) => {
8397
+ const { getImagePath: getImagePath2 } = (init_images(), __toCommonJS(exports_images));
8398
+ const company = getCompany(companyId);
8399
+ if (!company) {
8400
+ console.error(chalk.red("Company not found"));
8401
+ return;
8402
+ }
8403
+ const path = getImagePath2(companyId);
8404
+ if (path) {
8405
+ console.log(`${chalk.bold(company.name)}: ${chalk.cyan(path)}`);
8406
+ } else {
8407
+ console.log(chalk.yellow(`No logo set for ${company.name}`));
8408
+ }
8409
+ });
8103
8410
  program.parse(process.argv);
package/dist/index.d.ts CHANGED
@@ -61,4 +61,5 @@ export { parseEmailSignature, extractContactsFromEmailThread } from "./lib/signa
61
61
  export type { ParsedSignature } from "./lib/signature-parser.js";
62
62
  export { ingestMeetingParticipants } from "./lib/meeting-capture.js";
63
63
  export { findOrCreateContact } from "./db/contacts.js";
64
+ export { saveImage, getImagePath, getImageAsBase64, deleteImage, listImages, getImagesDir } from "./lib/images.js";
64
65
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -4376,6 +4376,85 @@ async function ingestMeetingParticipants(event, db) {
4376
4376
 
4377
4377
  // src/index.ts
4378
4378
  init_contacts();
4379
+
4380
+ // src/lib/images.ts
4381
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, copyFileSync, unlinkSync, readdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
4382
+ import { join as join3, extname, basename } from "path";
4383
+ var IMAGES_DIR = join3(process.env["HOME"] || "~", ".contacts", "images");
4384
+ function ensureImagesDir() {
4385
+ if (!existsSync3(IMAGES_DIR))
4386
+ mkdirSync2(IMAGES_DIR, { recursive: true });
4387
+ }
4388
+ function getImagesDir() {
4389
+ ensureImagesDir();
4390
+ return IMAGES_DIR;
4391
+ }
4392
+ function saveImage(entityId, source, options) {
4393
+ ensureImagesDir();
4394
+ deleteImage(entityId);
4395
+ const base64Match = source.match(/^data:image\/(\w+);base64,(.+)$/s);
4396
+ if (base64Match) {
4397
+ const ext2 = base64Match[1] === "jpeg" ? "jpg" : base64Match[1];
4398
+ const data = Buffer.from(base64Match[2], "base64");
4399
+ const filename2 = `${entityId}.${ext2}`;
4400
+ writeFileSync(join3(IMAGES_DIR, filename2), data);
4401
+ return filename2;
4402
+ }
4403
+ if (!existsSync3(source) && /^[A-Za-z0-9+/=\n\r]+$/.test(source.trim()) && source.length > 100) {
4404
+ const ext2 = options?.format || "jpg";
4405
+ const data = Buffer.from(source.trim(), "base64");
4406
+ const filename2 = `${entityId}.${ext2}`;
4407
+ writeFileSync(join3(IMAGES_DIR, filename2), data);
4408
+ return filename2;
4409
+ }
4410
+ if (!existsSync3(source)) {
4411
+ throw new Error(`Image file not found: ${source}`);
4412
+ }
4413
+ const ext = extname(source).slice(1).toLowerCase() || "jpg";
4414
+ const validExts = ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif", "ico"];
4415
+ if (!validExts.includes(ext)) {
4416
+ throw new Error(`Unsupported image format: ${ext}. Supported: ${validExts.join(", ")}`);
4417
+ }
4418
+ const filename = `${entityId}.${ext === "jpeg" ? "jpg" : ext}`;
4419
+ copyFileSync(source, join3(IMAGES_DIR, filename));
4420
+ return filename;
4421
+ }
4422
+ function getImagePath(entityId) {
4423
+ ensureImagesDir();
4424
+ const files = readdirSync(IMAGES_DIR);
4425
+ const match = files.find((f) => f.startsWith(`${entityId}.`));
4426
+ return match ? join3(IMAGES_DIR, match) : null;
4427
+ }
4428
+ function getImageAsBase64(entityId) {
4429
+ const path = getImagePath(entityId);
4430
+ if (!path)
4431
+ return null;
4432
+ const ext = extname(path).slice(1);
4433
+ const mime = ext === "jpg" ? "image/jpeg" : ext === "svg" ? "image/svg+xml" : `image/${ext}`;
4434
+ const data = readFileSync2(path);
4435
+ return `data:${mime};base64,${data.toString("base64")}`;
4436
+ }
4437
+ function deleteImage(entityId) {
4438
+ ensureImagesDir();
4439
+ const files = readdirSync(IMAGES_DIR);
4440
+ let deleted = false;
4441
+ for (const f of files) {
4442
+ if (f.startsWith(`${entityId}.`)) {
4443
+ unlinkSync(join3(IMAGES_DIR, f));
4444
+ deleted = true;
4445
+ }
4446
+ }
4447
+ return deleted;
4448
+ }
4449
+ function listImages() {
4450
+ ensureImagesDir();
4451
+ const files = readdirSync(IMAGES_DIR).filter((f) => !f.startsWith("."));
4452
+ return files.map((f) => ({
4453
+ entity_id: basename(f, extname(f)),
4454
+ filename: f,
4455
+ path: join3(IMAGES_DIR, f)
4456
+ }));
4457
+ }
4379
4458
  export {
4380
4459
  updateVendorCommunication,
4381
4460
  updateTag,
@@ -4395,6 +4474,7 @@ export {
4395
4474
  searchContacts,
4396
4475
  searchCompanies,
4397
4476
  saveLearning,
4477
+ saveImage,
4398
4478
  runConnector,
4399
4479
  resolveIdentity,
4400
4480
  resolveByPartial,
@@ -4435,6 +4515,7 @@ export {
4435
4515
  listNotesForContactAtCompany,
4436
4516
  listNotes,
4437
4517
  listMissingInvoices,
4518
+ listImages,
4438
4519
  listGroupsForContact,
4439
4520
  listGroupsForCompany,
4440
4521
  listGroups,
@@ -4472,6 +4553,9 @@ export {
4472
4553
  getNetworkStats,
4473
4554
  getLearnings,
4474
4555
  getJobHistory,
4556
+ getImagesDir,
4557
+ getImagePath,
4558
+ getImageAsBase64,
4475
4559
  getIdentities,
4476
4560
  getGroup,
4477
4561
  getGhostContacts,
@@ -4514,6 +4598,7 @@ export {
4514
4598
  deleteRelationship,
4515
4599
  deleteNote,
4516
4600
  deleteLearning,
4601
+ deleteImage,
4517
4602
  deleteGroup,
4518
4603
  deleteEvent,
4519
4604
  deleteDeal,
@@ -0,0 +1,30 @@
1
+ export declare function getImagesDir(): string;
2
+ /**
3
+ * Save an image for a contact or company.
4
+ * Accepts a file path (copies it) or base64 data (writes it).
5
+ * Returns the stored filename (e.g. "abc123.jpg")
6
+ */
7
+ export declare function saveImage(entityId: string, source: string, options?: {
8
+ format?: string;
9
+ }): string;
10
+ /**
11
+ * Get the image path for an entity. Returns null if no image exists.
12
+ */
13
+ export declare function getImagePath(entityId: string): string | null;
14
+ /**
15
+ * Get image as base64 data URI for embedding in responses.
16
+ */
17
+ export declare function getImageAsBase64(entityId: string): string | null;
18
+ /**
19
+ * Delete an entity's image.
20
+ */
21
+ export declare function deleteImage(entityId: string): boolean;
22
+ /**
23
+ * List all stored images.
24
+ */
25
+ export declare function listImages(): Array<{
26
+ entity_id: string;
27
+ filename: string;
28
+ path: string;
29
+ }>;
30
+ //# sourceMappingURL=images.d.ts.map
@@ -0,0 +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"}
package/dist/mcp/index.js CHANGED
@@ -4205,6 +4205,72 @@ function getCoverageGaps(companyId, db) {
4205
4205
  };
4206
4206
  }
4207
4207
 
4208
+ // src/lib/images.ts
4209
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, copyFileSync, unlinkSync, readdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
4210
+ import { join as join3, extname, basename } from "path";
4211
+ var IMAGES_DIR = join3(process.env["HOME"] || "~", ".contacts", "images");
4212
+ function ensureImagesDir() {
4213
+ if (!existsSync3(IMAGES_DIR))
4214
+ mkdirSync2(IMAGES_DIR, { recursive: true });
4215
+ }
4216
+ function saveImage(entityId, source, options) {
4217
+ ensureImagesDir();
4218
+ deleteImage(entityId);
4219
+ const base64Match = source.match(/^data:image\/(\w+);base64,(.+)$/s);
4220
+ if (base64Match) {
4221
+ const ext2 = base64Match[1] === "jpeg" ? "jpg" : base64Match[1];
4222
+ const data = Buffer.from(base64Match[2], "base64");
4223
+ const filename2 = `${entityId}.${ext2}`;
4224
+ writeFileSync(join3(IMAGES_DIR, filename2), data);
4225
+ return filename2;
4226
+ }
4227
+ if (!existsSync3(source) && /^[A-Za-z0-9+/=\n\r]+$/.test(source.trim()) && source.length > 100) {
4228
+ const ext2 = options?.format || "jpg";
4229
+ const data = Buffer.from(source.trim(), "base64");
4230
+ const filename2 = `${entityId}.${ext2}`;
4231
+ writeFileSync(join3(IMAGES_DIR, filename2), data);
4232
+ return filename2;
4233
+ }
4234
+ if (!existsSync3(source)) {
4235
+ throw new Error(`Image file not found: ${source}`);
4236
+ }
4237
+ const ext = extname(source).slice(1).toLowerCase() || "jpg";
4238
+ const validExts = ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif", "ico"];
4239
+ if (!validExts.includes(ext)) {
4240
+ throw new Error(`Unsupported image format: ${ext}. Supported: ${validExts.join(", ")}`);
4241
+ }
4242
+ const filename = `${entityId}.${ext === "jpeg" ? "jpg" : ext}`;
4243
+ copyFileSync(source, join3(IMAGES_DIR, filename));
4244
+ return filename;
4245
+ }
4246
+ function getImagePath(entityId) {
4247
+ ensureImagesDir();
4248
+ const files = readdirSync(IMAGES_DIR);
4249
+ const match = files.find((f) => f.startsWith(`${entityId}.`));
4250
+ return match ? join3(IMAGES_DIR, match) : null;
4251
+ }
4252
+ function getImageAsBase64(entityId) {
4253
+ const path = getImagePath(entityId);
4254
+ if (!path)
4255
+ return null;
4256
+ const ext = extname(path).slice(1);
4257
+ const mime = ext === "jpg" ? "image/jpeg" : ext === "svg" ? "image/svg+xml" : `image/${ext}`;
4258
+ const data = readFileSync2(path);
4259
+ return `data:${mime};base64,${data.toString("base64")}`;
4260
+ }
4261
+ function deleteImage(entityId) {
4262
+ ensureImagesDir();
4263
+ const files = readdirSync(IMAGES_DIR);
4264
+ let deleted = false;
4265
+ for (const f of files) {
4266
+ if (f.startsWith(`${entityId}.`)) {
4267
+ unlinkSync(join3(IMAGES_DIR, f));
4268
+ deleted = true;
4269
+ }
4270
+ }
4271
+ return deleted;
4272
+ }
4273
+
4208
4274
  // src/mcp/index.ts
4209
4275
  var server = new Server({ name: "contacts", version: "0.1.0" }, { capabilities: { tools: {} } });
4210
4276
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
@@ -5665,7 +5731,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
5665
5731
  { name: "set_deal_contact_role", description: "Assign a contact a buying committee role in a deal (economic_buyer, technical_evaluator, champion, blocker, influencer, user, sponsor, other).", inputSchema: { type: "object", properties: { deal_id: { type: "string" }, contact_id: { type: "string" }, account_role: { type: "string", enum: ["economic_buyer", "technical_evaluator", "champion", "blocker", "influencer", "user", "sponsor", "other"] } }, required: ["deal_id", "contact_id", "account_role"] } },
5666
5732
  { 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"] } },
5667
5733
  { 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"] } },
5668
- { 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" } } } } }
5734
+ { 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" } } } } },
5735
+ { 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"] } },
5736
+ { 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"] } },
5737
+ { name: "delete_contact_photo", description: "Remove a contact's profile photo.", inputSchema: { type: "object", properties: { contact_id: { type: "string" } }, required: ["contact_id"] } },
5738
+ { 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"] } },
5739
+ { 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"] } },
5740
+ { name: "delete_company_logo", description: "Remove a company's logo image.", inputSchema: { type: "object", properties: { company_id: { type: "string" } }, required: ["company_id"] } }
5669
5741
  ]
5670
5742
  }));
5671
5743
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -7156,6 +7228,48 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
7156
7228
  const events = db.query(sql).all(...params);
7157
7229
  return { content: [{ type: "text", text: JSON.stringify({ events }, null, 2) }] };
7158
7230
  }
7231
+ case "set_contact_photo": {
7232
+ const { contact_id, image, format } = a;
7233
+ const contact = getContact(contact_id);
7234
+ const filename = saveImage(contact_id, image, { format });
7235
+ updateContact(contact_id, { avatar_url: `~/.contacts/images/${filename}` });
7236
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true, contact_id, filename, avatar_url: `~/.contacts/images/${filename}` }) }] };
7237
+ }
7238
+ case "get_contact_photo": {
7239
+ const { contact_id } = a;
7240
+ const dataUri = getImageAsBase64(contact_id);
7241
+ if (!dataUri)
7242
+ return { content: [{ type: "text", text: JSON.stringify({ contact_id, has_photo: false, data: null }) }] };
7243
+ return { content: [{ type: "text", text: JSON.stringify({ contact_id, has_photo: true, data: dataUri }) }] };
7244
+ }
7245
+ case "delete_contact_photo": {
7246
+ const { contact_id } = a;
7247
+ const deleted = deleteImage(contact_id);
7248
+ if (deleted)
7249
+ updateContact(contact_id, { avatar_url: null });
7250
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true, deleted }) }] };
7251
+ }
7252
+ case "set_company_logo": {
7253
+ const { company_id, image, format } = a;
7254
+ const co = getCompany(company_id);
7255
+ const filename = saveImage(company_id, image, { format });
7256
+ updateCompany(company_id, { logo_url: `~/.contacts/images/${filename}` });
7257
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true, company_id, filename, logo_url: `~/.contacts/images/${filename}` }) }] };
7258
+ }
7259
+ case "get_company_logo": {
7260
+ const { company_id } = a;
7261
+ const dataUri = getImageAsBase64(company_id);
7262
+ if (!dataUri)
7263
+ return { content: [{ type: "text", text: JSON.stringify({ company_id, has_logo: false, data: null }) }] };
7264
+ return { content: [{ type: "text", text: JSON.stringify({ company_id, has_logo: true, data: dataUri }) }] };
7265
+ }
7266
+ case "delete_company_logo": {
7267
+ const { company_id } = a;
7268
+ const deleted = deleteImage(company_id);
7269
+ if (deleted)
7270
+ updateCompany(company_id, { logo_url: null });
7271
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true, deleted }) }] };
7272
+ }
7159
7273
  default:
7160
7274
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
7161
7275
  }
@@ -1,9 +1,26 @@
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
+ var __require = import.meta.require;
3
20
 
4
21
  // src/server/serve.ts
5
- import { existsSync as existsSync2 } from "fs";
6
- import { join as join2 } from "path";
22
+ import { existsSync as existsSync3 } from "fs";
23
+ import { join as join3 } from "path";
7
24
 
8
25
  // src/db/database.ts
9
26
  import { Database } from "bun:sqlite";
@@ -1706,8 +1723,69 @@ async function exportContacts(format, contacts) {
1706
1723
  }
1707
1724
  }
1708
1725
 
1726
+ // src/lib/images.ts
1727
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, copyFileSync, unlinkSync, readdirSync, readFileSync, writeFileSync } from "fs";
1728
+ import { join as join2, extname, basename } from "path";
1729
+ var IMAGES_DIR = join2(process.env["HOME"] || "~", ".contacts", "images");
1730
+ function ensureImagesDir() {
1731
+ if (!existsSync2(IMAGES_DIR))
1732
+ mkdirSync2(IMAGES_DIR, { recursive: true });
1733
+ }
1734
+ function getImagesDir() {
1735
+ ensureImagesDir();
1736
+ return IMAGES_DIR;
1737
+ }
1738
+ function saveImage(entityId, source, options) {
1739
+ ensureImagesDir();
1740
+ deleteImage(entityId);
1741
+ const base64Match = source.match(/^data:image\/(\w+);base64,(.+)$/s);
1742
+ if (base64Match) {
1743
+ const ext2 = base64Match[1] === "jpeg" ? "jpg" : base64Match[1];
1744
+ const data = Buffer.from(base64Match[2], "base64");
1745
+ const filename2 = `${entityId}.${ext2}`;
1746
+ writeFileSync(join2(IMAGES_DIR, filename2), data);
1747
+ return filename2;
1748
+ }
1749
+ if (!existsSync2(source) && /^[A-Za-z0-9+/=\n\r]+$/.test(source.trim()) && source.length > 100) {
1750
+ const ext2 = options?.format || "jpg";
1751
+ const data = Buffer.from(source.trim(), "base64");
1752
+ const filename2 = `${entityId}.${ext2}`;
1753
+ writeFileSync(join2(IMAGES_DIR, filename2), data);
1754
+ return filename2;
1755
+ }
1756
+ if (!existsSync2(source)) {
1757
+ throw new Error(`Image file not found: ${source}`);
1758
+ }
1759
+ const ext = extname(source).slice(1).toLowerCase() || "jpg";
1760
+ const validExts = ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif", "ico"];
1761
+ if (!validExts.includes(ext)) {
1762
+ throw new Error(`Unsupported image format: ${ext}. Supported: ${validExts.join(", ")}`);
1763
+ }
1764
+ const filename = `${entityId}.${ext === "jpeg" ? "jpg" : ext}`;
1765
+ copyFileSync(source, join2(IMAGES_DIR, filename));
1766
+ return filename;
1767
+ }
1768
+ function getImagePath(entityId) {
1769
+ ensureImagesDir();
1770
+ const files = readdirSync(IMAGES_DIR);
1771
+ const match = files.find((f) => f.startsWith(`${entityId}.`));
1772
+ return match ? join2(IMAGES_DIR, match) : null;
1773
+ }
1774
+ function deleteImage(entityId) {
1775
+ ensureImagesDir();
1776
+ const files = readdirSync(IMAGES_DIR);
1777
+ let deleted = false;
1778
+ for (const f of files) {
1779
+ if (f.startsWith(`${entityId}.`)) {
1780
+ unlinkSync(join2(IMAGES_DIR, f));
1781
+ deleted = true;
1782
+ }
1783
+ }
1784
+ return deleted;
1785
+ }
1786
+
1709
1787
  // src/server/serve.ts
1710
- var DASHBOARD_DIST = join2(import.meta.dir, "../../dashboard/dist");
1788
+ var DASHBOARD_DIST = join3(import.meta.dir, "../../dashboard/dist");
1711
1789
  function json(data, status = 200) {
1712
1790
  return new Response(JSON.stringify(data), {
1713
1791
  status,
@@ -1912,8 +1990,60 @@ async function handleExport(req) {
1912
1990
  }
1913
1991
  });
1914
1992
  }
1993
+ async function handleImages(req, _url, segments) {
1994
+ const entityId = segments[2];
1995
+ if (!entityId)
1996
+ return apiError("Entity ID required");
1997
+ if (req.method === "GET") {
1998
+ const imagePath = getImagePath(entityId);
1999
+ if (!imagePath || !existsSync3(imagePath)) {
2000
+ return new Response(null, { status: 404, headers: { "Content-Type": "text/plain" } });
2001
+ }
2002
+ return new Response(Bun.file(imagePath), {
2003
+ headers: { "Cache-Control": "public, max-age=3600" }
2004
+ });
2005
+ }
2006
+ if (req.method === "POST") {
2007
+ const contentType = req.headers.get("content-type") || "";
2008
+ if (contentType.includes("multipart/form-data")) {
2009
+ const formData = await req.formData();
2010
+ const file = formData.get("image");
2011
+ if (!file)
2012
+ return apiError("No image file in form data");
2013
+ const ext = file.name?.split(".").pop() || "jpg";
2014
+ const buffer = Buffer.from(await file.arrayBuffer());
2015
+ const tmpPath = join3(getImagesDir(), `_upload_${entityId}.${ext}`);
2016
+ const { writeFileSync: wfs } = await import("fs");
2017
+ wfs(tmpPath, buffer);
2018
+ try {
2019
+ const filename = saveImage(entityId, tmpPath);
2020
+ const { unlinkSync: unlinkSync2 } = await import("fs");
2021
+ try {
2022
+ unlinkSync2(tmpPath);
2023
+ } catch {}
2024
+ return json({ ok: true, entity_id: entityId, filename });
2025
+ } catch (e) {
2026
+ return apiError(e instanceof Error ? e.message : "Upload failed");
2027
+ }
2028
+ }
2029
+ const body = await parseJson(req);
2030
+ if (!body?.image)
2031
+ return apiError("Provide image as base64 string or file upload");
2032
+ try {
2033
+ const filename = saveImage(entityId, body.image, { format: body.format });
2034
+ return json({ ok: true, entity_id: entityId, filename });
2035
+ } catch (e) {
2036
+ return apiError(e instanceof Error ? e.message : "Upload failed");
2037
+ }
2038
+ }
2039
+ if (req.method === "DELETE") {
2040
+ const deleted = deleteImage(entityId);
2041
+ return json({ ok: true, deleted });
2042
+ }
2043
+ return apiError("Method not allowed", 405);
2044
+ }
1915
2045
  function serveStaticFile(filePath) {
1916
- if (!existsSync2(filePath))
2046
+ if (!existsSync3(filePath))
1917
2047
  return null;
1918
2048
  return new Response(Bun.file(filePath));
1919
2049
  }
@@ -1953,12 +2083,15 @@ function startServer(port) {
1953
2083
  case "export":
1954
2084
  response = req.method === "GET" ? await handleExport(req) : apiError("Method not allowed", 405);
1955
2085
  break;
2086
+ case "images":
2087
+ response = await handleImages(req, url, segments);
2088
+ break;
1956
2089
  default:
1957
2090
  response = apiError("Not found", 404);
1958
2091
  }
1959
2092
  } else {
1960
- const filePath = join2(DASHBOARD_DIST, url.pathname === "/" ? "index.html" : url.pathname);
1961
- response = serveStaticFile(filePath) ?? serveStaticFile(join2(DASHBOARD_DIST, "index.html")) ?? new Response("Not Found", { status: 404 });
2093
+ const filePath = join3(DASHBOARD_DIST, url.pathname === "/" ? "index.html" : url.pathname);
2094
+ response = serveStaticFile(filePath) ?? serveStaticFile(join3(DASHBOARD_DIST, "index.html")) ?? new Response("Not Found", { status: 404 });
1962
2095
  }
1963
2096
  } catch (err) {
1964
2097
  console.error("Request error:", err);
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/server/serve.ts"],"names":[],"mappings":"AAsRA,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAsE9C"}
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/server/serve.ts"],"names":[],"mappings":"AAoVA,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAyE9C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/contacts",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Contact management for AI coding agents — CLI + MCP + Web",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",