@hasna/contacts 0.5.3 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -2647,6 +2647,41 @@ var init_database = __esm(() => {
2647
2647
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
2648
2648
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2649
2649
  );
2650
+ `,
2651
+ `
2652
+ ALTER TABLE contacts ADD COLUMN sensitivity TEXT NOT NULL DEFAULT 'normal' CHECK(sensitivity IN ('normal','confidential','restricted'));
2653
+
2654
+ CREATE TABLE IF NOT EXISTS contact_documents (
2655
+ id TEXT PRIMARY KEY,
2656
+ contact_id TEXT NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
2657
+ doc_type TEXT NOT NULL,
2658
+ label TEXT,
2659
+ encrypted_value TEXT NOT NULL,
2660
+ iv TEXT NOT NULL,
2661
+ encrypted_file_path TEXT,
2662
+ metadata TEXT NOT NULL DEFAULT '{}',
2663
+ expires_at TEXT,
2664
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2665
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2666
+ );
2667
+
2668
+ CREATE TABLE IF NOT EXISTS contact_health (
2669
+ id TEXT PRIMARY KEY,
2670
+ contact_id TEXT NOT NULL UNIQUE REFERENCES contacts(id) ON DELETE CASCADE,
2671
+ blood_type TEXT,
2672
+ allergies TEXT NOT NULL DEFAULT '[]',
2673
+ medical_conditions TEXT NOT NULL DEFAULT '[]',
2674
+ medications TEXT NOT NULL DEFAULT '[]',
2675
+ emergency_contacts TEXT NOT NULL DEFAULT '[]',
2676
+ health_insurance_provider TEXT,
2677
+ health_insurance_id TEXT,
2678
+ primary_physician TEXT,
2679
+ primary_physician_phone TEXT,
2680
+ organ_donor INTEGER NOT NULL DEFAULT 0,
2681
+ notes TEXT,
2682
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2683
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2684
+ );
2650
2685
  `
2651
2686
  ];
2652
2687
  });
@@ -2696,6 +2731,7 @@ function rowToContact(row) {
2696
2731
  follow_up_at: row.follow_up_at ?? null,
2697
2732
  archived: !!row.archived,
2698
2733
  project_id: row.project_id ?? null,
2734
+ sensitivity: row.sensitivity ?? "normal",
2699
2735
  do_not_contact: !!row.do_not_contact,
2700
2736
  priority: row.priority ?? 3,
2701
2737
  timezone: row.timezone ?? null
@@ -2783,8 +2819,8 @@ function createContact(input, db) {
2783
2819
  const firstName = input.first_name ?? "";
2784
2820
  const lastName = input.last_name ?? "";
2785
2821
  const displayName = input.display_name ?? (firstName || lastName ? `${firstName} ${lastName}`.trim() : "Unnamed Contact");
2786
- d.run(`INSERT INTO contacts (id, first_name, last_name, display_name, nickname, avatar_url, notes, birthday, company_id, job_title, source, custom_fields, last_contacted_at, website, preferred_contact_method, status, follow_up_at, project_id, do_not_contact, priority, timezone, created_at, updated_at)
2787
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
2822
+ d.run(`INSERT INTO contacts (id, first_name, last_name, display_name, nickname, avatar_url, notes, birthday, company_id, job_title, source, custom_fields, last_contacted_at, website, preferred_contact_method, status, follow_up_at, project_id, sensitivity, do_not_contact, priority, timezone, created_at, updated_at)
2823
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
2788
2824
  id,
2789
2825
  firstName,
2790
2826
  lastName,
@@ -2803,6 +2839,7 @@ function createContact(input, db) {
2803
2839
  input.status ?? "active",
2804
2840
  input.follow_up_at ?? null,
2805
2841
  input.project_id ?? null,
2842
+ input.sensitivity ?? "normal",
2806
2843
  input.do_not_contact ? 1 : 0,
2807
2844
  input.priority ?? 3,
2808
2845
  input.timezone ?? null,
@@ -2851,6 +2888,7 @@ function listContacts(opts = {}, db) {
2851
2888
  order_by = "display_name",
2852
2889
  order_dir = "asc",
2853
2890
  include_dnc = false,
2891
+ include_restricted = false,
2854
2892
  priority_min,
2855
2893
  updated_since
2856
2894
  } = opts;
@@ -2861,6 +2899,9 @@ function listContacts(opts = {}, db) {
2861
2899
  if (!include_dnc) {
2862
2900
  conditions.push("c.do_not_contact = 0");
2863
2901
  }
2902
+ if (!include_restricted) {
2903
+ conditions.push("c.sensitivity != 'restricted'");
2904
+ }
2864
2905
  if (company_id) {
2865
2906
  conditions.push("c.company_id = ?");
2866
2907
  params.push(company_id);
@@ -2989,6 +3030,10 @@ function updateContact(id, input, db) {
2989
3030
  setClauses.push("project_id = ?");
2990
3031
  params.push(input.project_id);
2991
3032
  }
3033
+ if (input.sensitivity !== undefined) {
3034
+ setClauses.push("sensitivity = ?");
3035
+ params.push(input.sensitivity);
3036
+ }
2992
3037
  if (input.do_not_contact !== undefined) {
2993
3038
  setClauses.push("do_not_contact = ?");
2994
3039
  params.push(input.do_not_contact ? 1 : 0);
@@ -3036,26 +3081,26 @@ function searchContacts(query, db) {
3036
3081
  const ftsRows = d.query(`
3037
3082
  SELECT c.* FROM contacts c
3038
3083
  JOIN contacts_fts fts ON fts.id = c.id
3039
- WHERE contacts_fts MATCH ? AND c.archived = 0
3084
+ WHERE contacts_fts MATCH ? AND c.archived = 0 AND c.sensitivity != 'restricted'
3040
3085
  ORDER BY rank
3041
3086
  LIMIT 50
3042
3087
  `).all(`"${query.replace(/"/g, '""')}"*`);
3043
3088
  const emailRows = d.query(`
3044
3089
  SELECT DISTINCT c.* FROM contacts c
3045
3090
  JOIN emails e ON e.contact_id = c.id
3046
- WHERE e.address LIKE ? AND c.archived = 0
3091
+ WHERE e.address LIKE ? AND c.archived = 0 AND c.sensitivity != 'restricted'
3047
3092
  LIMIT 20
3048
3093
  `).all(`%${query}%`);
