@datasynx/agentic-crm 1.0.0 → 1.2.0

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.
Files changed (89) hide show
  1. package/README.md +8 -1
  2. package/dist/{ask-D8iYqDAr.js → ask-CDysGnRg.js} +2 -2
  3. package/dist/{ask-D8iYqDAr.js.map → ask-CDysGnRg.js.map} +1 -1
  4. package/dist/attachments-CX2GAtsw.cjs +517 -0
  5. package/dist/attachments-CX2GAtsw.cjs.map +1 -0
  6. package/dist/attachments-D207gXfN.js +514 -0
  7. package/dist/attachments-D207gXfN.js.map +1 -0
  8. package/dist/attachments-rLa96rOK.js +514 -0
  9. package/dist/attachments-rLa96rOK.js.map +1 -0
  10. package/dist/chunk-BfDYWZQ8.cjs +32 -0
  11. package/dist/chunk-BfDYWZQ8.cjs.map +1 -0
  12. package/dist/chunk-BhUZmQg5.js +32 -0
  13. package/dist/chunk-BhUZmQg5.js.map +1 -0
  14. package/dist/chunk-ChC83jai.js +2 -0
  15. package/dist/chunk-e_w8qqtP.js +32 -0
  16. package/dist/chunk-e_w8qqtP.js.map +1 -0
  17. package/dist/cli.js +20 -18
  18. package/dist/cli.js.map +1 -1
  19. package/dist/daemon/worker.js +3 -3
  20. package/dist/{doctor-C14-vnJ1.js → doctor-BFeelnq8.js} +2 -2
  21. package/dist/{doctor-C14-vnJ1.js.map → doctor-BFeelnq8.js.map} +1 -1
  22. package/dist/doctor-CYDaNmFn.js +2 -0
  23. package/dist/email-body-BFSRa0AW.cjs +42 -0
  24. package/dist/email-body-BFSRa0AW.cjs.map +1 -0
  25. package/dist/email-body-BOd7U-D2.js +42 -0
  26. package/dist/email-body-BOd7U-D2.js.map +1 -0
  27. package/dist/{gmail-sync-DueE6tl5.js → gmail-sync-B4Iu3AQb.js} +45 -15
  28. package/dist/gmail-sync-B4Iu3AQb.js.map +1 -0
  29. package/dist/{gmail-sync-GEy3oVvw.cjs → gmail-sync-BpSVESSe.cjs} +45 -15
  30. package/dist/gmail-sync-BpSVESSe.cjs.map +1 -0
  31. package/dist/{gmail-sync-C-NmibzS.js → gmail-sync-DIbrPnTK.js} +45 -15
  32. package/dist/gmail-sync-DIbrPnTK.js.map +1 -0
  33. package/dist/{gmail-webhook-handler-kGKpbY9h.js → gmail-webhook-handler-BzOFbvgh.js} +2 -2
  34. package/dist/{gmail-webhook-handler-kGKpbY9h.js.map → gmail-webhook-handler-BzOFbvgh.js.map} +1 -1
  35. package/dist/{gmail-webhook-handler-B26COilD.js → gmail-webhook-handler-CvSDW_Js.js} +1 -1
  36. package/dist/{google-drive-sync-D1n7WKZn.js → google-drive-sync-B_I1d54Y.js} +2 -2
  37. package/dist/{google-drive-sync-D1n7WKZn.js.map → google-drive-sync-B_I1d54Y.js.map} +1 -1
  38. package/dist/html-BaeOCZKE.js +36 -0
  39. package/dist/html-BaeOCZKE.js.map +1 -0
  40. package/dist/html-CmOku6jS.cjs +47 -0
  41. package/dist/html-CmOku6jS.cjs.map +1 -0
  42. package/dist/{import-hubspot-DB4n89jy.js → import-hubspot-CTId9IGV.js} +2 -2
  43. package/dist/{import-hubspot-DB4n89jy.js.map → import-hubspot-CTId9IGV.js.map} +1 -1
  44. package/dist/{index-pY7tYXwH.d.cts → index-CLUKKfGb.d.cts} +12 -8
  45. package/dist/index-CLUKKfGb.d.cts.map +1 -0
  46. package/dist/{index-B0IMMrp_.d.ts → index-D8jJ1VIt.d.ts} +14 -10
  47. package/dist/index-D8jJ1VIt.d.ts.map +1 -0
  48. package/dist/index.d.cts +12 -8
  49. package/dist/index.d.cts.map +1 -1
  50. package/dist/index.d.ts +14 -10
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/{interactions-writer-RJB8SWf2.js → interactions-writer-B2y-73lh.js} +1 -1
  53. package/dist/{interactions-writer-DbSyI2rt.js → interactions-writer-B8XAzdqR.js} +3 -2
  54. package/dist/interactions-writer-B8XAzdqR.js.map +1 -0
  55. package/dist/{interactions-writer-a2yzBd7T.cjs → interactions-writer-BRJNrefF.cjs} +3 -2
  56. package/dist/interactions-writer-BRJNrefF.cjs.map +1 -0
  57. package/dist/{interactions-writer-BZzUIgJd.js → interactions-writer-ZQcpFOh9.js} +3 -2
  58. package/dist/interactions-writer-ZQcpFOh9.js.map +1 -0
  59. package/dist/{knowledge-base-DHNc4hVj.js → knowledge-base-Bx2PKQR2.js} +10 -7
  60. package/dist/knowledge-base-Bx2PKQR2.js.map +1 -0
  61. package/dist/mcp-CdTJWTJf.d.cts.map +1 -1
  62. package/dist/mcp-CdTJWTJf.d.ts.map +1 -1
  63. package/dist/mcp.cjs +308 -150
  64. package/dist/mcp.cjs.map +1 -1
  65. package/dist/mcp.d.cts.map +1 -1
  66. package/dist/mcp.d.ts.map +1 -1
  67. package/dist/mcp.js +308 -150
  68. package/dist/mcp.js.map +1 -1
  69. package/dist/{microsoft-calendar-jIu9K5zX.js → microsoft-calendar-BgVR8GDv.js} +3 -3
  70. package/dist/{microsoft-calendar-jIu9K5zX.js.map → microsoft-calendar-BgVR8GDv.js.map} +1 -1
  71. package/dist/{microsoft-sync-R_r8HL-B.js → microsoft-sync-D30_XksI.js} +3 -3
  72. package/dist/{microsoft-sync-R_r8HL-B.js.map → microsoft-sync-D30_XksI.js.map} +1 -1
  73. package/dist/{nba-mTJ4yEqD.js → nba-DwdfM93s.js} +2 -2
  74. package/dist/{nba-mTJ4yEqD.js.map → nba-DwdfM93s.js.map} +1 -1
  75. package/dist/{server-DqSMYhSA.js → server-BhNLrnAD.js} +201 -145
  76. package/dist/server-BhNLrnAD.js.map +1 -0
  77. package/dist/{transcript-watcher-0mh2ZhmH.js → transcript-watcher-BoClrJAz.js} +2 -2
  78. package/dist/{transcript-watcher-0mh2ZhmH.js.map → transcript-watcher-BoClrJAz.js.map} +1 -1
  79. package/package.json +13 -2
  80. package/dist/gmail-sync-C-NmibzS.js.map +0 -1
  81. package/dist/gmail-sync-DueE6tl5.js.map +0 -1
  82. package/dist/gmail-sync-GEy3oVvw.cjs.map +0 -1
  83. package/dist/index-B0IMMrp_.d.ts.map +0 -1
  84. package/dist/index-pY7tYXwH.d.cts.map +0 -1
  85. package/dist/interactions-writer-BZzUIgJd.js.map +0 -1
  86. package/dist/interactions-writer-DbSyI2rt.js.map +0 -1
  87. package/dist/interactions-writer-a2yzBd7T.cjs.map +0 -1
  88. package/dist/knowledge-base-DHNc4hVj.js.map +0 -1
  89. package/dist/server-DqSMYhSA.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-sync-DIbrPnTK.js","names":["gmail","gmailApi"],"sources":["../src/schemas/agent-config.ts","../src/core/agent-notifier.ts","../src/sync/gmail-sync.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const AgentConfigSchema = z.object({\n slug: z.string().min(1),\n channel: z.enum([\"telegram\"]),\n wakeOn: z.array(z.enum([\"email\", \"calendar\"])).default([\"email\"]),\n createdAt: z.string(),\n lastWake: z.string().nullable().default(null),\n telegramChatId: z.string().optional(),\n});\n\nexport type AgentConfig = z.infer<typeof AgentConfigSchema>;\n","// src/core/agent-notifier.ts\n// Sends a Telegram wake notification when a new inbound email from a customer\n// domain is detected and an agent config exists for that customer slug.\n// All errors are swallowed — this is a notification feature and must never\n// crash the core loop.\n\nimport fs from \"fs\";\nimport https from \"https\";\nimport path from \"path\";\nimport { writeJsonFile } from \"../fs/json-store.js\";\nimport { AgentConfigSchema, type AgentConfig } from \"../schemas/agent-config.js\";\nimport { summarizeEmail } from \"./llm.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface WakeContext {\n trigger: \"email\" | \"calendar\";\n subject: string;\n from: string;\n snippet: string;\n}\n\n// ─── Agent config helpers ─────────────────────────────────────────────────────\n\nfunction agentConfigPath(dataDir: string, slug: string): string {\n return path.join(dataDir, \".agentic\", \"agents\", `${slug}.agent.json`);\n}\n\nfunction readAgentConfig(dataDir: string, slug: string): AgentConfig | null {\n const p = agentConfigPath(dataDir, slug);\n if (!fs.existsSync(p)) return null;\n try {\n const raw = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as unknown;\n const result = AgentConfigSchema.safeParse(raw);\n return result.success ? result.data : null;\n } catch {\n return null;\n }\n}\n\nfunction writeLastWake(dataDir: string, slug: string, config: AgentConfig): void {\n const p = agentConfigPath(dataDir, slug);\n try {\n const updated: AgentConfig = { ...config, lastWake: new Date().toISOString() };\n writeJsonFile(p, updated);\n } catch {\n // non-fatal — just a housekeeping write\n }\n}\n\n// ─── Telegram transport ───────────────────────────────────────────────────────\n\nfunction sendTelegramMessage(token: string, chatId: string, text: string): Promise<void> {\n const body = JSON.stringify({ chat_id: chatId, text, parse_mode: \"Markdown\" });\n return new Promise<void>((resolve, reject) => {\n const req = https.request(\n `https://api.telegram.org/bot${token}/sendMessage`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(body),\n },\n },\n (res) => {\n res.resume();\n resolve();\n }\n );\n req.on(\"error\", reject);\n req.write(body);\n req.end();\n });\n}\n\n// ─── Message builder ──────────────────────────────────────────────────────────\n\nfunction buildWakeMessage(\n slug: string,\n subject: string,\n summary: string,\n nextSteps: string[]\n): string {\n const suggestedAction = nextSteps[0] ?? \"Follow up within 24h\";\n return (\n `📧 New email from **${slug}**: ${subject}\\n` +\n `${summary}\\n\\n` +\n `💡 Suggested action: ${suggestedAction}`\n );\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Fire-and-forget notification: reads the agent config for `slug`, summarises\n * the inbound email with the LLM, and sends a Telegram message.\n *\n * Silently returns (no throw) when:\n * - no agent config exists for the slug\n * - TELEGRAM_BOT_TOKEN env var is not set\n * - no chat id is available (neither in config nor in TELEGRAM_CHAT_ID env var)\n * - any HTTPS / LLM error occurs\n */\nexport async function notifyAgentWake(\n dataDir: string,\n slug: string,\n context: WakeContext\n): Promise<void> {\n try {\n // 1. Read agent config — bail silently if not found\n const config = readAgentConfig(dataDir, slug);\n if (!config) return;\n\n // 2. Check for Telegram token — bail silently if absent\n const token = process.env[\"TELEGRAM_BOT_TOKEN\"];\n if (!token) return;\n\n // 3. Determine chat id — config takes precedence, fallback to env var\n const chatId = config.telegramChatId ?? process.env[\"TELEGRAM_CHAT_ID\"];\n if (!chatId) return;\n\n // 4. Summarise the email (LLM, with fallback built into summarizeEmail itself)\n const emailSummary = await summarizeEmail(context.subject, context.snippet, context.from);\n\n // 5. Build and send the Telegram message\n const text = buildWakeMessage(\n slug,\n context.subject,\n emailSummary.summary,\n emailSummary.nextSteps\n );\n await sendTelegramMessage(token, chatId, text);\n\n // 6. Update lastWake on success\n writeLastWake(dataDir, slug, config);\n } catch {\n // Swallow all errors — this is a notification feature, never crashes core loop\n }\n}\n","// src/sync/gmail-sync.ts\nimport fs from \"fs\";\nimport path from \"path\";\nimport { gmail as gmailApi, type gmail_v1 } from \"@googleapis/gmail\";\nimport type { OAuth2Client } from \"google-auth-library\";\nimport { readInteractions, appendInteraction } from \"../fs/interactions-writer.js\";\nimport { notifyAgentWake } from \"../core/agent-notifier.js\";\nimport { logger } from \"../core/logger.js\";\n\ninterface SyncOptions {\n slug: string;\n dataDir: string;\n auth: OAuth2Client;\n query: string;\n since?: Date;\n maxPages?: number;\n /** Download, convert and index email attachments (default true). */\n includeAttachments?: boolean;\n /** Per-attachment size cap in bytes. */\n maxAttachmentBytes?: number;\n}\n\n/**\n * Retry a function with exponential backoff on any error.\n * Delays: 1s, 2s, 4s, 8s … (2^attempt seconds), up to maxRetries retries.\n */\nexport async function retryWithBackoff<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {\n let attempt = 0;\n while (true) {\n try {\n return await fn();\n } catch (err) {\n if (attempt >= maxRetries) throw err;\n const delayMs = 1000 * Math.pow(2, attempt);\n await sleep(delayMs);\n attempt++;\n }\n }\n}\n\nexport async function syncGmail(opts: SyncOptions): Promise<{ synced: number; skipped: number }> {\n const gmail = gmailApi({ version: \"v1\", auth: opts.auth });\n const maxPages = opts.maxPages ?? 5;\n const includeAttachments = opts.includeAttachments ?? true;\n\n let q = opts.query;\n if (opts.since) {\n const after = Math.floor(opts.since.getTime() / 1000);\n q += ` after:${after}`;\n }\n\n // Collect all message stubs across pages (Task A — pagination)\n const allMessages: Array<{ id?: string | null; threadId?: string | null }> = [];\n let pageToken: string | undefined = undefined;\n let pagesFetched = 0;\n\n do {\n const listResp: { data: gmail_v1.Schema$ListMessagesResponse } =\n await gmail.users.messages.list({\n userId: \"me\",\n q,\n maxResults: 200,\n ...(pageToken ? { pageToken } : {}),\n });\n const pageMessages = listResp.data.messages ?? [];\n allMessages.push(...pageMessages);\n pageToken = listResp.data.nextPageToken ?? undefined;\n pagesFetched++;\n } while (pageToken && pagesFetched < maxPages);\n\n // Read existing interactions once before the loop — avoids O(messages) file reads\n let existingContent = await readInteractions(opts.dataDir, opts.slug);\n\n let synced = 0;\n let skipped = 0;\n\n for (const msg of allMessages) {\n if (!msg.id) continue;\n\n const source = `gmail://thread/${msg.threadId ?? msg.id}`;\n\n if (existingContent.includes(source)) {\n skipped++;\n continue;\n }\n\n // Rate limiting ~10 req/s\n await sleep(100);\n\n // Task B — exponential backoff retry on any error\n let msgData: gmail_v1.Schema$Message;\n try {\n const detail = await retryWithBackoff(() =>\n gmail.users.messages.get({\n userId: \"me\",\n id: msg.id!,\n // \"full\" exposes payload.parts so attachments can be downloaded;\n // fall back to lighter \"metadata\" when attachment sync is disabled.\n ...(includeAttachments\n ? { format: \"full\" }\n : { format: \"metadata\", metadataHeaders: [\"Subject\", \"From\", \"Date\"] }),\n })\n );\n msgData = detail.data;\n } catch (err) {\n logger.warn(\"gmail-sync\", \"skipping message after retries\", {\n messageId: msg.id,\n error: (err as Error).message,\n });\n skipped++;\n continue;\n }\n\n const headers = msgData.payload?.headers ?? [];\n const subject = headers.find((h) => h.name === \"Subject\")?.value ?? \"(no subject)\";\n const from = headers.find((h) => h.name === \"From\")?.value ?? \"\";\n const dateStr = headers.find((h) => h.name === \"Date\")?.value;\n const date = dateStr\n ? new Date(dateStr).toISOString().slice(0, 10)\n : new Date().toISOString().slice(0, 10);\n const snippet = msgData.snippet ?? \"\";\n\n // Extract the full inline body (plain preferred, else HTML->Markdown) so\n // summaries and search cover the whole message, not just the snippet.\n const { extractEmailBodyMarkdown } = await import(\"./email-body.js\");\n const body = (await extractEmailBodyMarkdown(msgData.payload ?? undefined)) || snippet;\n\n // LLM summary — non-blocking fallback to raw body/snippet if no API key or error\n const { summarizeEmail } = await import(\"../core/llm.js\");\n const emailSummary = await summarizeEmail(subject, body, from);\n\n // Download, convert and index attachments before logging the interaction so\n // the entry can link to the generated Markdown. Failures here are swallowed.\n let attachmentLinks: string[] = [];\n if (includeAttachments) {\n try {\n const { processMessageAttachments } = await import(\"./attachments.js\");\n const saved = await processMessageAttachments({\n gmail,\n dataDir: opts.dataDir,\n slug: opts.slug,\n messageId: msg.id,\n source,\n payload: msgData.payload ?? undefined,\n date,\n ...(opts.maxAttachmentBytes !== undefined\n ? { maxBytes: opts.maxAttachmentBytes }\n : {}),\n });\n attachmentLinks = saved.map((a) => a.markdownName);\n } catch (err) {\n logger.warn(\"gmail-sync\", \"attachment processing failed\", {\n messageId: msg.id,\n error: (err as Error).message,\n });\n }\n }\n\n await appendInteraction(opts.dataDir, opts.slug, {\n date,\n type: \"Email\",\n direction: detectDirection(from),\n with: from,\n subject,\n summary: emailSummary.summary,\n nextSteps: emailSummary.nextSteps,\n ...(attachmentLinks.length > 0 ? { attachments: attachmentLinks } : {}),\n sourceRef: source,\n synced: new Date().toISOString(),\n });\n\n // Append to in-memory string so within-batch duplicates are detected\n existingContent += source;\n\n // Index the full email (subject + body) into LanceDB for semantic search,\n // chunked so long threads stay searchable (non-blocking).\n const { indexInLanceDB } = await import(\"../core/lancedb.js\");\n const { chunkText } = await import(\"../core/chunk.js\");\n const bodyChunks = chunkText(`${subject}\\n${body}`);\n for (let i = 0; i < bodyChunks.length; i++) {\n const ref = i === 0 ? source : `${source}#${i}`;\n await indexInLanceDB(opts.dataDir, opts.slug, bodyChunks[i]!, ref, {\n date,\n type: \"Email\",\n }).catch((err: unknown) => {\n logger.error(\"gmail-sync\", \"LanceDB index failed\", { error: (err as Error).message });\n });\n }\n\n // Agent wake: notify if an agent config exists for this customer (fire-and-forget)\n if (agentConfigExists(opts.dataDir, opts.slug)) {\n notifyAgentWake(opts.dataDir, opts.slug, {\n trigger: \"email\",\n subject,\n from,\n snippet,\n }).catch(() => {\n // Notification is non-blocking; swallow all errors\n });\n }\n\n synced++;\n }\n\n return { synced, skipped };\n}\n\nfunction agentConfigExists(dataDir: string, slug: string): boolean {\n const configPath = path.join(dataDir, \".agentic\", \"agents\", `${slug}.agent.json`);\n return fs.existsSync(configPath);\n}\n\nfunction detectDirection(_from: string): \"inbound\" | \"outbound\" {\n return \"inbound\";\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { n as readSyncState, r as updateSlugSyncState } from "./sync-state-DMZgzpez.js";
2
- import { n as appendInteraction } from "./interactions-writer-DbSyI2rt.js";
2
+ import { n as appendInteraction } from "./interactions-writer-B8XAzdqR.js";
3
3
  import { n as readSubscriptions, s as writeSubscriptions } from "./push-manager-C0ECQgva.js";
4
4
  //#region src/sync/gmail-webhook-handler.ts
5
5
  function decodeGmailPubSubPayload(body) {
@@ -94,4 +94,4 @@ function buildGmailRenewFn(accessToken, topicName, registerFn) {
94
94
  //#endregion
95
95
  export { verifyGmailPubSubSignature as i, decodeGmailPubSubPayload as n, handleGmailPushEvent as r, buildGmailRenewFn as t };
96
96
 
97
- //# sourceMappingURL=gmail-webhook-handler-kGKpbY9h.js.map
97
+ //# sourceMappingURL=gmail-webhook-handler-BzOFbvgh.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"gmail-webhook-handler-kGKpbY9h.js","names":["b"],"sources":["../src/sync/gmail-webhook-handler.ts"],"sourcesContent":["import {\n readSubscriptions,\n writeSubscriptions,\n type PushSubscription,\n type RenewFn,\n} from \"./push-manager.js\";\nimport { updateSlugSyncState, readSyncState } from \"../fs/sync-state.js\";\nimport { appendInteraction } from \"../fs/interactions-writer.js\";\nimport type { HistoryMessage, WatchRegistration } from \"./gmail-push-watch.js\";\n\nexport interface GmailPubSubMessage {\n emailAddress: string;\n historyId: string;\n}\n\nexport function decodeGmailPubSubPayload(body: unknown): GmailPubSubMessage | null {\n try {\n const b = body as { message?: { data?: string } };\n const data = b?.message?.data;\n if (!data) return null;\n const decoded = Buffer.from(data, \"base64\").toString(\"utf-8\");\n const parsed = JSON.parse(decoded) as { emailAddress?: string; historyId?: string };\n if (!parsed.emailAddress || !parsed.historyId) return null;\n return { emailAddress: parsed.emailAddress, historyId: parsed.historyId };\n } catch {\n return null;\n }\n}\n\nexport function verifyGmailPubSubSignature(\n authHeader: string | undefined,\n expectedToken: string\n): boolean {\n if (!authHeader) return false;\n const token = authHeader.startsWith(\"Bearer \") ? authHeader.slice(7) : authHeader;\n return token === expectedToken;\n}\n\nfunction findSubscriptionByEmail(\n subs: PushSubscription[],\n emailAddress: string\n): PushSubscription | null {\n return (\n subs.find(\n (s) =>\n s.provider === \"gmail\" &&\n s.status === \"active\" &&\n (s.providerData.gmailEmailAddress === emailAddress || s.slug === emailAddress)\n ) ?? null\n );\n}\n\nexport type FetchHistoryFn = (\n accessToken: string,\n startHistoryId: string\n) => Promise<HistoryMessage[]>;\nexport type FetchMessageFn = (\n accessToken: string,\n messageId: string\n) => Promise<{\n id: string;\n threadId: string;\n subject: string;\n from: string;\n date: string;\n body: string;\n}>;\nexport type AppendInteractionFn = typeof appendInteraction;\n\nexport interface HandleGmailPushOptions {\n fetchHistoryFn?: FetchHistoryFn;\n fetchMessageFn?: FetchMessageFn;\n appendInteractionFn?: AppendInteractionFn;\n accessToken?: string;\n}\n\nexport { readSubscriptions };\n\nexport async function handleGmailPushEvent(\n dataDir: string,\n payload: GmailPubSubMessage,\n subscriptionId: string,\n options: HandleGmailPushOptions = {}\n): Promise<{ processed: number; slug: string | null }> {\n const subs = await readSubscriptions(dataDir);\n const sub = findSubscriptionByEmail(subs, payload.emailAddress);\n if (!sub) return { processed: 0, slug: null };\n\n const slug = sub.slug;\n const syncState = readSyncState(dataDir);\n const lastHistoryId =\n syncState[slug]?.lastGmailPushHistoryId ?? sub.providerData.gmailHistoryId ?? \"0\";\n\n // Skip if already processed\n if (BigInt(payload.historyId) <= BigInt(lastHistoryId)) {\n return { processed: 0, slug };\n }\n\n const startHistoryId = sub.providerData.gmailHistoryId ?? lastHistoryId;\n\n const {\n fetchHistoryFn,\n fetchMessageFn,\n appendInteractionFn = appendInteraction,\n accessToken = \"\",\n } = options;\n\n if (!fetchHistoryFn) return { processed: 0, slug };\n\n const messages = await fetchHistoryFn(accessToken, startHistoryId);\n let processed = 0;\n\n for (const msg of messages) {\n if (!fetchMessageFn) continue;\n try {\n const full = await fetchMessageFn(accessToken, msg.id);\n const sourceRef = `gmail://thread/${full.threadId}`;\n\n await appendInteractionFn(dataDir, slug, {\n date: new Date().toISOString().slice(0, 10),\n type: \"Email\",\n direction: \"inbound\",\n with: full.from,\n subject: full.subject,\n summary: full.body.slice(0, 300) || \"(no body)\",\n nextSteps: [],\n sourceRef,\n synced: new Date().toISOString(),\n });\n processed++;\n } catch {\n // Skip individual message errors\n }\n }\n\n // Update sync state\n updateSlugSyncState(dataDir, slug, { lastGmailPushHistoryId: payload.historyId });\n\n // Update subscription counters\n const subIdx = subs.findIndex((s) => s.id === sub.id);\n if (subIdx !== -1) {\n subs[subIdx] = {\n ...subs[subIdx]!,\n eventsProcessed: subs[subIdx]!.eventsProcessed + 1,\n lastEventAt: new Date().toISOString(),\n };\n await writeSubscriptions(dataDir, subs);\n }\n\n return { processed, slug };\n}\n\nexport type RegisterGmailWatchFn = (\n accessToken: string,\n topicName: string\n) => Promise<WatchRegistration>;\n\nexport function buildGmailRenewFn(\n accessToken: string,\n topicName: string,\n registerFn?: RegisterGmailWatchFn\n): RenewFn {\n return async (_sub: PushSubscription) => {\n const doRegister: RegisterGmailWatchFn =\n registerFn ??\n (async (token: string, topic: string) => {\n const { registerGmailWatch } = await import(\"./gmail-push-watch.js\");\n return registerGmailWatch(token, topic);\n });\n\n const registration = await doRegister(accessToken, topicName);\n return {\n expiresAt: new Date(Number(registration.expiration)).toISOString(),\n providerData: { gmailHistoryId: registration.historyId },\n };\n };\n}\n"],"mappings":";;;;AAeA,SAAgB,yBAAyB,MAA0C;CACjF,IAAI;EAEF,MAAM,OAAOA,MAAG,SAAS;EACzB,IAAI,CAAC,MAAM,OAAO;EAClB,MAAM,UAAU,OAAO,KAAK,MAAM,QAAQ,EAAE,SAAS,OAAO;EAC5D,MAAM,SAAS,KAAK,MAAM,OAAO;EACjC,IAAI,CAAC,OAAO,gBAAgB,CAAC,OAAO,WAAW,OAAO;EACtD,OAAO;GAAE,cAAc,OAAO;GAAc,WAAW,OAAO;EAAU;CAC1E,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,2BACd,YACA,eACS;CACT,IAAI,CAAC,YAAY,OAAO;CAExB,QADc,WAAW,WAAW,SAAS,IAAI,WAAW,MAAM,CAAC,IAAI,gBACtD;AACnB;AAEA,SAAS,wBACP,MACA,cACyB;CACzB,OACE,KAAK,MACF,MACC,EAAE,aAAa,WACf,EAAE,WAAW,aACZ,EAAE,aAAa,sBAAsB,gBAAgB,EAAE,SAAS,aACrE,KAAK;AAET;AA4BA,eAAsB,qBACpB,SACA,SACA,gBACA,UAAkC,CAAC,GACkB;CACrD,MAAM,OAAO,MAAM,kBAAkB,OAAO;CAC5C,MAAM,MAAM,wBAAwB,MAAM,QAAQ,YAAY;CAC9D,IAAI,CAAC,KAAK,OAAO;EAAE,WAAW;EAAG,MAAM;CAAK;CAE5C,MAAM,OAAO,IAAI;CAEjB,MAAM,gBADY,cAAc,OAEtB,EAAE,OAAO,0BAA0B,IAAI,aAAa,kBAAkB;CAGhF,IAAI,OAAO,QAAQ,SAAS,KAAK,OAAO,aAAa,GACnD,OAAO;EAAE,WAAW;EAAG;CAAK;CAG9B,MAAM,iBAAiB,IAAI,aAAa,kBAAkB;CAE1D,MAAM,EACJ,gBACA,gBACA,sBAAsB,mBACtB,cAAc,OACZ;CAEJ,IAAI,CAAC,gBAAgB,OAAO;EAAE,WAAW;EAAG;CAAK;CAEjD,MAAM,WAAW,MAAM,eAAe,aAAa,cAAc;CACjE,IAAI,YAAY;CAEhB,KAAK,MAAM,OAAO,UAAU;EAC1B,IAAI,CAAC,gBAAgB;EACrB,IAAI;GACF,MAAM,OAAO,MAAM,eAAe,aAAa,IAAI,EAAE;GACrD,MAAM,YAAY,kBAAkB,KAAK;GAEzC,MAAM,oBAAoB,SAAS,MAAM;IACvC,uBAAM,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;IAC1C,MAAM;IACN,WAAW;IACX,MAAM,KAAK;IACX,SAAS,KAAK;IACd,SAAS,KAAK,KAAK,MAAM,GAAG,GAAG,KAAK;IACpC,WAAW,CAAC;IACZ;IACA,yBAAQ,IAAI,KAAK,GAAE,YAAY;GACjC,CAAC;GACD;EACF,QAAQ,CAER;CACF;CAGA,oBAAoB,SAAS,MAAM,EAAE,wBAAwB,QAAQ,UAAU,CAAC;CAGhF,MAAM,SAAS,KAAK,WAAW,MAAM,EAAE,OAAO,IAAI,EAAE;CACpD,IAAI,WAAW,IAAI;EACjB,KAAK,UAAU;GACb,GAAG,KAAK;GACR,iBAAiB,KAAK,QAAS,kBAAkB;GACjD,8BAAa,IAAI,KAAK,GAAE,YAAY;EACtC;EACA,MAAM,mBAAmB,SAAS,IAAI;CACxC;CAEA,OAAO;EAAE;EAAW;CAAK;AAC3B;AAOA,SAAgB,kBACd,aACA,WACA,YACS;CACT,OAAO,OAAO,SAA2B;EAQvC,MAAM,eAAe,OANnB,eACC,OAAO,OAAe,UAAkB;GACvC,MAAM,EAAE,uBAAuB,MAAM,OAAO;GAC5C,OAAO,mBAAmB,OAAO,KAAK;EACxC,IAEoC,aAAa,SAAS;EAC5D,OAAO;GACL,WAAW,IAAI,KAAK,OAAO,aAAa,UAAU,CAAC,EAAE,YAAY;GACjE,cAAc,EAAE,gBAAgB,aAAa,UAAU;EACzD;CACF;AACF"}
1
+ {"version":3,"file":"gmail-webhook-handler-BzOFbvgh.js","names":["b"],"sources":["../src/sync/gmail-webhook-handler.ts"],"sourcesContent":["import {\n readSubscriptions,\n writeSubscriptions,\n type PushSubscription,\n type RenewFn,\n} from \"./push-manager.js\";\nimport { updateSlugSyncState, readSyncState } from \"../fs/sync-state.js\";\nimport { appendInteraction } from \"../fs/interactions-writer.js\";\nimport type { HistoryMessage, WatchRegistration } from \"./gmail-push-watch.js\";\n\nexport interface GmailPubSubMessage {\n emailAddress: string;\n historyId: string;\n}\n\nexport function decodeGmailPubSubPayload(body: unknown): GmailPubSubMessage | null {\n try {\n const b = body as { message?: { data?: string } };\n const data = b?.message?.data;\n if (!data) return null;\n const decoded = Buffer.from(data, \"base64\").toString(\"utf-8\");\n const parsed = JSON.parse(decoded) as { emailAddress?: string; historyId?: string };\n if (!parsed.emailAddress || !parsed.historyId) return null;\n return { emailAddress: parsed.emailAddress, historyId: parsed.historyId };\n } catch {\n return null;\n }\n}\n\nexport function verifyGmailPubSubSignature(\n authHeader: string | undefined,\n expectedToken: string\n): boolean {\n if (!authHeader) return false;\n const token = authHeader.startsWith(\"Bearer \") ? authHeader.slice(7) : authHeader;\n return token === expectedToken;\n}\n\nfunction findSubscriptionByEmail(\n subs: PushSubscription[],\n emailAddress: string\n): PushSubscription | null {\n return (\n subs.find(\n (s) =>\n s.provider === \"gmail\" &&\n s.status === \"active\" &&\n (s.providerData.gmailEmailAddress === emailAddress || s.slug === emailAddress)\n ) ?? null\n );\n}\n\nexport type FetchHistoryFn = (\n accessToken: string,\n startHistoryId: string\n) => Promise<HistoryMessage[]>;\nexport type FetchMessageFn = (\n accessToken: string,\n messageId: string\n) => Promise<{\n id: string;\n threadId: string;\n subject: string;\n from: string;\n date: string;\n body: string;\n}>;\nexport type AppendInteractionFn = typeof appendInteraction;\n\nexport interface HandleGmailPushOptions {\n fetchHistoryFn?: FetchHistoryFn;\n fetchMessageFn?: FetchMessageFn;\n appendInteractionFn?: AppendInteractionFn;\n accessToken?: string;\n}\n\nexport { readSubscriptions };\n\nexport async function handleGmailPushEvent(\n dataDir: string,\n payload: GmailPubSubMessage,\n subscriptionId: string,\n options: HandleGmailPushOptions = {}\n): Promise<{ processed: number; slug: string | null }> {\n const subs = await readSubscriptions(dataDir);\n const sub = findSubscriptionByEmail(subs, payload.emailAddress);\n if (!sub) return { processed: 0, slug: null };\n\n const slug = sub.slug;\n const syncState = readSyncState(dataDir);\n const lastHistoryId =\n syncState[slug]?.lastGmailPushHistoryId ?? sub.providerData.gmailHistoryId ?? \"0\";\n\n // Skip if already processed\n if (BigInt(payload.historyId) <= BigInt(lastHistoryId)) {\n return { processed: 0, slug };\n }\n\n const startHistoryId = sub.providerData.gmailHistoryId ?? lastHistoryId;\n\n const {\n fetchHistoryFn,\n fetchMessageFn,\n appendInteractionFn = appendInteraction,\n accessToken = \"\",\n } = options;\n\n if (!fetchHistoryFn) return { processed: 0, slug };\n\n const messages = await fetchHistoryFn(accessToken, startHistoryId);\n let processed = 0;\n\n for (const msg of messages) {\n if (!fetchMessageFn) continue;\n try {\n const full = await fetchMessageFn(accessToken, msg.id);\n const sourceRef = `gmail://thread/${full.threadId}`;\n\n await appendInteractionFn(dataDir, slug, {\n date: new Date().toISOString().slice(0, 10),\n type: \"Email\",\n direction: \"inbound\",\n with: full.from,\n subject: full.subject,\n summary: full.body.slice(0, 300) || \"(no body)\",\n nextSteps: [],\n sourceRef,\n synced: new Date().toISOString(),\n });\n processed++;\n } catch {\n // Skip individual message errors\n }\n }\n\n // Update sync state\n updateSlugSyncState(dataDir, slug, { lastGmailPushHistoryId: payload.historyId });\n\n // Update subscription counters\n const subIdx = subs.findIndex((s) => s.id === sub.id);\n if (subIdx !== -1) {\n subs[subIdx] = {\n ...subs[subIdx]!,\n eventsProcessed: subs[subIdx]!.eventsProcessed + 1,\n lastEventAt: new Date().toISOString(),\n };\n await writeSubscriptions(dataDir, subs);\n }\n\n return { processed, slug };\n}\n\nexport type RegisterGmailWatchFn = (\n accessToken: string,\n topicName: string\n) => Promise<WatchRegistration>;\n\nexport function buildGmailRenewFn(\n accessToken: string,\n topicName: string,\n registerFn?: RegisterGmailWatchFn\n): RenewFn {\n return async (_sub: PushSubscription) => {\n const doRegister: RegisterGmailWatchFn =\n registerFn ??\n (async (token: string, topic: string) => {\n const { registerGmailWatch } = await import(\"./gmail-push-watch.js\");\n return registerGmailWatch(token, topic);\n });\n\n const registration = await doRegister(accessToken, topicName);\n return {\n expiresAt: new Date(Number(registration.expiration)).toISOString(),\n providerData: { gmailHistoryId: registration.historyId },\n };\n };\n}\n"],"mappings":";;;;AAeA,SAAgB,yBAAyB,MAA0C;CACjF,IAAI;EAEF,MAAM,OAAOA,MAAG,SAAS;EACzB,IAAI,CAAC,MAAM,OAAO;EAClB,MAAM,UAAU,OAAO,KAAK,MAAM,QAAQ,EAAE,SAAS,OAAO;EAC5D,MAAM,SAAS,KAAK,MAAM,OAAO;EACjC,IAAI,CAAC,OAAO,gBAAgB,CAAC,OAAO,WAAW,OAAO;EACtD,OAAO;GAAE,cAAc,OAAO;GAAc,WAAW,OAAO;EAAU;CAC1E,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,2BACd,YACA,eACS;CACT,IAAI,CAAC,YAAY,OAAO;CAExB,QADc,WAAW,WAAW,SAAS,IAAI,WAAW,MAAM,CAAC,IAAI,gBACtD;AACnB;AAEA,SAAS,wBACP,MACA,cACyB;CACzB,OACE,KAAK,MACF,MACC,EAAE,aAAa,WACf,EAAE,WAAW,aACZ,EAAE,aAAa,sBAAsB,gBAAgB,EAAE,SAAS,aACrE,KAAK;AAET;AA4BA,eAAsB,qBACpB,SACA,SACA,gBACA,UAAkC,CAAC,GACkB;CACrD,MAAM,OAAO,MAAM,kBAAkB,OAAO;CAC5C,MAAM,MAAM,wBAAwB,MAAM,QAAQ,YAAY;CAC9D,IAAI,CAAC,KAAK,OAAO;EAAE,WAAW;EAAG,MAAM;CAAK;CAE5C,MAAM,OAAO,IAAI;CAEjB,MAAM,gBADY,cAAc,OAEtB,EAAE,OAAO,0BAA0B,IAAI,aAAa,kBAAkB;CAGhF,IAAI,OAAO,QAAQ,SAAS,KAAK,OAAO,aAAa,GACnD,OAAO;EAAE,WAAW;EAAG;CAAK;CAG9B,MAAM,iBAAiB,IAAI,aAAa,kBAAkB;CAE1D,MAAM,EACJ,gBACA,gBACA,sBAAsB,mBACtB,cAAc,OACZ;CAEJ,IAAI,CAAC,gBAAgB,OAAO;EAAE,WAAW;EAAG;CAAK;CAEjD,MAAM,WAAW,MAAM,eAAe,aAAa,cAAc;CACjE,IAAI,YAAY;CAEhB,KAAK,MAAM,OAAO,UAAU;EAC1B,IAAI,CAAC,gBAAgB;EACrB,IAAI;GACF,MAAM,OAAO,MAAM,eAAe,aAAa,IAAI,EAAE;GACrD,MAAM,YAAY,kBAAkB,KAAK;GAEzC,MAAM,oBAAoB,SAAS,MAAM;IACvC,uBAAM,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;IAC1C,MAAM;IACN,WAAW;IACX,MAAM,KAAK;IACX,SAAS,KAAK;IACd,SAAS,KAAK,KAAK,MAAM,GAAG,GAAG,KAAK;IACpC,WAAW,CAAC;IACZ;IACA,yBAAQ,IAAI,KAAK,GAAE,YAAY;GACjC,CAAC;GACD;EACF,QAAQ,CAER;CACF;CAGA,oBAAoB,SAAS,MAAM,EAAE,wBAAwB,QAAQ,UAAU,CAAC;CAGhF,MAAM,SAAS,KAAK,WAAW,MAAM,EAAE,OAAO,IAAI,EAAE;CACpD,IAAI,WAAW,IAAI;EACjB,KAAK,UAAU;GACb,GAAG,KAAK;GACR,iBAAiB,KAAK,QAAS,kBAAkB;GACjD,8BAAa,IAAI,KAAK,GAAE,YAAY;EACtC;EACA,MAAM,mBAAmB,SAAS,IAAI;CACxC;CAEA,OAAO;EAAE;EAAW;CAAK;AAC3B;AAOA,SAAgB,kBACd,aACA,WACA,YACS;CACT,OAAO,OAAO,SAA2B;EAQvC,MAAM,eAAe,OANnB,eACC,OAAO,OAAe,UAAkB;GACvC,MAAM,EAAE,uBAAuB,MAAM,OAAO;GAC5C,OAAO,mBAAmB,OAAO,KAAK;EACxC,IAEoC,aAAa,SAAS;EAC5D,OAAO;GACL,WAAW,IAAI,KAAK,OAAO,aAAa,UAAU,CAAC,EAAE,YAAY;GACjE,cAAc,EAAE,gBAAgB,aAAa,UAAU;EACzD;CACF;AACF"}
@@ -1,3 +1,3 @@
1
1
  import "./push-manager-C0ECQgva.js";
2
- import { t as buildGmailRenewFn } from "./gmail-webhook-handler-kGKpbY9h.js";
2
+ import { t as buildGmailRenewFn } from "./gmail-webhook-handler-BzOFbvgh.js";
3
3
  export { buildGmailRenewFn };
@@ -1,4 +1,4 @@
1
- import { i as readInteractions, n as appendInteraction } from "./interactions-writer-DbSyI2rt.js";
1
+ import { i as readInteractions, n as appendInteraction } from "./interactions-writer-B8XAzdqR.js";
2
2
  import { n as indexInLanceDB } from "./lancedb-CuHKNsNZ.js";
3
3
  import path from "path";
4
4
  import fs from "fs";
@@ -102,4 +102,4 @@ async function syncGoogleDriveFiles(opts) {
102
102
  //#endregion
103
103
  export { syncGoogleDriveFiles };
104
104
 
105
- //# sourceMappingURL=google-drive-sync-D1n7WKZn.js.map
105
+ //# sourceMappingURL=google-drive-sync-B_I1d54Y.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"google-drive-sync-D1n7WKZn.js","names":[],"sources":["../src/sync/google-drive-sync.ts"],"sourcesContent":["import { appendInteraction } from \"../fs/interactions-writer.js\";\nimport { indexInLanceDB } from \"../core/lancedb.js\";\nimport { readInteractions } from \"../fs/interactions-writer.js\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface DriveFile {\n id: string;\n name: string;\n mimeType: string;\n webViewLink?: string;\n modifiedTime?: string;\n size?: string;\n}\n\nexport interface DriveFilesResponse {\n files: DriveFile[];\n nextPageToken?: string;\n}\n\nexport interface DriveSyncOptions {\n slug: string;\n dataDir: string;\n accessToken: string;\n customerName?: string; // If not provided, use slug\n maxFiles?: number;\n}\n\nexport interface DriveSyncResult {\n synced: number;\n skipped: number;\n errors: string[];\n}\n\nconst GOOGLE_DOC_MIME = \"application/vnd.google-apps.document\";\nconst DRIVE_API_BASE = \"https://www.googleapis.com/drive/v3\";\n\nexport async function syncGoogleDriveFiles(opts: DriveSyncOptions): Promise<DriveSyncResult> {\n const { slug, dataDir, accessToken } = opts;\n const searchName = opts.customerName ?? slug;\n const maxFiles = opts.maxFiles ?? 200;\n\n const result: DriveSyncResult = { synced: 0, skipped: 0, errors: [] };\n\n // Load existing interactions to detect already-synced files\n let existingInteractions = \"\";\n try {\n existingInteractions = await readInteractions(dataDir, slug);\n } catch {\n existingInteractions = \"\";\n }\n\n const encodedQuery = encodeURIComponent(\n `name contains \"${searchName}\" and mimeType!=\"application/vnd.google-apps.folder\"`\n );\n const fields = encodeURIComponent(\n \"files(id,name,mimeType,webViewLink,modifiedTime,size),nextPageToken\"\n );\n\n let pageToken: string | undefined;\n let totalFetched = 0;\n\n do {\n let url = `${DRIVE_API_BASE}/files?q=${encodedQuery}&fields=${fields}&pageSize=50`;\n if (pageToken) {\n url += `&pageToken=${encodeURIComponent(pageToken)}`;\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n } catch (err) {\n result.errors.push(`Drive API request failed: ${(err as Error).message}`);\n break;\n }\n\n if (!response.ok) {\n result.errors.push(\n `Drive API error ${response.status}: ${await response.text().catch(() => \"unknown\")}`\n );\n break;\n }\n\n let data: DriveFilesResponse;\n try {\n data = (await response.json()) as DriveFilesResponse;\n } catch (err) {\n result.errors.push(`Failed to parse Drive API response: ${(err as Error).message}`);\n break;\n }\n\n const files = data.files ?? [];\n pageToken = data.nextPageToken;\n\n for (const file of files) {\n if (totalFetched >= maxFiles) break;\n totalFetched++;\n\n const sourceRef = `google://drive/${file.id}`;\n\n // Skip already-synced files\n if (existingInteractions.includes(sourceRef)) {\n result.skipped++;\n continue;\n }\n\n try {\n if (file.mimeType === GOOGLE_DOC_MIME) {\n // Export Google Doc as plain text\n const exportUrl = `${DRIVE_API_BASE}/files/${file.id}/export?mimeType=${encodeURIComponent(\"text/plain\")}`;\n const exportRes = await fetch(exportUrl, {\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n\n if (!exportRes.ok) {\n result.errors.push(`Failed to export doc '${file.name}': HTTP ${exportRes.status}`);\n continue;\n }\n\n const text = await exportRes.text();\n\n // Save to attachments directory\n const attachmentsDir = path.join(dataDir, \"customers\", slug, \"attachments\");\n fs.mkdirSync(attachmentsDir, { recursive: true });\n const safeFilename = file.name.replace(/[/\\\\?%*:|\"<>]/g, \"-\") + \".txt\";\n fs.writeFileSync(path.join(attachmentsDir, safeFilename), text, \"utf-8\");\n\n // Append interaction\n await appendInteraction(dataDir, slug, {\n date: file.modifiedTime\n ? file.modifiedTime.slice(0, 10)\n : new Date().toISOString().slice(0, 10),\n type: \"Note\",\n with: \"Google Drive\",\n summary: `Attachment: ${file.name}`,\n nextSteps: [],\n sourceRef,\n synced: new Date().toISOString(),\n });\n\n // Index in LanceDB\n const lanceOpts: { date?: string; type?: string } = { type: \"attachment\" };\n if (file.modifiedTime) lanceOpts.date = file.modifiedTime.slice(0, 10);\n await indexInLanceDB(dataDir, slug, text.slice(0, 2000), sourceRef, lanceOpts);\n } else {\n // Non-Doc file: record via appendInteraction (no binary download)\n await appendInteraction(dataDir, slug, {\n date: file.modifiedTime\n ? file.modifiedTime.slice(0, 10)\n : new Date().toISOString().slice(0, 10),\n type: \"Note\",\n with: \"Google Drive\",\n summary: `Attachment: ${file.name}${file.webViewLink ? ` — ${file.webViewLink}` : \"\"}`,\n nextSteps: [],\n sourceRef,\n synced: new Date().toISOString(),\n });\n }\n\n result.synced++;\n existingInteractions += sourceRef; // prevent double-sync within same run\n } catch (err) {\n result.errors.push(`Error processing '${file.name}': ${(err as Error).message}`);\n }\n }\n\n if (totalFetched >= maxFiles) break;\n } while (pageToken);\n\n return result;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"google-drive-sync-B_I1d54Y.js","names":[],"sources":["../src/sync/google-drive-sync.ts"],"sourcesContent":["import { appendInteraction } from \"../fs/interactions-writer.js\";\nimport { indexInLanceDB } from \"../core/lancedb.js\";\nimport { readInteractions } from \"../fs/interactions-writer.js\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface DriveFile {\n id: string;\n name: string;\n mimeType: string;\n webViewLink?: string;\n modifiedTime?: string;\n size?: string;\n}\n\nexport interface DriveFilesResponse {\n files: DriveFile[];\n nextPageToken?: string;\n}\n\nexport interface DriveSyncOptions {\n slug: string;\n dataDir: string;\n accessToken: string;\n customerName?: string; // If not provided, use slug\n maxFiles?: number;\n}\n\nexport interface DriveSyncResult {\n synced: number;\n skipped: number;\n errors: string[];\n}\n\nconst GOOGLE_DOC_MIME = \"application/vnd.google-apps.document\";\nconst DRIVE_API_BASE = \"https://www.googleapis.com/drive/v3\";\n\nexport async function syncGoogleDriveFiles(opts: DriveSyncOptions): Promise<DriveSyncResult> {\n const { slug, dataDir, accessToken } = opts;\n const searchName = opts.customerName ?? slug;\n const maxFiles = opts.maxFiles ?? 200;\n\n const result: DriveSyncResult = { synced: 0, skipped: 0, errors: [] };\n\n // Load existing interactions to detect already-synced files\n let existingInteractions = \"\";\n try {\n existingInteractions = await readInteractions(dataDir, slug);\n } catch {\n existingInteractions = \"\";\n }\n\n const encodedQuery = encodeURIComponent(\n `name contains \"${searchName}\" and mimeType!=\"application/vnd.google-apps.folder\"`\n );\n const fields = encodeURIComponent(\n \"files(id,name,mimeType,webViewLink,modifiedTime,size),nextPageToken\"\n );\n\n let pageToken: string | undefined;\n let totalFetched = 0;\n\n do {\n let url = `${DRIVE_API_BASE}/files?q=${encodedQuery}&fields=${fields}&pageSize=50`;\n if (pageToken) {\n url += `&pageToken=${encodeURIComponent(pageToken)}`;\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n } catch (err) {\n result.errors.push(`Drive API request failed: ${(err as Error).message}`);\n break;\n }\n\n if (!response.ok) {\n result.errors.push(\n `Drive API error ${response.status}: ${await response.text().catch(() => \"unknown\")}`\n );\n break;\n }\n\n let data: DriveFilesResponse;\n try {\n data = (await response.json()) as DriveFilesResponse;\n } catch (err) {\n result.errors.push(`Failed to parse Drive API response: ${(err as Error).message}`);\n break;\n }\n\n const files = data.files ?? [];\n pageToken = data.nextPageToken;\n\n for (const file of files) {\n if (totalFetched >= maxFiles) break;\n totalFetched++;\n\n const sourceRef = `google://drive/${file.id}`;\n\n // Skip already-synced files\n if (existingInteractions.includes(sourceRef)) {\n result.skipped++;\n continue;\n }\n\n try {\n if (file.mimeType === GOOGLE_DOC_MIME) {\n // Export Google Doc as plain text\n const exportUrl = `${DRIVE_API_BASE}/files/${file.id}/export?mimeType=${encodeURIComponent(\"text/plain\")}`;\n const exportRes = await fetch(exportUrl, {\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n\n if (!exportRes.ok) {\n result.errors.push(`Failed to export doc '${file.name}': HTTP ${exportRes.status}`);\n continue;\n }\n\n const text = await exportRes.text();\n\n // Save to attachments directory\n const attachmentsDir = path.join(dataDir, \"customers\", slug, \"attachments\");\n fs.mkdirSync(attachmentsDir, { recursive: true });\n const safeFilename = file.name.replace(/[/\\\\?%*:|\"<>]/g, \"-\") + \".txt\";\n fs.writeFileSync(path.join(attachmentsDir, safeFilename), text, \"utf-8\");\n\n // Append interaction\n await appendInteraction(dataDir, slug, {\n date: file.modifiedTime\n ? file.modifiedTime.slice(0, 10)\n : new Date().toISOString().slice(0, 10),\n type: \"Note\",\n with: \"Google Drive\",\n summary: `Attachment: ${file.name}`,\n nextSteps: [],\n sourceRef,\n synced: new Date().toISOString(),\n });\n\n // Index in LanceDB\n const lanceOpts: { date?: string; type?: string } = { type: \"attachment\" };\n if (file.modifiedTime) lanceOpts.date = file.modifiedTime.slice(0, 10);\n await indexInLanceDB(dataDir, slug, text.slice(0, 2000), sourceRef, lanceOpts);\n } else {\n // Non-Doc file: record via appendInteraction (no binary download)\n await appendInteraction(dataDir, slug, {\n date: file.modifiedTime\n ? file.modifiedTime.slice(0, 10)\n : new Date().toISOString().slice(0, 10),\n type: \"Note\",\n with: \"Google Drive\",\n summary: `Attachment: ${file.name}${file.webViewLink ? ` — ${file.webViewLink}` : \"\"}`,\n nextSteps: [],\n sourceRef,\n synced: new Date().toISOString(),\n });\n }\n\n result.synced++;\n existingInteractions += sourceRef; // prevent double-sync within same run\n } catch (err) {\n result.errors.push(`Error processing '${file.name}': ${(err as Error).message}`);\n }\n }\n\n if (totalFetched >= maxFiles) break;\n } while (pageToken);\n\n return result;\n}\n"],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ //#region src/sync/converters/html.ts
2
+ /**
3
+ * Convert an HTML fragment to Markdown using Turndown with the GitHub-flavored
4
+ * plugin (tables, strikethrough, task lists). Turndown and the plugin are
5
+ * loaded lazily so they stay out of the light default code path.
6
+ */
7
+ async function htmlToMarkdown(html) {
8
+ const TurndownService = (await import("turndown")).default;
9
+ const { gfm } = await import("@joplin/turndown-plugin-gfm");
10
+ const service = new TurndownService({
11
+ headingStyle: "atx",
12
+ codeBlockStyle: "fenced",
13
+ bulletListMarker: "-"
14
+ });
15
+ service.use(gfm);
16
+ return service.turndown(html).trim();
17
+ }
18
+ const htmlConverter = {
19
+ name: "html",
20
+ extensions: [
21
+ "html",
22
+ "htm",
23
+ "xhtml"
24
+ ],
25
+ mimeTypes: ["text/html", "application/xhtml+xml"],
26
+ async convert(buffer) {
27
+ return {
28
+ markdown: await htmlToMarkdown(buffer.toString("utf-8")),
29
+ meta: { format: "html" }
30
+ };
31
+ }
32
+ };
33
+ //#endregion
34
+ export { htmlToMarkdown as n, htmlConverter as t };
35
+
36
+ //# sourceMappingURL=html-BaeOCZKE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-BaeOCZKE.js","names":[],"sources":["../src/sync/converters/html.ts"],"sourcesContent":["// src/sync/converters/html.ts\nimport type { Converter, ConversionResult } from \"./types.js\";\n\n/**\n * Convert an HTML fragment to Markdown using Turndown with the GitHub-flavored\n * plugin (tables, strikethrough, task lists). Turndown and the plugin are\n * loaded lazily so they stay out of the light default code path.\n */\nexport async function htmlToMarkdown(html: string): Promise<string> {\n const TurndownService = (await import(\"turndown\")).default;\n const { gfm } = await import(\"@joplin/turndown-plugin-gfm\");\n const service = new TurndownService({\n headingStyle: \"atx\",\n codeBlockStyle: \"fenced\",\n bulletListMarker: \"-\",\n });\n service.use(gfm);\n return service.turndown(html).trim();\n}\n\nexport const htmlConverter: Converter = {\n name: \"html\",\n extensions: [\"html\", \"htm\", \"xhtml\"],\n mimeTypes: [\"text/html\", \"application/xhtml+xml\"],\n async convert(buffer: Buffer): Promise<ConversionResult> {\n const markdown = await htmlToMarkdown(buffer.toString(\"utf-8\"));\n return { markdown, meta: { format: \"html\" } };\n },\n};\n"],"mappings":";;;;;;AAQA,eAAsB,eAAe,MAA+B;CAClE,MAAM,mBAAmB,MAAM,OAAO,aAAa;CACnD,MAAM,EAAE,QAAQ,MAAM,OAAO;CAC7B,MAAM,UAAU,IAAI,gBAAgB;EAClC,cAAc;EACd,gBAAgB;EAChB,kBAAkB;CACpB,CAAC;CACD,QAAQ,IAAI,GAAG;CACf,OAAO,QAAQ,SAAS,IAAI,EAAE,KAAK;AACrC;AAEA,MAAa,gBAA2B;CACtC,MAAM;CACN,YAAY;EAAC;EAAQ;EAAO;CAAO;CACnC,WAAW,CAAC,aAAa,uBAAuB;CAChD,MAAM,QAAQ,QAA2C;EAEvD,OAAO;GAAE,UAAA,MADc,eAAe,OAAO,SAAS,OAAO,CAAC;GAC3C,MAAM,EAAE,QAAQ,OAAO;EAAE;CAC9C;AACF"}
@@ -0,0 +1,47 @@
1
+ //#region src/sync/converters/html.ts
2
+ /**
3
+ * Convert an HTML fragment to Markdown using Turndown with the GitHub-flavored
4
+ * plugin (tables, strikethrough, task lists). Turndown and the plugin are
5
+ * loaded lazily so they stay out of the light default code path.
6
+ */
7
+ async function htmlToMarkdown(html) {
8
+ const TurndownService = (await import("turndown")).default;
9
+ const { gfm } = await import("@joplin/turndown-plugin-gfm");
10
+ const service = new TurndownService({
11
+ headingStyle: "atx",
12
+ codeBlockStyle: "fenced",
13
+ bulletListMarker: "-"
14
+ });
15
+ service.use(gfm);
16
+ return service.turndown(html).trim();
17
+ }
18
+ const htmlConverter = {
19
+ name: "html",
20
+ extensions: [
21
+ "html",
22
+ "htm",
23
+ "xhtml"
24
+ ],
25
+ mimeTypes: ["text/html", "application/xhtml+xml"],
26
+ async convert(buffer) {
27
+ return {
28
+ markdown: await htmlToMarkdown(buffer.toString("utf-8")),
29
+ meta: { format: "html" }
30
+ };
31
+ }
32
+ };
33
+ //#endregion
34
+ Object.defineProperty(exports, "htmlConverter", {
35
+ enumerable: true,
36
+ get: function() {
37
+ return htmlConverter;
38
+ }
39
+ });
40
+ Object.defineProperty(exports, "htmlToMarkdown", {
41
+ enumerable: true,
42
+ get: function() {
43
+ return htmlToMarkdown;
44
+ }
45
+ });
46
+
47
+ //# sourceMappingURL=html-CmOku6jS.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-CmOku6jS.cjs","names":[],"sources":["../src/sync/converters/html.ts"],"sourcesContent":["// src/sync/converters/html.ts\nimport type { Converter, ConversionResult } from \"./types.js\";\n\n/**\n * Convert an HTML fragment to Markdown using Turndown with the GitHub-flavored\n * plugin (tables, strikethrough, task lists). Turndown and the plugin are\n * loaded lazily so they stay out of the light default code path.\n */\nexport async function htmlToMarkdown(html: string): Promise<string> {\n const TurndownService = (await import(\"turndown\")).default;\n const { gfm } = await import(\"@joplin/turndown-plugin-gfm\");\n const service = new TurndownService({\n headingStyle: \"atx\",\n codeBlockStyle: \"fenced\",\n bulletListMarker: \"-\",\n });\n service.use(gfm);\n return service.turndown(html).trim();\n}\n\nexport const htmlConverter: Converter = {\n name: \"html\",\n extensions: [\"html\", \"htm\", \"xhtml\"],\n mimeTypes: [\"text/html\", \"application/xhtml+xml\"],\n async convert(buffer: Buffer): Promise<ConversionResult> {\n const markdown = await htmlToMarkdown(buffer.toString(\"utf-8\"));\n return { markdown, meta: { format: \"html\" } };\n },\n};\n"],"mappings":";;;;;;AAQA,eAAsB,eAAe,MAA+B;CAClE,MAAM,mBAAmB,MAAM,OAAO,aAAa;CACnD,MAAM,EAAE,QAAQ,MAAM,OAAO;CAC7B,MAAM,UAAU,IAAI,gBAAgB;EAClC,cAAc;EACd,gBAAgB;EAChB,kBAAkB;CACpB,CAAC;CACD,QAAQ,IAAI,GAAG;CACf,OAAO,QAAQ,SAAS,IAAI,EAAE,KAAK;AACrC;AAEA,MAAa,gBAA2B;CACtC,MAAM;CACN,YAAY;EAAC;EAAQ;EAAO;CAAO;CACnC,WAAW,CAAC,aAAa,uBAAuB;CAChD,MAAM,QAAQ,QAA2C;EAEvD,OAAO;GAAE,UAAA,MADc,eAAe,OAAO,SAAS,OAAO,CAAC;GAC3C,MAAM,EAAE,QAAQ,OAAO;EAAE;CAC9C;AACF"}
@@ -535,7 +535,7 @@ async function runHubSpotCsvImport(exportDir, dataDir, opts = {}) {
535
535
  }
536
536
  const engagementsPath = path.join(exportDir, "engagements.csv");
537
537
  if (fs.existsSync(engagementsPath) && progress.phases.engagements.status !== "done") if (!dryRun) {
538
- const { appendInteraction, InteractionDedup } = await import("./interactions-writer-RJB8SWf2.js");
538
+ const { appendInteraction, InteractionDedup } = await import("./interactions-writer-B2y-73lh.js");
539
539
  const dedup = new InteractionDedup(dataDir);
540
540
  progress.phases.engagements.status = "in-progress";
541
541
  writeProgress(dataDir, progress);
@@ -591,4 +591,4 @@ async function runHubSpotCsvImport(exportDir, dataDir, opts = {}) {
591
591
  //#endregion
592
592
  export { analyzeHubSpotExport, runHubSpotCsvImport };
593
593
 
594
- //# sourceMappingURL=import-hubspot-DB4n89jy.js.map
594
+ //# sourceMappingURL=import-hubspot-CTId9IGV.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"import-hubspot-DB4n89jy.js","names":[],"sources":["../src/core/csv-stream.ts","../src/fs/contacts-writer.ts","../src/commands/import-hubspot.ts"],"sourcesContent":["import fs from \"fs\";\nimport readline from \"readline\";\n\nexport interface CsvStreamOptions {\n delimiter?: string;\n}\n\nfunction parseCSVLine(line: string, delimiter = \",\"): string[] {\n const result: string[] = [];\n let current = \"\";\n let inQuotes = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i]!;\n if (ch === '\"') {\n if (inQuotes && line[i + 1] === '\"') {\n current += '\"';\n i++;\n } else {\n inQuotes = !inQuotes;\n }\n } else if (ch === delimiter && !inQuotes) {\n result.push(current.trim());\n current = \"\";\n } else {\n current += ch;\n }\n }\n result.push(current.trim());\n return result;\n}\n\n/** Streaming line-by-line CSV parser — O(1) memory for arbitrarily large files. */\nexport async function* streamCSV(\n filePath: string,\n opts: CsvStreamOptions = {}\n): AsyncGenerator<Record<string, string>> {\n const delimiter = opts.delimiter ?? \",\";\n const stream = fs.createReadStream(filePath, { encoding: \"utf-8\" });\n const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });\n\n let headers: string[] | null = null;\n\n for await (const line of rl) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const values = parseCSVLine(trimmed, delimiter);\n if (!headers) {\n headers = values.map((h) => h.replace(/^\"|\"$/g, \"\").trim());\n continue;\n }\n const row: Record<string, string> = {};\n headers.forEach((h, i) => {\n row[h] = values[i] ?? \"\";\n });\n yield row;\n }\n}\n\n/** Synchronous full-load parser — for small files (<10MB). */\nexport function parseCSVSync(content: string, delimiter = \",\"): Array<Record<string, string>> {\n const lines = content.trim().split(\"\\n\");\n if (lines.length < 2) return [];\n const headers = (lines[0] ?? \"\").split(delimiter).map((h) => h.trim().replace(/^\"|\"$/g, \"\"));\n return lines.slice(1).map((line) => {\n const values = parseCSVLine(line, delimiter);\n const row: Record<string, string> = {};\n headers.forEach((h, i) => {\n row[h] = values[i] ?? \"\";\n });\n return row;\n });\n}\n","import path from \"path\";\nimport { z } from \"zod\";\nimport { readJsonFile, writeJsonFile } from \"./json-store.js\";\nimport { assertSafeSlug } from \"./customer-dir.js\";\n\nexport const CustomerContactSchema = z.object({\n email: z.string().email(),\n name: z.string().min(1),\n title: z.string().optional(),\n phone: z.string().optional(),\n department: z.string().optional(),\n linkedinUrl: z.string().url().optional(),\n isPrimary: z.boolean().default(false),\n hubspotId: z.string().optional(),\n hubspotOwnerId: z.string().optional(),\n createdAt: z.string().optional(),\n});\n\nexport type CustomerContact = z.infer<typeof CustomerContactSchema>;\n\nfunction contactsPath(dataDir: string, slug: string): string {\n return path.join(dataDir, \"customers\", slug, \"contacts.json\");\n}\n\nexport function listContacts(dataDir: string, slug: string): CustomerContact[] {\n const raw = readJsonFile<unknown>(contactsPath(dataDir, slug), []);\n if (!Array.isArray(raw)) return [];\n return raw.flatMap((item) => {\n const r = CustomerContactSchema.safeParse(item);\n return r.success ? [r.data] : [];\n });\n}\n\nexport function upsertContact(dataDir: string, slug: string, contact: CustomerContact): void {\n assertSafeSlug(slug);\n const contacts = listContacts(dataDir, slug);\n const idx = contacts.findIndex((c) => c.email.toLowerCase() === contact.email.toLowerCase());\n if (idx >= 0) {\n contacts[idx] = { ...contacts[idx], ...contact };\n } else {\n contacts.push(contact);\n }\n // Ensure only one primary\n if (contact.isPrimary) {\n for (const c of contacts) {\n if (c.email.toLowerCase() !== contact.email.toLowerCase()) {\n c.isPrimary = false;\n }\n }\n }\n writeJsonFile(contactsPath(dataDir, slug), contacts);\n}\n\nexport function getPrimaryContact(dataDir: string, slug: string): CustomerContact | null {\n const contacts = listContacts(dataDir, slug);\n return contacts.find((c) => c.isPrimary) ?? contacts[0] ?? null;\n}\n","import fs from \"fs\";\nimport path from \"path\";\nimport { createHash } from \"crypto\";\nimport { streamCSV } from \"../core/csv-stream.js\";\nimport { escapeRegExp } from \"../core/regex.js\";\nimport { writeFileAtomic } from \"../fs/atomic-write.js\";\nimport { writeJsonFile } from \"../fs/json-store.js\";\nimport { upsertContact } from \"../fs/contacts-writer.js\";\nimport type { PipelineDeal } from \"../schemas/pipeline.js\";\nimport type { InteractionEntry } from \"../schemas/interaction.js\";\n\nexport interface HubSpotImportResult {\n companiesProcessed: number;\n contactsImported: number;\n dealsImported: number;\n engagementsImported: number;\n errors: string[];\n customPropertiesSaved: number;\n ownersResolved: number;\n}\n\nexport interface HubSpotImportOptions {\n dryRun?: boolean;\n ownerMap?: Record<string, string>; // hubspot email → dxcrm actor\n resume?: boolean;\n analyzeOnly?: boolean;\n}\n\nexport interface HubSpotAnalysis {\n companiesFound: number;\n contactsFound: number;\n dealsFound: number;\n engagementsFound: number;\n customPropertiesDetected: string[];\n ownersDetected: string[];\n unknownStages: string[];\n unmappedContacts: number;\n estimatedMinutes: number;\n}\n\n// ─── Stage + Type maps ────────────────────────────────────────────────────────\n\nconst STAGE_MAP: Record<string, PipelineDeal[\"stage\"]> = {\n appointmentscheduled: \"qualified\",\n qualifiedtobuy: \"qualified\",\n presentationscheduled: \"proposal\",\n decisionmakerboughtin: \"negotiation\",\n contractsent: \"negotiation\",\n closedwon: \"won\",\n closedlost: \"lost\",\n // Additional HubSpot stages\n prospecting: \"lead\",\n qualification: \"qualified\",\n proposal: \"proposal\",\n negotiation: \"negotiation\",\n closedwon2: \"won\",\n closedlost2: \"lost\",\n};\n\nconst TYPE_MAP: Record<string, InteractionEntry[\"type\"]> = {\n NOTE: \"Note\",\n CALL: \"Call\",\n EMAIL: \"Email\",\n MEETING: \"Meeting\",\n TASK: \"Note\",\n LINKEDIN_MESSAGE: \"Email\",\n WHATSAPP_MESSAGE: \"Email\",\n POSTAL_MAIL: \"Note\",\n};\n\n// Known HubSpot columns → dxcrm main_facts fields\nconst COMPANY_FIELD_MAP: Record<string, string> = {\n hs_annual_revenue: \"annual_revenue\",\n num_associated_contacts: \"contact_count\",\n industry: \"industry\",\n city: \"city\",\n country: \"country\",\n hs_lead_status: \"lead_status\",\n lifecyclestage: \"lifecycle_stage\",\n numberofemployees: \"employee_count\",\n phone: \"phone\",\n address: \"address\",\n zip: \"zip\",\n state: \"state\",\n};\n\nconst KNOWN_COMPANY_COLUMNS = new Set([\n \"name\",\n \"Name\",\n \"domain\",\n \"Domain\",\n \"website\",\n \"Website\",\n \"phone\",\n \"Phone\",\n \"address\",\n \"Address\",\n \"city\",\n \"City\",\n \"country\",\n \"Country\",\n \"state\",\n \"State\",\n \"zip\",\n \"Zip\",\n \"industry\",\n \"Industry\",\n \"numberofemployees\",\n \"Number of Employees\",\n \"hs_annual_revenue\",\n \"Annual Revenue\",\n \"lifecyclestage\",\n \"Lifecycle Stage\",\n \"hubspot_owner_email\",\n \"HubSpot Owner Email\",\n \"create_date\",\n \"createdate\",\n \"hs_lastmodifieddate\",\n \"hs_object_id\",\n \"Record ID\",\n]);\n\n// ─── Utilities ────────────────────────────────────────────────────────────────\n\nfunction slugify(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 60);\n}\n\nfunction hashStr(s: string): string {\n return createHash(\"sha256\").update(s).digest(\"hex\").slice(0, 16);\n}\n\nfunction coerceDate(raw: string): string {\n if (!raw) return new Date().toISOString().slice(0, 10);\n // HubSpot timestamps: \"2026-01-15 14:30:00 UTC\", \"1705318200000\" (ms), \"2026-01-15\"\n if (/^\\d{13}$/.test(raw.trim())) {\n return new Date(parseInt(raw, 10)).toISOString().slice(0, 10);\n }\n const d = new Date(raw.trim());\n if (!isNaN(d.getTime())) return d.toISOString().slice(0, 10);\n return new Date().toISOString().slice(0, 10);\n}\n\n// ─── Customer creation ────────────────────────────────────────────────────────\n\nfunction ensureCustomer(\n dataDir: string,\n name: string,\n domain: string,\n email: string,\n dryRun: boolean\n): { slug: string; created: boolean } {\n const slug = slugify(name || \"unknown\");\n const customerDir = path.join(dataDir, \"customers\", slug);\n const mainFactsPath = path.join(customerDir, \"main_facts.md\");\n if (fs.existsSync(mainFactsPath)) return { slug, created: false };\n if (dryRun) return { slug, created: true };\n\n fs.mkdirSync(customerDir, { recursive: true });\n const today = new Date().toISOString().slice(0, 10);\n const lines = [\n \"---\",\n `name: ${name}`,\n domain ? `domain: ${domain}` : null,\n email ? `email: ${email}` : null,\n \"relationship_stage: prospect\",\n `created: ${today}`,\n `updated: ${today}`,\n `last_touchpoint: ${today}`,\n \"tags: []\",\n \"currency: EUR\",\n \"---\",\n ]\n .filter(Boolean)\n .join(\"\\n\");\n writeFileAtomic(mainFactsPath, `${lines}\\n\\n# Customer: ${name}\\n`);\n writeFileAtomic(path.join(customerDir, \"interactions.md\"), `# Interactions — ${name}\\n\\n`);\n writeFileAtomic(path.join(customerDir, \"pipeline.md\"), `# Pipeline — ${name}\\n\\n`);\n writeJsonFile(path.join(customerDir, \"sources.json\"), {\n gmail: {\n query: domain\n ? `from:${domain} OR to:${domain}`\n : email\n ? `from:${email} OR to:${email}`\n : \"\",\n enabled: true,\n },\n transcripts: { paths: [], extensions: [\".txt\", \".vtt\"], enabled: false },\n });\n return { slug, created: true };\n}\n\nfunction readMainFactsRaw(dataDir: string, slug: string): string {\n const p = path.join(dataDir, \"customers\", slug, \"main_facts.md\");\n return fs.existsSync(p) ? (fs.readFileSync(p, \"utf-8\") as string) : \"\";\n}\n\n/** Patch several frontmatter fields in main_facts.md with a single read+write. */\nfunction updateMainFactsFields(\n dataDir: string,\n slug: string,\n fields: Record<string, string | undefined>\n): void {\n const entries = Object.entries(fields).filter(\n (e): e is [string, string] => e[1] !== undefined && e[1] !== \"\"\n );\n if (entries.length === 0) return;\n const p = path.join(dataDir, \"customers\", slug, \"main_facts.md\");\n if (!fs.existsSync(p)) return;\n let content = fs.readFileSync(p, \"utf-8\") as string;\n for (const [field, value] of entries) {\n const regex = new RegExp(`^${escapeRegExp(field)}:.*$`, \"m\");\n if (regex.test(content)) {\n content = content.replace(regex, `${field}: ${value}`);\n } else {\n const firstDash = content.indexOf(\"---\");\n const secondDash = content.indexOf(\"---\", firstDash + 3);\n if (secondDash >= 0) {\n content = content.slice(0, secondDash) + `${field}: ${value}\\n` + content.slice(secondDash);\n }\n }\n }\n writeFileAtomic(p, content);\n}\n\n// ─── Custom Properties ────────────────────────────────────────────────────────\n\nfunction saveCustomProperties(dataDir: string, slug: string, props: Record<string, string>): void {\n if (Object.keys(props).length === 0) return;\n const p = path.join(dataDir, \"customers\", slug, \"custom_properties.json\");\n let existing: Record<string, unknown> = {};\n if (fs.existsSync(p)) {\n try {\n existing = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as Record<string, unknown>;\n } catch {\n existing = {};\n }\n }\n const merged = {\n source: \"hubspot-import\",\n importedAt: new Date().toISOString(),\n properties: { ...((existing[\"properties\"] as Record<string, string>) ?? {}), ...props },\n };\n writeJsonFile(p, merged);\n}\n\n// ─── Progress / Resume ────────────────────────────────────────────────────────\n\ninterface ImportProgress {\n importId: string;\n source: string;\n startedAt: string;\n phases: {\n companies: { status: \"done\" | \"in-progress\" | \"pending\"; processed: number };\n contacts: { status: \"done\" | \"in-progress\" | \"pending\"; processed: number };\n deals: { status: \"done\" | \"in-progress\" | \"pending\"; processed: number };\n engagements: { status: \"done\" | \"in-progress\" | \"pending\"; processed: number };\n };\n}\n\nfunction progressPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"import-progress.json\");\n}\n\nfunction readProgress(dataDir: string): ImportProgress | null {\n const p = progressPath(dataDir);\n if (!fs.existsSync(p)) return null;\n try {\n return JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as ImportProgress;\n } catch {\n return null;\n }\n}\n\nfunction writeProgress(dataDir: string, progress: ImportProgress): void {\n fs.mkdirSync(path.dirname(progressPath(dataDir)), { recursive: true });\n writeJsonFile(progressPath(dataDir), progress);\n}\n\nfunction clearProgress(dataDir: string): void {\n const p = progressPath(dataDir);\n if (fs.existsSync(p)) fs.unlinkSync(p);\n}\n\n// ─── Analyze ──────────────────────────────────────────────────────────────────\n\nexport async function analyzeHubSpotExport(exportDir: string): Promise<HubSpotAnalysis> {\n const analysis: HubSpotAnalysis = {\n companiesFound: 0,\n contactsFound: 0,\n dealsFound: 0,\n engagementsFound: 0,\n customPropertiesDetected: [],\n ownersDetected: [],\n unknownStages: [],\n unmappedContacts: 0,\n estimatedMinutes: 0,\n };\n\n const customProps = new Set<string>();\n const owners = new Set<string>();\n const unknownStages = new Set<string>();\n const companyNames = new Set<string>();\n\n // Companies\n const companiesPath = path.join(exportDir, \"companies.csv\");\n if (fs.existsSync(companiesPath)) {\n for await (const row of streamCSV(companiesPath)) {\n analysis.companiesFound++;\n const name = (row[\"name\"] ?? row[\"Name\"] ?? \"\").trim();\n if (name) companyNames.add(name.toLowerCase());\n const owner = row[\"hubspot_owner_email\"] ?? row[\"HubSpot Owner Email\"] ?? \"\";\n if (owner) owners.add(owner);\n // Detect custom columns\n for (const key of Object.keys(row)) {\n if (!KNOWN_COMPANY_COLUMNS.has(key) && row[key]) customProps.add(key);\n }\n }\n }\n\n // Contacts\n const contactsPath = path.join(exportDir, \"contacts.csv\");\n if (fs.existsSync(contactsPath)) {\n for await (const row of streamCSV(contactsPath)) {\n analysis.contactsFound++;\n const company = (row[\"company\"] ?? row[\"Company\"] ?? row[\"associated_company\"] ?? \"\").trim();\n if (company && !companyNames.has(company.toLowerCase())) analysis.unmappedContacts++;\n const owner = row[\"contact_owner\"] ?? row[\"Contact Owner\"] ?? \"\";\n if (owner) owners.add(owner);\n }\n }\n\n // Deals\n const dealsPath = path.join(exportDir, \"deals.csv\");\n if (fs.existsSync(dealsPath)) {\n for await (const row of streamCSV(dealsPath)) {\n analysis.dealsFound++;\n const stage = (row[\"dealstage\"] ?? row[\"Deal Stage\"] ?? \"\").trim().toLowerCase();\n if (stage && !STAGE_MAP[stage]) unknownStages.add(stage);\n }\n }\n\n // Engagements\n const engagementsPath = path.join(exportDir, \"engagements.csv\");\n if (fs.existsSync(engagementsPath)) {\n for await (const _row of streamCSV(engagementsPath)) {\n analysis.engagementsFound++;\n }\n }\n\n analysis.customPropertiesDetected = Array.from(customProps).slice(0, 50);\n analysis.ownersDetected = Array.from(owners);\n analysis.unknownStages = Array.from(unknownStages);\n\n const totalRows =\n analysis.companiesFound +\n analysis.contactsFound +\n analysis.dealsFound +\n analysis.engagementsFound;\n analysis.estimatedMinutes = Math.ceil(totalRows / 2000); // ~2000 rows/min\n\n return analysis;\n}\n\n// ─── Main Import ──────────────────────────────────────────────────────────────\n\nexport async function runHubSpotCsvImport(\n exportDir: string,\n dataDir: string,\n opts: HubSpotImportOptions = {}\n): Promise<HubSpotImportResult> {\n const result: HubSpotImportResult = {\n companiesProcessed: 0,\n contactsImported: 0,\n dealsImported: 0,\n engagementsImported: 0,\n errors: [],\n customPropertiesSaved: 0,\n ownersResolved: 0,\n };\n\n const dryRun = opts.dryRun ?? false;\n const ownerMap = opts.ownerMap ?? {};\n\n // Resume handling\n let progress: ImportProgress | null = null;\n if (opts.resume) {\n progress = readProgress(dataDir);\n if (progress) {\n console.error(`[import] Resuming import ${progress.importId}...`);\n }\n }\n\n if (!progress) {\n progress = {\n importId: `hs-import-${new Date().toISOString().replace(/[:.]/g, \"-\").slice(0, 19)}`,\n source: exportDir,\n startedAt: new Date().toISOString(),\n phases: {\n companies: { status: \"pending\", processed: 0 },\n contacts: { status: \"pending\", processed: 0 },\n deals: { status: \"pending\", processed: 0 },\n engagements: { status: \"pending\", processed: 0 },\n },\n };\n }\n\n const companySlugMap = new Map<string, string>(); // name.lower → slug\n const emailSlugMap = new Map<string, string>(); // email.lower → slug\n\n // ── Phase 1: Companies ──────────────────────────────────────────────────────\n const companiesPath = path.join(exportDir, \"companies.csv\");\n if (fs.existsSync(companiesPath) && progress.phases.companies.status !== \"done\") {\n progress.phases.companies.status = \"in-progress\";\n if (!dryRun) writeProgress(dataDir, progress);\n\n for await (const row of streamCSV(companiesPath)) {\n const name = (row[\"name\"] ?? row[\"Name\"] ?? \"\").trim();\n if (!name) continue;\n\n const domain = (\n row[\"domain\"] ??\n row[\"Domain\"] ??\n row[\"website\"] ??\n row[\"Website\"] ??\n \"\"\n ).trim();\n const hubspotId = (row[\"hs_object_id\"] ?? row[\"Record ID\"] ?? \"\").trim();\n\n try {\n const { slug, created } = ensureCustomer(dataDir, name, domain, \"\", dryRun);\n companySlugMap.set(name.toLowerCase(), slug);\n result.companiesProcessed++;\n\n if (!dryRun && created) {\n // Map known fields + owner + HubSpot id, batched into one read+write.\n const factsPatch: Record<string, string | undefined> = {};\n for (const [hsKey, dxKey] of Object.entries(COMPANY_FIELD_MAP)) {\n const val = row[hsKey] ?? \"\";\n if (val) factsPatch[dxKey] = val;\n }\n\n const ownerEmail = row[\"hubspot_owner_email\"] ?? row[\"HubSpot Owner Email\"] ?? \"\";\n if (ownerEmail && ownerMap[ownerEmail]) {\n factsPatch[\"assigned_rep\"] = ownerMap[ownerEmail]!;\n result.ownersResolved++;\n }\n\n if (hubspotId) factsPatch[\"hubspot_company_id\"] = hubspotId;\n\n updateMainFactsFields(dataDir, slug, factsPatch);\n\n // Custom properties — everything not in known columns\n const customProps: Record<string, string> = {};\n for (const [key, val] of Object.entries(row)) {\n if (!KNOWN_COMPANY_COLUMNS.has(key) && val) customProps[key] = val;\n }\n if (Object.keys(customProps).length > 0) {\n saveCustomProperties(dataDir, slug, customProps);\n result.customPropertiesSaved += Object.keys(customProps).length;\n }\n }\n } catch (err) {\n result.errors.push(`Company '${name}': ${(err as Error).message}`);\n }\n\n progress.phases.companies.processed++;\n }\n\n progress.phases.companies.status = \"done\";\n if (!dryRun) writeProgress(dataDir, progress);\n } else if (progress.phases.companies.status === \"done\") {\n // Rebuild maps from disk for resume\n const customersDir = path.join(dataDir, \"customers\");\n if (fs.existsSync(customersDir)) {\n for (const slug of fs.readdirSync(customersDir)) {\n const mf = path.join(customersDir, slug, \"main_facts.md\");\n if (!fs.existsSync(mf)) continue;\n const content = fs.readFileSync(mf, \"utf-8\") as string;\n const nameMatch = content.match(/^name:\\s*(.+)$/m);\n if (nameMatch?.[1]) companySlugMap.set(nameMatch[1].trim().toLowerCase(), slug);\n }\n }\n }\n\n // ── Phase 2: Contacts ───────────────────────────────────────────────────────\n const contactsPath = path.join(exportDir, \"contacts.csv\");\n if (fs.existsSync(contactsPath) && progress.phases.contacts.status !== \"done\") {\n progress.phases.contacts.status = \"in-progress\";\n if (!dryRun) writeProgress(dataDir, progress);\n\n for await (const row of streamCSV(contactsPath)) {\n const firstName = (row[\"firstname\"] ?? row[\"First Name\"] ?? \"\").trim();\n const lastName = (row[\"lastname\"] ?? row[\"Last Name\"] ?? \"\").trim();\n const email = (row[\"email\"] ?? row[\"Email\"] ?? \"\").trim();\n const companyName = (\n row[\"company\"] ??\n row[\"Company\"] ??\n row[\"associated_company\"] ??\n row[\"Associated Company\"] ??\n \"\"\n ).trim();\n const phone = (row[\"phone\"] ?? row[\"Phone\"] ?? row[\"mobilephone\"] ?? \"\").trim();\n const title = (row[\"jobtitle\"] ?? row[\"Job Title\"] ?? \"\").trim();\n const department = (row[\"department\"] ?? row[\"Department\"] ?? \"\").trim();\n const hubspotId = (row[\"vid\"] ?? row[\"Contact ID\"] ?? row[\"hs_object_id\"] ?? \"\").trim();\n\n let slug = companySlugMap.get(companyName.toLowerCase());\n\n if (!slug && companyName) {\n const domain = (row[\"website\"] ?? \"\").trim();\n try {\n const { slug: newSlug, created } = ensureCustomer(\n dataDir,\n companyName,\n domain,\n email,\n dryRun\n );\n slug = newSlug;\n companySlugMap.set(companyName.toLowerCase(), newSlug);\n if (created) result.companiesProcessed++;\n } catch (err) {\n result.errors.push(`Auto-company '${companyName}': ${(err as Error).message}`);\n }\n }\n\n if (!slug) continue;\n\n if (!dryRun) {\n const contactName = [firstName, lastName].filter(Boolean).join(\" \");\n const isFirst = !fs.existsSync(path.join(dataDir, \"customers\", slug, \"contacts.json\"));\n\n // Multi-contact support\n if (email || contactName) {\n const contactEntry = {\n email: email || `${slugify(contactName)}@unknown.local`,\n name: contactName || email,\n ...(title ? { title } : {}),\n ...(phone ? { phone } : {}),\n ...(department ? { department } : {}),\n ...(hubspotId ? { hubspotId } : {}),\n isPrimary: isFirst,\n createdAt: new Date().toISOString(),\n };\n try {\n upsertContact(dataDir, slug, contactEntry);\n } catch {\n /* skip invalid */\n }\n }\n\n // Update main_facts primary contact (first contact only) — batched into\n // a single read+write instead of one per field.\n const existing = readMainFactsRaw(dataDir, slug);\n const factsPatch: Record<string, string | undefined> = {};\n if (email && !existing.includes(\"email:\")) factsPatch[\"email\"] = email;\n if (phone && !existing.includes(\"phone:\")) factsPatch[\"phone\"] = phone;\n if (contactName && !existing.includes(\"primary_contact:\"))\n factsPatch[\"primary_contact\"] = contactName;\n\n // Owner mapping\n const ownerEmail = row[\"contact_owner\"] ?? row[\"Contact Owner\"] ?? \"\";\n if (ownerEmail && ownerMap[ownerEmail] && !existing.includes(\"assigned_rep:\")) {\n factsPatch[\"assigned_rep\"] = ownerMap[ownerEmail]!;\n result.ownersResolved++;\n }\n updateMainFactsFields(dataDir, slug, factsPatch);\n }\n\n if (email) emailSlugMap.set(email.toLowerCase(), slug);\n result.contactsImported++;\n progress.phases.contacts.processed++;\n }\n\n progress.phases.contacts.status = \"done\";\n if (!dryRun) writeProgress(dataDir, progress);\n }\n\n // ── Phase 3: Deals ──────────────────────────────────────────────────────────\n const dealsPath = path.join(exportDir, \"deals.csv\");\n if (fs.existsSync(dealsPath) && progress.phases.deals.status !== \"done\") {\n if (!dryRun) {\n const { upsertDeal } = await import(\"../fs/pipeline-writer.js\");\n progress.phases.deals.status = \"in-progress\";\n writeProgress(dataDir, progress);\n\n for await (const row of streamCSV(dealsPath)) {\n const dealName = (row[\"dealname\"] ?? row[\"Deal Name\"] ?? row[\"name\"] ?? \"\").trim();\n if (!dealName) continue;\n\n const companyName = (\n row[\"associated_company\"] ??\n row[\"Associated Company\"] ??\n row[\"company\"] ??\n \"\"\n ).trim();\n const amountStr = (row[\"amount\"] ?? row[\"Amount\"] ?? \"0\").trim().replace(/[^0-9.]/g, \"\");\n const stageRaw = (row[\"dealstage\"] ?? row[\"Deal Stage\"] ?? \"\").trim().toLowerCase();\n const closeDateRaw = (\n row[\"closedate\"] ??\n row[\"Close Date\"] ??\n row[\"close_date\"] ??\n \"\"\n ).trim();\n const currency = (row[\"deal_currency_code\"] ?? row[\"Currency\"] ?? \"EUR\").trim();\n const dealId = (row[\"hs_deal_id\"] ?? row[\"hs_object_id\"] ?? row[\"Record ID\"] ?? \"\").trim();\n const ownerEmail = (row[\"hubspot_owner_email\"] ?? row[\"HubSpot Owner Email\"] ?? \"\").trim();\n const description = (row[\"description\"] ?? row[\"Description\"] ?? \"\").trim();\n\n const slug =\n companySlugMap.get(companyName.toLowerCase()) ?? slugify(companyName || \"unknown\");\n const stage = STAGE_MAP[stageRaw] ?? \"qualified\";\n const amount = parseFloat(amountStr) || 0;\n const closeDate = coerceDate(closeDateRaw);\n\n const notesParts: string[] = [];\n if (dealId) notesParts.push(`hubspot://deal/${dealId}`);\n if (description) notesParts.push(description.slice(0, 200));\n if (ownerEmail && ownerMap[ownerEmail]) notesParts.push(`owner:${ownerMap[ownerEmail]}`);\n\n const deal: PipelineDeal = {\n name: dealName,\n stage,\n value: amount,\n currency: currency || \"EUR\",\n probability: stage === \"won\" ? 1 : stage === \"lost\" ? 0 : 0.5,\n close_date: closeDate,\n updated: new Date().toISOString().slice(0, 10),\n ...(notesParts.length > 0 ? { notes: notesParts.join(\" | \") } : {}),\n };\n\n try {\n await upsertDeal(dataDir, slug, deal);\n result.dealsImported++;\n } catch (err) {\n result.errors.push(`Deal '${dealName}': ${(err as Error).message}`);\n }\n\n progress.phases.deals.processed++;\n }\n\n progress.phases.deals.status = \"done\";\n writeProgress(dataDir, progress);\n } else {\n // Dry run count\n for await (const row of streamCSV(dealsPath)) {\n if ((row[\"dealname\"] ?? row[\"name\"] ?? \"\").trim()) result.dealsImported++;\n }\n progress.phases.deals.status = \"done\";\n }\n }\n\n // ── Phase 4: Engagements ────────────────────────────────────────────────────\n const engagementsPath = path.join(exportDir, \"engagements.csv\");\n if (fs.existsSync(engagementsPath) && progress.phases.engagements.status !== \"done\") {\n if (!dryRun) {\n const { appendInteraction, InteractionDedup } = await import(\"../fs/interactions-writer.js\");\n const dedup = new InteractionDedup(dataDir);\n progress.phases.engagements.status = \"in-progress\";\n writeProgress(dataDir, progress);\n\n for await (const row of streamCSV(engagementsPath)) {\n const engType = (\n row[\"engagement_type\"] ??\n row[\"Engagement Type\"] ??\n row[\"type\"] ??\n row[\"Type\"] ??\n \"NOTE\"\n )\n .trim()\n .toUpperCase();\n const timestamp = (\n row[\"hs_timestamp\"] ??\n row[\"Timestamp\"] ??\n row[\"date\"] ??\n row[\"createdate\"] ??\n \"\"\n ).trim();\n const body = (\n row[\"hs_body_preview\"] ??\n row[\"Body\"] ??\n row[\"notes\"] ??\n row[\"Notes\"] ??\n row[\"hs_note_body\"] ??\n \"\"\n ).trim();\n const subject = (row[\"subject\"] ?? row[\"Subject\"] ?? \"\").trim();\n const contactEmail = (\n row[\"associated_contact_email\"] ??\n row[\"Contact Email\"] ??\n row[\"from_email\"] ??\n \"\"\n )\n .trim()\n .toLowerCase();\n const engId = (\n row[\"id\"] ??\n row[\"engagement_id\"] ??\n row[\"hs_object_id\"] ??\n hashStr(timestamp + body)\n ).trim();\n const callDuration = (row[\"call_duration\"] ?? row[\"hs_call_duration\"] ?? \"\").trim();\n const callOutcome = (row[\"call_outcome\"] ?? row[\"hs_call_disposition\"] ?? \"\").trim();\n const callRecording = (\n row[\"call_recording_url\"] ??\n row[\"hs_call_recording_url\"] ??\n \"\"\n ).trim();\n\n const slug =\n emailSlugMap.get(contactEmail) ??\n companySlugMap.get((row[\"associated_company\"] ?? \"\").toLowerCase().trim());\n if (!slug) continue;\n\n const sourceRef = `hubspot://engagement/${engId}`;\n try {\n if (await dedup.seen(slug, sourceRef)) continue;\n\n const date = coerceDate(timestamp);\n const type = TYPE_MAP[engType] ?? \"Note\";\n\n // Build rich summary\n const summaryParts: string[] = [];\n if (subject) summaryParts.push(`Subject: ${subject}`);\n if (body) summaryParts.push(body.slice(0, 500));\n if (callDuration) summaryParts.push(`Duration: ${callDuration}s`);\n if (callOutcome) summaryParts.push(`Outcome: ${callOutcome}`);\n if (callRecording) summaryParts.push(`Recording: ${callRecording}`);\n\n const summary = summaryParts.join(\" | \") || `${type} imported from HubSpot`;\n\n await appendInteraction(dataDir, slug, {\n date,\n type,\n with: contactEmail || slug,\n summary,\n nextSteps: [],\n sourceRef,\n synced: new Date().toISOString(),\n });\n dedup.markAppended(slug, sourceRef);\n result.engagementsImported++;\n } catch (err) {\n result.errors.push(`Engagement ${engId}: ${(err as Error).message}`);\n }\n\n progress.phases.engagements.processed++;\n }\n\n progress.phases.engagements.status = \"done\";\n writeProgress(dataDir, progress);\n } else {\n for await (const _row of streamCSV(engagementsPath)) result.engagementsImported++;\n progress.phases.engagements.status = \"done\";\n }\n }\n\n // Done — clear progress file\n if (!dryRun) clearProgress(dataDir);\n\n return result;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"import-hubspot-CTId9IGV.js","names":[],"sources":["../src/core/csv-stream.ts","../src/fs/contacts-writer.ts","../src/commands/import-hubspot.ts"],"sourcesContent":["import fs from \"fs\";\nimport readline from \"readline\";\n\nexport interface CsvStreamOptions {\n delimiter?: string;\n}\n\nfunction parseCSVLine(line: string, delimiter = \",\"): string[] {\n const result: string[] = [];\n let current = \"\";\n let inQuotes = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i]!;\n if (ch === '\"') {\n if (inQuotes && line[i + 1] === '\"') {\n current += '\"';\n i++;\n } else {\n inQuotes = !inQuotes;\n }\n } else if (ch === delimiter && !inQuotes) {\n result.push(current.trim());\n current = \"\";\n } else {\n current += ch;\n }\n }\n result.push(current.trim());\n return result;\n}\n\n/** Streaming line-by-line CSV parser — O(1) memory for arbitrarily large files. */\nexport async function* streamCSV(\n filePath: string,\n opts: CsvStreamOptions = {}\n): AsyncGenerator<Record<string, string>> {\n const delimiter = opts.delimiter ?? \",\";\n const stream = fs.createReadStream(filePath, { encoding: \"utf-8\" });\n const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });\n\n let headers: string[] | null = null;\n\n for await (const line of rl) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const values = parseCSVLine(trimmed, delimiter);\n if (!headers) {\n headers = values.map((h) => h.replace(/^\"|\"$/g, \"\").trim());\n continue;\n }\n const row: Record<string, string> = {};\n headers.forEach((h, i) => {\n row[h] = values[i] ?? \"\";\n });\n yield row;\n }\n}\n\n/** Synchronous full-load parser — for small files (<10MB). */\nexport function parseCSVSync(content: string, delimiter = \",\"): Array<Record<string, string>> {\n const lines = content.trim().split(\"\\n\");\n if (lines.length < 2) return [];\n const headers = (lines[0] ?? \"\").split(delimiter).map((h) => h.trim().replace(/^\"|\"$/g, \"\"));\n return lines.slice(1).map((line) => {\n const values = parseCSVLine(line, delimiter);\n const row: Record<string, string> = {};\n headers.forEach((h, i) => {\n row[h] = values[i] ?? \"\";\n });\n return row;\n });\n}\n","import path from \"path\";\nimport { z } from \"zod\";\nimport { readJsonFile, writeJsonFile } from \"./json-store.js\";\nimport { assertSafeSlug } from \"./customer-dir.js\";\n\nexport const CustomerContactSchema = z.object({\n email: z.string().email(),\n name: z.string().min(1),\n title: z.string().optional(),\n phone: z.string().optional(),\n department: z.string().optional(),\n linkedinUrl: z.string().url().optional(),\n isPrimary: z.boolean().default(false),\n hubspotId: z.string().optional(),\n hubspotOwnerId: z.string().optional(),\n createdAt: z.string().optional(),\n});\n\nexport type CustomerContact = z.infer<typeof CustomerContactSchema>;\n\nfunction contactsPath(dataDir: string, slug: string): string {\n return path.join(dataDir, \"customers\", slug, \"contacts.json\");\n}\n\nexport function listContacts(dataDir: string, slug: string): CustomerContact[] {\n const raw = readJsonFile<unknown>(contactsPath(dataDir, slug), []);\n if (!Array.isArray(raw)) return [];\n return raw.flatMap((item) => {\n const r = CustomerContactSchema.safeParse(item);\n return r.success ? [r.data] : [];\n });\n}\n\nexport function upsertContact(dataDir: string, slug: string, contact: CustomerContact): void {\n assertSafeSlug(slug);\n const contacts = listContacts(dataDir, slug);\n const idx = contacts.findIndex((c) => c.email.toLowerCase() === contact.email.toLowerCase());\n if (idx >= 0) {\n contacts[idx] = { ...contacts[idx], ...contact };\n } else {\n contacts.push(contact);\n }\n // Ensure only one primary\n if (contact.isPrimary) {\n for (const c of contacts) {\n if (c.email.toLowerCase() !== contact.email.toLowerCase()) {\n c.isPrimary = false;\n }\n }\n }\n writeJsonFile(contactsPath(dataDir, slug), contacts);\n}\n\nexport function getPrimaryContact(dataDir: string, slug: string): CustomerContact | null {\n const contacts = listContacts(dataDir, slug);\n return contacts.find((c) => c.isPrimary) ?? contacts[0] ?? null;\n}\n","import fs from \"fs\";\nimport path from \"path\";\nimport { createHash } from \"crypto\";\nimport { streamCSV } from \"../core/csv-stream.js\";\nimport { escapeRegExp } from \"../core/regex.js\";\nimport { writeFileAtomic } from \"../fs/atomic-write.js\";\nimport { writeJsonFile } from \"../fs/json-store.js\";\nimport { upsertContact } from \"../fs/contacts-writer.js\";\nimport type { PipelineDeal } from \"../schemas/pipeline.js\";\nimport type { InteractionEntry } from \"../schemas/interaction.js\";\n\nexport interface HubSpotImportResult {\n companiesProcessed: number;\n contactsImported: number;\n dealsImported: number;\n engagementsImported: number;\n errors: string[];\n customPropertiesSaved: number;\n ownersResolved: number;\n}\n\nexport interface HubSpotImportOptions {\n dryRun?: boolean;\n ownerMap?: Record<string, string>; // hubspot email → dxcrm actor\n resume?: boolean;\n analyzeOnly?: boolean;\n}\n\nexport interface HubSpotAnalysis {\n companiesFound: number;\n contactsFound: number;\n dealsFound: number;\n engagementsFound: number;\n customPropertiesDetected: string[];\n ownersDetected: string[];\n unknownStages: string[];\n unmappedContacts: number;\n estimatedMinutes: number;\n}\n\n// ─── Stage + Type maps ────────────────────────────────────────────────────────\n\nconst STAGE_MAP: Record<string, PipelineDeal[\"stage\"]> = {\n appointmentscheduled: \"qualified\",\n qualifiedtobuy: \"qualified\",\n presentationscheduled: \"proposal\",\n decisionmakerboughtin: \"negotiation\",\n contractsent: \"negotiation\",\n closedwon: \"won\",\n closedlost: \"lost\",\n // Additional HubSpot stages\n prospecting: \"lead\",\n qualification: \"qualified\",\n proposal: \"proposal\",\n negotiation: \"negotiation\",\n closedwon2: \"won\",\n closedlost2: \"lost\",\n};\n\nconst TYPE_MAP: Record<string, InteractionEntry[\"type\"]> = {\n NOTE: \"Note\",\n CALL: \"Call\",\n EMAIL: \"Email\",\n MEETING: \"Meeting\",\n TASK: \"Note\",\n LINKEDIN_MESSAGE: \"Email\",\n WHATSAPP_MESSAGE: \"Email\",\n POSTAL_MAIL: \"Note\",\n};\n\n// Known HubSpot columns → dxcrm main_facts fields\nconst COMPANY_FIELD_MAP: Record<string, string> = {\n hs_annual_revenue: \"annual_revenue\",\n num_associated_contacts: \"contact_count\",\n industry: \"industry\",\n city: \"city\",\n country: \"country\",\n hs_lead_status: \"lead_status\",\n lifecyclestage: \"lifecycle_stage\",\n numberofemployees: \"employee_count\",\n phone: \"phone\",\n address: \"address\",\n zip: \"zip\",\n state: \"state\",\n};\n\nconst KNOWN_COMPANY_COLUMNS = new Set([\n \"name\",\n \"Name\",\n \"domain\",\n \"Domain\",\n \"website\",\n \"Website\",\n \"phone\",\n \"Phone\",\n \"address\",\n \"Address\",\n \"city\",\n \"City\",\n \"country\",\n \"Country\",\n \"state\",\n \"State\",\n \"zip\",\n \"Zip\",\n \"industry\",\n \"Industry\",\n \"numberofemployees\",\n \"Number of Employees\",\n \"hs_annual_revenue\",\n \"Annual Revenue\",\n \"lifecyclestage\",\n \"Lifecycle Stage\",\n \"hubspot_owner_email\",\n \"HubSpot Owner Email\",\n \"create_date\",\n \"createdate\",\n \"hs_lastmodifieddate\",\n \"hs_object_id\",\n \"Record ID\",\n]);\n\n// ─── Utilities ────────────────────────────────────────────────────────────────\n\nfunction slugify(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 60);\n}\n\nfunction hashStr(s: string): string {\n return createHash(\"sha256\").update(s).digest(\"hex\").slice(0, 16);\n}\n\nfunction coerceDate(raw: string): string {\n if (!raw) return new Date().toISOString().slice(0, 10);\n // HubSpot timestamps: \"2026-01-15 14:30:00 UTC\", \"1705318200000\" (ms), \"2026-01-15\"\n if (/^\\d{13}$/.test(raw.trim())) {\n return new Date(parseInt(raw, 10)).toISOString().slice(0, 10);\n }\n const d = new Date(raw.trim());\n if (!isNaN(d.getTime())) return d.toISOString().slice(0, 10);\n return new Date().toISOString().slice(0, 10);\n}\n\n// ─── Customer creation ────────────────────────────────────────────────────────\n\nfunction ensureCustomer(\n dataDir: string,\n name: string,\n domain: string,\n email: string,\n dryRun: boolean\n): { slug: string; created: boolean } {\n const slug = slugify(name || \"unknown\");\n const customerDir = path.join(dataDir, \"customers\", slug);\n const mainFactsPath = path.join(customerDir, \"main_facts.md\");\n if (fs.existsSync(mainFactsPath)) return { slug, created: false };\n if (dryRun) return { slug, created: true };\n\n fs.mkdirSync(customerDir, { recursive: true });\n const today = new Date().toISOString().slice(0, 10);\n const lines = [\n \"---\",\n `name: ${name}`,\n domain ? `domain: ${domain}` : null,\n email ? `email: ${email}` : null,\n \"relationship_stage: prospect\",\n `created: ${today}`,\n `updated: ${today}`,\n `last_touchpoint: ${today}`,\n \"tags: []\",\n \"currency: EUR\",\n \"---\",\n ]\n .filter(Boolean)\n .join(\"\\n\");\n writeFileAtomic(mainFactsPath, `${lines}\\n\\n# Customer: ${name}\\n`);\n writeFileAtomic(path.join(customerDir, \"interactions.md\"), `# Interactions — ${name}\\n\\n`);\n writeFileAtomic(path.join(customerDir, \"pipeline.md\"), `# Pipeline — ${name}\\n\\n`);\n writeJsonFile(path.join(customerDir, \"sources.json\"), {\n gmail: {\n query: domain\n ? `from:${domain} OR to:${domain}`\n : email\n ? `from:${email} OR to:${email}`\n : \"\",\n enabled: true,\n },\n transcripts: { paths: [], extensions: [\".txt\", \".vtt\"], enabled: false },\n });\n return { slug, created: true };\n}\n\nfunction readMainFactsRaw(dataDir: string, slug: string): string {\n const p = path.join(dataDir, \"customers\", slug, \"main_facts.md\");\n return fs.existsSync(p) ? (fs.readFileSync(p, \"utf-8\") as string) : \"\";\n}\n\n/** Patch several frontmatter fields in main_facts.md with a single read+write. */\nfunction updateMainFactsFields(\n dataDir: string,\n slug: string,\n fields: Record<string, string | undefined>\n): void {\n const entries = Object.entries(fields).filter(\n (e): e is [string, string] => e[1] !== undefined && e[1] !== \"\"\n );\n if (entries.length === 0) return;\n const p = path.join(dataDir, \"customers\", slug, \"main_facts.md\");\n if (!fs.existsSync(p)) return;\n let content = fs.readFileSync(p, \"utf-8\") as string;\n for (const [field, value] of entries) {\n const regex = new RegExp(`^${escapeRegExp(field)}:.*$`, \"m\");\n if (regex.test(content)) {\n content = content.replace(regex, `${field}: ${value}`);\n } else {\n const firstDash = content.indexOf(\"---\");\n const secondDash = content.indexOf(\"---\", firstDash + 3);\n if (secondDash >= 0) {\n content = content.slice(0, secondDash) + `${field}: ${value}\\n` + content.slice(secondDash);\n }\n }\n }\n writeFileAtomic(p, content);\n}\n\n// ─── Custom Properties ────────────────────────────────────────────────────────\n\nfunction saveCustomProperties(dataDir: string, slug: string, props: Record<string, string>): void {\n if (Object.keys(props).length === 0) return;\n const p = path.join(dataDir, \"customers\", slug, \"custom_properties.json\");\n let existing: Record<string, unknown> = {};\n if (fs.existsSync(p)) {\n try {\n existing = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as Record<string, unknown>;\n } catch {\n existing = {};\n }\n }\n const merged = {\n source: \"hubspot-import\",\n importedAt: new Date().toISOString(),\n properties: { ...((existing[\"properties\"] as Record<string, string>) ?? {}), ...props },\n };\n writeJsonFile(p, merged);\n}\n\n// ─── Progress / Resume ────────────────────────────────────────────────────────\n\ninterface ImportProgress {\n importId: string;\n source: string;\n startedAt: string;\n phases: {\n companies: { status: \"done\" | \"in-progress\" | \"pending\"; processed: number };\n contacts: { status: \"done\" | \"in-progress\" | \"pending\"; processed: number };\n deals: { status: \"done\" | \"in-progress\" | \"pending\"; processed: number };\n engagements: { status: \"done\" | \"in-progress\" | \"pending\"; processed: number };\n };\n}\n\nfunction progressPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"import-progress.json\");\n}\n\nfunction readProgress(dataDir: string): ImportProgress | null {\n const p = progressPath(dataDir);\n if (!fs.existsSync(p)) return null;\n try {\n return JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as ImportProgress;\n } catch {\n return null;\n }\n}\n\nfunction writeProgress(dataDir: string, progress: ImportProgress): void {\n fs.mkdirSync(path.dirname(progressPath(dataDir)), { recursive: true });\n writeJsonFile(progressPath(dataDir), progress);\n}\n\nfunction clearProgress(dataDir: string): void {\n const p = progressPath(dataDir);\n if (fs.existsSync(p)) fs.unlinkSync(p);\n}\n\n// ─── Analyze ──────────────────────────────────────────────────────────────────\n\nexport async function analyzeHubSpotExport(exportDir: string): Promise<HubSpotAnalysis> {\n const analysis: HubSpotAnalysis = {\n companiesFound: 0,\n contactsFound: 0,\n dealsFound: 0,\n engagementsFound: 0,\n customPropertiesDetected: [],\n ownersDetected: [],\n unknownStages: [],\n unmappedContacts: 0,\n estimatedMinutes: 0,\n };\n\n const customProps = new Set<string>();\n const owners = new Set<string>();\n const unknownStages = new Set<string>();\n const companyNames = new Set<string>();\n\n // Companies\n const companiesPath = path.join(exportDir, \"companies.csv\");\n if (fs.existsSync(companiesPath)) {\n for await (const row of streamCSV(companiesPath)) {\n analysis.companiesFound++;\n const name = (row[\"name\"] ?? row[\"Name\"] ?? \"\").trim();\n if (name) companyNames.add(name.toLowerCase());\n const owner = row[\"hubspot_owner_email\"] ?? row[\"HubSpot Owner Email\"] ?? \"\";\n if (owner) owners.add(owner);\n // Detect custom columns\n for (const key of Object.keys(row)) {\n if (!KNOWN_COMPANY_COLUMNS.has(key) && row[key]) customProps.add(key);\n }\n }\n }\n\n // Contacts\n const contactsPath = path.join(exportDir, \"contacts.csv\");\n if (fs.existsSync(contactsPath)) {\n for await (const row of streamCSV(contactsPath)) {\n analysis.contactsFound++;\n const company = (row[\"company\"] ?? row[\"Company\"] ?? row[\"associated_company\"] ?? \"\").trim();\n if (company && !companyNames.has(company.toLowerCase())) analysis.unmappedContacts++;\n const owner = row[\"contact_owner\"] ?? row[\"Contact Owner\"] ?? \"\";\n if (owner) owners.add(owner);\n }\n }\n\n // Deals\n const dealsPath = path.join(exportDir, \"deals.csv\");\n if (fs.existsSync(dealsPath)) {\n for await (const row of streamCSV(dealsPath)) {\n analysis.dealsFound++;\n const stage = (row[\"dealstage\"] ?? row[\"Deal Stage\"] ?? \"\").trim().toLowerCase();\n if (stage && !STAGE_MAP[stage]) unknownStages.add(stage);\n }\n }\n\n // Engagements\n const engagementsPath = path.join(exportDir, \"engagements.csv\");\n if (fs.existsSync(engagementsPath)) {\n for await (const _row of streamCSV(engagementsPath)) {\n analysis.engagementsFound++;\n }\n }\n\n analysis.customPropertiesDetected = Array.from(customProps).slice(0, 50);\n analysis.ownersDetected = Array.from(owners);\n analysis.unknownStages = Array.from(unknownStages);\n\n const totalRows =\n analysis.companiesFound +\n analysis.contactsFound +\n analysis.dealsFound +\n analysis.engagementsFound;\n analysis.estimatedMinutes = Math.ceil(totalRows / 2000); // ~2000 rows/min\n\n return analysis;\n}\n\n// ─── Main Import ──────────────────────────────────────────────────────────────\n\nexport async function runHubSpotCsvImport(\n exportDir: string,\n dataDir: string,\n opts: HubSpotImportOptions = {}\n): Promise<HubSpotImportResult> {\n const result: HubSpotImportResult = {\n companiesProcessed: 0,\n contactsImported: 0,\n dealsImported: 0,\n engagementsImported: 0,\n errors: [],\n customPropertiesSaved: 0,\n ownersResolved: 0,\n };\n\n const dryRun = opts.dryRun ?? false;\n const ownerMap = opts.ownerMap ?? {};\n\n // Resume handling\n let progress: ImportProgress | null = null;\n if (opts.resume) {\n progress = readProgress(dataDir);\n if (progress) {\n console.error(`[import] Resuming import ${progress.importId}...`);\n }\n }\n\n if (!progress) {\n progress = {\n importId: `hs-import-${new Date().toISOString().replace(/[:.]/g, \"-\").slice(0, 19)}`,\n source: exportDir,\n startedAt: new Date().toISOString(),\n phases: {\n companies: { status: \"pending\", processed: 0 },\n contacts: { status: \"pending\", processed: 0 },\n deals: { status: \"pending\", processed: 0 },\n engagements: { status: \"pending\", processed: 0 },\n },\n };\n }\n\n const companySlugMap = new Map<string, string>(); // name.lower → slug\n const emailSlugMap = new Map<string, string>(); // email.lower → slug\n\n // ── Phase 1: Companies ──────────────────────────────────────────────────────\n const companiesPath = path.join(exportDir, \"companies.csv\");\n if (fs.existsSync(companiesPath) && progress.phases.companies.status !== \"done\") {\n progress.phases.companies.status = \"in-progress\";\n if (!dryRun) writeProgress(dataDir, progress);\n\n for await (const row of streamCSV(companiesPath)) {\n const name = (row[\"name\"] ?? row[\"Name\"] ?? \"\").trim();\n if (!name) continue;\n\n const domain = (\n row[\"domain\"] ??\n row[\"Domain\"] ??\n row[\"website\"] ??\n row[\"Website\"] ??\n \"\"\n ).trim();\n const hubspotId = (row[\"hs_object_id\"] ?? row[\"Record ID\"] ?? \"\").trim();\n\n try {\n const { slug, created } = ensureCustomer(dataDir, name, domain, \"\", dryRun);\n companySlugMap.set(name.toLowerCase(), slug);\n result.companiesProcessed++;\n\n if (!dryRun && created) {\n // Map known fields + owner + HubSpot id, batched into one read+write.\n const factsPatch: Record<string, string | undefined> = {};\n for (const [hsKey, dxKey] of Object.entries(COMPANY_FIELD_MAP)) {\n const val = row[hsKey] ?? \"\";\n if (val) factsPatch[dxKey] = val;\n }\n\n const ownerEmail = row[\"hubspot_owner_email\"] ?? row[\"HubSpot Owner Email\"] ?? \"\";\n if (ownerEmail && ownerMap[ownerEmail]) {\n factsPatch[\"assigned_rep\"] = ownerMap[ownerEmail]!;\n result.ownersResolved++;\n }\n\n if (hubspotId) factsPatch[\"hubspot_company_id\"] = hubspotId;\n\n updateMainFactsFields(dataDir, slug, factsPatch);\n\n // Custom properties — everything not in known columns\n const customProps: Record<string, string> = {};\n for (const [key, val] of Object.entries(row)) {\n if (!KNOWN_COMPANY_COLUMNS.has(key) && val) customProps[key] = val;\n }\n if (Object.keys(customProps).length > 0) {\n saveCustomProperties(dataDir, slug, customProps);\n result.customPropertiesSaved += Object.keys(customProps).length;\n }\n }\n } catch (err) {\n result.errors.push(`Company '${name}': ${(err as Error).message}`);\n }\n\n progress.phases.companies.processed++;\n }\n\n progress.phases.companies.status = \"done\";\n if (!dryRun) writeProgress(dataDir, progress);\n } else if (progress.phases.companies.status === \"done\") {\n // Rebuild maps from disk for resume\n const customersDir = path.join(dataDir, \"customers\");\n if (fs.existsSync(customersDir)) {\n for (const slug of fs.readdirSync(customersDir)) {\n const mf = path.join(customersDir, slug, \"main_facts.md\");\n if (!fs.existsSync(mf)) continue;\n const content = fs.readFileSync(mf, \"utf-8\") as string;\n const nameMatch = content.match(/^name:\\s*(.+)$/m);\n if (nameMatch?.[1]) companySlugMap.set(nameMatch[1].trim().toLowerCase(), slug);\n }\n }\n }\n\n // ── Phase 2: Contacts ───────────────────────────────────────────────────────\n const contactsPath = path.join(exportDir, \"contacts.csv\");\n if (fs.existsSync(contactsPath) && progress.phases.contacts.status !== \"done\") {\n progress.phases.contacts.status = \"in-progress\";\n if (!dryRun) writeProgress(dataDir, progress);\n\n for await (const row of streamCSV(contactsPath)) {\n const firstName = (row[\"firstname\"] ?? row[\"First Name\"] ?? \"\").trim();\n const lastName = (row[\"lastname\"] ?? row[\"Last Name\"] ?? \"\").trim();\n const email = (row[\"email\"] ?? row[\"Email\"] ?? \"\").trim();\n const companyName = (\n row[\"company\"] ??\n row[\"Company\"] ??\n row[\"associated_company\"] ??\n row[\"Associated Company\"] ??\n \"\"\n ).trim();\n const phone = (row[\"phone\"] ?? row[\"Phone\"] ?? row[\"mobilephone\"] ?? \"\").trim();\n const title = (row[\"jobtitle\"] ?? row[\"Job Title\"] ?? \"\").trim();\n const department = (row[\"department\"] ?? row[\"Department\"] ?? \"\").trim();\n const hubspotId = (row[\"vid\"] ?? row[\"Contact ID\"] ?? row[\"hs_object_id\"] ?? \"\").trim();\n\n let slug = companySlugMap.get(companyName.toLowerCase());\n\n if (!slug && companyName) {\n const domain = (row[\"website\"] ?? \"\").trim();\n try {\n const { slug: newSlug, created } = ensureCustomer(\n dataDir,\n companyName,\n domain,\n email,\n dryRun\n );\n slug = newSlug;\n companySlugMap.set(companyName.toLowerCase(), newSlug);\n if (created) result.companiesProcessed++;\n } catch (err) {\n result.errors.push(`Auto-company '${companyName}': ${(err as Error).message}`);\n }\n }\n\n if (!slug) continue;\n\n if (!dryRun) {\n const contactName = [firstName, lastName].filter(Boolean).join(\" \");\n const isFirst = !fs.existsSync(path.join(dataDir, \"customers\", slug, \"contacts.json\"));\n\n // Multi-contact support\n if (email || contactName) {\n const contactEntry = {\n email: email || `${slugify(contactName)}@unknown.local`,\n name: contactName || email,\n ...(title ? { title } : {}),\n ...(phone ? { phone } : {}),\n ...(department ? { department } : {}),\n ...(hubspotId ? { hubspotId } : {}),\n isPrimary: isFirst,\n createdAt: new Date().toISOString(),\n };\n try {\n upsertContact(dataDir, slug, contactEntry);\n } catch {\n /* skip invalid */\n }\n }\n\n // Update main_facts primary contact (first contact only) — batched into\n // a single read+write instead of one per field.\n const existing = readMainFactsRaw(dataDir, slug);\n const factsPatch: Record<string, string | undefined> = {};\n if (email && !existing.includes(\"email:\")) factsPatch[\"email\"] = email;\n if (phone && !existing.includes(\"phone:\")) factsPatch[\"phone\"] = phone;\n if (contactName && !existing.includes(\"primary_contact:\"))\n factsPatch[\"primary_contact\"] = contactName;\n\n // Owner mapping\n const ownerEmail = row[\"contact_owner\"] ?? row[\"Contact Owner\"] ?? \"\";\n if (ownerEmail && ownerMap[ownerEmail] && !existing.includes(\"assigned_rep:\")) {\n factsPatch[\"assigned_rep\"] = ownerMap[ownerEmail]!;\n result.ownersResolved++;\n }\n updateMainFactsFields(dataDir, slug, factsPatch);\n }\n\n if (email) emailSlugMap.set(email.toLowerCase(), slug);\n result.contactsImported++;\n progress.phases.contacts.processed++;\n }\n\n progress.phases.contacts.status = \"done\";\n if (!dryRun) writeProgress(dataDir, progress);\n }\n\n // ── Phase 3: Deals ──────────────────────────────────────────────────────────\n const dealsPath = path.join(exportDir, \"deals.csv\");\n if (fs.existsSync(dealsPath) && progress.phases.deals.status !== \"done\") {\n if (!dryRun) {\n const { upsertDeal } = await import(\"../fs/pipeline-writer.js\");\n progress.phases.deals.status = \"in-progress\";\n writeProgress(dataDir, progress);\n\n for await (const row of streamCSV(dealsPath)) {\n const dealName = (row[\"dealname\"] ?? row[\"Deal Name\"] ?? row[\"name\"] ?? \"\").trim();\n if (!dealName) continue;\n\n const companyName = (\n row[\"associated_company\"] ??\n row[\"Associated Company\"] ??\n row[\"company\"] ??\n \"\"\n ).trim();\n const amountStr = (row[\"amount\"] ?? row[\"Amount\"] ?? \"0\").trim().replace(/[^0-9.]/g, \"\");\n const stageRaw = (row[\"dealstage\"] ?? row[\"Deal Stage\"] ?? \"\").trim().toLowerCase();\n const closeDateRaw = (\n row[\"closedate\"] ??\n row[\"Close Date\"] ??\n row[\"close_date\"] ??\n \"\"\n ).trim();\n const currency = (row[\"deal_currency_code\"] ?? row[\"Currency\"] ?? \"EUR\").trim();\n const dealId = (row[\"hs_deal_id\"] ?? row[\"hs_object_id\"] ?? row[\"Record ID\"] ?? \"\").trim();\n const ownerEmail = (row[\"hubspot_owner_email\"] ?? row[\"HubSpot Owner Email\"] ?? \"\").trim();\n const description = (row[\"description\"] ?? row[\"Description\"] ?? \"\").trim();\n\n const slug =\n companySlugMap.get(companyName.toLowerCase()) ?? slugify(companyName || \"unknown\");\n const stage = STAGE_MAP[stageRaw] ?? \"qualified\";\n const amount = parseFloat(amountStr) || 0;\n const closeDate = coerceDate(closeDateRaw);\n\n const notesParts: string[] = [];\n if (dealId) notesParts.push(`hubspot://deal/${dealId}`);\n if (description) notesParts.push(description.slice(0, 200));\n if (ownerEmail && ownerMap[ownerEmail]) notesParts.push(`owner:${ownerMap[ownerEmail]}`);\n\n const deal: PipelineDeal = {\n name: dealName,\n stage,\n value: amount,\n currency: currency || \"EUR\",\n probability: stage === \"won\" ? 1 : stage === \"lost\" ? 0 : 0.5,\n close_date: closeDate,\n updated: new Date().toISOString().slice(0, 10),\n ...(notesParts.length > 0 ? { notes: notesParts.join(\" | \") } : {}),\n };\n\n try {\n await upsertDeal(dataDir, slug, deal);\n result.dealsImported++;\n } catch (err) {\n result.errors.push(`Deal '${dealName}': ${(err as Error).message}`);\n }\n\n progress.phases.deals.processed++;\n }\n\n progress.phases.deals.status = \"done\";\n writeProgress(dataDir, progress);\n } else {\n // Dry run count\n for await (const row of streamCSV(dealsPath)) {\n if ((row[\"dealname\"] ?? row[\"name\"] ?? \"\").trim()) result.dealsImported++;\n }\n progress.phases.deals.status = \"done\";\n }\n }\n\n // ── Phase 4: Engagements ────────────────────────────────────────────────────\n const engagementsPath = path.join(exportDir, \"engagements.csv\");\n if (fs.existsSync(engagementsPath) && progress.phases.engagements.status !== \"done\") {\n if (!dryRun) {\n const { appendInteraction, InteractionDedup } = await import(\"../fs/interactions-writer.js\");\n const dedup = new InteractionDedup(dataDir);\n progress.phases.engagements.status = \"in-progress\";\n writeProgress(dataDir, progress);\n\n for await (const row of streamCSV(engagementsPath)) {\n const engType = (\n row[\"engagement_type\"] ??\n row[\"Engagement Type\"] ??\n row[\"type\"] ??\n row[\"Type\"] ??\n \"NOTE\"\n )\n .trim()\n .toUpperCase();\n const timestamp = (\n row[\"hs_timestamp\"] ??\n row[\"Timestamp\"] ??\n row[\"date\"] ??\n row[\"createdate\"] ??\n \"\"\n ).trim();\n const body = (\n row[\"hs_body_preview\"] ??\n row[\"Body\"] ??\n row[\"notes\"] ??\n row[\"Notes\"] ??\n row[\"hs_note_body\"] ??\n \"\"\n ).trim();\n const subject = (row[\"subject\"] ?? row[\"Subject\"] ?? \"\").trim();\n const contactEmail = (\n row[\"associated_contact_email\"] ??\n row[\"Contact Email\"] ??\n row[\"from_email\"] ??\n \"\"\n )\n .trim()\n .toLowerCase();\n const engId = (\n row[\"id\"] ??\n row[\"engagement_id\"] ??\n row[\"hs_object_id\"] ??\n hashStr(timestamp + body)\n ).trim();\n const callDuration = (row[\"call_duration\"] ?? row[\"hs_call_duration\"] ?? \"\").trim();\n const callOutcome = (row[\"call_outcome\"] ?? row[\"hs_call_disposition\"] ?? \"\").trim();\n const callRecording = (\n row[\"call_recording_url\"] ??\n row[\"hs_call_recording_url\"] ??\n \"\"\n ).trim();\n\n const slug =\n emailSlugMap.get(contactEmail) ??\n companySlugMap.get((row[\"associated_company\"] ?? \"\").toLowerCase().trim());\n if (!slug) continue;\n\n const sourceRef = `hubspot://engagement/${engId}`;\n try {\n if (await dedup.seen(slug, sourceRef)) continue;\n\n const date = coerceDate(timestamp);\n const type = TYPE_MAP[engType] ?? \"Note\";\n\n // Build rich summary\n const summaryParts: string[] = [];\n if (subject) summaryParts.push(`Subject: ${subject}`);\n if (body) summaryParts.push(body.slice(0, 500));\n if (callDuration) summaryParts.push(`Duration: ${callDuration}s`);\n if (callOutcome) summaryParts.push(`Outcome: ${callOutcome}`);\n if (callRecording) summaryParts.push(`Recording: ${callRecording}`);\n\n const summary = summaryParts.join(\" | \") || `${type} imported from HubSpot`;\n\n await appendInteraction(dataDir, slug, {\n date,\n type,\n with: contactEmail || slug,\n summary,\n nextSteps: [],\n sourceRef,\n synced: new Date().toISOString(),\n });\n dedup.markAppended(slug, sourceRef);\n result.engagementsImported++;\n } catch (err) {\n result.errors.push(`Engagement ${engId}: ${(err as Error).message}`);\n }\n\n progress.phases.engagements.processed++;\n }\n\n progress.phases.engagements.status = \"done\";\n writeProgress(dataDir, progress);\n } else {\n for await (const _row of streamCSV(engagementsPath)) result.engagementsImported++;\n progress.phases.engagements.status = \"done\";\n }\n }\n\n // Done — clear progress file\n if (!dryRun) clearProgress(dataDir);\n\n return result;\n}\n"],"mappings":"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"}
@@ -95,12 +95,12 @@ declare const MainFactsSchema: z.ZodObject<{
95
95
  created: z.ZodEffects<z.ZodString, string, unknown>;
96
96
  updated: z.ZodEffects<z.ZodString, string, unknown>;
97
97
  }, "strip", z.ZodTypeAny, {
98
- created: string;
99
98
  name: string;
100
- relationship_stage: "prospect" | "active" | "churned" | "paused";
101
99
  currency: string;
102
- tags: string[];
103
100
  updated: string;
101
+ created: string;
102
+ relationship_stage: "prospect" | "active" | "churned" | "paused";
103
+ tags: string[];
104
104
  domain?: string | undefined;
105
105
  email?: string | undefined;
106
106
  phone?: string | undefined;
@@ -111,17 +111,17 @@ declare const MainFactsSchema: z.ZodObject<{
111
111
  }, {
112
112
  name: string;
113
113
  relationship_stage: "prospect" | "active" | "churned" | "paused";
114
+ currency?: string | undefined;
115
+ updated?: unknown;
114
116
  created?: unknown;
115
117
  domain?: string | undefined;
116
118
  email?: string | undefined;
117
119
  phone?: string | undefined;
118
120
  industry?: string | undefined;
119
121
  deal_value?: number | undefined;
120
- currency?: string | undefined;
121
122
  primary_contact?: string | undefined;
122
123
  timezone?: string | undefined;
123
124
  tags?: string[] | undefined;
124
- updated?: unknown;
125
125
  }>;
126
126
  type MainFacts = z.infer<typeof MainFactsSchema>;
127
127
  //# sourceMappingURL=main-facts.d.ts.map
@@ -135,6 +135,8 @@ declare const InteractionEntrySchema: z.ZodObject<{
135
135
  subject: z.ZodOptional<z.ZodString>;
136
136
  summary: z.ZodString;
137
137
  nextSteps: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
138
+ /** Relative links (from the customer dir) to converted attachment Markdown. */
139
+ attachments: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
138
140
  sourceRef: z.ZodString;
139
141
  synced: z.ZodString;
140
142
  }, "strip", z.ZodTypeAny, {
@@ -147,6 +149,7 @@ declare const InteractionEntrySchema: z.ZodObject<{
147
149
  synced: string;
148
150
  direction?: "inbound" | "outbound" | undefined;
149
151
  subject?: string | undefined;
152
+ attachments?: string[] | undefined;
150
153
  }, {
151
154
  type: "Email" | "Call" | "Meeting" | "Note" | "Demo" | "Proposal" | "Contract" | "Other";
152
155
  date: string;
@@ -157,6 +160,7 @@ declare const InteractionEntrySchema: z.ZodObject<{
157
160
  direction?: "inbound" | "outbound" | undefined;
158
161
  subject?: string | undefined;
159
162
  nextSteps?: string[] | undefined;
163
+ attachments?: string[] | undefined;
160
164
  }>;
161
165
  type InteractionEntry = z.infer<typeof InteractionEntrySchema>;
162
166
  //# sourceMappingURL=interaction.d.ts.map
@@ -173,17 +177,17 @@ declare const PipelineDealSchema: z.ZodObject<{
173
177
  updated: z.ZodString;
174
178
  }, "strip", z.ZodTypeAny, {
175
179
  name: string;
180
+ stage: "lead" | "qualified" | "proposal" | "negotiation" | "won" | "lost";
176
181
  currency: string;
177
182
  updated: string;
178
- stage: "lead" | "qualified" | "proposal" | "negotiation" | "won" | "lost";
179
183
  value?: number | undefined;
180
184
  probability?: number | undefined;
181
185
  close_date?: string | undefined;
182
186
  notes?: string | undefined;
183
187
  }, {
184
188
  name: string;
185
- updated: string;
186
189
  stage: "lead" | "qualified" | "proposal" | "negotiation" | "won" | "lost";
190
+ updated: string;
187
191
  value?: number | undefined;
188
192
  currency?: string | undefined;
189
193
  probability?: number | undefined;
@@ -539,4 +543,4 @@ declare const VERSION = "0.1.0";
539
543
 
540
544
  //#endregion
541
545
  export { type GlobalSources, type InteractionEntry, type KbArticle, type MainFacts, type PipelineDeal, type QuoteLineItem, type Quote as QuoteRecord, type SurveyDefinition, type SurveyResponse, type TicketPriority, type Ticket as TicketRecord, type TicketStatus, VERSION, canSeeCustomer, clearSession, createCustomer, customerExists, filterAuditLog, getRbacConfig, getRole, getSession, readAuditLog, readMainFacts, runAudit, runBackup, runValidate, setSession };
542
- //# sourceMappingURL=index-pY7tYXwH.d.cts.map
546
+ //# sourceMappingURL=index-CLUKKfGb.d.cts.map