@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 +327 -20
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +85 -0
- package/dist/lib/images.d.ts +30 -0
- package/dist/lib/images.d.ts.map +1 -0
- package/dist/mcp/index.js +115 -1
- package/dist/server/index.js +139 -6
- package/dist/server/serve.d.ts.map +1 -1
- package/package.json +1 -1
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
|
|
4393
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
4644
|
-
response = serveStaticFile(filePath) ?? serveStaticFile(
|
|
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
|
-
|
|
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
|
|
6078
|
-
import { extname, join as
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
7039
|
+
const backupDir = join5(process.env["HOME"] || "~", ".contacts", "backups");
|
|
6813
7040
|
if (opts.list) {
|
|
6814
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
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
|
-
|
|
7073
|
+
mkdirSync4(backupDir, { recursive: true });
|
|
6847
7074
|
const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
6848
|
-
const dest = opts.output ||
|
|
6849
|
-
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -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
|
|
6
|
-
import { join as
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
1961
|
-
response = serveStaticFile(filePath) ?? serveStaticFile(
|
|
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":"
|
|
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"}
|