3049
3094
  const phoneRows = d.query(`
3050
3095
  SELECT DISTINCT c.* FROM contacts c
3051
3096
  JOIN phones p ON p.contact_id = c.id
3052
- WHERE p.number LIKE ? AND c.archived = 0
3097
+ WHERE p.number LIKE ? AND c.archived = 0 AND c.sensitivity != 'restricted'
3053
3098
  LIMIT 20
3054
3099
  `).all(`%${query}%`);
3055
3100
  const companyRows = d.query(`
3056
3101
  SELECT DISTINCT c.* FROM contacts c
3057
3102
  JOIN companies co ON co.id = c.company_id
3058
- WHERE co.name LIKE ? AND c.archived = 0
3103
+ WHERE co.name LIKE ? AND c.archived = 0 AND c.sensitivity != 'restricted'
3059
3104
  LIMIT 20
3060
3105
  `).all(`%${query}%`);
3061
3106
  const seen = new Set;
@@ -3537,6 +3582,7 @@ function listCompanyEmployees(companyId, db) {
3537
3582
  follow_up_at: r.follow_up_at ?? null,
3538
3583
  archived: !!r.archived,
3539
3584
  project_id: r.project_id ?? null,
3585
+ sensitivity: r.sensitivity ?? "normal",
3540
3586
  do_not_contact: !!r.do_not_contact,
3541
3587
  priority: r.priority ?? 3,
3542
3588
  timezone: r.timezone ?? null
@@ -3939,6 +3985,7 @@ function listContactsByTag(tagId, db) {
3939
3985
  follow_up_at: r.follow_up_at ?? null,
3940
3986
  archived: !!r.archived,
3941
3987
  project_id: r.project_id ?? null,
3988
+ sensitivity: r.sensitivity ?? "normal",
3942
3989
  do_not_contact: !!r.do_not_contact,
3943
3990
  priority: r.priority ?? 3,
3944
3991
  timezone: r.timezone ?? null
@@ -6001,6 +6048,418 @@ var init_org_chart = __esm(() => {
6001
6048
  init_database();
6002
6049
  });
6003
6050
 
6051
+ // src/lib/vault.ts
6052
+ var exports_vault = {};
6053
+ __export(exports_vault, {
6054
+ unlockVault: () => unlockVault,
6055
+ requireVault: () => requireVault,
6056
+ lockVault: () => lockVault,
6057
+ isVaultUnlocked: () => isVaultUnlocked,
6058
+ isVaultInitialized: () => isVaultInitialized,
6059
+ initVault: () => initVault,
6060
+ getDocumentsDir: () => getDocumentsDir,
6061
+ encryptFile: () => encryptFile,
6062
+ encrypt: () => encrypt,
6063
+ decryptFile: () => decryptFile,
6064
+ decrypt: () => decrypt
6065
+ });
6066
+ import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
6067
+ import { join as join5 } from "path";
6068
+ import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync, createHash } from "crypto";
6069
+ function deriveKey(passphrase, salt) {
6070
+ return pbkdf2Sync(passphrase, salt, 1e5, 32, "sha512");
6071
+ }
6072
+ function initVault(passphrase) {
6073
+ if (!existsSync5(VAULT_DIR))
6074
+ mkdirSync4(VAULT_DIR, { recursive: true });
6075
+ if (!existsSync5(DOCUMENTS_DIR))
6076
+ mkdirSync4(DOCUMENTS_DIR, { recursive: true });
6077
+ const salt = randomBytes(32);
6078
+ const key = deriveKey(passphrase, salt);
6079
+ const keyHash = createHash("sha256").update(key).digest("hex");
6080
+ const config = { salt: salt.toString("hex"), key_hash: keyHash, created_at: new Date().toISOString() };
6081
+ writeFileSync3(VAULT_CONFIG, JSON.stringify(config, null, 2));
6082
+ _derivedKey = key;
6083
+ }
6084
+ function isVaultInitialized() {
6085
+ return existsSync5(VAULT_CONFIG);
6086
+ }
6087
+ function unlockVault(passphrase) {
6088
+ if (!existsSync5(VAULT_CONFIG))
6089
+ throw new Error("Vault not initialized. Run 'contacts vault init' first.");
6090
+ const config = JSON.parse(readFileSync3(VAULT_CONFIG, "utf-8"));
6091
+ const salt = Buffer.from(config.salt, "hex");
6092
+ const key = deriveKey(passphrase, salt);
6093
+ const keyHash = createHash("sha256").update(key).digest("hex");
6094
+ if (keyHash !== config.key_hash)
6095
+ return false;
6096
+ _derivedKey = key;
6097
+ return true;
6098
+ }
6099
+ function lockVault() {
6100
+ _derivedKey = null;
6101
+ }
6102
+ function isVaultUnlocked() {
6103
+ return _derivedKey !== null;
6104
+ }
6105
+ function requireVault() {
6106
+ if (!_derivedKey)
6107
+ throw new Error("Vault is locked. Unlock with 'contacts vault unlock' or vault_unlock MCP tool first.");
6108
+ return _derivedKey;
6109
+ }
6110
+ function encrypt(plaintext) {
6111
+ const key = requireVault();
6112
+ const iv = randomBytes(16);
6113
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
6114
+ let encrypted = cipher.update(plaintext, "utf8", "hex");
6115
+ encrypted += cipher.final("hex");
6116
+ const authTag = cipher.getAuthTag().toString("hex");
6117
+ return { ciphertext: encrypted + ":" + authTag, iv: iv.toString("hex") };
6118
+ }
6119
+ function decrypt(ciphertext, iv) {
6120
+ const key = requireVault();
6121
+ const [encData, authTag] = ciphertext.split(":");
6122
+ if (!encData || !authTag)
6123
+ throw new Error("Invalid ciphertext format");
6124
+ const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(iv, "hex"));
6125
+ decipher.setAuthTag(Buffer.from(authTag, "hex"));
6126
+ let decrypted = decipher.update(encData, "hex", "utf8");
6127
+ decrypted += decipher.final("utf8");
6128
+ return decrypted;
6129
+ }
6130
+ function encryptFile(sourcePath, entityId) {
6131
+ const key = requireVault();
6132
+ if (!existsSync5(DOCUMENTS_DIR))
6133
+ mkdirSync4(DOCUMENTS_DIR, { recursive: true });
6134
+ const data = readFileSync3(sourcePath);
6135
+ const iv = randomBytes(16);
6136
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
6137
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
6138
+ const authTag = cipher.getAuthTag();
6139
+ const destPath = join5(DOCUMENTS_DIR, `${entityId}.enc`);
6140
+ const output = Buffer.concat([iv, authTag, encrypted]);
6141
+ writeFileSync3(destPath, output);
6142
+ return destPath;
6143
+ }
6144
+ function decryptFile(encPath) {
6145
+ const key = requireVault();
6146
+ const data = readFileSync3(encPath);
6147
+ const iv = data.subarray(0, 16);
6148
+ const authTag = data.subarray(16, 32);
6149
+ const encrypted = data.subarray(32);
6150
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
6151
+ decipher.setAuthTag(authTag);
6152
+ return Buffer.concat([decipher.update(encrypted), decipher.final()]);
6153
+ }
6154
+ function getDocumentsDir() {
6155
+ if (!existsSync5(DOCUMENTS_DIR))
6156
+ mkdirSync4(DOCUMENTS_DIR, { recursive: true });
6157
+ return DOCUMENTS_DIR;
6158
+ }
6159
+ var VAULT_DIR, VAULT_CONFIG, DOCUMENTS_DIR, _derivedKey = null;
6160
+ var init_vault = __esm(() => {
6161
+ VAULT_DIR = join5(process.env["HOME"] || "~", ".contacts");
6162
+ VAULT_CONFIG = join5(VAULT_DIR, "vault.json");
6163
+ DOCUMENTS_DIR = join5(VAULT_DIR, "documents");
6164
+ });
6165
+
6166
+ // src/db/documents.ts
6167
+ var exports_documents = {};
6168
+ __export(exports_documents, {
6169
+ listDocuments: () => listDocuments,
6170
+ getDocument: () => getDocument,
6171
+ deleteDocument: () => deleteDocument,
6172
+ addDocument: () => addDocument,
6173
+ DOCUMENT_TYPES: () => DOCUMENT_TYPES
6174
+ });
6175
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2 } from "fs";
6176
+ function addDocument(input, db) {
6177
+ requireVault();
6178
+ const _db2 = db || getDatabase();
6179
+ const id = uuid();
6180
+ const { ciphertext, iv } = encrypt(input.value);
6181
+ let encFilePath = null;
6182
+ if (input.file_path) {
6183
+ encFilePath = encryptFile(input.file_path, id);
6184
+ }
6185
+ _db2.query(`INSERT INTO contact_documents (id, contact_id, doc_type, label, encrypted_value, iv, encrypted_file_path, metadata, expires_at, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)`).run(id, input.contact_id, input.doc_type, input.label ?? null, ciphertext, iv, encFilePath, JSON.stringify(input.metadata || {}), input.expires_at ?? null, now(), now());
6186
+ return getDocument(id, _db2);
6187
+ }
6188
+ function getDocument(id, db) {
6189
+ requireVault();
6190
+ const _db2 = db || getDatabase();
6191
+ const row = _db2.query(`SELECT * FROM contact_documents WHERE id = ?`).get(id);
6192
+ if (!row)
6193
+ throw new Error(`Document not found: ${id}`);
6194
+ return rowToDoc(row);
6195
+ }
6196
+ function listDocuments(contactId, db) {
6197
+ const _db2 = db || getDatabase();
6198
+ const rows = _db2.query(`SELECT id, doc_type, label, encrypted_file_path, expires_at, created_at FROM contact_documents WHERE contact_id = ? ORDER BY created_at DESC`).all(contactId);
6199
+ return rows.map((r) => ({
6200
+ id: r.id,
6201
+ doc_type: r.doc_type,
6202
+ label: r.label,
6203
+ has_file: !!r.encrypted_file_path,
6204
+ expires_at: r.expires_at,
6205
+ created_at: r.created_at
6206
+ }));
6207
+ }
6208
+ function deleteDocument(id, db) {
6209
+ const _db2 = db || getDatabase();
6210
+ const row = _db2.query(`SELECT encrypted_file_path FROM contact_documents WHERE id = ?`).get(id);
6211
+ if (row?.encrypted_file_path && existsSync6(row.encrypted_file_path)) {
6212
+ try {
6213
+ unlinkSync2(row.encrypted_file_path);
6214
+ } catch {}
6215
+ }
6216
+ _db2.query(`DELETE FROM contact_documents WHERE id = ?`).run(id);
6217
+ }
6218
+ function rowToDoc(row) {
6219
+ return {
6220
+ id: row.id,
6221
+ contact_id: row.contact_id,
6222
+ doc_type: row.doc_type,
6223
+ label: row.label,
6224
+ value: decrypt(row.encrypted_value, row.iv),
6225
+ has_file: !!row.encrypted_file_path,
6226
+ metadata: JSON.parse(row.metadata || "{}"),
6227
+ expires_at: row.expires_at,
6228
+ created_at: row.created_at,
6229
+ updated_at: row.updated_at
6230
+ };
6231
+ }
6232
+ var DOCUMENT_TYPES;
6233
+ var init_documents = __esm(() => {
6234
+ init_database();
6235
+ init_vault();
6236
+ DOCUMENT_TYPES = [
6237
+ "passport",
6238
+ "national_id",
6239
+ "tax_id",
6240
+ "ssn",
6241
+ "drivers_license",
6242
+ "bank_account",
6243
+ "visa",
6244
+ "insurance",
6245
+ "contract",
6246
+ "certificate",
6247
+ "medical_record",
6248
+ "prescription",
6249
+ "allergy_list",
6250
+ "vaccination",
6251
+ "blood_type",
6252
+ "health_insurance",
6253
+ "medical_condition",
6254
+ "emergency_contact_medical",
6255
+ "other"
6256
+ ];
6257
+ });
6258
+
6259
+ // src/lib/document-scanner.ts
6260
+ var exports_document_scanner = {};
6261
+ __export(exports_document_scanner, {
6262
+ scanDocument: () => scanDocument
6263
+ });
6264
+ import { readFileSync as readFileSync4, existsSync as existsSync7 } from "fs";
6265
+ import { extname as extname2 } from "path";
6266
+ async function scanDocument(imageSource, docType) {
6267
+ const apiKey = process.env["OPENAI_API_KEY"];
6268
+ if (!apiKey) {
6269
+ throw new Error("OPENAI_API_KEY not set. Set it in ~/.secrets or environment to use document scanning.");
6270
+ }
6271
+ let imageData;
6272
+ if (imageSource.startsWith("data:image/")) {
6273
+ imageData = imageSource;
6274
+ } else if (existsSync7(imageSource)) {
6275
+ const buffer = readFileSync4(imageSource);
6276
+ const ext = extname2(imageSource).slice(1).toLowerCase();
6277
+ const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "png" ? "image/png" : ext === "webp" ? "image/webp" : ext === "gif" ? "image/gif" : "image/jpeg";
6278
+ imageData = `data:${mime};base64,${buffer.toString("base64")}`;
6279
+ } else if (/^[A-Za-z0-9+/=\n\r]+$/.test(imageSource.trim()) && imageSource.length > 100) {
6280
+ imageData = `data:image/jpeg;base64,${imageSource.trim()}`;
6281
+ } else {
6282
+ throw new Error(`Image source not found or invalid: ${imageSource.slice(0, 50)}...`);
6283
+ }
6284
+ const typeHint = docType ? ` This is a ${docType} document.` : "";
6285
+ const prompt = `Extract all text and structured data from this document image.${typeHint} Return a JSON object with these fields:
6286
+ - document_type: detected type (passport, national_id, drivers_license, tax_document, medical_record, prescription, insurance_card, bank_statement, visa, certificate, contract, other)
6287
+ - full_name: full name as shown
6288
+ - date_of_birth: in YYYY-MM-DD format if visible
6289
+ - document_number: main ID/document number
6290
+ - issuing_country: country code or name
6291
+ - issue_date: in YYYY-MM-DD format if visible
6292
+ - expiry_date: in YYYY-MM-DD format if visible
6293
+ - address: full address if visible
6294
+ - nationality: if visible
6295
+ - gender: if visible
6296
+ - mrz_code: Machine Readable Zone text if this is a passport/ID with MRZ
6297
+ - phone: any phone numbers visible
6298
+ - email: any email addresses visible
6299
+ - additional_fields: object with any other visible structured data
6300
+ - raw_text: all visible text transcribed
6301
+
6302
+ Only include fields that are actually visible in the document. Return valid JSON only.`;
6303
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
6304
+ method: "POST",
6305
+ headers: {
6306
+ Authorization: `Bearer ${apiKey}`,
6307
+ "Content-Type": "application/json"
6308
+ },
6309
+ body: JSON.stringify({
6310
+ model: "gpt-4o",
6311
+ messages: [
6312
+ {
6313
+ role: "user",
6314
+ content: [
6315
+ { type: "text", text: prompt },
6316
+ { type: "image_url", image_url: { url: imageData, detail: "high" } }
6317
+ ]
6318
+ }
6319
+ ],
6320
+ max_tokens: 2000,
6321
+ temperature: 0
6322
+ })
6323
+ });
6324
+ if (!response.ok) {
6325
+ const err = await response.text();
6326
+ throw new Error(`OpenAI API error: ${response.status} \u2014 ${err}`);
6327
+ }
6328
+ const data = await response.json();
6329
+ const content = data.choices?.[0]?.message?.content || "";
6330
+ const jsonMatch = content.match(/```json\s*([\s\S]*?)```/) || content.match(/\{[\s\S]*\}/);
6331
+ if (!jsonMatch) {
6332
+ return { fields: {}, raw_text: content, document_type: docType || "unknown", confidence: 0.3 };
6333
+ }
6334
+ try {
6335
+ const parsed = JSON.parse(jsonMatch[1] || jsonMatch[0]);
6336
+ const { document_type, raw_text, additional_fields, ...mainFields } = parsed;
6337
+ const fields = {};
6338
+ for (const [k, v] of Object.entries(mainFields)) {
6339
+ if (v && typeof v === "string")
6340
+ fields[k] = v;
6341
+ }
6342
+ if (additional_fields && typeof additional_fields === "object") {
6343
+ for (const [k, v] of Object.entries(additional_fields)) {
6344
+ if (v && typeof v === "string")
6345
+ fields[k] = v;
6346
+ }
6347
+ }
6348
+ return {
6349
+ fields,
6350
+ raw_text: raw_text || content,
6351
+ document_type: document_type || docType || "unknown",
6352
+ confidence: Object.keys(fields).length > 3 ? 0.9 : Object.keys(fields).length > 0 ? 0.7 : 0.3
6353
+ };
6354
+ } catch {
6355
+ return { fields: {}, raw_text: content, document_type: docType || "unknown", confidence: 0.3 };
6356
+ }
6357
+ }
6358
+ var init_document_scanner = () => {};
6359
+
6360
+ // src/db/health.ts
6361
+ var exports_health = {};
6362
+ __export(exports_health, {
6363
+ setHealthData: () => setHealthData,
6364
+ getHealthData: () => getHealthData,
6365
+ deleteHealthData: () => deleteHealthData
6366
+ });
6367
+ function setHealthData(contactId, input, db) {
6368
+ requireVault();
6369
+ const _db2 = db || getDatabase();
6370
+ const existing = _db2.query(`SELECT id FROM contact_health WHERE contact_id = ?`).get(contactId);
6371
+ if (existing) {
6372
+ const sets = [];
6373
+ const params = [];
6374
+ if (input.blood_type !== undefined) {
6375
+ sets.push("blood_type = ?");
6376
+ params.push(input.blood_type);
6377
+ }
6378
+ if (input.allergies !== undefined) {
6379
+ sets.push("allergies = ?");
6380
+ params.push(JSON.stringify(input.allergies));
6381
+ }
6382
+ if (input.medical_conditions !== undefined) {
6383
+ sets.push("medical_conditions = ?");
6384
+ params.push(JSON.stringify(input.medical_conditions));
6385
+ }
6386
+ if (input.medications !== undefined) {
6387
+ sets.push("medications = ?");
6388
+ params.push(JSON.stringify(input.medications));
6389
+ }
6390
+ if (input.emergency_contacts !== undefined) {
6391
+ sets.push("emergency_contacts = ?");
6392
+ params.push(JSON.stringify(input.emergency_contacts));
6393
+ }
6394
+ if (input.health_insurance_provider !== undefined) {
6395
+ sets.push("health_insurance_provider = ?");
6396
+ params.push(input.health_insurance_provider);
6397
+ }
6398
+ if (input.health_insurance_id !== undefined) {
6399
+ sets.push("health_insurance_id = ?");
6400
+ params.push(input.health_insurance_id);
6401
+ }
6402
+ if (input.primary_physician !== undefined) {
6403
+ sets.push("primary_physician = ?");
6404
+ params.push(input.primary_physician);
6405
+ }
6406
+ if (input.primary_physician_phone !== undefined) {
6407
+ sets.push("primary_physician_phone = ?");
6408
+ params.push(input.primary_physician_phone);
6409
+ }
6410
+ if (input.organ_donor !== undefined) {
6411
+ sets.push("organ_donor = ?");
6412
+ params.push(input.organ_donor ? 1 : 0);
6413
+ }
6414
+ if (input.notes !== undefined) {
6415
+ sets.push("notes = ?");
6416
+ params.push(input.notes);
6417
+ }
6418
+ if (sets.length) {
6419
+ sets.push("updated_at = ?");
6420
+ params.push(now());
6421
+ params.push(contactId);
6422
+ _db2.query(`UPDATE contact_health SET ${sets.join(", ")} WHERE contact_id = ?`).run(...params);
6423
+ }
6424
+ } else {
6425
+ const id = uuid();
6426
+ _db2.query(`INSERT INTO contact_health (id, contact_id, blood_type, allergies, medical_conditions, medications, emergency_contacts, health_insurance_provider, health_insurance_id, primary_physician, primary_physician_phone, organ_donor, notes, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(id, contactId, input.blood_type ?? null, JSON.stringify(input.allergies || []), JSON.stringify(input.medical_conditions || []), JSON.stringify(input.medications || []), JSON.stringify(input.emergency_contacts || []), input.health_insurance_provider ?? null, input.health_insurance_id ?? null, input.primary_physician ?? null, input.primary_physician_phone ?? null, input.organ_donor ? 1 : 0, input.notes ?? null, now(), now());
6427
+ }
6428
+ return getHealthData(contactId, _db2);
6429
+ }
6430
+ function getHealthData(contactId, db) {
6431
+ requireVault();
6432
+ const _db2 = db || getDatabase();
6433
+ const row = _db2.query(`SELECT * FROM contact_health WHERE contact_id = ?`).get(contactId);
6434
+ if (!row)
6435
+ return null;
6436
+ return {
6437
+ id: row.id,
6438
+ contact_id: row.contact_id,
6439
+ blood_type: row.blood_type,
6440
+ allergies: JSON.parse(row.allergies || "[]"),
6441
+ medical_conditions: JSON.parse(row.medical_conditions || "[]"),
6442
+ medications: JSON.parse(row.medications || "[]"),
6443
+ emergency_contacts: JSON.parse(row.emergency_contacts || "[]"),
6444
+ health_insurance_provider: row.health_insurance_provider,
6445
+ health_insurance_id: row.health_insurance_id,
6446
+ primary_physician: row.primary_physician,
6447
+ primary_physician_phone: row.primary_physician_phone,
6448
+ organ_donor: !!row.organ_donor,
6449
+ notes: row.notes,
6450
+ created_at: row.created_at,
6451
+ updated_at: row.updated_at
6452
+ };
6453
+ }
6454
+ function deleteHealthData(contactId, db) {
6455
+ const _db2 = db || getDatabase();
6456
+ _db2.query(`DELETE FROM contact_health WHERE contact_id = ?`).run(contactId);
6457
+ }
6458
+ var init_health = __esm(() => {
6459
+ init_database();
6460
+ init_vault();
6461
+ });
6462
+
6004
6463
  // node_modules/commander/esm.mjs
6005
6464
  var import__ = __toESM(require_commander(), 1);
6006
6465
  var {
@@ -6301,8 +6760,8 @@ function readConfig() {
6301
6760
  }
6302
6761
 
6303
6762
  // src/cli/index.tsx
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";
6763
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync8, copyFileSync as copyFileSync2, statSync, mkdirSync as mkdirSync5, readdirSync as readdirSync2 } from "fs";
6764
+ import { extname as extname3, join as join6 } from "path";
6306
6765
  function renderTable(headers, rows) {
6307
6766
  const colWidths = headers.map((h) => h.length);
6308
6767
  for (const row of rows) {
@@ -6407,7 +6866,7 @@ async function confirm(question) {
6407
6866
  const answer = await prompt(question + " [y/N]");
6408
6867
  return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
6409
6868
  }
6410
- program.name("contacts").description("Open Contacts \u2014 contact management for AI coding agents").version("0.5.0");
6869
+ program.name("contacts").description("Open Contacts \u2014 contact management for AI coding agents").version("0.6.0");
6411
6870
  function collect(val, prev) {
6412
6871
  return [...prev, val];
6413
6872
  }
@@ -6473,10 +6932,11 @@ Add New Contact
6473
6932
  \u2713 Contact created: ${contact.display_name} (${contact.id})
6474
6933
  `));
6475
6934
  });
