clivly 0.3.0-next.4 → 0.3.0-next.6

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/drizzle.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { d as SyncSource, t as NamespaceIntrospector } from "./namespace-probe-CcYne5Je.cjs";
1
+ import { m as SyncSource, t as NamespaceIntrospector } from "./namespace-probe-CkHCj9tQ.cjs";
2
2
  import { Table } from "drizzle-orm";
3
3
 
4
4
  //#region ../sdk/src/drizzle.d.ts
package/drizzle.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { d as SyncSource, t as NamespaceIntrospector } from "./namespace-probe-DjWzU9gG.mjs";
1
+ import { m as SyncSource, t as NamespaceIntrospector } from "./namespace-probe-CA4Vj40g.mjs";
2
2
  import { Table } from "drizzle-orm";
3
3
 
4
4
  //#region ../sdk/src/drizzle.d.ts
package/index.cjs CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require_entity_config = require("./entity-config-DMuZpchz.cjs");
4
4
  require("./mapping-score-i3p8LWDF.cjs");
5
5
  const require_core_entity_heuristics = require("./core-entity-heuristics.cjs");
6
- const require_sdk = require("./sdk-r4khXzoH.cjs");
6
+ const require_sdk = require("./sdk-D1BHQDf5.cjs");
7
7
  let node_child_process = require("node:child_process");
8
8
  let node_fs = require("node:fs");
9
9
  let node_module = require("node:module");
@@ -533,6 +533,71 @@ function isEntrypoint(argv1, moduleUrl, realpath = node_fs.realpathSync) {
533
533
  return resolve(argv1) === resolve(modulePath);
534
534
  }
535
535
  //#endregion
536
+ //#region src/load-env.ts
537
+ const LINE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/;
538
+ const NEWLINE = /\r?\n/;
539
+ const ESCAPED_NEWLINE = /\\n/g;
540
+ const ESCAPED_CARRIAGE = /\\r/g;
541
+ const TRAILING_COMMENT = /\s#/;
542
+ /**
543
+ * Parse `.env` file text into a flat key→value map. Understands `#` comments
544
+ * (whole-line and trailing on unquoted values), single/double quotes,
545
+ * `export KEY=`, and `\n` escapes inside double quotes. Later duplicate keys
546
+ * win, matching how a shell would source the file top-to-bottom.
547
+ */
548
+ function parseEnv(content) {
549
+ const out = {};
550
+ for (const rawLine of content.split(NEWLINE)) {
551
+ const line = rawLine.trimStart();
552
+ if (line === "" || line.startsWith("#")) continue;
553
+ const match = LINE.exec(line);
554
+ const key = match?.[1];
555
+ if (!key) continue;
556
+ out[key] = parseValue(match[2] ?? "");
557
+ }
558
+ return out;
559
+ }
560
+ function parseValue(raw) {
561
+ const value = raw.trim();
562
+ if (value === "") return "";
563
+ const quote = value[0];
564
+ if (quote === "\"" || quote === "'") {
565
+ const end = value.indexOf(quote, 1);
566
+ const inner = end === -1 ? value.slice(1) : value.slice(1, end);
567
+ return quote === "\"" ? inner.replace(ESCAPED_NEWLINE, "\n").replace(ESCAPED_CARRIAGE, "\r") : inner;
568
+ }
569
+ const commentAt = value.search(TRAILING_COMMENT);
570
+ return (commentAt === -1 ? value : value.slice(0, commentAt)).trim();
571
+ }
572
+ /**
573
+ * Load `.env` then `.env.local` from `cwd` into `env` (default `process.env`),
574
+ * WITHOUT overriding anything already set. Precedence, strongest first:
575
+ * real environment (shell) > `.env.local` > `.env`. This mirrors the framework
576
+ * convention developers already expect (Next.js, Vite), so pointing
577
+ * `CLIVLY_API_URL` at a staging host from the shell still wins over the file.
578
+ */
579
+ function loadEnv(cwd, env = process.env) {
580
+ const files = [];
581
+ const merged = {};
582
+ for (const name of [".env", ".env.local"]) {
583
+ const path = (0, node_path.join)(cwd, name);
584
+ if (!(0, node_fs.existsSync)(path)) continue;
585
+ files.push(name);
586
+ try {
587
+ Object.assign(merged, parseEnv((0, node_fs.readFileSync)(path, "utf8")));
588
+ } catch {}
589
+ }
590
+ const applied = [];
591
+ for (const [key, value] of Object.entries(merged)) if (env[key] === void 0) {
592
+ env[key] = value;
593
+ applied.push(key);
594
+ }
595
+ return {
596
+ applied,
597
+ files
598
+ };
599
+ }
600
+ //#endregion
536
601
  //#region src/login.ts
537
602
  const POLL_INTERVAL_MS = 2e3;
538
603
  const POLL_BACKOFF_MULTIPLIER = 1.5;
@@ -752,15 +817,25 @@ function placeholderContact(todos) {
752
817
  idField: null
753
818
  };
754
819
  }
