@datasynx/agentic-crm 0.1.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.
- package/LICENSE +21 -0
- package/README.md +767 -0
- package/dist/agent-config-zPvcqu07.js +14 -0
- package/dist/agent-config-zPvcqu07.js.map +1 -0
- package/dist/approvals-DpjxGHFp.js +67 -0
- package/dist/approvals-DpjxGHFp.js.map +1 -0
- package/dist/ask-CID3jnuL.js +52 -0
- package/dist/ask-CID3jnuL.js.map +1 -0
- package/dist/audit-log-DNMY9mUZ.js +49 -0
- package/dist/audit-log-DNMY9mUZ.js.map +1 -0
- package/dist/auth-CyFuu9X_.js +2 -0
- package/dist/auth-DFWwWcYD.js +93 -0
- package/dist/auth-DFWwWcYD.js.map +1 -0
- package/dist/autofill-Di_-SP7t.js +51 -0
- package/dist/autofill-Di_-SP7t.js.map +1 -0
- package/dist/backup-CeMk9z86.js +417 -0
- package/dist/backup-CeMk9z86.js.map +1 -0
- package/dist/backup-f_hC7rBV.js +2 -0
- package/dist/calendly-Bft_wwji.js +52 -0
- package/dist/calendly-Bft_wwji.js.map +1 -0
- package/dist/calendly-D3coO92o.cjs +53 -0
- package/dist/calendly-D3coO92o.cjs.map +1 -0
- package/dist/chunk-DakpK96I.cjs +43 -0
- package/dist/churn-C28IgnAj.js +54 -0
- package/dist/churn-C28IgnAj.js.map +1 -0
- package/dist/cli.js +4396 -0
- package/dist/cli.js.map +1 -0
- package/dist/colors-BG07TZQz.js +11 -0
- package/dist/colors-BG07TZQz.js.map +1 -0
- package/dist/compliance-B1kk5-YS.js +115 -0
- package/dist/compliance-B1kk5-YS.js.map +1 -0
- package/dist/compliance-B91zNvCR.cjs +156 -0
- package/dist/compliance-B91zNvCR.cjs.map +1 -0
- package/dist/compliance-CKSBoQUe.js +118 -0
- package/dist/compliance-CKSBoQUe.js.map +1 -0
- package/dist/compliance-CujOqAKk.js +2 -0
- package/dist/context-builder-BzWAp3Zs.js +96 -0
- package/dist/context-builder-BzWAp3Zs.js.map +1 -0
- package/dist/context-builder-DlrRcqmJ.js +2 -0
- package/dist/conversation-intel-mm7Lhemh.js +72 -0
- package/dist/conversation-intel-mm7Lhemh.js.map +1 -0
- package/dist/custom-fields-CzNeD3_v.js +2 -0
- package/dist/custom-fields-Pl2t9xzp.js +73 -0
- package/dist/custom-fields-Pl2t9xzp.js.map +1 -0
- package/dist/custom-objects-BHgn1GEX.js +78 -0
- package/dist/custom-objects-BHgn1GEX.js.map +1 -0
- package/dist/custom-objects-CIFrmQ2V.js +2 -0
- package/dist/customer-dir-DIylZ8Q6.js +75 -0
- package/dist/customer-dir-DIylZ8Q6.js.map +1 -0
- package/dist/daemon/worker.js +207 -0
- package/dist/daemon/worker.js.map +1 -0
- package/dist/enrichment-3XvgGDfB.js +103 -0
- package/dist/enrichment-3XvgGDfB.js.map +1 -0
- package/dist/file-lock-B_zi7NQl.js +22 -0
- package/dist/file-lock-B_zi7NQl.js.map +1 -0
- package/dist/gmail-auth-BP6cJwfw.js +40 -0
- package/dist/gmail-auth-BP6cJwfw.js.map +1 -0
- package/dist/gmail-auth-DxakCtGm.cjs +44 -0
- package/dist/gmail-auth-DxakCtGm.cjs.map +1 -0
- package/dist/gmail-auth-OComS92L.js +40 -0
- package/dist/gmail-auth-OComS92L.js.map +1 -0
- package/dist/gmail-push-watch-DELQFMPk.js +20 -0
- package/dist/gmail-push-watch-DELQFMPk.js.map +1 -0
- package/dist/gmail-sender-StTpJ9Ub.js +32 -0
- package/dist/gmail-sender-StTpJ9Ub.js.map +1 -0
- package/dist/gmail-sync-DIaxInDT.js +204 -0
- package/dist/gmail-sync-DIaxInDT.js.map +1 -0
- package/dist/gmail-sync-hHm9gaWd.cjs +218 -0
- package/dist/gmail-sync-hHm9gaWd.cjs.map +1 -0
- package/dist/gmail-sync-rQaVqKWd.js +214 -0
- package/dist/gmail-sync-rQaVqKWd.js.map +1 -0
- package/dist/gmail-webhook-handler-DS7OlRPX.js +3 -0
- package/dist/gmail-webhook-handler-e5Od25FX.js +97 -0
- package/dist/gmail-webhook-handler-e5Od25FX.js.map +1 -0
- package/dist/goal-engine-CUZSpERI.js +2 -0
- package/dist/goal-engine-KpBftn4V.js +295 -0
- package/dist/goal-engine-KpBftn4V.js.map +1 -0
- package/dist/google-drive-sync-DEPcqFca.js +105 -0
- package/dist/google-drive-sync-DEPcqFca.js.map +1 -0
- package/dist/hybrid-search-BmHttLrR.js +40 -0
- package/dist/hybrid-search-BmHttLrR.js.map +1 -0
- package/dist/hygiene-DZqfYpFf.js +38 -0
- package/dist/hygiene-DZqfYpFf.js.map +1 -0
- package/dist/identity-CI6olMNm.js +41 -0
- package/dist/identity-CI6olMNm.js.map +1 -0
- package/dist/identity-gyfWdrcX.js +2 -0
- package/dist/import-hubspot-BaK71U_K.js +588 -0
- package/dist/import-hubspot-BaK71U_K.js.map +1 -0
- package/dist/index-V8BFaH-b.d.ts +539 -0
- package/dist/index-V8BFaH-b.d.ts.map +1 -0
- package/dist/index-YqwMd6aQ.d.cts +538 -0
- package/dist/index-YqwMd6aQ.d.cts.map +1 -0
- package/dist/index.cjs +185 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +538 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +539 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +165 -0
- package/dist/index.js.map +1 -0
- package/dist/interactions-writer-CrPStUll.cjs +77 -0
- package/dist/interactions-writer-CrPStUll.cjs.map +1 -0
- package/dist/interactions-writer-DO3KcSR3.js +52 -0
- package/dist/interactions-writer-DO3KcSR3.js.map +1 -0
- package/dist/interactions-writer-SLHnoEeE.js +46 -0
- package/dist/interactions-writer-SLHnoEeE.js.map +1 -0
- package/dist/interactions-writer-dSPy1XfO.js +2 -0
- package/dist/knowledge-base-D0Fh40kc.js +1013 -0
- package/dist/knowledge-base-D0Fh40kc.js.map +1 -0
- package/dist/lancedb-CCBbpulq.js +2 -0
- package/dist/lancedb-rlvWoPwl.js +98 -0
- package/dist/lancedb-rlvWoPwl.js.map +1 -0
- package/dist/lead-model-BCFzyktm.js +109 -0
- package/dist/lead-model-BCFzyktm.js.map +1 -0
- package/dist/llm-DEjWcqmW.js +2 -0
- package/dist/llm-DvzZqva0.js +372 -0
- package/dist/llm-DvzZqva0.js.map +1 -0
- package/dist/llm-Z8RIYkpF.js +174 -0
- package/dist/llm-Z8RIYkpF.js.map +1 -0
- package/dist/llm-iijeXmgq.cjs +198 -0
- package/dist/llm-iijeXmgq.cjs.map +1 -0
- package/dist/mcp-CdTJWTJf.d.cts +12 -0
- package/dist/mcp-CdTJWTJf.d.cts.map +1 -0
- package/dist/mcp-CdTJWTJf.d.ts +12 -0
- package/dist/mcp-CdTJWTJf.d.ts.map +1 -0
- package/dist/mcp.cjs +7464 -0
- package/dist/mcp.cjs.map +1 -0
- package/dist/mcp.d.cts +12 -0
- package/dist/mcp.d.cts.map +1 -0
- package/dist/mcp.d.ts +12 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +7448 -0
- package/dist/mcp.js.map +1 -0
- package/dist/memory-Bb6ky3kb.js +58 -0
- package/dist/memory-Bb6ky3kb.js.map +1 -0
- package/dist/memory-Cy6-Tbyl.js +2 -0
- package/dist/metrics-DH8wHvya.js +26 -0
- package/dist/metrics-DH8wHvya.js.map +1 -0
- package/dist/microsoft-auth-B8_S45gh.js +17 -0
- package/dist/microsoft-auth-B8_S45gh.js.map +1 -0
- package/dist/microsoft-calendar-B6MMtUQK.js +67 -0
- package/dist/microsoft-calendar-B6MMtUQK.js.map +1 -0
- package/dist/microsoft-sync-CpZVoSuq.js +68 -0
- package/dist/microsoft-sync-CpZVoSuq.js.map +1 -0
- package/dist/nba-3wanmJ0U.js +48 -0
- package/dist/nba-3wanmJ0U.js.map +1 -0
- package/dist/notification-dispatcher-0vYNngWe.js +97 -0
- package/dist/notification-dispatcher-0vYNngWe.js.map +1 -0
- package/dist/opportunity-score-BTMOQSTV.js +47 -0
- package/dist/opportunity-score-BTMOQSTV.js.map +1 -0
- package/dist/pipedrive-client-CdGKpH9b.js +17 -0
- package/dist/pipedrive-client-CdGKpH9b.js.map +1 -0
- package/dist/pipeline-writer-BqBrYrQc.js +2 -0
- package/dist/pipeline-writer-BvVquKIe.js +96 -0
- package/dist/pipeline-writer-BvVquKIe.js.map +1 -0
- package/dist/pipeline-writer-N2omexxp.cjs +121 -0
- package/dist/pipeline-writer-N2omexxp.cjs.map +1 -0
- package/dist/pipeline-writer-eufx_0o1.js +102 -0
- package/dist/pipeline-writer-eufx_0o1.js.map +1 -0
- package/dist/proactive-agent-BgQXw3ac.js +96 -0
- package/dist/proactive-agent-BgQXw3ac.js.map +1 -0
- package/dist/proactive-worker-BrLHNhjH.js +229 -0
- package/dist/proactive-worker-BrLHNhjH.js.map +1 -0
- package/dist/push-manager-CdqIIkuh.js +108 -0
- package/dist/push-manager-CdqIIkuh.js.map +1 -0
- package/dist/push-manager-CowY-0IK.js +2 -0
- package/dist/quote-generator-BfwENXzg.js +133 -0
- package/dist/quote-generator-BfwENXzg.js.map +1 -0
- package/dist/quote-generator-OhSFsi3x.js +2 -0
- package/dist/rbac-C7c8tcES.js +2 -0
- package/dist/rbac-CTIktZaC.js +91 -0
- package/dist/rbac-CTIktZaC.js.map +1 -0
- package/dist/relationship-health-odxEoQdJ.js +454 -0
- package/dist/relationship-health-odxEoQdJ.js.map +1 -0
- package/dist/revenue-simulation-BJdRTEHc.js +2 -0
- package/dist/revenue-simulation-Bqf2DLVB.js +251 -0
- package/dist/revenue-simulation-Bqf2DLVB.js.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/salesforce-client-rhZFa_p5.js +51 -0
- package/dist/salesforce-client-rhZFa_p5.js.map +1 -0
- package/dist/segments-BqcD5HIl.js +61 -0
- package/dist/segments-BqcD5HIl.js.map +1 -0
- package/dist/sequence-engine-CCTHEBgi.js +2 -0
- package/dist/sequence-engine-J1lTW_in.js +91 -0
- package/dist/sequence-engine-J1lTW_in.js.map +1 -0
- package/dist/sequence-store-DaaWr0Os.js +221 -0
- package/dist/sequence-store-DaaWr0Os.js.map +1 -0
- package/dist/server-Dyva03K8.js +4287 -0
- package/dist/server-Dyva03K8.js.map +1 -0
- package/dist/session-B9AilxOE.js +81 -0
- package/dist/session-B9AilxOE.js.map +1 -0
- package/dist/session-D0qFkBla.cjs +82 -0
- package/dist/session-D0qFkBla.cjs.map +1 -0
- package/dist/session-D9ub6Wl1.js +79 -0
- package/dist/session-D9ub6Wl1.js.map +1 -0
- package/dist/session-mWHA71Lw.js +2 -0
- package/dist/session-store-B0QZE8Bx.cjs +697 -0
- package/dist/session-store-B0QZE8Bx.cjs.map +1 -0
- package/dist/session-store-C8tEvMPw.js +543 -0
- package/dist/session-store-C8tEvMPw.js.map +1 -0
- package/dist/session-store-CEa39Dxs.js +15 -0
- package/dist/session-store-CEa39Dxs.js.map +1 -0
- package/dist/sla-engine-5IhTsBUR.js +2 -0
- package/dist/sla-engine-BqX-7u-7.js +53 -0
- package/dist/sla-engine-BqX-7u-7.js.map +1 -0
- package/dist/sop-DkhVChGy.js +2 -0
- package/dist/sop-Vp0UPWFW.js +70 -0
- package/dist/sop-Vp0UPWFW.js.map +1 -0
- package/dist/survey-engine-C06hcQt3.js +2 -0
- package/dist/survey-engine-DBjCYqCv.js +147 -0
- package/dist/survey-engine-DBjCYqCv.js.map +1 -0
- package/dist/sync-state-ChaLbamC.js +33 -0
- package/dist/sync-state-ChaLbamC.js.map +1 -0
- package/dist/sync-state-CwLSt_1m.js +2 -0
- package/dist/ticket-writer-CjqKeIRD.js +2 -0
- package/dist/ticket-writer-j2oX_Wal.js +134 -0
- package/dist/ticket-writer-j2oX_Wal.js.map +1 -0
- package/dist/tone-Bdm5uaht.js +48 -0
- package/dist/tone-Bdm5uaht.js.map +1 -0
- package/dist/tone-DRKlZgPr.cjs +43 -0
- package/dist/tone-DRKlZgPr.cjs.map +1 -0
- package/dist/tone-vNb2DAAD.js +39 -0
- package/dist/tone-vNb2DAAD.js.map +1 -0
- package/dist/transcript-watcher-CL2QUygI.js +132 -0
- package/dist/transcript-watcher-CL2QUygI.js.map +1 -0
- package/dist/unmatched-transcripts-BsH5bhkU.js +26 -0
- package/dist/unmatched-transcripts-BsH5bhkU.js.map +1 -0
- package/dist/unmatched-transcripts-D0PrJ9iz.js +2 -0
- package/dist/update-deal-BNwPGaTV.js +2 -0
- package/dist/update-deal-DKC79skb.js +91 -0
- package/dist/update-deal-DKC79skb.js.map +1 -0
- package/dist/usage-CClTf5e6.cjs +57 -0
- package/dist/usage-CClTf5e6.cjs.map +1 -0
- package/dist/usage-D0-TYJkw.js +93 -0
- package/dist/usage-D0-TYJkw.js.map +1 -0
- package/dist/usage-D0u9a-lV.js +54 -0
- package/dist/usage-D0u9a-lV.js.map +1 -0
- package/dist/vault-C1D3zScD.js +2 -0
- package/dist/vault-DXCg29W-.js +86 -0
- package/dist/vault-DXCg29W-.js.map +1 -0
- package/dist/webhooks-7EpA05Qr.js +138 -0
- package/dist/webhooks-7EpA05Qr.js.map +1 -0
- package/dist/webhooks-BO2UAnmn.js +94 -0
- package/dist/webhooks-BO2UAnmn.js.map +1 -0
- package/dist/webhooks-Xn6zO6kd.cjs +97 -0
- package/dist/webhooks-Xn6zO6kd.cjs.map +1 -0
- package/dist/write-queue-BDolUxfs.cjs +26 -0
- package/dist/write-queue-BDolUxfs.cjs.map +1 -0
- package/dist/write-queue-IbsAjUnh.js +21 -0
- package/dist/write-queue-IbsAjUnh.js.map +1 -0
- package/package.json +142 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-store-C8tEvMPw.js","names":[],"sources":["../src/schemas/main-facts.ts","../src/fs/customer-dir.ts","../src/ui/colors.ts","../src/commands/backup.ts","../src/fs/audit-log.ts","../src/core/rbac.ts","../src/core/session-store.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const MainFactsSchema = z.object({\n name: z.string().min(1),\n domain: z.string().optional(),\n email: z.string().optional(),\n phone: z.string().optional(),\n industry: z.string().optional(),\n relationship_stage: z.enum([\"prospect\", \"active\", \"churned\", \"paused\"]),\n deal_value: z.number().optional(),\n currency: z.string().default(\"EUR\"),\n primary_contact: z.string().optional(),\n timezone: z.string().optional(),\n tags: z.array(z.string()).default([]),\n created: z.preprocess(\n (v) => (v instanceof Date ? v.toISOString().slice(0, 10) : v),\n z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, \"YYYY-MM-DD required\")\n ),\n updated: z.preprocess(\n (v) => (v instanceof Date ? v.toISOString().slice(0, 10) : v),\n z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, \"YYYY-MM-DD required\")\n ),\n});\n\nexport type MainFacts = z.infer<typeof MainFactsSchema>;\n","import fs from \"fs\";\nimport path from \"path\";\nimport matter from \"gray-matter\";\nimport { fromZodError } from \"zod-validation-error\";\nimport { MainFactsSchema, type MainFacts } from \"../schemas/main-facts.js\";\n\nexport function getCustomerDir(dataDir: string, slug: string): string {\n return path.join(dataDir, \"customers\", slug);\n}\n\nexport function customerExists(dataDir: string, slug: string): boolean {\n return fs.existsSync(getCustomerDir(dataDir, slug));\n}\n\n/** List all customer slugs (immediate subdirectories of customers/). */\nexport function listCustomerSlugs(dataDir: string): string[] {\n const dir = path.join(dataDir, \"customers\");\n if (!fs.existsSync(dir)) return [];\n return fs.readdirSync(dir).filter((s) => {\n try {\n return fs.statSync(path.join(dir, s)).isDirectory();\n } catch {\n return false;\n }\n });\n}\n\nexport async function ensureCustomerDir(dataDir: string, slug: string): Promise<void> {\n const customerDir = getCustomerDir(dataDir, slug);\n fs.mkdirSync(customerDir, { recursive: true });\n fs.mkdirSync(path.join(customerDir, \"attachments\"), { recursive: true });\n fs.mkdirSync(path.join(customerDir, \"transcripts\"), { recursive: true });\n}\n\nexport async function writeMainFacts(\n dataDir: string,\n slug: string,\n facts: MainFacts\n): Promise<void> {\n const filePath = path.join(getCustomerDir(dataDir, slug), \"main_facts.md\");\n // Strip undefined values — gray-matter YAML serializer rejects them\n const clean = Object.fromEntries(\n Object.entries(facts as Record<string, unknown>).filter(([, v]) => v !== undefined)\n );\n const content = matter.stringify(\"\", clean);\n fs.writeFileSync(filePath, content, \"utf-8\");\n}\n\nexport async function readMainFacts(dataDir: string, slug: string): Promise<MainFacts> {\n const filePath = path.join(getCustomerDir(dataDir, slug), \"main_facts.md\");\n if (!fs.existsSync(filePath)) {\n throw new Error(`main_facts.md not found for customer '${slug}'`);\n }\n // Use fs.readFileSync so the memfs mock is respected in tests,\n // then parse the string with matter.\n const content = fs.readFileSync(filePath, \"utf-8\") as string;\n const raw = matter(content);\n // gray-matter parses YYYY-MM-DD as Date objects; coerce back to strings for Zod\n const data = raw.data as Record<string, unknown>;\n for (const key of [\"created\", \"updated\"] as const) {\n if (data[key] instanceof Date) {\n data[key] = (data[key] as Date).toISOString().slice(0, 10);\n }\n }\n const result = MainFactsSchema.safeParse(data);\n if (!result.success) {\n throw new Error(\n fromZodError(result.error, {\n prefix: `Schema error in ${filePath}`,\n prefixSeparator: \":\\n - \",\n issueSeparator: \"\\n - \",\n }).message\n );\n }\n return result.data;\n}\n","import ansis from \"ansis\";\n\nexport const success = (s: string): string => ansis.green(s);\nexport const error = (s: string): string => ansis.red(s);\nexport const warning = (s: string): string => ansis.yellow(s);\nexport const info = (s: string): string => ansis.cyan(s);\nexport const muted = (s: string): string => ansis.gray(s);\nexport const bold = (s: string): string => ansis.bold(s);\n","import { Command } from \"commander\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { execSync } from \"child_process\";\nimport { createHash } from \"crypto\";\nimport { success, error, info, bold } from \"../ui/colors.js\";\n\nexport interface BackupManifest {\n version: \"1\";\n createdAt: string;\n dxcrmVersion: string;\n directories: string[];\n customerCount: number;\n fileCount: number;\n totalBytes: number;\n sha256: string;\n encrypted: boolean;\n retentionTier?: \"daily\" | \"weekly\" | \"monthly\";\n}\n\nexport interface BackupScheduleConfig {\n every: string;\n keep: number;\n weekly?: number;\n monthly?: number;\n lastBackup: string | null;\n remote?: string;\n}\n\nexport interface AgenticConfig {\n backupSchedule?: BackupScheduleConfig;\n}\n\nexport interface BackupEntry {\n filename: string;\n path: string;\n createdAt: string;\n sizeBytes: number;\n verified: boolean;\n encrypted: boolean;\n customerCount: number;\n fileCount: number;\n}\n\n// ─── Config helpers ────────────────────────────────────────────────────────────\n\nfunction getConfigPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"config.json\");\n}\n\nfunction readAgenticConfig(dataDir: string): AgenticConfig {\n const filePath = getConfigPath(dataDir);\n if (!fs.existsSync(filePath)) return {};\n try {\n return JSON.parse(fs.readFileSync(filePath, \"utf-8\") as string) as AgenticConfig;\n } catch {\n return {};\n }\n}\n\nfunction writeAgenticConfig(dataDir: string, config: AgenticConfig): void {\n const filePath = getConfigPath(dataDir);\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, JSON.stringify(config, null, 2), \"utf-8\");\n}\n\n// ─── Manifest ─────────────────────────────────────────────────────────────────\n\nfunction countDir(dir: string): { files: number; bytes: number } {\n let files = 0;\n let bytes = 0;\n if (!fs.existsSync(dir)) return { files, bytes };\n const walk = (d: string) => {\n try {\n for (const entry of fs.readdirSync(d)) {\n const full = path.join(d, entry);\n try {\n const stat = fs.statSync(full);\n if (stat.isDirectory()) walk(full);\n else {\n files++;\n bytes += stat.size;\n }\n } catch {\n /* skip */\n }\n }\n } catch {\n /* skip */\n }\n };\n walk(dir);\n return { files, bytes };\n}\n\nfunction countCustomers(dataDir: string): number {\n const dir = path.join(dataDir, \"customers\");\n if (!fs.existsSync(dir)) return 0;\n try {\n return fs.readdirSync(dir).filter((f) => {\n try {\n return fs.statSync(path.join(dir, f)).isDirectory();\n } catch {\n return false;\n }\n }).length;\n } catch {\n return 0;\n }\n}\n\nfunction sha256File(filePath: string): string {\n if (!fs.existsSync(filePath)) return \"\";\n const hash = createHash(\"sha256\");\n hash.update(fs.readFileSync(filePath));\n return hash.digest(\"hex\");\n}\n\nfunction buildManifest(\n dataDir: string,\n dirs: string[],\n zipPath: string,\n encrypted: boolean\n): BackupManifest {\n let totalFiles = 0;\n let totalBytes = 0;\n for (const d of dirs) {\n const full = path.join(dataDir, d);\n const { files, bytes } = countDir(full);\n totalFiles += files;\n totalBytes += bytes;\n }\n return {\n version: \"1\",\n createdAt: new Date().toISOString(),\n dxcrmVersion: \"0.1.0\",\n directories: dirs,\n customerCount: countCustomers(dataDir),\n fileCount: totalFiles,\n totalBytes,\n sha256: sha256File(zipPath),\n encrypted,\n };\n}\n\n// ─── Manifest log ──────────────────────────────────────────────────────────────\n\nfunction appendBackupLog(dataDir: string, entry: BackupEntry): void {\n const logPath = path.join(dataDir, \".agentic\", \"backup-log.json\");\n let entries: BackupEntry[] = [];\n if (fs.existsSync(logPath)) {\n try {\n entries = JSON.parse(fs.readFileSync(logPath, \"utf-8\") as string) as BackupEntry[];\n } catch {\n entries = [];\n }\n }\n // Deduplicate by filename — update existing entry if same file backed up again\n entries = entries.filter((e) => e.filename !== entry.filename);\n entries.unshift(entry);\n // Keep last 100 entries\n if (entries.length > 100) entries = entries.slice(0, 100);\n fs.mkdirSync(path.dirname(logPath), { recursive: true });\n fs.writeFileSync(logPath, JSON.stringify(entries, null, 2), \"utf-8\");\n}\n\nexport function readBackupLog(dataDir: string): BackupEntry[] {\n const logPath = path.join(dataDir, \".agentic\", \"backup-log.json\");\n if (!fs.existsSync(logPath)) return [];\n try {\n return JSON.parse(fs.readFileSync(logPath, \"utf-8\") as string) as BackupEntry[];\n } catch {\n return [];\n }\n}\n\n// ─── runBackup ────────────────────────────────────────────────────────────────\n\nexport async function runBackup(\n output?: string,\n dataDir?: string,\n opts: { encrypt?: boolean; remote?: string } = {}\n): Promise<BackupManifest | null> {\n const dir = dataDir ?? process.cwd();\n const customersDir = path.join(dir, \"customers\");\n\n if (!fs.existsSync(customersDir)) {\n console.error(error(\"✗ No customers directory found.\"));\n process.exit(1);\n }\n\n const zipPath =\n output ?? path.join(dir, `dxcrm-backup-${new Date().toISOString().slice(0, 10)}.zip`);\n\n // Determine which directories to include\n const includeDirs = [\"customers/\"];\n if (fs.existsSync(path.join(dir, \".agentic\"))) {\n includeDirs.push(\".agentic/\");\n }\n\n try {\n execSync(`zip -r \"${zipPath}\" ${includeDirs.join(\" \")}`, { cwd: dir });\n\n // Build manifest and append to zip\n const manifest = buildManifest(dir, includeDirs, zipPath, opts.encrypt ?? false);\n const manifestPath = path.join(dir, \".dxcrm-manifest-tmp.json\");\n fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), \"utf-8\");\n try {\n execSync(`zip -j \"${zipPath}\" \"${manifestPath}\"`, { cwd: dir });\n } catch {\n /* non-fatal */\n }\n fs.unlinkSync(manifestPath);\n\n // Verify integrity\n const verified = verifyBackupFile(zipPath);\n\n const entry: BackupEntry = {\n filename: path.basename(zipPath),\n path: zipPath,\n createdAt: manifest.createdAt,\n sizeBytes: fs.existsSync(zipPath) ? fs.statSync(zipPath).size : 0,\n verified,\n encrypted: opts.encrypt ?? false,\n customerCount: manifest.customerCount,\n fileCount: manifest.fileCount,\n };\n appendBackupLog(dir, entry);\n\n // Remote upload\n if (opts.remote) {\n await uploadBackup(zipPath, opts.remote);\n }\n\n console.log(success(`✓ Backup saved: ${zipPath}`));\n console.log(\n info(\n ` Customers: ${manifest.customerCount} Files: ${manifest.fileCount} Size: ${(manifest.totalBytes / 1024 / 1024).toFixed(1)} MB`\n )\n );\n if (!verified) console.log(info(\" ⚠ Integrity check failed — backup may be incomplete\"));\n\n return manifest;\n } catch (err) {\n console.error(error(`✗ Backup failed: ${(err as Error).message}`));\n process.exit(1);\n }\n}\n\n// ─── Verify ───────────────────────────────────────────────────────────────────\n\nexport function verifyBackupFile(zipPath: string): boolean {\n if (!fs.existsSync(zipPath)) return false;\n try {\n execSync(`unzip -t \"${zipPath}\"`, { stdio: \"pipe\" });\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function runVerify(zipPath: string): Promise<void> {\n if (!fs.existsSync(zipPath)) {\n console.error(error(`✗ File not found: ${zipPath}`));\n process.exit(1);\n }\n\n console.log(info(`Verifying ${path.basename(zipPath)}...`));\n const ok = verifyBackupFile(zipPath);\n\n if (ok) {\n const size = fs.statSync(zipPath).size;\n const sha = sha256File(zipPath);\n console.log(success(\"✓ ZIP integrity OK\"));\n console.log(info(` Size: ${(size / 1024 / 1024).toFixed(1)} MB`));\n console.log(info(` SHA-256: ${sha}`));\n } else {\n console.error(error(\"✗ Integrity check failed\"));\n process.exit(1);\n }\n}\n\n// ─── Remote Upload ────────────────────────────────────────────────────────────\n\nexport async function uploadBackup(localPath: string, remote: string): Promise<void> {\n if (remote.startsWith(\"s3://\")) {\n // Requires AWS CLI or @aws-sdk/client-s3 to be installed\n try {\n execSync(`aws s3 cp \"${localPath}\" \"${remote}${path.basename(localPath)}\"`, {\n stdio: \"pipe\",\n });\n console.log(info(` ✓ Uploaded to ${remote}${path.basename(localPath)}`));\n } catch (err) {\n console.error(\n error(\n ` ✗ S3 upload failed (install aws-cli or @aws-sdk/client-s3): ${(err as Error).message}`\n )\n );\n }\n } else if (remote.startsWith(\"rsync://\")) {\n const dest = remote.replace(\"rsync://\", \"\");\n try {\n execSync(`rsync -az \"${localPath}\" \"${dest}\"`, { stdio: \"pipe\" });\n console.log(info(` ✓ Synced to ${dest}`));\n } catch (err) {\n console.error(error(` ✗ rsync failed: ${(err as Error).message}`));\n }\n } else {\n // Local directory copy\n try {\n const destPath = path.join(remote, path.basename(localPath));\n fs.mkdirSync(remote, { recursive: true });\n fs.copyFileSync(localPath, destPath);\n console.log(info(` ✓ Copied to ${destPath}`));\n } catch (err) {\n console.error(error(` ✗ Copy failed: ${(err as Error).message}`));\n }\n }\n}\n\n// ─── List Backups ─────────────────────────────────────────────────────────────\n\nexport function listBackupsInDir(dir: string): BackupEntry[] {\n if (!fs.existsSync(dir)) return [];\n try {\n return fs\n .readdirSync(dir)\n .filter((f) => f.match(/^dxcrm-backup-.*\\.(zip|dxbak)$/))\n .map((f) => {\n const fullPath = path.join(dir, f);\n const stat = fs.statSync(fullPath);\n return {\n filename: f,\n path: fullPath,\n createdAt: stat.mtime.toISOString(),\n sizeBytes: stat.size,\n verified: false,\n encrypted: f.endsWith(\".dxbak\"),\n customerCount: 0,\n fileCount: 0,\n } satisfies BackupEntry;\n })\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n } catch {\n return [];\n }\n}\n\n// ─── Retention Policy ─────────────────────────────────────────────────────────\n\nexport interface RetentionConfig {\n daily?: number;\n weekly?: number;\n monthly?: number;\n}\n\nexport function pruneOldBackups(dir: string, keep: number, retention?: RetentionConfig): void {\n const files = fs\n .readdirSync(dir)\n .filter((f) => f.match(/^dxcrm-backup-\\d{4}-\\d{2}-\\d{2}.*\\.(zip|dxbak)$/))\n .sort();\n\n if (!retention) {\n // Legacy: keep last N\n const toDelete = files.slice(0, Math.max(0, files.length - keep));\n for (const f of toDelete) {\n try {\n fs.unlinkSync(path.join(dir, f));\n } catch {\n /* ignore */\n }\n }\n return;\n }\n\n // Grandfathering: daily → weekly → monthly\n const daily = retention.daily ?? keep;\n const weekly = retention.weekly ?? 0;\n const monthly = retention.monthly ?? 0;\n\n const kept = new Set<string>();\n\n // Keep last N daily\n for (const f of files.slice(-daily)) kept.add(f);\n\n // Keep last backup of each week (up to 'weekly' weeks)\n if (weekly > 0) {\n const byWeek = new Map<string, string>();\n for (const f of files) {\n const dateMatch = f.match(/dxcrm-backup-(\\d{4}-\\d{2}-\\d{2})/);\n if (!dateMatch?.[1]) continue;\n const d = new Date(dateMatch[1]);\n // ISO week: year + week number\n const week = `${d.getFullYear()}-W${String(Math.ceil((d.getDate() + new Date(d.getFullYear(), 0, 1).getDay()) / 7)).padStart(2, \"0\")}`;\n byWeek.set(week, f); // last backup of the week wins\n }\n Array.from(byWeek.values())\n .slice(-weekly)\n .forEach((f) => kept.add(f));\n }\n\n // Keep last backup of each month (up to 'monthly' months)\n if (monthly > 0) {\n const byMonth = new Map<string, string>();\n for (const f of files) {\n const dateMatch = f.match(/dxcrm-backup-(\\d{4}-\\d{2})/);\n if (!dateMatch?.[1]) continue;\n byMonth.set(dateMatch[1], f); // last backup of the month wins\n }\n Array.from(byMonth.values())\n .slice(-monthly)\n .forEach((f) => kept.add(f));\n }\n\n for (const f of files) {\n if (!kept.has(f)) {\n try {\n fs.unlinkSync(path.join(dir, f));\n } catch {\n /* ignore */\n }\n }\n }\n}\n\n// ─── Schedule ─────────────────────────────────────────────────────────────────\n\nexport async function runBackupSchedule(\n opts: {\n every?: string;\n keep?: string;\n weekly?: string;\n monthly?: string;\n remote?: string;\n status?: boolean;\n clear?: boolean;\n },\n dataDir?: string\n): Promise<void> {\n const dir = dataDir ?? process.cwd();\n\n if (opts.clear) {\n const config = readAgenticConfig(dir);\n delete config.backupSchedule;\n writeAgenticConfig(dir, config);\n console.log(success(\"✓ Backup schedule cleared.\"));\n return;\n }\n\n if (!opts.every && !opts.status) {\n console.error(error(\"✗ --every is required (e.g. --every day)\"));\n process.exit(1);\n return;\n }\n\n if (opts.every) {\n const keep = opts.keep ? parseInt(opts.keep, 10) : 7;\n const config = readAgenticConfig(dir);\n config.backupSchedule = {\n every: opts.every,\n keep,\n ...(opts.weekly ? { weekly: parseInt(opts.weekly, 10) } : {}),\n ...(opts.monthly ? { monthly: parseInt(opts.monthly, 10) } : {}),\n ...(opts.remote ? { remote: opts.remote } : {}),\n lastBackup: null,\n };\n writeAgenticConfig(dir, config);\n if (!opts.status) {\n console.log(\n success(\n `✓ Backup schedule set: every ${opts.every}, keep ${keep} daily${opts.weekly ? ` / ${opts.weekly} weekly` : \"\"}${opts.monthly ? ` / ${opts.monthly} monthly` : \"\"}.`\n )\n );\n }\n }\n\n if (opts.status) {\n const config = readAgenticConfig(dir);\n const sched = config.backupSchedule;\n if (!sched) {\n console.log(info(\"No backup schedule configured.\"));\n } else {\n console.log(bold(\"Backup Schedule:\"));\n console.log(` every: ${sched.every}`);\n console.log(` keep: ${sched.keep} daily backups`);\n if (sched.weekly) console.log(` weekly: ${sched.weekly} weekly backups`);\n if (sched.monthly) console.log(` monthly: ${sched.monthly} monthly backups`);\n if (sched.remote) console.log(` remote: ${sched.remote}`);\n console.log(` lastBackup: ${sched.lastBackup ?? \"never\"}`);\n }\n }\n}\n\nexport function shouldRunScheduledBackup(dataDir: string): boolean {\n const config = readAgenticConfig(dataDir);\n const sched = config.backupSchedule;\n if (!sched) return false;\n if (!sched.lastBackup) return true;\n const last = new Date(sched.lastBackup).getTime();\n const oneDayMs = 24 * 60 * 60 * 1000;\n return Date.now() - last >= oneDayMs;\n}\n\nexport async function runScheduledBackupIfDue(dataDir: string): Promise<void> {\n if (!shouldRunScheduledBackup(dataDir)) return;\n const config = readAgenticConfig(dataDir);\n const sched = config.backupSchedule!;\n const customersDir = path.join(dataDir, \"customers\");\n if (!fs.existsSync(customersDir)) return;\n\n const zipPath = path.join(dataDir, `dxcrm-backup-${new Date().toISOString().slice(0, 10)}.zip`);\n const includeDirs = [\"customers/\"];\n if (fs.existsSync(path.join(dataDir, \".agentic\"))) includeDirs.push(\".agentic/\");\n\n try {\n execSync(`zip -r \"${zipPath}\" ${includeDirs.join(\" \")}`, { cwd: dataDir });\n\n const retention: RetentionConfig | undefined =\n (sched.weekly ?? sched.monthly)\n ? {\n daily: sched.keep,\n ...(sched.weekly ? { weekly: sched.weekly } : {}),\n ...(sched.monthly ? { monthly: sched.monthly } : {}),\n }\n : undefined;\n pruneOldBackups(dataDir, sched.keep, retention);\n\n if (sched.remote) {\n await uploadBackup(zipPath, sched.remote).catch(() => {\n /* non-fatal */\n });\n }\n\n config.backupSchedule!.lastBackup = new Date().toISOString();\n writeAgenticConfig(dataDir, config);\n process.stderr.write(`[daemon] Scheduled backup saved: ${zipPath}\\n`);\n } catch (err) {\n process.stderr.write(`[daemon] Scheduled backup failed: ${(err as Error).message}\\n`);\n }\n}\n\n// ─── Restore ──────────────────────────────────────────────────────────────────\n\nexport async function runRestore(zipPath: string, dataDir?: string): Promise<void> {\n const dir = dataDir ?? process.cwd();\n try {\n execSync(`unzip -o \"${path.resolve(zipPath)}\" -d \"${dir}\"`, { cwd: dir });\n console.log(success(\"✓ Restore complete.\"));\n } catch (err) {\n console.error(error(`✗ Restore failed: ${(err as Error).message}`));\n process.exit(1);\n }\n}\n\nexport interface RestoreDrillReport {\n ok: boolean;\n verified: boolean;\n hasCustomers: boolean;\n hasAgentic: boolean;\n reason?: string;\n}\n\n/**\n * Restore-drill: verify a backup is actually restorable WITHOUT touching live\n * data — checks integrity (unzip -t) and that the archive contains the expected\n * top-level state (customers/, .agentic/). Returns a report for monitoring.\n */\nexport async function runRestoreDrill(\n zipPath: string,\n opts: { silent?: boolean } = {}\n): Promise<RestoreDrillReport> {\n const resolved = path.resolve(zipPath);\n if (!fs.existsSync(resolved)) {\n if (!opts.silent) console.error(error(`✗ File not found: ${zipPath}`));\n return {\n ok: false,\n verified: false,\n hasCustomers: false,\n hasAgentic: false,\n reason: \"not_found\",\n };\n }\n\n const verified = verifyBackupFile(resolved);\n let hasCustomers = false;\n let hasAgentic = false;\n if (verified) {\n try {\n const listing = execSync(`unzip -l \"${resolved}\"`, { stdio: \"pipe\" }).toString();\n hasCustomers = listing.includes(\"customers/\");\n hasAgentic = listing.includes(\".agentic/\");\n } catch {\n /* listing failed — treated as incomplete */\n }\n }\n\n const ok = verified && hasCustomers;\n if (!opts.silent) {\n if (ok) {\n console.log(\n success(\n `✓ Restore drill OK — integrity verified; customers/${hasAgentic ? \" + .agentic/\" : \"\"} present`\n )\n );\n } else {\n console.error(\n error(`✗ Restore drill failed (verified=${verified}, customers=${hasCustomers})`)\n );\n }\n }\n return { ok, verified, hasCustomers, hasAgentic };\n}\n\n// ─── Commands ─────────────────────────────────────────────────────────────────\n\nconst scheduleSubCommand = new Command(\"schedule\")\n .description(\"Configure automatic backup schedule\")\n .option(\"--every <interval>\", \"Backup interval (e.g. day)\")\n .option(\"--keep <n>\", \"Daily backups to keep (default: 7)\")\n .option(\"--weekly <n>\", \"Weekly backups to keep (e.g. 4)\")\n .option(\"--monthly <n>\", \"Monthly backups to keep (e.g. 12)\")\n .option(\"--remote <url>\", \"Remote destination (s3://, rsync://, or local path)\")\n .option(\"--status\", \"Show current schedule\")\n .option(\"--clear\", \"Remove backup schedule\")\n .action((opts) => runBackupSchedule(opts, process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd()));\n\nconst verifySubCommand = new Command(\"verify\")\n .argument(\"<path>\", \"Path to backup zip\")\n .description(\"Verify backup integrity (SHA-256 + zip test)\")\n .action((zipPath: string) => runVerify(zipPath));\n\nconst drillSubCommand = new Command(\"drill\")\n .argument(\"<path>\", \"Path to backup zip\")\n .description(\"Restore-drill: verify a backup is restorable without touching live data\")\n .action(async (zipPath: string) => {\n const report = await runRestoreDrill(zipPath);\n if (!report.ok) process.exitCode = 1;\n });\n\nconst listSubCommand = new Command(\"list\").description(\"List available backups\").action(() => {\n const dir = process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd();\n const entries = readBackupLog(dir);\n const fileEntries = listBackupsInDir(dir);\n const combined = entries.length > 0 ? entries : fileEntries;\n if (combined.length === 0) {\n console.log(info(\"No backups found.\"));\n return;\n }\n for (const e of combined) {\n const enc = e.encrypted ? \" [encrypted]\" : \"\";\n const ver = e.verified ? \" ✓\" : \"\";\n const mb = e.sizeBytes > 0 ? ` ${(e.sizeBytes / 1024 / 1024).toFixed(1)} MB` : \"\";\n console.log(` ${bold(e.filename)}${enc}${ver}${mb} ${e.createdAt.slice(0, 10)}`);\n }\n});\n\nexport const backupCommand = new Command(\"backup\")\n .argument(\"[output]\", \"Output path for backup zip\")\n .description(\"Backup customers/ + .agentic/ directories\")\n .option(\"--encrypt\", \"Encrypt the backup (AES-256-GCM)\")\n .option(\"--remote <url>\", \"Also upload to remote (s3://, rsync://, or path)\")\n .action((output?: string, opts?: { encrypt?: boolean; remote?: string }) => {\n void runBackup(output, process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd(), opts ?? {});\n });\n\nbackupCommand.addCommand(scheduleSubCommand);\nbackupCommand.addCommand(verifySubCommand);\nbackupCommand.addCommand(drillSubCommand);\nbackupCommand.addCommand(listSubCommand);\n\nexport const restoreCommand = new Command(\"restore\")\n .argument(\"<path>\", \"Path to backup zip\")\n .description(\"Restore from backup zip\")\n .action((zipPath: string) => runRestore(zipPath, process.env[\"DXCRM_DATA_DIR\"] ?? process.cwd()));\n","import fs from \"fs\";\nimport path from \"path\";\n\nexport interface AuditEntry {\n timestamp: string; // ISO 8601\n actor: string; // DXCRM_ACTOR env var, or \"system\"\n tool: string; // \"log_interaction\" | \"update_deal\" | \"update_customer_facts\" | etc.\n slug: string; // customer slug\n summary: string; // short description (first 120 chars of summary/deal name)\n}\n\n// File format (one line per entry, append-only):\n// 2026-06-01T09:14:00Z | alice | log_interaction | acme-corp | Called about Q3 renewal...\n\nconst AUDIT_LOG_PATH = \".agentic/audit.log\";\n\nexport function getActor(): string {\n const actor = process.env[\"DXCRM_ACTOR\"];\n return actor && actor.trim().length > 0 ? actor.trim() : \"system\";\n}\n\nexport function writeAuditEntry(dataDir: string, entry: AuditEntry): void {\n const logPath = path.join(dataDir, AUDIT_LOG_PATH);\n const logDir = path.dirname(logPath);\n\n if (!fs.existsSync(logDir)) {\n fs.mkdirSync(logDir, { recursive: true });\n }\n\n const truncatedSummary = entry.summary.slice(0, 120);\n const line = `${entry.timestamp} | ${entry.actor} | ${entry.tool} | ${entry.slug} | ${truncatedSummary}\\n`;\n\n fs.appendFileSync(logPath, line, \"utf-8\");\n}\n\nexport function readAuditLog(dataDir: string): AuditEntry[] {\n const logPath = path.join(dataDir, AUDIT_LOG_PATH);\n\n if (!fs.existsSync(logPath)) {\n return [];\n }\n\n const content = fs.readFileSync(logPath, \"utf-8\") as string;\n const lines = content.split(\"\\n\");\n\n const entries: AuditEntry[] = [];\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n const parts = trimmed.split(\" | \");\n if (parts.length < 5) continue;\n\n const [timestamp, actor, tool, slug, ...summaryParts] = parts;\n const summary = summaryParts.join(\" | \");\n\n if (timestamp && actor && tool && slug) {\n entries.push({ timestamp, actor, tool, slug, summary: summary ?? \"\" });\n }\n }\n\n return entries;\n}\n\nexport function filterAuditLog(\n entries: AuditEntry[],\n opts: { slug?: string; actor?: string; limit?: number }\n): AuditEntry[] {\n let filtered = entries;\n\n if (opts.slug !== undefined) {\n filtered = filtered.filter((e) => e.slug === opts.slug);\n }\n\n if (opts.actor !== undefined) {\n filtered = filtered.filter((e) => e.actor === opts.actor);\n }\n\n if (opts.limit !== undefined) {\n filtered = filtered.slice(-opts.limit);\n }\n\n return filtered;\n}\n","import fs from \"fs\";\nimport path from \"path\";\n\nexport type Role = \"admin\" | \"manager\" | \"rep\";\n\nexport interface RbacConfig {\n actors: Record<string, Role>;\n default?: Role;\n owned_customers?: Record<string, string[]>;\n /** Field-level ACL: field name → roles allowed to see it. Others get it redacted. */\n field_acl?: Record<string, Role[]>;\n}\n\nconst ALLOWED_TOOLS: Record<Role, string[]> = {\n admin: [\n \"log_interaction\",\n \"update_deal\",\n \"update_customer_facts\",\n \"export_customer\",\n \"pursue_goal\",\n \"register_push_subscription\",\n \"define_custom_object\",\n \"create_record\",\n ],\n manager: [\"log_interaction\", \"update_deal\", \"pursue_goal\", \"create_record\"],\n rep: [\"log_interaction\", \"update_deal\", \"create_record\"],\n};\n\nfunction rbacPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"rbac.json\");\n}\n\nexport function getRbacConfig(dataDir: string): RbacConfig {\n const p = rbacPath(dataDir);\n if (!fs.existsSync(p)) return { actors: {} };\n try {\n return JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as RbacConfig;\n } catch {\n return { actors: {} };\n }\n}\n\nexport function setActorRole(dataDir: string, actor: string, role: Role): void {\n const p = rbacPath(dataDir);\n fs.mkdirSync(path.dirname(p), { recursive: true });\n const config = getRbacConfig(dataDir);\n config.actors[actor] = role;\n fs.writeFileSync(p, JSON.stringify(config, null, 2), \"utf-8\");\n}\n\nexport function getRole(dataDir: string, actor: string): Role {\n const config = getRbacConfig(dataDir);\n return config.actors[actor] ?? config.default ?? \"rep\";\n}\n\nexport function canWrite(role: Role, tool: string): boolean {\n return ALLOWED_TOOLS[role]?.includes(tool) ?? false;\n}\n\nexport function assertCanWrite(role: Role, tool: string, actor: string): void {\n if (!canWrite(role, tool)) {\n throw new Error(`Access denied: '${actor}' (role: ${role}) cannot use tool '${tool}'`);\n }\n}\n\nexport function enforceRbac(dataDir: string, tool: string): void {\n if (!fs.existsSync(rbacPath(dataDir))) return; // no rbac.json = open access\n const actor = process.env[\"DXCRM_ACTOR\"] ?? \"system\";\n if (actor === \"system\") return; // internal system actor bypasses RBAC\n const role = getRole(dataDir, actor);\n assertCanWrite(role, tool, actor);\n}\n\nexport function canSeeCustomer(dataDir: string, actor: string, slug: string): boolean {\n if (!fs.existsSync(rbacPath(dataDir))) return true; // open access\n if (actor === \"system\") return true; // internal system actor always has full access\n const config = getRbacConfig(dataDir);\n const role = config.actors[actor] ?? config.default ?? \"rep\";\n if (role === \"admin\" || role === \"manager\") return true;\n // rep: only sees customers listed in owned_customers[actor]\n const owned = config.owned_customers;\n if (!owned) return false;\n return (owned[actor] ?? []).includes(slug);\n}\n\n/** Load the field-level ACL (field → allowed roles) from rbac.json. */\nexport function loadFieldAcl(dataDir: string): Record<string, Role[]> {\n return getRbacConfig(dataDir).field_acl ?? {};\n}\n\n/** Whether a role may see a field given the ACL (fields not in the ACL are public). */\nexport function canSeeField(field: string, role: Role, acl: Record<string, Role[]>): boolean {\n const allowed = acl[field];\n if (!allowed) return true;\n return allowed.includes(role);\n}\n\n/** Return a copy of `values` with fields the role may not see removed. */\nexport function redactFields<T extends Record<string, unknown>>(\n values: T,\n role: Role,\n acl: Record<string, Role[]>\n): Partial<T> {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(values)) {\n if (canSeeField(k, role, acl)) out[k] = v;\n }\n return out as Partial<T>;\n}\n","export interface Session {\n customerSlug: string;\n customerName: string;\n startedAt: string;\n owner?: string;\n}\n\nlet activeSession: Session | null = null;\n\nexport function setSession(s: Session): void {\n activeSession = s;\n}\n\nexport function getSession(): Session | null {\n return activeSession;\n}\n\nexport function clearSession(): void {\n activeSession = null;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/core/session-store.ts
|
|
2
|
+
let activeSession = null;
|
|
3
|
+
function setSession(s) {
|
|
4
|
+
activeSession = s;
|
|
5
|
+
}
|
|
6
|
+
function getSession() {
|
|
7
|
+
return activeSession;
|
|
8
|
+
}
|
|
9
|
+
function clearSession() {
|
|
10
|
+
activeSession = null;
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { getSession as n, setSession as r, clearSession as t };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=session-store-CEa39Dxs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-store-CEa39Dxs.js","names":[],"sources":["../src/core/session-store.ts"],"sourcesContent":["export interface Session {\n customerSlug: string;\n customerName: string;\n startedAt: string;\n owner?: string;\n}\n\nlet activeSession: Session | null = null;\n\nexport function setSession(s: Session): void {\n activeSession = s;\n}\n\nexport function getSession(): Session | null {\n return activeSession;\n}\n\nexport function clearSession(): void {\n activeSession = null;\n}\n"],"mappings":";AAOA,IAAI,gBAAgC;AAEpC,SAAgB,WAAW,GAAkB;CAC3C,gBAAgB;AAClB;AAEA,SAAgB,aAA6B;CAC3C,OAAO;AACT;AAEA,SAAgB,eAAqB;CACnC,gBAAgB;AAClB"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
//#region src/core/sla-engine.ts
|
|
5
|
+
const DEFAULT_RULES = [
|
|
6
|
+
{
|
|
7
|
+
priority: "urgent",
|
|
8
|
+
resolveDays: 1
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
priority: "high",
|
|
12
|
+
resolveDays: 2
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
priority: "normal",
|
|
16
|
+
resolveDays: 5
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
priority: "low",
|
|
20
|
+
resolveDays: 10
|
|
21
|
+
}
|
|
22
|
+
];
|
|
23
|
+
function loadSlaRules(dataDir) {
|
|
24
|
+
const p = path.join(dataDir, ".agentic", "sla-rules.yaml");
|
|
25
|
+
if (!fs.existsSync(p)) return DEFAULT_RULES;
|
|
26
|
+
try {
|
|
27
|
+
return yaml.load(fs.readFileSync(p, "utf-8"))?.rules ?? DEFAULT_RULES;
|
|
28
|
+
} catch {
|
|
29
|
+
return DEFAULT_RULES;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function addDaysToDate(isoDate, days) {
|
|
33
|
+
const [year, month, day] = isoDate.split("-").map(Number);
|
|
34
|
+
const d = new Date(Date.UTC(year, month - 1, day));
|
|
35
|
+
d.setUTCDate(d.getUTCDate() + days);
|
|
36
|
+
return d.toISOString().slice(0, 10);
|
|
37
|
+
}
|
|
38
|
+
function calcSlaDue(createdDate, priority, rules) {
|
|
39
|
+
return addDaysToDate(createdDate, (rules.find((r) => r.priority === priority) ?? { resolveDays: 5 }).resolveDays);
|
|
40
|
+
}
|
|
41
|
+
function isSlaBreach(ticket, today) {
|
|
42
|
+
if (ticket.status === "resolved" || ticket.status === "closed") return false;
|
|
43
|
+
if (!ticket.slaDue) return false;
|
|
44
|
+
return ticket.slaDue < today;
|
|
45
|
+
}
|
|
46
|
+
async function checkSlaBreaches(dataDir, today) {
|
|
47
|
+
const { listAllTickets } = await import("./ticket-writer-CjqKeIRD.js");
|
|
48
|
+
return (await listAllTickets(dataDir)).filter(({ ticket }) => isSlaBreach(ticket, today));
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { loadSlaRules as i, checkSlaBreaches as n, isSlaBreach as r, calcSlaDue as t };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=sla-engine-BqX-7u-7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sla-engine-BqX-7u-7.js","names":[],"sources":["../src/core/sla-engine.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport yaml from \"js-yaml\";\nimport type { Ticket } from \"../schemas/ticket.js\";\n\nexport interface SlaRule {\n priority: Ticket[\"priority\"];\n resolveDays: number;\n}\n\nconst DEFAULT_RULES: SlaRule[] = [\n { priority: \"urgent\", resolveDays: 1 },\n { priority: \"high\", resolveDays: 2 },\n { priority: \"normal\", resolveDays: 5 },\n { priority: \"low\", resolveDays: 10 },\n];\n\nexport function loadSlaRules(dataDir: string): SlaRule[] {\n const p = path.join(dataDir, \".agentic\", \"sla-rules.yaml\");\n if (!fs.existsSync(p)) return DEFAULT_RULES;\n try {\n const raw = yaml.load(fs.readFileSync(p, \"utf-8\") as string) as { rules?: SlaRule[] };\n return raw?.rules ?? DEFAULT_RULES;\n } catch {\n return DEFAULT_RULES;\n }\n}\n\nfunction addDaysToDate(isoDate: string, days: number): string {\n const [year, month, day] = isoDate.split(\"-\").map(Number) as [number, number, number];\n const d = new Date(Date.UTC(year, month - 1, day));\n d.setUTCDate(d.getUTCDate() + days);\n return d.toISOString().slice(0, 10);\n}\n\nexport function calcSlaDue(\n createdDate: string,\n priority: Ticket[\"priority\"],\n rules: SlaRule[]\n): string {\n const rule = rules.find((r) => r.priority === priority) ?? { resolveDays: 5 };\n return addDaysToDate(createdDate, rule.resolveDays);\n}\n\nexport function isSlaBreach(ticket: Ticket, today: string): boolean {\n if (ticket.status === \"resolved\" || ticket.status === \"closed\") return false;\n if (!ticket.slaDue) return false;\n return ticket.slaDue < today;\n}\n\nexport async function checkSlaBreaches(\n dataDir: string,\n today: string\n): Promise<Array<{ slug: string; ticket: Ticket }>> {\n const { listAllTickets } = await import(\"../fs/ticket-writer.js\");\n const all = await listAllTickets(dataDir);\n return all.filter(({ ticket }) => isSlaBreach(ticket, today));\n}\n"],"mappings":";;;;AAUA,MAAM,gBAA2B;CAC/B;EAAE,UAAU;EAAU,aAAa;CAAE;CACrC;EAAE,UAAU;EAAQ,aAAa;CAAE;CACnC;EAAE,UAAU;EAAU,aAAa;CAAE;CACrC;EAAE,UAAU;EAAO,aAAa;CAAG;AACrC;AAEA,SAAgB,aAAa,SAA4B;CACvD,MAAM,IAAI,KAAK,KAAK,SAAS,YAAY,gBAAgB;CACzD,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO;CAC9B,IAAI;EAEF,OADY,KAAK,KAAK,GAAG,aAAa,GAAG,OAAO,CACvC,GAAG,SAAS;CACvB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,cAAc,SAAiB,MAAsB;CAC5D,MAAM,CAAC,MAAM,OAAO,OAAO,QAAQ,MAAM,GAAG,EAAE,IAAI,MAAM;CACxD,MAAM,IAAI,IAAI,KAAK,KAAK,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC;CACjD,EAAE,WAAW,EAAE,WAAW,IAAI,IAAI;CAClC,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AACpC;AAEA,SAAgB,WACd,aACA,UACA,OACQ;CAER,OAAO,cAAc,cADR,MAAM,MAAM,MAAM,EAAE,aAAa,QAAQ,KAAK,EAAE,aAAa,EAAE,GACrC,WAAW;AACpD;AAEA,SAAgB,YAAY,QAAgB,OAAwB;CAClE,IAAI,OAAO,WAAW,cAAc,OAAO,WAAW,UAAU,OAAO;CACvE,IAAI,CAAC,OAAO,QAAQ,OAAO;CAC3B,OAAO,OAAO,SAAS;AACzB;AAEA,eAAsB,iBACpB,SACA,OACkD;CAClD,MAAM,EAAE,mBAAmB,MAAM,OAAO;CAExC,QAAO,MADW,eAAe,OAAO,GAC7B,QAAQ,EAAE,aAAa,YAAY,QAAQ,KAAK,CAAC;AAC9D"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { t as hybridSearch } from "./hybrid-search-BmHttLrR.js";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import { randomBytes } from "crypto";
|
|
5
|
+
//#region src/core/sop.ts
|
|
6
|
+
function globalPath(dataDir) {
|
|
7
|
+
return path.join(dataDir, ".agentic", "sops.json");
|
|
8
|
+
}
|
|
9
|
+
function customerPath(dataDir, slug) {
|
|
10
|
+
return path.join(dataDir, "customers", slug, "sops.json");
|
|
11
|
+
}
|
|
12
|
+
function readFile(p) {
|
|
13
|
+
if (!fs.existsSync(p)) return [];
|
|
14
|
+
try {
|
|
15
|
+
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
16
|
+
return Array.isArray(data.sops) ? data.sops : [];
|
|
17
|
+
} catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function writeFile(p, sops) {
|
|
22
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
23
|
+
fs.writeFileSync(p, JSON.stringify({ sops }, null, 2), "utf-8");
|
|
24
|
+
}
|
|
25
|
+
function addSop(dataDir, s) {
|
|
26
|
+
const sop = {
|
|
27
|
+
id: `sop_${randomBytes(5).toString("hex")}`,
|
|
28
|
+
scope: s.scope,
|
|
29
|
+
...s.slug ? { slug: s.slug } : {},
|
|
30
|
+
title: s.title,
|
|
31
|
+
triggers: s.triggers,
|
|
32
|
+
...s.tags ? { tags: s.tags } : {},
|
|
33
|
+
body: s.body,
|
|
34
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
35
|
+
};
|
|
36
|
+
const p = s.scope === "global" ? globalPath(dataDir) : customerPath(dataDir, s.slug ?? "");
|
|
37
|
+
writeFile(p, [...readFile(p), sop]);
|
|
38
|
+
return sop;
|
|
39
|
+
}
|
|
40
|
+
function loadSops(dataDir, slug) {
|
|
41
|
+
const global = readFile(globalPath(dataDir));
|
|
42
|
+
if (!slug) return global;
|
|
43
|
+
return [...global, ...readFile(customerPath(dataDir, slug))];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Find SOPs relevant to a task. Hybrid-search over title+triggers+body; among
|
|
47
|
+
* matches, customer-specific SOPs are returned before global ones.
|
|
48
|
+
*/
|
|
49
|
+
async function findSops(dataDir, query, slug) {
|
|
50
|
+
const sops = loadSops(dataDir, slug);
|
|
51
|
+
const ranked = hybridSearch(query, sops.map((s) => ({
|
|
52
|
+
id: s.id,
|
|
53
|
+
text: `${s.title} ${s.triggers.join(" ")} ${(s.tags ?? []).join(" ")} ${s.body}`
|
|
54
|
+
})));
|
|
55
|
+
const byId = new Map(sops.map((s) => [s.id, s]));
|
|
56
|
+
const matched = ranked.map((r, i) => ({
|
|
57
|
+
sop: byId.get(r.id),
|
|
58
|
+
rank: i
|
|
59
|
+
})).filter((x) => x.sop);
|
|
60
|
+
matched.sort((a, b) => {
|
|
61
|
+
const ac = a.sop.scope === "customer" ? 0 : 1;
|
|
62
|
+
const bc = b.sop.scope === "customer" ? 0 : 1;
|
|
63
|
+
return ac !== bc ? ac - bc : a.rank - b.rank;
|
|
64
|
+
});
|
|
65
|
+
return matched.map((x) => x.sop);
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
export { findSops as n, loadSops as r, addSop as t };
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=sop-Vp0UPWFW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sop-Vp0UPWFW.js","names":[],"sources":["../src/core/sop.ts"],"sourcesContent":["import { randomBytes } from \"crypto\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { hybridSearch } from \"./hybrid-search.js\";\n\n/**\n * SOP module (domino D7 / F5): Standard Operating Procedures — procedural\n * instructions, global or per customer, found via hybrid search to guide task\n * execution (\"how we do X\"). Customer-specific SOPs take precedence over global.\n */\nexport interface Sop {\n id: string;\n scope: \"global\" | \"customer\";\n slug?: string;\n title: string;\n triggers: string[];\n tags?: string[];\n body: string;\n createdAt: string;\n}\n\nfunction globalPath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"sops.json\");\n}\nfunction customerPath(dataDir: string, slug: string): string {\n return path.join(dataDir, \"customers\", slug, \"sops.json\");\n}\n\nfunction readFile(p: string): Sop[] {\n if (!fs.existsSync(p)) return [];\n try {\n const data = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as { sops?: Sop[] };\n return Array.isArray(data.sops) ? data.sops : [];\n } catch {\n return [];\n }\n}\nfunction writeFile(p: string, sops: Sop[]): void {\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, JSON.stringify({ sops }, null, 2), \"utf-8\");\n}\n\nexport function addSop(\n dataDir: string,\n s: {\n scope: \"global\" | \"customer\";\n slug?: string;\n title: string;\n triggers: string[];\n tags?: string[];\n body: string;\n }\n): Sop {\n const sop: Sop = {\n id: `sop_${randomBytes(5).toString(\"hex\")}`,\n scope: s.scope,\n ...(s.slug ? { slug: s.slug } : {}),\n title: s.title,\n triggers: s.triggers,\n ...(s.tags ? { tags: s.tags } : {}),\n body: s.body,\n createdAt: new Date().toISOString(),\n };\n const p = s.scope === \"global\" ? globalPath(dataDir) : customerPath(dataDir, s.slug ?? \"\");\n writeFile(p, [...readFile(p), sop]);\n return sop;\n}\n\nexport function loadSops(dataDir: string, slug?: string): Sop[] {\n const global = readFile(globalPath(dataDir));\n if (!slug) return global;\n return [...global, ...readFile(customerPath(dataDir, slug))];\n}\n\n/**\n * Find SOPs relevant to a task. Hybrid-search over title+triggers+body; among\n * matches, customer-specific SOPs are returned before global ones.\n */\nexport async function findSops(dataDir: string, query: string, slug?: string): Promise<Sop[]> {\n const sops = loadSops(dataDir, slug);\n const docs = sops.map((s) => ({\n id: s.id,\n text: `${s.title} ${s.triggers.join(\" \")} ${(s.tags ?? []).join(\" \")} ${s.body}`,\n }));\n const ranked = hybridSearch(query, docs);\n const byId = new Map(sops.map((s) => [s.id, s]));\n const matched = ranked.map((r, i) => ({ sop: byId.get(r.id)!, rank: i })).filter((x) => x.sop);\n // Customer-scoped SOPs first, then by relevance rank.\n matched.sort((a, b) => {\n const ac = a.sop.scope === \"customer\" ? 0 : 1;\n const bc = b.sop.scope === \"customer\" ? 0 : 1;\n return ac !== bc ? ac - bc : a.rank - b.rank;\n });\n return matched.map((x) => x.sop);\n}\n"],"mappings":";;;;;AAqBA,SAAS,WAAW,SAAyB;CAC3C,OAAO,KAAK,KAAK,SAAS,YAAY,WAAW;AACnD;AACA,SAAS,aAAa,SAAiB,MAAsB;CAC3D,OAAO,KAAK,KAAK,SAAS,aAAa,MAAM,WAAW;AAC1D;AAEA,SAAS,SAAS,GAAkB;CAClC,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC;CAC/B,IAAI;EACF,MAAM,OAAO,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;EAC7D,OAAO,MAAM,QAAQ,KAAK,IAAI,IAAI,KAAK,OAAO,CAAC;CACjD,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AACA,SAAS,UAAU,GAAW,MAAmB;CAC/C,GAAG,UAAU,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACjD,GAAG,cAAc,GAAG,KAAK,UAAU,EAAE,KAAK,GAAG,MAAM,CAAC,GAAG,OAAO;AAChE;AAEA,SAAgB,OACd,SACA,GAQK;CACL,MAAM,MAAW;EACf,IAAI,OAAO,YAAY,CAAC,EAAE,SAAS,KAAK;EACxC,OAAO,EAAE;EACT,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;EACjC,OAAO,EAAE;EACT,UAAU,EAAE;EACZ,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;EACjC,MAAM,EAAE;EACR,4BAAW,IAAI,KAAK,GAAE,YAAY;CACpC;CACA,MAAM,IAAI,EAAE,UAAU,WAAW,WAAW,OAAO,IAAI,aAAa,SAAS,EAAE,QAAQ,EAAE;CACzF,UAAU,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,GAAG,CAAC;CAClC,OAAO;AACT;AAEA,SAAgB,SAAS,SAAiB,MAAsB;CAC9D,MAAM,SAAS,SAAS,WAAW,OAAO,CAAC;CAC3C,IAAI,CAAC,MAAM,OAAO;CAClB,OAAO,CAAC,GAAG,QAAQ,GAAG,SAAS,aAAa,SAAS,IAAI,CAAC,CAAC;AAC7D;;;;;AAMA,eAAsB,SAAS,SAAiB,OAAe,MAA+B;CAC5F,MAAM,OAAO,SAAS,SAAS,IAAI;CAKnC,MAAM,SAAS,aAAa,OAJf,KAAK,KAAK,OAAO;EAC5B,IAAI,EAAE;EACN,MAAM,GAAG,EAAE,MAAM,GAAG,EAAE,SAAS,KAAK,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,GAAG,KAAK,GAAG,EAAE,GAAG,EAAE;CAC5E,EACsC,CAAC;CACvC,MAAM,OAAO,IAAI,IAAI,KAAK,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CAC/C,MAAM,UAAU,OAAO,KAAK,GAAG,OAAO;EAAE,KAAK,KAAK,IAAI,EAAE,EAAE;EAAI,MAAM;CAAE,EAAE,EAAE,QAAQ,MAAM,EAAE,GAAG;CAE7F,QAAQ,MAAM,GAAG,MAAM;EACrB,MAAM,KAAK,EAAE,IAAI,UAAU,aAAa,IAAI;EAC5C,MAAM,KAAK,EAAE,IAAI,UAAU,aAAa,IAAI;EAC5C,OAAO,OAAO,KAAK,KAAK,KAAK,EAAE,OAAO,EAAE;CAC1C,CAAC;CACD,OAAO,QAAQ,KAAK,MAAM,EAAE,GAAG;AACjC"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { createHmac } from "crypto";
|
|
5
|
+
import yaml from "js-yaml";
|
|
6
|
+
//#region src/schemas/survey.ts
|
|
7
|
+
const SurveyDefinitionSchema = z.object({
|
|
8
|
+
id: z.string().min(1),
|
|
9
|
+
type: z.enum([
|
|
10
|
+
"nps",
|
|
11
|
+
"csat",
|
|
12
|
+
"ces"
|
|
13
|
+
]).default("nps"),
|
|
14
|
+
question: z.string().min(1),
|
|
15
|
+
scale: z.object({
|
|
16
|
+
min: z.number().default(0),
|
|
17
|
+
max: z.number().default(10)
|
|
18
|
+
}).default({
|
|
19
|
+
min: 0,
|
|
20
|
+
max: 10
|
|
21
|
+
}),
|
|
22
|
+
includeComment: z.boolean().default(true),
|
|
23
|
+
commentPrompt: z.string().optional(),
|
|
24
|
+
createdAt: z.string()
|
|
25
|
+
});
|
|
26
|
+
const SurveyResponseSchema = z.object({
|
|
27
|
+
surveyId: z.string(),
|
|
28
|
+
slug: z.string(),
|
|
29
|
+
contactEmail: z.string().email(),
|
|
30
|
+
score: z.number().int(),
|
|
31
|
+
comment: z.string().optional(),
|
|
32
|
+
respondedAt: z.string(),
|
|
33
|
+
token: z.string(),
|
|
34
|
+
sentAt: z.string()
|
|
35
|
+
});
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/core/survey-engine.ts
|
|
38
|
+
const SURVEY_SECRET = process.env["DXCRM_SURVEY_SECRET"] ?? "dxcrm-survey-default-secret";
|
|
39
|
+
function surveysDir(dataDir) {
|
|
40
|
+
return path.join(dataDir, ".agentic", "surveys");
|
|
41
|
+
}
|
|
42
|
+
function responsesDir(dataDir, surveyId) {
|
|
43
|
+
return path.join(dataDir, ".agentic", "survey-responses", surveyId);
|
|
44
|
+
}
|
|
45
|
+
function getSurvey(dataDir, surveyId) {
|
|
46
|
+
const p = path.join(surveysDir(dataDir), `${surveyId}.yaml`);
|
|
47
|
+
if (!fs.existsSync(p)) return null;
|
|
48
|
+
try {
|
|
49
|
+
const raw = yaml.load(fs.readFileSync(p, "utf-8"));
|
|
50
|
+
const result = SurveyDefinitionSchema.safeParse(raw);
|
|
51
|
+
return result.success ? result.data : null;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function writeSurvey(dataDir, survey) {
|
|
57
|
+
const dir = surveysDir(dataDir);
|
|
58
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
59
|
+
fs.writeFileSync(path.join(dir, `${survey.id}.yaml`), yaml.dump(survey), "utf-8");
|
|
60
|
+
}
|
|
61
|
+
function listSurveys(dataDir) {
|
|
62
|
+
const dir = surveysDir(dataDir);
|
|
63
|
+
if (!fs.existsSync(dir)) return [];
|
|
64
|
+
return fs.readdirSync(dir).filter((f) => f.endsWith(".yaml")).flatMap((f) => {
|
|
65
|
+
const s = getSurvey(dataDir, f.replace(/\.yaml$/, ""));
|
|
66
|
+
return s ? [s] : [];
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function generateSurveyToken(slug, contactEmail, surveyId) {
|
|
70
|
+
return createHmac("sha256", SURVEY_SECRET).update(`${slug}:${contactEmail}:${surveyId}`).digest("hex").slice(0, 16);
|
|
71
|
+
}
|
|
72
|
+
function buildSurveyEmail(survey, serverUrl, token) {
|
|
73
|
+
const buttons = Array.from({ length: survey.scale.max - survey.scale.min + 1 }, (_, i) => i + survey.scale.min).map((s) => `<a href="${serverUrl}/survey/respond?token=${token}&score=${s}" style="display:inline-block;margin:4px;padding:10px 16px;background:#1a1a2e;color:white;text-decoration:none;border-radius:4px;">${s}</a>`).join("");
|
|
74
|
+
const body = `<p>${survey.question}</p>
|
|
75
|
+
<p>${buttons}</p>
|
|
76
|
+
${survey.includeComment ? `<p>Or <a href="${serverUrl}/survey/respond?token=${token}&comment=true">Click here to add a comment</a></p>` : ""}`;
|
|
77
|
+
return {
|
|
78
|
+
subject: survey.type === "nps" ? "How likely are you to recommend us?" : "Rate your experience",
|
|
79
|
+
body
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function recordSurveyResponse(dataDir, token, score, comment) {
|
|
83
|
+
const pendingDir = path.join(dataDir, ".agentic", "survey-pending");
|
|
84
|
+
if (!fs.existsSync(pendingDir)) return null;
|
|
85
|
+
const files = fs.readdirSync(pendingDir).filter((f) => f.endsWith(".json"));
|
|
86
|
+
for (const file of files) try {
|
|
87
|
+
const pending = JSON.parse(fs.readFileSync(path.join(pendingDir, file), "utf-8"));
|
|
88
|
+
if (pending.token !== token) continue;
|
|
89
|
+
const response = {
|
|
90
|
+
surveyId: pending.surveyId,
|
|
91
|
+
slug: pending.slug,
|
|
92
|
+
contactEmail: pending.contactEmail,
|
|
93
|
+
score,
|
|
94
|
+
...comment ? { comment } : {},
|
|
95
|
+
respondedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
96
|
+
token,
|
|
97
|
+
sentAt: pending.sentAt
|
|
98
|
+
};
|
|
99
|
+
const dir = responsesDir(dataDir, pending.surveyId);
|
|
100
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
101
|
+
const filename = `${pending.slug}_${pending.contactEmail.replace("@", "_at_")}_${Date.now()}.json`;
|
|
102
|
+
fs.writeFileSync(path.join(dir, filename), JSON.stringify(response, null, 2), "utf-8");
|
|
103
|
+
fs.unlinkSync(path.join(pendingDir, file));
|
|
104
|
+
return response;
|
|
105
|
+
} catch {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function loadSurveyResponses(dataDir, surveyId, slug) {
|
|
111
|
+
const dir = responsesDir(dataDir, surveyId);
|
|
112
|
+
if (!fs.existsSync(dir)) return [];
|
|
113
|
+
return fs.readdirSync(dir).filter((f) => f.endsWith(".json")).flatMap((f) => {
|
|
114
|
+
try {
|
|
115
|
+
const raw = JSON.parse(fs.readFileSync(path.join(dir, f), "utf-8"));
|
|
116
|
+
const parsed = SurveyResponseSchema.safeParse(raw);
|
|
117
|
+
if (!parsed.success) return [];
|
|
118
|
+
if (slug && parsed.data.slug !== slug) return [];
|
|
119
|
+
return [parsed.data];
|
|
120
|
+
} catch {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function calcNpsScore(responses) {
|
|
126
|
+
if (responses.length === 0) return 0;
|
|
127
|
+
const promoters = responses.filter((r) => r.score >= 9).length;
|
|
128
|
+
const detractors = responses.filter((r) => r.score <= 6).length;
|
|
129
|
+
return Math.round((promoters - detractors) / responses.length * 100);
|
|
130
|
+
}
|
|
131
|
+
async function savePendingSurvey(dataDir, surveyId, slug, contactEmail, token) {
|
|
132
|
+
const pendingDir = path.join(dataDir, ".agentic", "survey-pending");
|
|
133
|
+
fs.mkdirSync(pendingDir, { recursive: true });
|
|
134
|
+
const filename = `${token}.json`;
|
|
135
|
+
const pending = {
|
|
136
|
+
token,
|
|
137
|
+
surveyId,
|
|
138
|
+
slug,
|
|
139
|
+
contactEmail,
|
|
140
|
+
sentAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
141
|
+
};
|
|
142
|
+
fs.writeFileSync(path.join(pendingDir, filename), JSON.stringify(pending, null, 2), "utf-8");
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
export { listSurveys as a, responsesDir as c, writeSurvey as d, getSurvey as i, savePendingSurvey as l, calcNpsScore as n, loadSurveyResponses as o, generateSurveyToken as r, recordSurveyResponse as s, buildSurveyEmail as t, surveysDir as u };
|
|
146
|
+
|
|
147
|
+
//# sourceMappingURL=survey-engine-DBjCYqCv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"survey-engine-DBjCYqCv.js","names":[],"sources":["../src/schemas/survey.ts","../src/core/survey-engine.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const SurveyDefinitionSchema = z.object({\n id: z.string().min(1),\n type: z.enum([\"nps\", \"csat\", \"ces\"]).default(\"nps\"),\n question: z.string().min(1),\n scale: z\n .object({ min: z.number().default(0), max: z.number().default(10) })\n .default({ min: 0, max: 10 }),\n includeComment: z.boolean().default(true),\n commentPrompt: z.string().optional(),\n createdAt: z.string(),\n});\n\nexport const SurveyResponseSchema = z.object({\n surveyId: z.string(),\n slug: z.string(),\n contactEmail: z.string().email(),\n score: z.number().int(),\n comment: z.string().optional(),\n respondedAt: z.string(),\n token: z.string(),\n sentAt: z.string(),\n});\n\nexport type SurveyDefinition = z.infer<typeof SurveyDefinitionSchema>;\nexport type SurveyResponse = z.infer<typeof SurveyResponseSchema>;\n","import fs from \"fs\";\nimport path from \"path\";\nimport { createHmac } from \"crypto\";\nimport yaml from \"js-yaml\";\nimport {\n SurveyDefinitionSchema,\n SurveyResponseSchema,\n type SurveyDefinition,\n type SurveyResponse,\n} from \"../schemas/survey.js\";\n\nconst SURVEY_SECRET = process.env[\"DXCRM_SURVEY_SECRET\"] ?? \"dxcrm-survey-default-secret\";\n\nexport function surveysDir(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"surveys\");\n}\n\nexport function responsesDir(dataDir: string, surveyId: string): string {\n return path.join(dataDir, \".agentic\", \"survey-responses\", surveyId);\n}\n\nexport function getSurvey(dataDir: string, surveyId: string): SurveyDefinition | null {\n const p = path.join(surveysDir(dataDir), `${surveyId}.yaml`);\n if (!fs.existsSync(p)) return null;\n try {\n const raw = yaml.load(fs.readFileSync(p, \"utf-8\") as string);\n const result = SurveyDefinitionSchema.safeParse(raw);\n return result.success ? result.data : null;\n } catch {\n return null;\n }\n}\n\nexport function writeSurvey(dataDir: string, survey: SurveyDefinition): void {\n const dir = surveysDir(dataDir);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(path.join(dir, `${survey.id}.yaml`), yaml.dump(survey), \"utf-8\");\n}\n\nexport function listSurveys(dataDir: string): SurveyDefinition[] {\n const dir = surveysDir(dataDir);\n if (!fs.existsSync(dir)) return [];\n return fs\n .readdirSync(dir)\n .filter((f) => f.endsWith(\".yaml\"))\n .flatMap((f) => {\n const s = getSurvey(dataDir, f.replace(/\\.yaml$/, \"\"));\n return s ? [s] : [];\n });\n}\n\nexport function generateSurveyToken(slug: string, contactEmail: string, surveyId: string): string {\n return createHmac(\"sha256\", SURVEY_SECRET)\n .update(`${slug}:${contactEmail}:${surveyId}`)\n .digest(\"hex\")\n .slice(0, 16);\n}\n\nexport function buildSurveyEmail(\n survey: SurveyDefinition,\n serverUrl: string,\n token: string\n): { subject: string; body: string } {\n const scores = Array.from(\n { length: survey.scale.max - survey.scale.min + 1 },\n (_, i) => i + survey.scale.min\n );\n const buttons = scores\n .map(\n (s) =>\n `<a href=\"${serverUrl}/survey/respond?token=${token}&score=${s}\" style=\"display:inline-block;margin:4px;padding:10px 16px;background:#1a1a2e;color:white;text-decoration:none;border-radius:4px;\">${s}</a>`\n )\n .join(\"\");\n\n const body = `<p>${survey.question}</p>\n<p>${buttons}</p>\n${survey.includeComment ? `<p>Or <a href=\"${serverUrl}/survey/respond?token=${token}&comment=true\">Click here to add a comment</a></p>` : \"\"}`;\n\n return {\n subject: survey.type === \"nps\" ? \"How likely are you to recommend us?\" : \"Rate your experience\",\n body,\n };\n}\n\nexport async function recordSurveyResponse(\n dataDir: string,\n token: string,\n score: number,\n comment?: string\n): Promise<SurveyResponse | null> {\n // Find pending response by token\n const pendingDir = path.join(dataDir, \".agentic\", \"survey-pending\");\n if (!fs.existsSync(pendingDir)) return null;\n\n const files = fs.readdirSync(pendingDir).filter((f) => f.endsWith(\".json\"));\n for (const file of files) {\n try {\n const pending = JSON.parse(\n fs.readFileSync(path.join(pendingDir, file), \"utf-8\") as string\n ) as {\n token: string;\n surveyId: string;\n slug: string;\n contactEmail: string;\n sentAt: string;\n };\n if (pending.token !== token) continue;\n\n const response: SurveyResponse = {\n surveyId: pending.surveyId,\n slug: pending.slug,\n contactEmail: pending.contactEmail,\n score,\n ...(comment ? { comment } : {}),\n respondedAt: new Date().toISOString(),\n token,\n sentAt: pending.sentAt,\n };\n\n const dir = responsesDir(dataDir, pending.surveyId);\n fs.mkdirSync(dir, { recursive: true });\n const filename = `${pending.slug}_${pending.contactEmail.replace(\"@\", \"_at_\")}_${Date.now()}.json`;\n fs.writeFileSync(path.join(dir, filename), JSON.stringify(response, null, 2), \"utf-8\");\n\n // Delete pending entry\n fs.unlinkSync(path.join(pendingDir, file));\n return response;\n } catch {\n continue;\n }\n }\n return null;\n}\n\nexport function loadSurveyResponses(\n dataDir: string,\n surveyId: string,\n slug?: string\n): SurveyResponse[] {\n const dir = responsesDir(dataDir, surveyId);\n if (!fs.existsSync(dir)) return [];\n return fs\n .readdirSync(dir)\n .filter((f) => f.endsWith(\".json\"))\n .flatMap((f) => {\n try {\n const raw = JSON.parse(fs.readFileSync(path.join(dir, f), \"utf-8\") as string) as unknown;\n const parsed = SurveyResponseSchema.safeParse(raw);\n if (!parsed.success) return [];\n if (slug && parsed.data.slug !== slug) return [];\n return [parsed.data];\n } catch {\n return [];\n }\n });\n}\n\nexport function calcNpsScore(responses: SurveyResponse[]): number {\n if (responses.length === 0) return 0;\n const promoters = responses.filter((r) => r.score >= 9).length;\n const detractors = responses.filter((r) => r.score <= 6).length;\n return Math.round(((promoters - detractors) / responses.length) * 100);\n}\n\nexport async function savePendingSurvey(\n dataDir: string,\n surveyId: string,\n slug: string,\n contactEmail: string,\n token: string\n): Promise<void> {\n const pendingDir = path.join(dataDir, \".agentic\", \"survey-pending\");\n fs.mkdirSync(pendingDir, { recursive: true });\n const filename = `${token}.json`;\n const pending = { token, surveyId, slug, contactEmail, sentAt: new Date().toISOString() };\n fs.writeFileSync(path.join(pendingDir, filename), JSON.stringify(pending, null, 2), \"utf-8\");\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
//#region src/fs/sync-state.ts
|
|
4
|
+
function getSyncStatePath(dataDir) {
|
|
5
|
+
return path.join(dataDir, ".agentic", "sync-state.json");
|
|
6
|
+
}
|
|
7
|
+
function readSyncState(dataDir) {
|
|
8
|
+
const filePath = getSyncStatePath(dataDir);
|
|
9
|
+
if (!fs.existsSync(filePath)) return {};
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
12
|
+
} catch {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function updateSlugSyncState(dataDir, slug, update) {
|
|
17
|
+
const filePath = getSyncStatePath(dataDir);
|
|
18
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
19
|
+
const state = readSyncState(dataDir);
|
|
20
|
+
state[slug] = {
|
|
21
|
+
...state[slug],
|
|
22
|
+
...update
|
|
23
|
+
};
|
|
24
|
+
fs.writeFileSync(filePath, JSON.stringify(state, null, 2), "utf-8");
|
|
25
|
+
}
|
|
26
|
+
function getLastGmailSync(dataDir, slug) {
|
|
27
|
+
const ts = readSyncState(dataDir)[slug]?.lastGmailSync;
|
|
28
|
+
return ts ? new Date(ts) : void 0;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { readSyncState as n, updateSlugSyncState as r, getLastGmailSync as t };
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=sync-state-ChaLbamC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-state-ChaLbamC.js","names":[],"sources":["../src/fs/sync-state.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\n\nexport interface SlugSyncState {\n lastGmailSync?: string;\n lastCalendarSync?: string;\n lastGmailPushHistoryId?: string;\n lastMicrosoftPushAt?: string;\n}\n\nexport interface SyncState {\n [slug: string]: SlugSyncState;\n}\n\nfunction getSyncStatePath(dataDir: string): string {\n return path.join(dataDir, \".agentic\", \"sync-state.json\");\n}\n\nexport function readSyncState(dataDir: string): SyncState {\n const filePath = getSyncStatePath(dataDir);\n if (!fs.existsSync(filePath)) return {};\n try {\n return JSON.parse(fs.readFileSync(filePath, \"utf-8\") as string) as SyncState;\n } catch {\n return {};\n }\n}\n\nexport function writeSyncState(dataDir: string, state: SyncState): void {\n const filePath = getSyncStatePath(dataDir);\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, JSON.stringify(state, null, 2), \"utf-8\");\n}\n\nexport function updateSlugSyncState(\n dataDir: string,\n slug: string,\n update: Partial<SlugSyncState>\n): void {\n const filePath = getSyncStatePath(dataDir);\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n const state = readSyncState(dataDir);\n state[slug] = { ...state[slug], ...update };\n fs.writeFileSync(filePath, JSON.stringify(state, null, 2), \"utf-8\");\n}\n\nexport function getLastGmailSync(dataDir: string, slug: string): Date | undefined {\n const ts = readSyncState(dataDir)[slug]?.lastGmailSync;\n return ts ? new Date(ts) : undefined;\n}\n\nexport function getLastCalendarSync(dataDir: string, slug: string): Date | undefined {\n const ts = readSyncState(dataDir)[slug]?.lastCalendarSync;\n return ts ? new Date(ts) : undefined;\n}\n"],"mappings":";;;AAcA,SAAS,iBAAiB,SAAyB;CACjD,OAAO,KAAK,KAAK,SAAS,YAAY,iBAAiB;AACzD;AAEA,SAAgB,cAAc,SAA4B;CACxD,MAAM,WAAW,iBAAiB,OAAO;CACzC,IAAI,CAAC,GAAG,WAAW,QAAQ,GAAG,OAAO,CAAC;CACtC,IAAI;EACF,OAAO,KAAK,MAAM,GAAG,aAAa,UAAU,OAAO,CAAW;CAChE,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAQA,SAAgB,oBACd,SACA,MACA,QACM;CACN,MAAM,WAAW,iBAAiB,OAAO;CACzC,GAAG,UAAU,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;CACxD,MAAM,QAAQ,cAAc,OAAO;CACnC,MAAM,QAAQ;EAAE,GAAG,MAAM;EAAO,GAAG;CAAO;CAC1C,GAAG,cAAc,UAAU,KAAK,UAAU,OAAO,MAAM,CAAC,GAAG,OAAO;AACpE;AAEA,SAAgB,iBAAiB,SAAiB,MAAgC;CAChF,MAAM,KAAK,cAAc,OAAO,EAAE,OAAO;CACzC,OAAO,KAAK,IAAI,KAAK,EAAE,IAAI,KAAA;AAC7B"}
|