@hasna/contacts 0.5.1 → 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 +339 -30
- package/dist/db/freshness.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +97 -10
- package/dist/lib/images.d.ts +30 -0
- package/dist/lib/images.d.ts.map +1 -0
- package/dist/mcp/index.js +127 -11
- 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
|
|
@@ -5648,16 +5875,18 @@ function getFreshnessScore(contactId, db) {
|
|
|
5648
5875
|
}
|
|
5649
5876
|
function getStaleContacts(threshold = 40, db) {
|
|
5650
5877
|
const _db2 = db || getDatabase();
|
|
5651
|
-
const rows = _db2.query(`SELECT
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5878
|
+
const rows = _db2.query(`SELECT * FROM (
|
|
5879
|
+
SELECT c.id as contact_id, c.display_name,
|
|
5880
|
+
(CASE WHEN c.job_title IS NOT NULL THEN 15 ELSE 0 END +
|
|
5881
|
+
CASE WHEN c.company_id IS NOT NULL THEN 15 ELSE 0 END +
|
|
5882
|
+
CASE WHEN c.last_contacted_at IS NOT NULL THEN 20 ELSE 0 END +
|
|
5883
|
+
CASE WHEN EXISTS(SELECT 1 FROM emails WHERE contact_id=c.id) THEN 20 ELSE 0 END +
|
|
5884
|
+
CASE WHEN EXISTS(SELECT 1 FROM phones WHERE contact_id=c.id) THEN 15 ELSE 0 END +
|
|
5885
|
+
CASE WHEN c.notes IS NOT NULL THEN 10 ELSE 0 END +
|
|
5886
|
+
CASE WHEN EXISTS(SELECT 1 FROM contact_tags WHERE contact_id=c.id) THEN 5 ELSE 0 END
|
|
5887
|
+
) as score
|
|
5888
|
+
FROM contacts c WHERE c.archived=0
|
|
5889
|
+
) WHERE score < ? ORDER BY score ASC LIMIT 100`).all(threshold);
|
|
5661
5890
|
return rows;
|
|
5662
5891
|
}
|
|
5663
5892
|
function markFieldVerified(contactId, fieldName, source, db) {
|
|
@@ -6072,8 +6301,8 @@ function readConfig() {
|
|
|
6072
6301
|
}
|
|
6073
6302
|
|
|
6074
6303
|
// src/cli/index.tsx
|
|
6075
|
-
import { readFileSync as
|
|
6076
|
-
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";
|
|
6077
6306
|
function renderTable(headers, rows) {
|
|
6078
6307
|
const colWidths = headers.map((h) => h.length);
|
|
6079
6308
|
for (const row of rows) {
|
|
@@ -6484,13 +6713,13 @@ Add New Tag
|
|
|
6484
6713
|
`));
|
|
6485
6714
|
});
|
|
6486
6715
|
program.command("import <file>").description("Import contacts from CSV, vCard (.vcf), or JSON file").action(async (file) => {
|
|
6487
|
-
if (!
|
|
6716
|
+
if (!existsSync5(file)) {
|
|
6488
6717
|
console.error(chalk.red(`
|
|
6489
6718
|
File not found: ${file}
|
|
6490
6719
|
`));
|
|
6491
6720
|
process.exit(1);
|
|
6492
6721
|
}
|
|
6493
|
-
const ext =
|
|
6722
|
+
const ext = extname2(file).toLowerCase();
|
|
6494
6723
|
const formatMap = {
|
|
6495
6724
|
".csv": "csv",
|
|
6496
6725
|
".vcf": "vcf",
|
|
@@ -6504,7 +6733,7 @@ Unsupported file type: ${ext}. Use .csv, .vcf, or .json
|
|
|
6504
6733
|
`));
|
|
6505
6734
|
process.exit(1);
|
|
6506
6735
|
}
|
|
6507
|
-
const data =
|
|
6736
|
+
const data = readFileSync3(file, "utf8");
|
|
6508
6737
|
console.log(chalk.blue(`
|
|
6509
6738
|
Importing ${format.toUpperCase()} from ${file}...
|
|
6510
6739
|
`));
|
|
@@ -6535,7 +6764,7 @@ Invalid format: ${format}. Use csv, vcf, or json
|
|
|
6535
6764
|
const { contacts } = listContacts({ limit: 1e5 });
|
|
6536
6765
|
const output = await exportContacts(format, contacts);
|
|
6537
6766
|
if (opts.output) {
|
|
6538
|
-
|
|
6767
|
+
writeFileSync3(opts.output, output, "utf8");
|
|
6539
6768
|
console.log(chalk.green(`
|
|
6540
6769
|
\u2713 Exported ${contacts.length} contact(s) to ${opts.output}
|
|
6541
6770
|
`));
|
|
@@ -6807,15 +7036,15 @@ program.command("init").description("Show setup info, stats, and configuration")
|
|
|
6807
7036
|
console.log();
|
|
6808
7037
|
});
|
|
6809
7038
|
program.command("backup").description("Backup the contacts database").option("--output <path>", "Output path").option("--list", "List existing backups").action((opts) => {
|
|
6810
|
-
const backupDir =
|
|
7039
|
+
const backupDir = join5(process.env["HOME"] || "~", ".contacts", "backups");
|
|
6811
7040
|
if (opts.list) {
|
|
6812
|
-
if (!
|
|
7041
|
+
if (!existsSync5(backupDir)) {
|
|
6813
7042
|
console.log(chalk.gray(`
|
|
6814
7043
|
No backups found.
|
|
6815
7044
|
`));
|
|
6816
7045
|
return;
|
|
6817
7046
|
}
|
|
6818
|
-
const files =
|
|
7047
|
+
const files = readdirSync2(backupDir).filter((f) => f.endsWith(".db")).sort().reverse();
|
|
6819
7048
|
if (files.length === 0) {
|
|
6820
7049
|
console.log(chalk.gray(`
|
|
6821
7050
|
No backups found.
|
|
@@ -6826,7 +7055,7 @@ No backups found.
|
|
|
6826
7055
|
Existing Backups:
|
|
6827
7056
|
`));
|
|
6828
7057
|
for (const f of files) {
|
|
6829
|
-
const filePath =
|
|
7058
|
+
const filePath = join5(backupDir, f);
|
|
6830
7059
|
const size2 = statSync(filePath).size;
|
|
6831
7060
|
const mtime = statSync(filePath).mtime.toISOString().slice(0, 19).replace("T", " ");
|
|
6832
7061
|
console.log(` ${chalk.cyan(f)} ${chalk.gray(`${(size2 / 1024).toFixed(1)} KB ${mtime}`)}`);
|
|
@@ -6835,16 +7064,16 @@ Existing Backups:
|
|
|
6835
7064
|
return;
|
|
6836
7065
|
}
|
|
6837
7066
|
const src = getDbPath();
|
|
6838
|
-
if (!
|
|
7067
|
+
if (!existsSync5(src)) {
|
|
6839
7068
|
console.error(chalk.red(`
|
|
6840
7069
|
Database not found: ${src}
|
|
6841
7070
|
`));
|
|
6842
7071
|
process.exit(1);
|
|
6843
7072
|
}
|
|
6844
|
-
|
|
7073
|
+
mkdirSync4(backupDir, { recursive: true });
|
|
6845
7074
|
const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
6846
|
-
const dest = opts.output ||
|
|
6847
|
-
|
|
7075
|
+
const dest = opts.output || join5(backupDir, `contacts-${ts}.db`);
|
|
7076
|
+
copyFileSync2(src, dest);
|
|
6848
7077
|
const size = statSync(dest).size;
|
|
6849
7078
|
console.log(chalk.green(`
|
|
6850
7079
|
\u2713 Backed up to ${dest} (${(size / 1024).toFixed(1)} KB)
|
|
@@ -8098,4 +8327,84 @@ dealsTeamCmd.command("assign <deal-id> <contact-id>").description("Assign a cont
|
|
|
8098
8327
|
\u2713 ${contact.display_name} assigned as ${opts.role || "other"} in deal ${dealId}
|
|
8099
8328
|
`));
|
|
8100
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
|
+
});
|
|
8101
8410
|
program.parse(process.argv);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"freshness.d.ts","sourceRoot":"","sources":["../../src/db/freshness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAC;IACvE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAID,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,cAAc,CAoFlF;AAED,wBAAgB,gBAAgB,CAAC,SAAS,SAAK,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"freshness.d.ts","sourceRoot":"","sources":["../../src/db/freshness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAC;IACvE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAID,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,cAAc,CAoFlF;AAED,wBAAgB,gBAAgB,CAAC,SAAS,SAAK,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBlI;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAsB5G"}
|
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
|
@@ -4121,16 +4121,18 @@ function getFreshnessScore(contactId, db) {
|
|
|
4121
4121
|
}
|
|
4122
4122
|
function getStaleContacts(threshold = 40, db) {
|
|
4123
4123
|
const _db2 = db || getDatabase();
|
|
4124
|
-
const rows = _db2.query(`SELECT
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4124
|
+
const rows = _db2.query(`SELECT * FROM (
|
|
4125
|
+
SELECT c.id as contact_id, c.display_name,
|
|
4126
|
+
(CASE WHEN c.job_title IS NOT NULL THEN 15 ELSE 0 END +
|
|
4127
|
+
CASE WHEN c.company_id IS NOT NULL THEN 15 ELSE 0 END +
|
|
4128
|
+
CASE WHEN c.last_contacted_at IS NOT NULL THEN 20 ELSE 0 END +
|
|
4129
|
+
CASE WHEN EXISTS(SELECT 1 FROM emails WHERE contact_id=c.id) THEN 20 ELSE 0 END +
|
|
4130
|
+
CASE WHEN EXISTS(SELECT 1 FROM phones WHERE contact_id=c.id) THEN 15 ELSE 0 END +
|
|
4131
|
+
CASE WHEN c.notes IS NOT NULL THEN 10 ELSE 0 END +
|
|
4132
|
+
CASE WHEN EXISTS(SELECT 1 FROM contact_tags WHERE contact_id=c.id) THEN 5 ELSE 0 END
|
|
4133
|
+
) as score
|
|
4134
|
+
FROM contacts c WHERE c.archived=0
|
|
4135
|
+
) WHERE score < ? ORDER BY score ASC LIMIT 100`).all(threshold);
|
|
4134
4136
|
return rows;
|
|
4135
4137
|
}
|
|
4136
4138
|
function markFieldVerified(contactId, fieldName, source, db) {
|
|
@@ -4374,6 +4376,85 @@ async function ingestMeetingParticipants(event, db) {
|
|
|
4374
4376
|
|
|
4375
4377
|
// src/index.ts
|
|
4376
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
|
+
}
|
|
4377
4458
|
export {
|
|
4378
4459
|
updateVendorCommunication,
|
|
4379
4460
|
updateTag,
|
|
@@ -4393,6 +4474,7 @@ export {
|
|
|
4393
4474
|
searchContacts,
|
|
4394
4475
|
searchCompanies,
|
|
4395
4476
|
saveLearning,
|
|
4477
|
+
saveImage,
|
|
4396
4478
|
runConnector,
|
|
4397
4479
|
resolveIdentity,
|
|
4398
4480
|
resolveByPartial,
|
|
@@ -4433,6 +4515,7 @@ export {
|
|
|
4433
4515
|
listNotesForContactAtCompany,
|
|
4434
4516
|
listNotes,
|
|
4435
4517
|
listMissingInvoices,
|
|
4518
|
+
listImages,
|
|
4436
4519
|
listGroupsForContact,
|
|
4437
4520
|
listGroupsForCompany,
|
|
4438
4521
|
listGroups,
|
|
@@ -4470,6 +4553,9 @@ export {
|
|
|
4470
4553
|
getNetworkStats,
|
|
4471
4554
|
getLearnings,
|
|
4472
4555
|
getJobHistory,
|
|
4556
|
+
getImagesDir,
|
|
4557
|
+
getImagePath,
|
|
4558
|
+
getImageAsBase64,
|
|
4473
4559
|
getIdentities,
|
|
4474
4560
|
getGroup,
|
|
4475
4561
|
getGhostContacts,
|
|
@@ -4512,6 +4598,7 @@ export {
|
|
|
4512
4598
|
deleteRelationship,
|
|
4513
4599
|
deleteNote,
|
|
4514
4600
|
deleteLearning,
|
|
4601
|
+
deleteImage,
|
|
4515
4602
|
deleteGroup,
|
|
4516
4603
|
deleteEvent,
|
|
4517
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
|
@@ -4140,16 +4140,18 @@ function getFreshnessScore(contactId, db) {
|
|
|
4140
4140
|
}
|
|
4141
4141
|
function getStaleContacts(threshold = 40, db) {
|
|
4142
4142
|
const _db2 = db || getDatabase();
|
|
4143
|
-
const rows = _db2.query(`SELECT
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4143
|
+
const rows = _db2.query(`SELECT * FROM (
|
|
4144
|
+
SELECT c.id as contact_id, c.display_name,
|
|
4145
|
+
(CASE WHEN c.job_title IS NOT NULL THEN 15 ELSE 0 END +
|
|
4146
|
+
CASE WHEN c.company_id IS NOT NULL THEN 15 ELSE 0 END +
|
|
4147
|
+
CASE WHEN c.last_contacted_at IS NOT NULL THEN 20 ELSE 0 END +
|
|
4148
|
+
CASE WHEN EXISTS(SELECT 1 FROM emails WHERE contact_id=c.id) THEN 20 ELSE 0 END +
|
|
4149
|
+
CASE WHEN EXISTS(SELECT 1 FROM phones WHERE contact_id=c.id) THEN 15 ELSE 0 END +
|
|
4150
|
+
CASE WHEN c.notes IS NOT NULL THEN 10 ELSE 0 END +
|
|
4151
|
+
CASE WHEN EXISTS(SELECT 1 FROM contact_tags WHERE contact_id=c.id) THEN 5 ELSE 0 END
|
|
4152
|
+
) as score
|
|
4153
|
+
FROM contacts c WHERE c.archived=0
|
|
4154
|
+
) WHERE score < ? ORDER BY score ASC LIMIT 100`).all(threshold);
|
|
4153
4155
|
return rows;
|
|
4154
4156
|
}
|
|
4155
4157
|
function markFieldVerified(contactId, fieldName, source, db) {
|
|
@@ -4203,6 +4205,72 @@ function getCoverageGaps(companyId, db) {
|
|
|
4203
4205
|
};
|
|
4204
4206
|
}
|
|
4205
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
|
+
|
|
4206
4274
|
// src/mcp/index.ts
|
|
4207
4275
|
var server = new Server({ name: "contacts", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
4208
4276
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
@@ -5663,7 +5731,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
5663
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"] } },
|
|
5664
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"] } },
|
|
5665
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"] } },
|
|
5666
|
-
{ 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"] } }
|
|
5667
5741
|
]
|
|
5668
5742
|
}));
|
|
5669
5743
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -7154,6 +7228,48 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
7154
7228
|
const events = db.query(sql).all(...params);
|
|
7155
7229
|
return { content: [{ type: "text", text: JSON.stringify({ events }, null, 2) }] };
|
|
7156
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
|
+
}
|
|
7157
7273
|
default:
|
|
7158
7274
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
7159
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"}
|