820
+ function hasMappedFields(entity) {
821
+ return Object.keys(entity.fields).length > 0;
822
+ }
755
823
  function planInit(schema) {
756
824
  const todos = [];
757
825
  const { contact: contactTable, company: companyTable } = selectEntities(schema.tables);
758
- const contactDetected = contactTable !== null;
759
- const contact = contactTable ? buildEntity(contactTable, "contact", todos) : placeholderContact(todos);
826
+ let contact = contactTable ? buildEntity(contactTable, "contact", todos) : placeholderContact(todos);
827
+ let contactDetected = contactTable !== null;
828
+ if (!hasMappedFields(contact)) {
829
+ contact = placeholderContact(todos);
830
+ contactDetected = false;
831
+ }
760
832
  let company = null;
761
833
  if (companyTable) {
762
- company = buildEntity(companyTable, "company", todos);
763
- if (require_core_entity_heuristics.matchesConcept(companyTable.tableName, "contact")) todos.push(`"${companyTable.tableName}" matches both contact and company naming — assigned to company. Swap it to your contact entity if that's wrong.`);
834
+ const candidate = buildEntity(companyTable, "company", todos);
835
+ if (hasMappedFields(candidate)) {
836
+ company = candidate;
837
+ if (require_core_entity_heuristics.matchesConcept(companyTable.tableName, "contact")) todos.push(`"${companyTable.tableName}" matches both contact and company naming — assigned to company. Swap it to your contact entity if that's wrong.`);
838
+ } else todos.push(`"${companyTable.tableName}" looked like a company but no fields could be mapped — left it out. Add it to \`entities\` once you know which columns map to name/domain.`);
764
839
  }
765
840
  if (contactTable) {
766
841
  const scored = require_core_entity_heuristics.scoreRelationships(toScoredTable(contactTable), schema.tables.map(toScoredTable));
@@ -1019,10 +1094,14 @@ const handler = clivly.createClivlyHandler();
1019
1094
  case "nextjs": return `${preamble}
1020
1095
  export const POST = (request: Request) => handler(request);
1021
1096
  `;
1022
- case "tanstack-start": return `import { createAPIFileRoute } from "@tanstack/react-start/api";
1097
+ case "tanstack-start": return `import { createFileRoute } from "@tanstack/react-router";
1023
1098
  ${preamble}
1024
- export const APIRoute = createAPIFileRoute("__ROUTE__")({
1025
- POST: ({ request }) => handler(request),
1099
+ export const Route = createFileRoute("__ROUTE__")({
1100
+ server: {
1101
+ handlers: {
1102
+ POST: ({ request }) => handler(request),
1103
+ },
1104
+ },
1026
1105
  });
1027
1106
  `;
1028
1107
  case "sveltekit": return `import type { RequestHandler } from "./$types";
@@ -1165,13 +1244,33 @@ function resolveConfigPath(cwd, configPath) {
1165
1244
  function firstLine(message) {
1166
1245
  return message.split("\n")[0]?.trim() ?? "";
1167
1246
  }
1247
+ /**
1248
+ * Describe why the config failed to load, in one line the developer can act on.
1249
+ *
1250
+ * A config that fails its own schema validation throws a zod error carrying
1251
+ * structured `issues`, but whose `message` is a multi-line JSON dump — its
1252
+ * first line is a bare "[", so `firstLine` alone reduced the entire diagnosis
1253
+ * to a single bracket. Render the issues as `path: message` instead, which is
1254
+ * what actually names the entity to fix. Anything else keeps the require-stack
1255
+ * trimming `firstLine` exists for.
1256
+ */
1257
+ function describeLoadError(error) {
1258
+ const issues = error.issues;
1259
+ if (Array.isArray(issues) && issues.length > 0) return issues.map((raw) => {
1260
+ const issue = raw;
1261
+ const path = Array.isArray(issue.path) ? issue.path.join(".") : "";
1262
+ const message = typeof issue.message === "string" ? issue.message : "invalid value";
1263
+ return path ? `${path}: ${message}` : message;
1264
+ }).join("; ");
1265
+ return firstLine(error?.message ?? String(error));
1266
+ }
1168
1267
  async function loadConfigSdk(path, requiredMethod) {
1169
1268
  const jiti$1 = (0, jiti.createJiti)(require("url").pathToFileURL(__filename).href);
1170
1269
  let mod;
1171
1270
  try {
1172
1271
  mod = await jiti$1.import(path);
1173
1272
  } catch (error) {
1174
- throw new Error(firstLine(error.message));
1273
+ throw new Error(describeLoadError(error));
1175
1274
  }
1176
1275
  const sdk = mod.default ?? mod;
1177
1276
  if (typeof sdk?.[requiredMethod] !== "function") throw new Error("default export is not a Clivly SDK instance");
@@ -1654,7 +1753,7 @@ function previewMutations(deps, mutations) {
1654
1753
  }
1655
1754
  function printNextSteps(out, authComplete, loginRan) {
1656
1755
  out("\nNext steps:\n");
1657
- out(loginRan ? " 1. Credentials written by `clivly login`\n" : " 1. Set the CLIVLY_* values in .env\n");
1756
+ out(loginRan ? " 1. Credentials written by `clivly login`\n" : " 1. Add CLIVLY_API_KEY to .env — create one in the Clivly dashboard\n under Integrations → Create API Key (or Settings → API keys)\n");
1658
1757
  out(" 2. Set CLIVLY_SYNC_TRIGGER_URL to your public tick route\n");
1659
1758
  out(" Local dev? Run `clivly dev` to open a tunnel, or skip this step until you deploy (remote sync is deploy-only locally).\n");
1660
1759
  const step = authComplete ? 3 : 4;
@@ -1872,6 +1971,7 @@ function runInitFromArgv(argv, cwd) {
1872
1971
  }
1873
1972
  async function main(argv) {
1874
1973
  const [command, ...rest] = argv;
1974
+ loadEnv(process.cwd());
1875
1975
  switch (command) {
1876
1976
  case "init": return await runInitFromArgv(rest, process.cwd());
1877
1977
  case "push-schema": return await runPushSchema({
package/index.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { t as CANONICAL_FIELDS } from "./entity-config-D3YpQB0z.mjs";
3
3
  import "./mapping-score-CPPvwoZC.mjs";
4
4
  import { matchesConcept, requiredFieldsFor, scoreFieldMap, scoreRelationships, suggestCursorField, toFieldMap } from "./core-entity-heuristics.mjs";
5
- import { t as createClivlySDK } from "./sdk-DtFmeuwl.mjs";
5
+ import { t as createClivlySDK } from "./sdk-DdzBxgMx.mjs";
6
6
  import { createRequire } from "node:module";
7
7
  import { spawn } from "node:child_process";
8
8
  import { accessSync, constants, existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
@@ -532,6 +532,71 @@ function isEntrypoint(argv1, moduleUrl, realpath = realpathSync) {
532
532
  return resolve(argv1) === resolve(modulePath);
533
533
  }
534
534
  //#endregion
535
+ //#region src/load-env.ts
536
+ const LINE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/;
537
+ const NEWLINE = /\r?\n/;
538
+ const ESCAPED_NEWLINE = /\\n/g;
539
+ const ESCAPED_CARRIAGE = /\\r/g;
540
+ const TRAILING_COMMENT = /\s#/;
541
+ /**
542
+ * Parse `.env` file text into a flat key→value map. Understands `#` comments
543
+ * (whole-line and trailing on unquoted values), single/double quotes,
544
+ * `export KEY=`, and `\n` escapes inside double quotes. Later duplicate keys
545
+ * win, matching how a shell would source the file top-to-bottom.
546
+ */
547
+ function parseEnv(content) {
548
+ const out = {};
549
+ for (const rawLine of content.split(NEWLINE)) {
550
+ const line = rawLine.trimStart();
551
+ if (line === "" || line.startsWith("#")) continue;
552
+ const match = LINE.exec(line);
553
+ const key = match?.[1];
554
+ if (!key) continue;
555
+ out[key] = parseValue(match[2] ?? "");
556
+ }
557
+ return out;
558
+ }
559
+ function parseValue(raw) {
560
+ const value = raw.trim();
561
+ if (value === "") return "";
562
+ const quote = value[0];
563
+ if (quote === "\"" || quote === "'") {
564
+ const end = value.indexOf(quote, 1);
565
+ const inner = end === -1 ? value.slice(1) : value.slice(1, end);
566
+ return quote === "\"" ? inner.replace(ESCAPED_NEWLINE, "\n").replace(ESCAPED_CARRIAGE, "\r") : inner;
567
+ }
568
+ const commentAt = value.search(TRAILING_COMMENT);
569
+ return (commentAt === -1 ? value : value.slice(0, commentAt)).trim();
570
+ }
571
+ /**
572
+ * Load `.env` then `.env.local` from `cwd` into `env` (default `process.env`),
573
+ * WITHOUT overriding anything already set. Precedence, strongest first:
574
+ * real environment (shell) > `.env.local` > `.env`. This mirrors the framework
575
+ * convention developers already expect (Next.js, Vite), so pointing
576
+ * `CLIVLY_API_URL` at a staging host from the shell still wins over the file.
577
+ */
578
+ function loadEnv(cwd, env = process.env) {
579
+ const files = [];
580
+ const merged = {};
581
+ for (const name of [".env", ".env.local"]) {
582
+ const path = join(cwd, name);
583
+ if (!existsSync(path)) continue;
584
+ files.push(name);
585
+ try {
586
+ Object.assign(merged, parseEnv(readFileSync(path, "utf8")));
587
+ } catch {}
588
+ }
589
+ const applied = [];
590
+ for (const [key, value] of Object.entries(merged)) if (env[key] === void 0) {
591
+ env[key] = value;
592
+ applied.push(key);
593
+ }
594
+ return {
595
+ applied,
596
+ files
597
+ };
598
+ }
599
+ //#endregion
535
600
  //#region src/login.ts
536
601
  const POLL_INTERVAL_MS = 2e3;
537
602
  const POLL_BACKOFF_MULTIPLIER = 1.5;
@@ -751,15 +816,25 @@ function placeholderContact(todos) {
751
816
  idField: null
752
817
  };
753
818
  }
819
+ function hasMappedFields(entity) {
820
+ return Object.keys(entity.fields).length > 0;
821
+ }
754
822
  function planInit(schema) {
755
823
  const todos = [];
756
824
  const { contact: contactTable, company: companyTable } = selectEntities(schema.tables);
757
- const contactDetected = contactTable !== null;
758
- const contact = contactTable ? buildEntity(contactTable, "contact", todos) : placeholderContact(todos);
825
+ let contact = contactTable ? buildEntity(contactTable, "contact", todos) : placeholderContact(todos);
826
+ let contactDetected = contactTable !== null;
827
+ if (!hasMappedFields(contact)) {
828
+ contact = placeholderContact(todos);
829
+ contactDetected = false;
830
+ }
759
831
  let company = null;
760
832
  if (companyTable) {
761
- company = buildEntity(companyTable, "company", todos);
762
- if (matchesConcept(companyTable.tableName, "contact")) todos.push(`"${companyTable.tableName}" matches both contact and company naming — assigned to company. Swap it to your contact entity if that's wrong.`);
833
+ const candidate = buildEntity(companyTable, "company", todos);
834
+ if (hasMappedFields(candidate)) {
835
+ company = candidate;
836
+ if (matchesConcept(companyTable.tableName, "contact")) todos.push(`"${companyTable.tableName}" matches both contact and company naming — assigned to company. Swap it to your contact entity if that's wrong.`);
837
+ } else todos.push(`"${companyTable.tableName}" looked like a company but no fields could be mapped — left it out. Add it to \`entities\` once you know which columns map to name/domain.`);
763
838
  }
764
839
  if (contactTable) {
765
840
  const scored = scoreRelationships(toScoredTable(contactTable), schema.tables.map(toScoredTable));
@@ -1018,10 +1093,14 @@ const handler = clivly.createClivlyHandler();
1018
1093
  case "nextjs": return `${preamble}
1019
1094
  export const POST = (request: Request) => handler(request);
1020
1095
  `;
1021
- case "tanstack-start": return `import { createAPIFileRoute } from "@tanstack/react-start/api";
1096
+ case "tanstack-start": return `import { createFileRoute } from "@tanstack/react-router";
1022
1097
  ${preamble}
1023
- export const APIRoute = createAPIFileRoute("__ROUTE__")({
1024
- POST: ({ request }) => handler(request),
1098
+ export const Route = createFileRoute("__ROUTE__")({
1099
+ server: {
1100
+ handlers: {
1101
+ POST: ({ request }) => handler(request),
1102
+ },
1103
+ },
1025
1104
  });
1026
1105
  `;
1027
1106
  case "sveltekit": return `import type { RequestHandler } from "./$types";
@@ -1164,13 +1243,33 @@ function resolveConfigPath(cwd, configPath) {
1164
1243
  function firstLine(message) {
1165
1244
  return message.split("\n")[0]?.trim() ?? "";
1166
1245
  }
1246
+ /**
1247
+ * Describe why the config failed to load, in one line the developer can act on.
1248
+ *
1249
+ * A config that fails its own schema validation throws a zod error carrying
1250
+ * structured `issues`, but whose `message` is a multi-line JSON dump — its
1251
+ * first line is a bare "[", so `firstLine` alone reduced the entire diagnosis
1252
+ * to a single bracket. Render the issues as `path: message` instead, which is
1253
+ * what actually names the entity to fix. Anything else keeps the require-stack
1254
+ * trimming `firstLine` exists for.
1255
+ */
1256
+ function describeLoadError(error) {
1257
+ const issues = error.issues;
1258
+ if (Array.isArray(issues) && issues.length > 0) return issues.map((raw) => {
1259
+ const issue = raw;
1260
+ const path = Array.isArray(issue.path) ? issue.path.join(".") : "";
1261
+ const message = typeof issue.message === "string" ? issue.message : "invalid value";
1262
+ return path ? `${path}: ${message}` : message;
1263
+ }).join("; ");
1264
+ return firstLine(error?.message ?? String(error));
1265
+ }
1167
1266
  async function loadConfigSdk(path, requiredMethod) {
1168
1267
  const jiti = createJiti(import.meta.url);
1169
1268
  let mod;
1170
1269
  try {
1171
1270
  mod = await jiti.import(path);
1172
1271
  } catch (error) {
1173
- throw new Error(firstLine(error.message));
1272
+ throw new Error(describeLoadError(error));
1174
1273
  }
1175
1274
  const sdk = mod.default ?? mod;
1176
1275
  if (typeof sdk?.[requiredMethod] !== "function") throw new Error("default export is not a Clivly SDK instance");
@@ -1653,7 +1752,7 @@ function previewMutations(deps, mutations) {
1653
1752
  }
1654
1753
  function printNextSteps(out, authComplete, loginRan) {
1655
1754
  out("\nNext steps:\n");
1656
- out(loginRan ? " 1. Credentials written by `clivly login`\n" : " 1. Set the CLIVLY_* values in .env\n");
1755
+ out(loginRan ? " 1. Credentials written by `clivly login`\n" : " 1. Add CLIVLY_API_KEY to .env — create one in the Clivly dashboard\n under Integrations → Create API Key (or Settings → API keys)\n");
1657
1756
  out(" 2. Set CLIVLY_SYNC_TRIGGER_URL to your public tick route\n");
1658
1757
  out(" Local dev? Run `clivly dev` to open a tunnel, or skip this step until you deploy (remote sync is deploy-only locally).\n");
1659
1758
  const step = authComplete ? 3 : 4;
@@ -1871,6 +1970,7 @@ function runInitFromArgv(argv, cwd) {
1871
1970
  }
1872
1971
  async function main(argv) {
1873
1972
  const [command, ...rest] = argv;
1973
+ loadEnv(process.cwd());
1874
1974
  switch (command) {
1875
1975
  case "init": return await runInitFromArgv(rest, process.cwd());
1876
1976
  case "push-schema": return await runPushSchema({
@@ -179,6 +179,21 @@ interface ClivlyUser {
179
179
  id: string;
180
180
  name?: string;
181
181
  }
182
+ interface ChatWidgetVisitor {
183
+ email: string;
184
+ name: string;
185
+ }
186
+ interface ChatSessionRequestBody {
187
+ visitor: ChatWidgetVisitor;
188
+ visitorToken?: string | null;
189
+ widgetId: string;
190
+ }
191
+ interface CreateChatSessionHandlerOptions {
192
+ allowedOrigins?: string[];
193
+ apiKey: string;
194
+ apiUrl?: string;
195
+ fetchImpl?: typeof fetch;
196
+ }
182
197
  interface DoctorCheck {
183
198
  /** Human-readable outcome — what passed, or why it failed. */
184
199
  detail: string;
@@ -313,4 +328,4 @@ interface NamespaceIntrospector {
313
328
  sampleSelect(table: string): Promise<void>;
314
329
  }
315
330
  //#endregion
316
- export { ClivlySDKConfig as a, DiscoveredTable as c, SyncSource as d, ClivlySDK as i, DoctorCheck as l, ClivlyConnectReason as n, ClivlyUser as o, ClivlyConnectResult as r, CursorStore as s, NamespaceIntrospector as t, DoctorReport as u };
331
+ export { ClivlyConnectResult as a, ClivlyUser as c, DiscoveredTable as d, DoctorCheck as f, ClivlyConnectReason as i, CreateChatSessionHandlerOptions as l, SyncSource as m, ChatSessionRequestBody as n, ClivlySDK as o, DoctorReport as p, ChatWidgetVisitor as r, ClivlySDKConfig as s, NamespaceIntrospector as t, CursorStore as u };
@@ -179,6 +179,21 @@ interface ClivlyUser {
179
179
  id: string;
180
180
  name?: string;
181
181
  }
182
+ interface ChatWidgetVisitor {
183
+ email: string;
184
+ name: string;
185
+ }
186
+ interface ChatSessionRequestBody {
187
+ visitor: ChatWidgetVisitor;
188
+ visitorToken?: string | null;
189
+ widgetId: string;
190
+ }
191
+ interface CreateChatSessionHandlerOptions {
192
+ allowedOrigins?: string[];
193
+ apiKey: string;
194
+ apiUrl?: string;
195
+ fetchImpl?: typeof fetch;
196
+ }
182
197
  interface DoctorCheck {
183
198
  /** Human-readable outcome — what passed, or why it failed. */
184
199
  detail: string;
@@ -313,4 +328,4 @@ interface NamespaceIntrospector {
313
328
  sampleSelect(table: string): Promise<void>;
314
329
  }
315
330
  //#endregion
316
- export { ClivlySDKConfig as a, DiscoveredTable as c, SyncSource as d, ClivlySDK as i, DoctorCheck as l, ClivlyConnectReason as n, ClivlyUser as o, ClivlyConnectResult as r, CursorStore as s, NamespaceIntrospector as t, DoctorReport as u };
331
+ export { ClivlyConnectResult as a, ClivlyUser as c, DiscoveredTable as d, DoctorCheck as f, ClivlyConnectReason as i, CreateChatSessionHandlerOptions as l, SyncSource as m, ChatSessionRequestBody as n, ClivlySDK as o, DoctorReport as p, ChatWidgetVisitor as r, ClivlySDKConfig as s, NamespaceIntrospector as t, CursorStore as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clivly",
3
- "version": "0.3.0-next.4",
3
+ "version": "0.3.0-next.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Clivly CLI — scaffold a CRM into your app (clivly init).",
@@ -11,7 +11,7 @@ function checkCustomObjectSlugs(config) {
11
11
  name: "Custom object slugs",
12
12
  ok: false,
13
13
  skipped: true,
14
- detail: "No custom objects configured."
14
+ detail: "No custom objects in clivly.config. Objects added in the dashboard need a matching source.custom entry here to sync."
15
15
  };
16
16
  const problems = [];
17
17
  const seen = /* @__PURE__ */ new Map();
@@ -36,6 +36,49 @@ function checkCustomObjectSlugs(config) {
36
36
  };
37
37
  }
38
38
  //#endregion
39
+ //#region ../sdk/src/fetch-error.ts
40
+ /** Deepest `Error` reachable through the `.cause` chain (the original error if none). */
41
+ function rootError(error) {
42
+ let current = error;
43
+ const seen = /* @__PURE__ */ new Set();
44
+ while (current instanceof Error && current.cause !== void 0 && !seen.has(current)) {
45
+ seen.add(current);
46
+ current = current.cause;
47
+ }
48
+ return current;
49
+ }
50
+ function codeOf(error) {
51
+ const code = error?.code;
52
+ return typeof code === "string" ? code : void 0;
53
+ }
54
+ const CODE_HINTS = {
55
+ ECONNREFUSED: "the database refused the connection — is Postgres running and is your database URL (e.g. DATABASE_URL) pointing at it?",
56
+ ENOTFOUND: "the database host couldn't be resolved — check the host in your connection string.",
57
+ ETIMEDOUT: "the database connection timed out — check the host, port, and firewall.",
58
+ "28P01": "password authentication failed — check the credentials in your connection string.",
59
+ "28000": "the database rejected the connection role — check the user in your connection string.",
60
+ "3D000": "that database does not exist — check the database name in your connection string.",
61
+ "08006": "the database connection was lost — check that Postgres is reachable."
62
+ };
63
+ const NEWLINE = /\r?\n/;
64
+ /** One-line, single-spaced version of an error message (drops the SQL/params blob). */
65
+ function firstLine(message) {
66
+ const line = message.split(NEWLINE)[0]?.trim() ?? "";
67
+ return line === "" ? message.trim() : line;
68
+ }
69
+ /**
70
+ * Build the `detail` string for a failed sample fetch of `entity`.
71
+ * Recognised connection errors get a plain-English hint; everything else
72
+ * surfaces the root cause's first line rather than the wrapper's SQL dump.
73
+ */
74
+ function describeFetchError(entity, error) {
75
+ const root = rootError(error);
76
+ const code = codeOf(root) ?? codeOf(error);
77
+ const hint = code ? CODE_HINTS[code] : void 0;
78
+ if (hint) return `Couldn't read "${entity}": ${hint}`;
79
+ return `Couldn't read "${entity}": ${firstLine(root instanceof Error ? root.message : String(root?.message ?? root ?? "unknown error"))}`;
80
+ }
81
+ //#endregion
39
82
  //#region ../sdk/src/namespace-probe.ts
40
83
  /**
41
84
  * Verify the tables the integration depends on are genuinely reachable:
@@ -187,7 +230,7 @@ async function runDoctor(args) {
187
230
  checks.push({
188
231
  name: `Sample fetch (${source.entity})`,
189
232
  ok: false,
190
- detail: `"${source.entity}" fetchPage threw: ${error.message}`
233
+ detail: describeFetchError(source.entity, error)
191
234
  });
192
235
  }
193
236
  const result = await connect();
@@ -374,6 +417,88 @@ function resolveTriggerUrl(args) {
374
417
  return args.observedUrl;
375
418
  }
376
419
  //#endregion
420
+ //#region ../sdk/src/chat-session.ts
421
+ const DEFAULT_API_URL$1 = "https://api.clivly.com";
422
+ const JSON_HEADERS = { "content-type": "application/json" };
423
+ const TRAILING_SLASH$1 = /\/$/;
424
+ function jsonResponse$1(body, status) {
425
+ return new Response(JSON.stringify(body), {
426
+ status,
427
+ headers: JSON_HEADERS
428
+ });
429
+ }
430
+ function normalizeApiUrl(apiUrl) {
431
+ return apiUrl.replace(TRAILING_SLASH$1, "");
432
+ }
433
+ function resolveOrigin(req) {
434
+ const headerOrigin = req.headers.get("origin");
435
+ if (headerOrigin) return headerOrigin;
436
+ try {
437
+ return new URL(req.url).origin;
438
+ } catch {
439
+ return null;
440
+ }
441
+ }
442
+ function isChatSessionRequestBody(value) {
443
+ if (typeof value !== "object" || value === null) return false;
444
+ const body = value;
445
+ if (typeof body.widgetId !== "string" || body.widgetId.trim().length === 0) return false;
446
+ if (typeof body.visitor !== "object" || body.visitor === null || Array.isArray(body.visitor)) return false;
447
+ const visitor = body.visitor;
448
+ if (typeof visitor.name !== "string" || visitor.name.trim().length === 0) return false;
449
+ if (typeof visitor.email !== "string" || visitor.email.trim().length === 0) return false;
450
+ return body.visitorToken === void 0 || body.visitorToken === null || typeof body.visitorToken === "string";
451
+ }
452
+ function originAllowed(origin, allowedOrigins) {
453
+ if (!allowedOrigins || allowedOrigins.length === 0) return true;
454
+ if (!origin) return false;
455
+ return allowedOrigins.includes(origin);
456
+ }
457
+ function createChatSessionHandler({ allowedOrigins, apiKey, apiUrl = DEFAULT_API_URL$1, fetchImpl = fetch }) {
458
+ const sessionUrl = `${normalizeApiUrl(apiUrl)}/clivly/widget/session`;
459
+ return async (request) => {
460
+ if (request.method !== "POST") return new Response(JSON.stringify({ error: "method_not_allowed" }), {
461
+ status: 405,
462
+ headers: {
463
+ ...JSON_HEADERS,
464
+ allow: "POST"
465
+ }
466
+ });
467
+ const origin = resolveOrigin(request);
468
+ if (!originAllowed(origin, allowedOrigins)) return jsonResponse$1({ error: "origin_not_allowed" }, 403);
469
+ let body;
470
+ try {
471
+ body = await request.json();
472
+ } catch {
473
+ return jsonResponse$1({ error: "invalid_json" }, 400);
474
+ }
475
+ if (!isChatSessionRequestBody(body)) return jsonResponse$1({ error: "invalid_request" }, 400);
476
+ try {
477
+ const upstream = await fetchImpl(sessionUrl, {
478
+ method: "POST",
479
+ headers: {
480
+ authorization: `Bearer ${apiKey}`,
481
+ "content-type": "application/json",
482
+ ...origin ? { origin } : {}
483
+ },
484
+ body: JSON.stringify({
485
+ widgetId: body.widgetId,
486
+ visitor: body.visitor,
487
+ visitorToken: body.visitorToken,
488
+ origin
489
+ })
490
+ });
491
+ const responseBody = await upstream.text();
492
+ return new Response(responseBody, {
493
+ status: upstream.status,
494
+ headers: { "content-type": upstream.headers.get("content-type") ?? "application/json" }
495
+ });
496
+ } catch {
497
+ return jsonResponse$1({ error: "upstream_unreachable" }, 502);
498
+ }
499
+ };
500
+ }
501
+ //#endregion
377
502
  //#region ../sdk/src/errors.ts
378
503
  var ClivlyError = class ClivlyError extends Error {
379
504
  /** HTTP status, or null when the request never completed (network error). */
@@ -912,6 +1037,12 @@ Object.defineProperty(exports, "ClivlyRateLimitError", {
912
1037
  return ClivlyRateLimitError;
913
1038
  }
914
1039
  });
1040
+ Object.defineProperty(exports, "createChatSessionHandler", {
1041
+ enumerable: true,
1042
+ get: function() {
1043
+ return createChatSessionHandler;
1044
+ }
1045
+ });
915
1046
  Object.defineProperty(exports, "createClivlySDK", {
916
1047
  enumerable: true,
917
1048
  get: function() {
@@ -11,7 +11,7 @@ function checkCustomObjectSlugs(config) {
11
11
  name: "Custom object slugs",
12
12
  ok: false,
13
13
  skipped: true,
14
- detail: "No custom objects configured."
14
+ detail: "No custom objects in clivly.config. Objects added in the dashboard need a matching source.custom entry here to sync."
15
15
  };
16
16
  const problems = [];
17
17
  const seen = /* @__PURE__ */ new Map();
@@ -36,6 +36,49 @@ function checkCustomObjectSlugs(config) {
36
36
  };
37
37
  }
38
38
  //#endregion
39
+ //#region ../sdk/src/fetch-error.ts
40
+ /** Deepest `Error` reachable through the `.cause` chain (the original error if none). */
41
+ function rootError(error) {
42
+ let current = error;
43
+ const seen = /* @__PURE__ */ new Set();
44
+ while (current instanceof Error && current.cause !== void 0 && !seen.has(current)) {
45
+ seen.add(current);
46
+ current = current.cause;
47
+ }
48
+ return current;
49
+ }
50
+ function codeOf(error) {
51
+ const code = error?.code;
52
+ return typeof code === "string" ? code : void 0;
53
+ }
54
+ const CODE_HINTS = {
55
+ ECONNREFUSED: "the database refused the connection — is Postgres running and is your database URL (e.g. DATABASE_URL) pointing at it?",
56
+ ENOTFOUND: "the database host couldn't be resolved — check the host in your connection string.",
57
+ ETIMEDOUT: "the database connection timed out — check the host, port, and firewall.",
58
+ "28P01": "password authentication failed — check the credentials in your connection string.",
59
+ "28000": "the database rejected the connection role — check the user in your connection string.",
60
+ "3D000": "that database does not exist — check the database name in your connection string.",
61
+ "08006": "the database connection was lost — check that Postgres is reachable."
62
+ };
63
+ const NEWLINE = /\r?\n/;
64
+ /** One-line, single-spaced version of an error message (drops the SQL/params blob). */
65
+ function firstLine(message) {
66
+ const line = message.split(NEWLINE)[0]?.trim() ?? "";
67
+ return line === "" ? message.trim() : line;
68
+ }
69
+ /**
70
+ * Build the `detail` string for a failed sample fetch of `entity`.
71
+ * Recognised connection errors get a plain-English hint; everything else
72
+ * surfaces the root cause's first line rather than the wrapper's SQL dump.
73
+ */
74
+ function describeFetchError(entity, error) {
75
+ const root = rootError(error);
76
+ const code = codeOf(root) ?? codeOf(error);
77
+ const hint = code ? CODE_HINTS[code] : void 0;
78
+ if (hint) return `Couldn't read "${entity}": ${hint}`;
79
+ return `Couldn't read "${entity}": ${firstLine(root instanceof Error ? root.message : String(root?.message ?? root ?? "unknown error"))}`;
80
+ }
81
+ //#endregion
39
82
  //#region ../sdk/src/namespace-probe.ts
40
83
  /**
41
84
  * Verify the tables the integration depends on are genuinely reachable:
@@ -187,7 +230,7 @@ async function runDoctor(args) {
187
230
  checks.push({
188
231
  name: `Sample fetch (${source.entity})`,
189
232
  ok: false,
190
- detail: `"${source.entity}" fetchPage threw: ${error.message}`
233
+ detail: describeFetchError(source.entity, error)
191
234
  });
192
235
  }
193
236
  const result = await connect();
@@ -374,6 +417,88 @@ function resolveTriggerUrl(args) {
374
417
  return args.observedUrl;
375
418
  }
376
419
  //#endregion
420
+ //#region ../sdk/src/chat-session.ts
421
+ const DEFAULT_API_URL$1 = "https://api.clivly.com";
422
+ const JSON_HEADERS = { "content-type": "application/json" };
423
+ const TRAILING_SLASH$1 = /\/$/;
424
+ function jsonResponse$1(body, status) {
425
+ return new Response(JSON.stringify(body), {
426
+ status,
427
+ headers: JSON_HEADERS
428
+ });
429
+ }
430
+ function normalizeApiUrl(apiUrl) {
431
+ return apiUrl.replace(TRAILING_SLASH$1, "");
432
+ }
433
+ function resolveOrigin(req) {
434
+ const headerOrigin = req.headers.get("origin");
435
+ if (headerOrigin) return headerOrigin;
436
+ try {
437
+ return new URL(req.url).origin;
438
+ } catch {
439
+ return null;
440
+ }
441
+ }
442
+ function isChatSessionRequestBody(value) {
443
+ if (typeof value !== "object" || value === null) return false;
444
+ const body = value;
445
+ if (typeof body.widgetId !== "string" || body.widgetId.trim().length === 0) return false;
446
+ if (typeof body.visitor !== "object" || body.visitor === null || Array.isArray(body.visitor)) return false;
447
+ const visitor = body.visitor;
448
+ if (typeof visitor.name !== "string" || visitor.name.trim().length === 0) return false;
449
+ if (typeof visitor.email !== "string" || visitor.email.trim().length === 0) return false;
450
+ return body.visitorToken === void 0 || body.visitorToken === null || typeof body.visitorToken === "string";
451
+ }
452
+ function originAllowed(origin, allowedOrigins) {
453
+ if (!allowedOrigins || allowedOrigins.length === 0) return true;
454
+ if (!origin) return false;
455
+ return allowedOrigins.includes(origin);
456
+ }
457
+ function createChatSessionHandler({ allowedOrigins, apiKey, apiUrl = DEFAULT_API_URL$1, fetchImpl = fetch }) {
458
+ const sessionUrl = `${normalizeApiUrl(apiUrl)}/clivly/widget/session`;
459
+ return async (request) => {
460
+ if (request.method !== "POST") return new Response(JSON.stringify({ error: "method_not_allowed" }), {
461
+ status: 405,
462
+ headers: {
463
+ ...JSON_HEADERS,
464
+ allow: "POST"
465
+ }
466
+ });
467
+ const origin = resolveOrigin(request);
468
+ if (!originAllowed(origin, allowedOrigins)) return jsonResponse$1({ error: "origin_not_allowed" }, 403);
469
+ let body;
470
+ try {
471
+ body = await request.json();
472
+ } catch {
473
+ return jsonResponse$1({ error: "invalid_json" }, 400);
474
+ }
475
+ if (!isChatSessionRequestBody(body)) return jsonResponse$1({ error: "invalid_request" }, 400);
476
+ try {
477
+ const upstream = await fetchImpl(sessionUrl, {
478
+ method: "POST",
479
+ headers: {
480
+ authorization: `Bearer ${apiKey}`,
481
+ "content-type": "application/json",
482
+ ...origin ? { origin } : {}
483
+ },
484
+ body: JSON.stringify({
485
+ widgetId: body.widgetId,
486
+ visitor: body.visitor,
487
+ visitorToken: body.visitorToken,
488
+ origin
489
+ })
490
+ });
491
+ const responseBody = await upstream.text();
492
+ return new Response(responseBody, {
493
+ status: upstream.status,
494
+ headers: { "content-type": upstream.headers.get("content-type") ?? "application/json" }
495
+ });
496
+ } catch {
497
+ return jsonResponse$1({ error: "upstream_unreachable" }, 502);
498
+ }
499
+ };
500
+ }
501
+ //#endregion
377
502
  //#region ../sdk/src/errors.ts
378
503
  var ClivlyError = class ClivlyError extends Error {
379
504
  /** HTTP status, or null when the request never completed (network error). */
@@ -888,4 +1013,4 @@ function createClivlySDK(config) {
888
1013
  };
889
1014
  }
890
1015
  //#endregion
891
- export { ClivlyRateLimitError as a, ClivlyNetworkError as i, ClivlyAuthError as n, ClivlyError as r, createClivlySDK as t };
1016
+ export { ClivlyRateLimitError as a, ClivlyNetworkError as i, ClivlyAuthError as n, createChatSessionHandler as o, ClivlyError as r, createClivlySDK as t };
package/sdk.cjs CHANGED
@@ -1,7 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sdk = require("./sdk-r4khXzoH.cjs");
2
+ const require_sdk = require("./sdk-D1BHQDf5.cjs");
3
3
  exports.ClivlyAuthError = require_sdk.ClivlyAuthError;
4
4
  exports.ClivlyError = require_sdk.ClivlyError;
5
5
  exports.ClivlyNetworkError = require_sdk.ClivlyNetworkError;
6
6
  exports.ClivlyRateLimitError = require_sdk.ClivlyRateLimitError;
7
+ exports.createChatSessionHandler = require_sdk.createChatSessionHandler;
7
8
  exports.createClivlySDK = require_sdk.createClivlySDK;
package/sdk.d.cts CHANGED
@@ -1,5 +1,13 @@
1
- import { a as ClivlySDKConfig, c as DiscoveredTable, i as ClivlySDK, l as DoctorCheck, n as ClivlyConnectReason, o as ClivlyUser, r as ClivlyConnectResult, s as CursorStore, u as DoctorReport } from "./namespace-probe-CcYne5Je.cjs";
1
+ import { a as ClivlyConnectResult, c as ClivlyUser, d as DiscoveredTable, f as DoctorCheck, i as ClivlyConnectReason, l as CreateChatSessionHandlerOptions, n as ChatSessionRequestBody, o as ClivlySDK, p as DoctorReport, r as ChatWidgetVisitor, s as ClivlySDKConfig, u as CursorStore } from "./namespace-probe-CkHCj9tQ.cjs";
2
2
 
3
+ //#region ../sdk/src/chat-session.d.ts
4
+ declare function createChatSessionHandler({
5
+ allowedOrigins,
6
+ apiKey,
7
+ apiUrl,
8
+ fetchImpl
9
+ }: CreateChatSessionHandlerOptions): (request: Request) => Promise<Response>;
10
+ //#endregion
3
11
  //#region ../sdk/src/errors.d.ts
4
12
  declare class ClivlyError extends Error {
5
13
  /** HTTP status, or null when the request never completed (network error). */
@@ -30,4 +38,4 @@ declare class ClivlyNetworkError extends ClivlyError {}
30
38
  */
31
39
  declare function createClivlySDK(config: ClivlySDKConfig): ClivlySDK;
32
40
  //#endregion
33
- export { ClivlyAuthError, type ClivlyConnectReason, type ClivlyConnectResult, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, type ClivlySDK, type ClivlySDKConfig, type ClivlyUser, type CursorStore, type DiscoveredTable, type DoctorCheck, type DoctorReport, createClivlySDK };
41
+ export { type ChatSessionRequestBody, type ChatWidgetVisitor, ClivlyAuthError, type ClivlyConnectReason, type ClivlyConnectResult, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, type ClivlySDK, type ClivlySDKConfig, type ClivlyUser, type CreateChatSessionHandlerOptions, type CursorStore, type DiscoveredTable, type DoctorCheck, type DoctorReport, createChatSessionHandler, createClivlySDK };
package/sdk.d.mts CHANGED
@@ -1,5 +1,13 @@
1
- import { a as ClivlySDKConfig, c as DiscoveredTable, i as ClivlySDK, l as DoctorCheck, n as ClivlyConnectReason, o as ClivlyUser, r as ClivlyConnectResult, s as CursorStore, u as DoctorReport } from "./namespace-probe-DjWzU9gG.mjs";
1
+ import { a as ClivlyConnectResult, c as ClivlyUser, d as DiscoveredTable, f as DoctorCheck, i as ClivlyConnectReason, l as CreateChatSessionHandlerOptions, n as ChatSessionRequestBody, o as ClivlySDK, p as DoctorReport, r as ChatWidgetVisitor, s as ClivlySDKConfig, u as CursorStore } from "./namespace-probe-CA4Vj40g.mjs";
2
2
 
3
+ //#region ../sdk/src/chat-session.d.ts
4
+ declare function createChatSessionHandler({
5
+ allowedOrigins,
6
+ apiKey,
7
+ apiUrl,
8
+ fetchImpl
9
+ }: CreateChatSessionHandlerOptions): (request: Request) => Promise<Response>;
10
+ //#endregion
3
11
  //#region ../sdk/src/errors.d.ts
4
12
  declare class ClivlyError extends Error {
5
13
  /** HTTP status, or null when the request never completed (network error). */
@@ -30,4 +38,4 @@ declare class ClivlyNetworkError extends ClivlyError {}
30
38
  */
31
39
  declare function createClivlySDK(config: ClivlySDKConfig): ClivlySDK;
32
40
  //#endregion
33
- export { ClivlyAuthError, type ClivlyConnectReason, type ClivlyConnectResult, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, type ClivlySDK, type ClivlySDKConfig, type ClivlyUser, type CursorStore, type DiscoveredTable, type DoctorCheck, type DoctorReport, createClivlySDK };
41
+ export { type ChatSessionRequestBody, type ChatWidgetVisitor, ClivlyAuthError, type ClivlyConnectReason, type ClivlyConnectResult, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, type ClivlySDK, type ClivlySDKConfig, type ClivlyUser, type CreateChatSessionHandlerOptions, type CursorStore, type DiscoveredTable, type DoctorCheck, type DoctorReport, createChatSessionHandler, createClivlySDK };
package/sdk.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as ClivlyRateLimitError, i as ClivlyNetworkError, n as ClivlyAuthError, r as ClivlyError, t as createClivlySDK } from "./sdk-DtFmeuwl.mjs";
2
- export { ClivlyAuthError, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, createClivlySDK };
1
+ import { a as ClivlyRateLimitError, i as ClivlyNetworkError, n as ClivlyAuthError, o as createChatSessionHandler, r as ClivlyError, t as createClivlySDK } from "./sdk-DdzBxgMx.mjs";
2
+ export { ClivlyAuthError, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, createChatSessionHandler, createClivlySDK };
package/vite.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { i as ClivlySDK } from "./namespace-probe-CcYne5Je.cjs";
1
+ import { o as ClivlySDK } from "./namespace-probe-CkHCj9tQ.cjs";
2
2
  import { Plugin } from "vite";
3
3
 
4
4
  //#region ../sdk/src/vite.d.ts
package/vite.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { i as ClivlySDK } from "./namespace-probe-DjWzU9gG.mjs";
1
+ import { o as ClivlySDK } from "./namespace-probe-CA4Vj40g.mjs";
2
2
  import { Plugin } from "vite";
3
3
 
4
4
  //#region ../sdk/src/vite.d.ts