6476
- program.command("list").description("List contacts").option("--tag <tag_id>", "Filter by tag ID").option("--company <id>", "Filter by company ID").option("--limit <n>", "Max results", "50").action(async (opts) => {
6935
+ program.command("list").description("List contacts").option("--tag <tag_id>", "Filter by tag ID").option("--company <id>", "Filter by company ID").option("--include-restricted", "Include restricted-sensitivity contacts").option("--limit <n>", "Max results", "50").action(async (opts) => {
6477
6936
  const result = listContacts({
6478
6937
  tag_id: opts.tag,
6479
6938
  company_id: opts.company,
6939
+ include_restricted: opts.includeRestricted,
6480
6940
  limit: parseInt(opts.limit, 10)
6481
6941
  });
6482
6942
  if (result.contacts.length === 0) {
@@ -6713,13 +7173,13 @@ Add New Tag
6713
7173
  `));
6714
7174
  });
6715
7175
  program.command("import <file>").description("Import contacts from CSV, vCard (.vcf), or JSON file").action(async (file) => {
6716
- if (!existsSync5(file)) {
7176
+ if (!existsSync8(file)) {
6717
7177
  console.error(chalk.red(`
6718
7178
  File not found: ${file}
6719
7179
  `));
6720
7180
  process.exit(1);
6721
7181
  }
6722
- const ext = extname2(file).toLowerCase();
7182
+ const ext = extname3(file).toLowerCase();
6723
7183
  const formatMap = {
6724
7184
  ".csv": "csv",
6725
7185
  ".vcf": "vcf",
@@ -6733,7 +7193,7 @@ Unsupported file type: ${ext}. Use .csv, .vcf, or .json
6733
7193
  `));
6734
7194
  process.exit(1);
6735
7195
  }
6736
- const data = readFileSync3(file, "utf8");
7196
+ const data = readFileSync5(file, "utf8");
6737
7197
  console.log(chalk.blue(`
6738
7198
  Importing ${format.toUpperCase()} from ${file}...
6739
7199
  `));
@@ -6764,7 +7224,7 @@ Invalid format: ${format}. Use csv, vcf, or json
6764
7224
  const { contacts } = listContacts({ limit: 1e5 });
6765
7225
  const output = await exportContacts(format, contacts);
6766
7226
  if (opts.output) {
6767
- writeFileSync3(opts.output, output, "utf8");
7227
+ writeFileSync4(opts.output, output, "utf8");
6768
7228
  console.log(chalk.green(`
6769
7229
  \u2713 Exported ${contacts.length} contact(s) to ${opts.output}
6770
7230
  `));
@@ -7036,9 +7496,9 @@ program.command("init").description("Show setup info, stats, and configuration")
7036
7496
  console.log();
7037
7497
  });
7038
7498
  program.command("backup").description("Backup the contacts database").option("--output <path>", "Output path").option("--list", "List existing backups").action((opts) => {
7039
- const backupDir = join5(process.env["HOME"] || "~", ".contacts", "backups");
7499
+ const backupDir = join6(process.env["HOME"] || "~", ".contacts", "backups");
7040
7500
  if (opts.list) {
7041
- if (!existsSync5(backupDir)) {
7501
+ if (!existsSync8(backupDir)) {
7042
7502
  console.log(chalk.gray(`
7043
7503
  No backups found.
7044
7504
  `));
@@ -7055,7 +7515,7 @@ No backups found.
7055
7515
  Existing Backups:
7056
7516
  `));
7057
7517
  for (const f of files) {
7058
- const filePath = join5(backupDir, f);
7518
+ const filePath = join6(backupDir, f);
7059
7519
  const size2 = statSync(filePath).size;
7060
7520
  const mtime = statSync(filePath).mtime.toISOString().slice(0, 19).replace("T", " ");
7061
7521
  console.log(` ${chalk.cyan(f)} ${chalk.gray(`${(size2 / 1024).toFixed(1)} KB ${mtime}`)}`);
@@ -7064,15 +7524,15 @@ Existing Backups:
7064
7524
  return;
7065
7525
  }
7066
7526
  const src = getDbPath();
7067
- if (!existsSync5(src)) {
7527
+ if (!existsSync8(src)) {
7068
7528
  console.error(chalk.red(`
7069
7529
  Database not found: ${src}
7070
7530
  `));
7071
7531
  process.exit(1);
7072
7532
  }
7073
- mkdirSync4(backupDir, { recursive: true });
7533
+ mkdirSync5(backupDir, { recursive: true });
7074
7534
  const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
7075
- const dest = opts.output || join5(backupDir, `contacts-${ts}.db`);
7535
+ const dest = opts.output || join6(backupDir, `contacts-${ts}.db`);
7076
7536
  copyFileSync2(src, dest);
7077
7537
  const size = statSync(dest).size;
7078
7538
  console.log(chalk.green(`
@@ -8407,4 +8867,256 @@ logoCmd.command("show <company-id>").description("Show the path to a company's l
8407
8867
  console.log(chalk.yellow(`No logo set for ${company.name}`));
8408
8868
  }
8409
8869
  });
8870
+ program.command("set-sensitivity <id> <level>").description("Set contact sensitivity level (normal, confidential, restricted)").action((id, level) => {
8871
+ if (!["normal", "confidential", "restricted"].includes(level)) {
8872
+ console.error(chalk.red(`
8873
+ Invalid sensitivity level: ${level}. Use: normal, confidential, restricted
8874
+ `));
8875
+ process.exit(1);
8876
+ }
8877
+ const contact = getContact(id);
8878
+ updateContact(id, { sensitivity: level });
8879
+ console.log(chalk.green(`
8880
+ Sensitivity set to ${level} for ${contact.display_name}
8881
+ `));
8882
+ });
8883
+ var vaultCmd = program.command("vault").description("Manage the encrypted document vault");
8884
+ function promptPassphrase(promptText) {
8885
+ const { createInterface } = __require("readline");
8886
+ return new Promise((resolve2) => {
8887
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
8888
+ process.stdout.write(promptText);
8889
+ rl.question("", (answer) => {
8890
+ rl.close();
8891
+ resolve2(answer);
8892
+ });
8893
+ });
8894
+ }
8895
+ vaultCmd.command("init").description("Initialize the encrypted vault").option("--passphrase <pass>", "Passphrase (non-interactive)").action(async (opts) => {
8896
+ const { initVault: initVault2, isVaultInitialized: isVaultInitialized2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
8897
+ if (isVaultInitialized2()) {
8898
+ console.log(chalk.yellow(`
8899
+ Vault already initialized. Use "contacts vault unlock" to access it.
8900
+ `));
8901
+ return;
8902
+ }
8903
+ let passphrase;
8904
+ if (opts.passphrase) {
8905
+ passphrase = opts.passphrase;
8906
+ } else {
8907
+ passphrase = await promptPassphrase("Enter vault passphrase: ");
8908
+ if (!passphrase) {
8909
+ console.error(chalk.red("Passphrase is required."));
8910
+ process.exit(1);
8911
+ }
8912
+ const confirm2 = await promptPassphrase("Confirm passphrase: ");
8913
+ if (passphrase !== confirm2) {
8914
+ console.error(chalk.red("Passphrases do not match."));
8915
+ process.exit(1);
8916
+ }
8917
+ }
8918
+ initVault2(passphrase);
8919
+ console.log(chalk.green(`
8920
+ Vault initialized and unlocked.
8921
+ `));
8922
+ });
8923
+ vaultCmd.command("unlock").description("Unlock the vault").option("--passphrase <pass>", "Passphrase (non-interactive)").action(async (opts) => {
8924
+ const { unlockVault: unlockVault2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
8925
+ const passphrase = opts.passphrase || await promptPassphrase("Enter vault passphrase: ");
8926
+ const ok = unlockVault2(passphrase);
8927
+ if (!ok) {
8928
+ console.error(chalk.red(`
8929
+ Invalid passphrase.
8930
+ `));
8931
+ process.exit(1);
8932
+ }
8933
+ console.log(chalk.green(`
8934
+ Vault unlocked.
8935
+ `));
8936
+ });
8937
+ vaultCmd.command("lock").description("Lock the vault").action(async () => {
8938
+ const { lockVault: lockVault2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
8939
+ lockVault2();
8940
+ console.log(chalk.green(`
8941
+ Vault locked.
8942
+ `));
8943
+ });
8944
+ vaultCmd.command("status").description("Show vault status").action(async () => {
8945
+ const { isVaultInitialized: isVaultInitialized2, isVaultUnlocked: isVaultUnlocked2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
8946
+ const initialized = isVaultInitialized2();
8947
+ const unlocked = isVaultUnlocked2();
8948
+ console.log(chalk.bold.blue(`
8949
+ Vault Status:`));
8950
+ console.log(` Initialized: ${initialized ? chalk.green("yes") : chalk.red("no")}`);
8951
+ console.log(` Unlocked: ${unlocked ? chalk.green("yes") : chalk.red("no")}`);
8952
+ if (initialized) {
8953
+ const db = getDatabase();
8954
+ try {
8955
+ const docCount = db.query("SELECT COUNT(*) as n FROM contact_documents").get().n;
8956
+ console.log(` Documents: ${chalk.cyan(String(docCount))}`);
8957
+ } catch {}
8958
+ }
8959
+ console.log();
8960
+ });
8961
+ var docsCmd = program.command("docs").description("Manage encrypted contact documents");
8962
+ docsCmd.command("add <contact-id>").description("Add an encrypted document").option("--type <type>", "Document type (passport, national_id, tax_id, ssn, drivers_license, bank_account, visa, insurance, contract, certificate, medical_record, prescription, allergy_list, vaccination, blood_type, health_insurance, medical_condition, emergency_contact_medical, other)", "other").option("--label <label>", "Document label").option("--value <value>", "Document value (required)").option("--file <path>", "File to encrypt and attach").option("--expires <date>", "Expiry date (YYYY-MM-DD)").action(async (contactId, opts) => {
8963
+ const { addDocument: addDocument2 } = await Promise.resolve().then(() => (init_documents(), exports_documents));
8964
+ if (!opts.value) {
8965
+ console.error(chalk.red("--value is required"));
8966
+ process.exit(1);
8967
+ }
8968
+ const doc = addDocument2({
8969
+ contact_id: contactId,
8970
+ doc_type: opts.type || "other",
8971
+ label: opts.label,
8972
+ value: opts.value,
8973
+ file_path: opts.file,
8974
+ expires_at: opts.expires
8975
+ });
8976
+ console.log(chalk.green(`
8977
+ Document added: ${doc.doc_type} (${doc.id})
8978
+ `));
8979
+ });
8980
+ docsCmd.command("list <contact-id>").description("List documents for a contact (metadata only)").action(async (contactId) => {
8981
+ const { listDocuments: listDocuments2 } = await Promise.resolve().then(() => (init_documents(), exports_documents));
8982
+ const docs = listDocuments2(contactId);
8983
+ if (!docs.length) {
8984
+ console.log(chalk.gray(`
8985
+ No documents found.
8986
+ `));
8987
+ return;
8988
+ }
8989
+ console.log();
8990
+ renderTable(["Type", "Label", "Has File", "Expires", "Created"], docs.map((d) => ({
8991
+ Type: d.doc_type,
8992
+ Label: d.label || "",
8993
+ "Has File": d.has_file ? "yes" : "no",
8994
+ Expires: d.expires_at ? d.expires_at.slice(0, 10) : "",
8995
+ Created: d.created_at.slice(0, 10)
8996
+ })));
8997
+ console.log(chalk.gray(`
8998
+ ${docs.length} document(s)
8999
+ `));
9000
+ });
9001
+ docsCmd.command("show <doc-id>").description("Show a document with decrypted value (vault must be unlocked)").action(async (docId) => {
9002
+ const { getDocument: getDocument2 } = await Promise.resolve().then(() => (init_documents(), exports_documents));
9003
+ const doc = getDocument2(docId);
9004
+ console.log(chalk.bold.blue(`
9005
+ Document: ${doc.doc_type}`));
9006
+ if (doc.label)
9007
+ console.log(chalk.gray(" Label: ") + doc.label);
9008
+ console.log(chalk.gray(" Value: ") + doc.value);
9009
+ console.log(chalk.gray(" Has File:") + (doc.has_file ? " yes" : " no"));
9010
+ if (doc.expires_at)
9011
+ console.log(chalk.gray(" Expires: ") + doc.expires_at.slice(0, 10));
9012
+ console.log(chalk.gray(` ID: ${doc.id}
9013
+ `));
9014
+ });
9015
+ docsCmd.command("remove <doc-id>").description("Delete a document").action(async (docId) => {
9016
+ const { deleteDocument: deleteDocument2 } = await Promise.resolve().then(() => (init_documents(), exports_documents));
9017
+ deleteDocument2(docId);
9018
+ console.log(chalk.green(`
9019
+ Document deleted: ${docId}
9020
+ `));
9021
+ });
9022
+ docsCmd.command("scan <image-path>").description("Scan a document image using AI vision").option("--contact <id>", "Contact ID to associate with").option("--type <type>", "Document type hint").action(async (imagePath, opts) => {
9023
+ const { scanDocument: scanDocument2 } = await Promise.resolve().then(() => (init_document_scanner(), exports_document_scanner));
9024
+ console.log(chalk.blue(`
9025
+ Scanning document...
9026
+ `));
9027
+ const result = await scanDocument2(imagePath, opts.type);
9028
+ console.log(chalk.bold(` Type: ${result.document_type} Confidence: ${(result.confidence * 100).toFixed(0)}%
9029
+ `));
9030
+ console.log(chalk.yellow(" Extracted fields:"));
9031
+ for (const [k, v] of Object.entries(result.fields)) {
9032
+ console.log(` ${chalk.gray(k.padEnd(20))} ${v}`);
9033
+ }
9034
+ console.log();
9035
+ });
9036
+ docsCmd.command("types").description("List all valid document types").action(async () => {
9037
+ const { DOCUMENT_TYPES: DOCUMENT_TYPES2 } = await Promise.resolve().then(() => (init_documents(), exports_documents));
9038
+ console.log(chalk.bold.blue(`
9039
+ Document Types:
9040
+ `));
9041
+ for (const t of DOCUMENT_TYPES2) {
9042
+ console.log(` ${chalk.cyan(t)}`);
9043
+ }
9044
+ console.log();
9045
+ });
9046
+ var healthCmd = program.command("health").description("Manage contact health data (vault required)");
9047
+ healthCmd.command("show <id>").description("Show health data for a contact").action(async (id) => {
9048
+ const { getHealthData: getHealthData2 } = await Promise.resolve().then(() => (init_health(), exports_health));
9049
+ const contact = getContact(id);
9050
+ const health = getHealthData2(id);
9051
+ if (!health) {
9052
+ console.log(chalk.gray(`
9053
+ No health data for ${contact.display_name}.
9054
+ `));
9055
+ return;
9056
+ }
9057
+ console.log(chalk.bold.blue(`
9058
+ Health: ${contact.display_name}
9059
+ `));
9060
+ if (health.blood_type)
9061
+ console.log(chalk.gray(" Blood Type: ") + health.blood_type);
9062
+ if (health.allergies.length)
9063
+ console.log(chalk.gray(" Allergies: ") + health.allergies.join(", "));
9064
+ if (health.medical_conditions.length)
9065
+ console.log(chalk.gray(" Conditions: ") + health.medical_conditions.join(", "));
9066
+ if (health.medications.length)
9067
+ console.log(chalk.gray(" Medications: ") + health.medications.join(", "));
9068
+ if (health.emergency_contacts.length) {
9069
+ console.log(chalk.yellow(`
9070
+ Emergency Contacts:`));
9071
+ for (const ec of health.emergency_contacts) {
9072
+ console.log(` ${chalk.bold(ec.name)} ${ec.phone} ${chalk.gray(ec.relationship)}`);
9073
+ }
9074
+ }
9075
+ if (health.health_insurance_provider)
9076
+ console.log(chalk.gray(`
9077
+ Insurance: `) + `${health.health_insurance_provider} (${health.health_insurance_id || "no ID"})`);
9078
+ if (health.primary_physician)
9079
+ console.log(chalk.gray(" Physician: ") + `${health.primary_physician} ${health.primary_physician_phone || ""}`);
9080
+ console.log(chalk.gray(" Organ Donor: ") + (health.organ_donor ? "yes" : "no"));
9081
+ if (health.notes)
9082
+ console.log(chalk.gray(" Notes: ") + health.notes);
9083
+ console.log();
9084
+ });
9085
+ healthCmd.command("set <id>").description("Set health data for a contact").option("--blood-type <type>", "Blood type (e.g. A+, O-)").option("--allergies <list>", "Comma-separated allergies").option("--conditions <list>", "Comma-separated medical conditions").option("--medications <list>", "Comma-separated medications").option("--insurance-provider <name>", "Health insurance provider").option("--insurance-id <id>", "Health insurance ID").option("--physician <name>", "Primary physician").option("--physician-phone <phone>", "Physician phone").option("--organ-donor", "Mark as organ donor").option("--notes <text>", "Health notes").action(async (id, opts) => {
9086
+ const { setHealthData: setHealthData2 } = await Promise.resolve().then(() => (init_health(), exports_health));
9087
+ const contact = getContact(id);
9088
+ const input = {};
9089
+ if (opts.bloodType)
9090
+ input.blood_type = opts.bloodType;
9091
+ if (opts.allergies)
9092
+ input.allergies = opts.allergies.split(",").map((s) => s.trim());
9093
+ if (opts.conditions)
9094
+ input.medical_conditions = opts.conditions.split(",").map((s) => s.trim());
9095
+ if (opts.medications)
9096
+ input.medications = opts.medications.split(",").map((s) => s.trim());
9097
+ if (opts.insuranceProvider)
9098
+ input.health_insurance_provider = opts.insuranceProvider;
9099
+ if (opts.insuranceId)
9100
+ input.health_insurance_id = opts.insuranceId;
9101
+ if (opts.physician)
9102
+ input.primary_physician = opts.physician;
9103
+ if (opts.physicianPhone)
9104
+ input.primary_physician_phone = opts.physicianPhone;
9105
+ if (opts.organDonor !== undefined)
9106
+ input.organ_donor = opts.organDonor;
9107
+ if (opts.notes)
9108
+ input.notes = opts.notes;
9109
+ setHealthData2(id, input);
9110
+ console.log(chalk.green(`
9111
+ Health data updated for ${contact.display_name}
9112
+ `));
9113
+ });
9114
+ healthCmd.command("clear <id>").description("Delete all health data for a contact").action(async (id) => {
9115
+ const { deleteHealthData: deleteHealthData2 } = await Promise.resolve().then(() => (init_health(), exports_health));
9116
+ const contact = getContact(id);
9117
+ deleteHealthData2(id);
9118
+ console.log(chalk.green(`
9119
+ Health data cleared for ${contact.display_name}
9120
+ `));
9121
+ });
8410
9122
  program.parse(process.argv);