clivly 0.7.2 → 0.7.4

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/README.md CHANGED
@@ -1,189 +1,441 @@
1
1
  # clivly
2
2
 
3
- Clivly's consolidated public package.
3
+ The consolidated public package for [Clivly](https://clivly.com) — the CRM that
4
+ lives inside your app. It is two things in one install:
4
5
 
5
- It serves two roles:
6
+ - **A CLI** (`npx clivly …`) that scaffolds your integration, pairs your machine
7
+ with a workspace, and diagnoses the setup.
8
+ - **A runtime**, imported from typed subpaths (`clivly/sdk`, `clivly/drizzle`,
9
+ `clivly/vite`, `clivly/auth/*`).
6
10
 
7
- - the CLI you run as `npx clivly ...`
8
- - the package that exposes the stable runtime subpaths such as `clivly/sdk`
11
+ > **The canonical end-to-end integration guide is
12
+ > [Connecting to Clivly Cloud](https://github.com/amani-joseph/clivly.com/blob/master/docs/guides/connecting-to-clivly-cloud.md).**
13
+ > This README is the command-level reference.
9
14
 
10
- ## Install
15
+ ## Prerequisites
16
+
17
+ | Requirement | Version / note |
18
+ | --- | --- |
19
+ | Node.js | **≥ 22** |
20
+ | An existing app | The CLI scaffolds into a project you already have |
21
+ | Drizzle ORM | Required for `clivly init` to generate a config. Prisma/Kysely/raw-SQL projects hand-write a `fetchPage` source instead |
22
+ | `cloudflared` on `PATH` | Only for `clivly dev` |
23
+
24
+ ## Installation
11
25
 
12
26
  ```bash
13
27
  npm install clivly
14
28
  ```
15
29
 
16
- Stable releases publish on npm's `latest` tag. Use `@next` only when you are
17
- intentionally testing a prerelease build.
18
-
19
- This package requires Node `>=22`.
30
+ Published on npm's `latest` tag. Once installed, `npx clivly …` uses your local
31
+ copy rather than re-fetching. Use `@next` only when deliberately testing a
32
+ prerelease cut.
20
33
 
21
- ## CLI commands
34
+ ## Five-minute quick start
22
35
 
23
36
  ```bash
24
- npx clivly init # scaffold config, route files, and .env placeholders
25
- npx clivly add widget # scaffold the chat-session route for the widget
26
- npx clivly login # pair this machine and write credentials to .env
27
- npx clivly status # check your setup, step by step
28
- npx clivly dev # tunnel localhost so remote sync can reach you
29
- npx clivly push-schema # report your schema to Clivly without deploying
37
+ npm install clivly
38
+ npx clivly init # scaffold config + routes + .env, then pair this machine
39
+ npx clivly status # check every setup step until they all pass
30
40
  ```
31
41
 
32
- `ping` and `doctor` remain available as focused aliases around parts of the
33
- `status` ladder.
42
+ `clivly init` writes exactly three files (plus `.env` keys) — a
43
+ `clivly.config.ts` you own, and two route files Clivly owns. It creates no
44
+ database tables and does not modify your ORM schema.
34
45
 
35
- ## Runtime imports
46
+ ---
36
47
 
37
- Application code should import from subpaths, not from the package root:
48
+ ## Commands
38
49
 
39
- ```ts
40
- import { createClivlySDK, defineClivlyConfig } from "clivly/sdk";
41
- import { fromDrizzle } from "clivly/drizzle";
42
- import { clivlyVite } from "clivly/vite";
43
- import { createClerkAuthAdapter } from "clivly/auth/clerk";
44
- import { createWorkOSAuthAdapter } from "clivly/auth/workos";
45
- import { createBetterAuthAdapter } from "clivly/auth/better-auth";
46
- ```
50
+ | Command | What it does |
51
+ | --- | --- |
52
+ | [`clivly init`](#clivly-init) | Scaffold config, routes and env placeholders for your app |
53
+ | [`clivly login`](#clivly-login) | Pair this machine and write credentials to your env file |
54
+ | [`clivly status`](#clivly-status) | Check your setup, step by step. Read-only |
55
+ | [`clivly add widget`](#clivly-add-widget) | Scaffold the chat-session route |
56
+ | [`clivly push-schema`](#clivly-push-schema) | Report your schema to Clivly without deploying |
57
+ | [`clivly dev`](#clivly-dev) | Tunnel localhost so remote sync can reach you |
58
+ | `clivly --version` | Print the installed `clivly` version |
59
+ | [`clivly ping` / `connect` / `doctor`](#deprecated-aliases) | Deprecated aliases running parts of `status` |
47
60
 
48
- Do **not** import application code from `clivly` (the package root). The root
49
- entry is the CLI.
61
+ Every command accepts `--help` (or `-h`), which **describes without acting**.
50
62
 
51
- Provider and framework integrations use optional peer dependencies. Install only
52
- what your app actually uses, such as `drizzle-orm`, `vite`, `@clerk/backend`,
53
- `@workos-inc/node`, or `better-auth`.
63
+ ---
54
64
 
55
- ## `clivly init`
65
+ ### `clivly init`
56
66
 
57
- `clivly init` detects your framework from `package.json`, locates your Drizzle
58
- schema, and writes:
67
+ ```bash
68
+ npx clivly init [--force] [--dry-run] [--diff] [--yes] [--no-login] [--no-browser]
69
+ [--schema <path|package>] [--db-import <specifier>]
70
+ [--auth-import <specifier>] [--contact-table <table>]
71
+ [--company-table <table>]
72
+ ```
59
73
 
60
- - `clivly.config.ts`
61
- - the supported Clivly route files for your framework
62
- - missing `.env` placeholders
74
+ Detects your framework, ORM, auth adapter and app layout, then scaffolds:
63
75
 
64
- It currently targets Drizzle-backed integrations. Prisma and Kysely projects
65
- can still integrate, but should provide hand-written `fetchPage` sources rather
66
- than relying on the generated Drizzle path.
76
+ | File | Owned by Clivly |
77
+ | --- | --- |
78
+ | `clivly.config.ts` | No — yours to edit |
79
+ | The tick route for your framework | Yes — `--force` to overwrite |
80
+ | The auth-verify route for your framework | Yes — `--force` to overwrite |
67
81
 
68
- Useful flags:
82
+ Plus `CLIVLY_SECRET_KEY`, `CLIVLY_SYNC_TRIGGER_URL` and
83
+ `CLIVLY_SYNC_TRIGGER_SECRET` placeholders in your env file.
69
84
 
70
85
  | Flag | Purpose |
71
86
  | --- | --- |
72
- | `--schema <path\|package>` | Where your Drizzle schema lives |
73
- | `--db-import <specifier>` | Where the Drizzle client lives when it is not beside the schema |
74
- | `--auth-import <specifier>` | Where your auth instance lives |
75
- | `--contact-table <name>` | Force the contact entity table |
76
- | `--company-table <name>` | Force the company entity table |
77
- | `--force` | Overwrite generated files / `.env` values |
78
- | `--no-login` | Skip the browser pairing step |
79
- | `--no-browser` | Print the approval URL instead of opening it |
87
+ | `--dry-run` | Show what would be written; write nothing |
88
+ | `--diff` | Show a diff for files that already exist |
89
+ | `--force` | Overwrite the files Clivly owns |
90
+ | `--yes` | Skip prompts |
91
+ | `--no-login` | Skip browser pairing |
92
+ | `--no-browser` | Pair, but print the URL instead of opening it |
93
+ | `--schema <path\|package>` | Where your Drizzle tables live |
94
+ | `--db-import <specifier>` | Where the Drizzle instance lives, when it is not beside the schema. Accepts `@acme/database#database` to name the export |
95
+ | `--auth-import <specifier>` | Where your better-auth instance lives. Same `#exportName` form. Overrides on-disk probing, which cannot see across a package boundary |
96
+ | `--contact-table <name>` | Use this table as the contact entity, skipping the naming heuristics |
97
+ | `--company-table <name>` | Same for companies |
98
+
99
+ `--contact-table` and `--company-table` accept either the SQL table name or the
100
+ name it is exported under in your schema module — whichever you have in front of
101
+ you.
102
+
103
+ When `init` finishes and `CLIVLY_SECRET_KEY` is still empty, it hands you
104
+ straight into `clivly login` so you don't end up staring at a blank env file.
105
+ That step is interactive, so it is skipped automatically when there is no TTY or
106
+ when `CI` is set.
107
+
108
+ #### Framework support
109
+
110
+ Route scaffolding exists for these five. Anywhere else, mount the handler
111
+ yourself — it is a plain `(Request) => Promise<Response>`.
112
+
113
+ | Framework | Tick route written |
114
+ | --- | --- |
115
+ | Next.js | `app/api/clivly/tick/route.ts` (or `src/app/…`) |
116
+ | TanStack Start | `src/routes/api/clivly/tick.ts` |
117
+ | SvelteKit | `src/routes/api/clivly/tick/+server.ts` |
118
+ | Remix | `app/routes/api.clivly.tick.ts` |
119
+ | Nuxt | `server/api/clivly/tick.ts` |
120
+
121
+ Plain Vue apps can still integrate Clivly, but they need a manual server layer
122
+ for the tick route unless they are on Nuxt.
80
123
 
81
- If no usable contact entity can be found, `init` writes nothing rather than
82
- generating a broken config.
124
+ #### ORM support
83
125
 
84
- Supported first-class route scaffolding today:
126
+ `init` generates a config for **Drizzle only**. Prisma and Kysely projects
127
+ integrate through a hand-written `fetchPage` source — see the
128
+ [Non-Drizzle sources](https://github.com/amani-joseph/clivly.com/blob/master/docs/guides/connecting-to-clivly-cloud.md#non-drizzle-sources)
129
+ section of the guide.
85
130
 
86
- - Next.js
87
- - TanStack Start
88
- - SvelteKit
89
- - Remix
90
- - Nuxt
131
+ Use `--schema` when your Drizzle schema is not in a common location or in
132
+ `drizzle.config.ts`.
91
133
 
92
- ## `clivly add widget`
134
+ #### Monorepos
135
+
136
+ In a workspace, `init` reads your `pnpm-workspace.yaml` (or `workspaces` array)
137
+ and imports anything owned by a *sibling* package by **package name** rather
138
+ than by a relative path that climbs out of your app:
139
+
140
+ ```ts
141
+ import * as schema from "@acme/database"; // not "../../packages/database/src/index"
142
+ ```
143
+
144
+ `--schema` accepts a package specifier as well as a path:
93
145
 
94
146
  ```bash
95
- npx clivly add widget
147
+ npx clivly init --schema @acme/database # the package entry
148
+ npx clivly init --schema @acme/database/src/schema.ts # a subpath inside it
149
+ npx clivly init --schema ./src/db/schema.ts # a plain path
150
+ ```
151
+
152
+ A real file always wins over a same-named package, so existing relative paths
153
+ keep their meaning.
154
+
155
+ #### When no contact entity can be found
156
+
157
+ `init` **writes nothing** rather than scaffolding a config that cannot compile.
158
+ Clivly needs one entity before a config will load, and it will not invent one:
159
+
96
160
  ```
161
+ No usable contact entity
97
162
 
98
- Scaffolds the host-side widget session route that mounts
99
- `createChatSessionHandler()` from `clivly/sdk`. It is idempotent and supports
100
- `--dry-run` and `--force`.
163
+ Tables in @acme/database:
164
+ widgets
165
+ invoices
166
+
167
+ Pick the one that holds your people and re-run:
168
+ clivly init --schema @acme/database --contact-table widgets
169
+
170
+ Nothing was written — Clivly needs one entity before a config can load.
171
+ ```
101
172
 
102
- ## `clivly login`
173
+ ---
174
+
175
+ ### `clivly login`
103
176
 
104
177
  ```bash
105
178
  npx clivly login [--force] [--rotate] [--api-url <url>] [--no-browser]
106
179
  ```
107
180
 
108
- Pairs this machine with a Clivly workspace and writes:
181
+ Pairs this machine with a Clivly workspace and writes `CLIVLY_SECRET_KEY` and
182
+ `CLIVLY_SYNC_TRIGGER_SECRET` into your project's env file. It creates a pairing
183
+ session, prints (and by default opens) an approval link, and waits while you
184
+ approve it in a signed-in browser. The approval link is always printed, so an
185
+ SSH or headless session can be approved by opening the link elsewhere.
186
+
187
+ | Flag | Effect |
188
+ | --- | --- |
189
+ | `--no-browser` | Print the approval URL without opening a browser |
190
+ | `--force` | Overwrite env values that already have a real value. Without it, existing values are kept and reported as skipped; an empty placeholder (`CLIVLY_SECRET_KEY=`) is always filled in |
191
+ | `--rotate` | Rotate the workspace's remote-sync trigger secret instead of revealing the current one. **Your already-deployed app stops receiving sync triggers until you redeploy it with the new secret** |
192
+ | `--api-url <url>` | Point at a non-default backend |
193
+
194
+ Before anything is written, the CLI prints which workspace approved the pairing
195
+ and asks you to confirm. Declining writes nothing and exits non-zero. In a
196
+ non-interactive run (no TTY, or `CI` set) the workspace is still printed but you
197
+ are not prompted, so scripted pairing cannot hang.
198
+
199
+ #### Which env file gets written
109
200
 
110
- - `CLIVLY_SECRET_KEY`
111
- - `CLIVLY_SYNC_TRIGGER_SECRET`
201
+ The CLI follows your framework's convention. This is not cosmetic on Workers:
202
+ wrangler reads `.dev.vars` and nothing else, so credentials written to `.env`
203
+ would never load.
112
204
 
113
- into your project's `.env`.
205
+ | Framework | Env file |
206
+ | --- | --- |
207
+ | Next.js | `.env.local` |
208
+ | TanStack Start, SvelteKit, Remix, Nuxt | `.env` |
209
+ | Hono on Cloudflare Workers | `.dev.vars` |
210
+ | Anything else | `.env` |
114
211
 
115
- The approval URL is always printed, so headless or SSH sessions can still be
116
- approved in another browser.
212
+ #### Pairing availability
117
213
 
118
- Notes:
214
+ The pairing endpoints are feature-flagged server-side
215
+ (`CLIVLY_CLI_PAIRING_ENABLED`). **Clivly Cloud has pairing enabled**, so
216
+ `clivly login` is the normal path there. The flag defaults to `false`, so a
217
+ self-hosted backend must opt in — otherwise `clivly login` says so and exits
218
+ non-zero.
119
219
 
120
- - `--force` overwrites existing real values
121
- - `--rotate` rotates the remote-sync trigger secret instead of revealing the
122
- current one
123
- - pairing is feature-flagged server-side with `CLIVLY_CLI_PAIRING_ENABLED`
220
+ #### Manual credential path
124
221
 
125
- If pairing is unavailable, you can still set credentials manually and then run
126
- `clivly status`.
222
+ If pairing is unavailable, every credential can be set by hand. Nothing about
223
+ the CLI requires pairing.
127
224
 
128
- ## `clivly status`
225
+ | Value | Where to get it | Notes |
226
+ | --- | --- | --- |
227
+ | `CLIVLY_SECRET_KEY` | Dashboard → **Connect app**, or **Settings → API keys** | **Shown once.** Copy it immediately; you cannot retrieve it later, only regenerate. Regenerating **revokes the previous key**, so redeploy after rotating. The legacy name `CLIVLY_API_KEY` is still accepted as a fallback |
228
+ | `CLIVLY_SYNC_TRIGGER_SECRET` | Dashboard → **Integrations → Remote sync** | Must match the value in your app's env exactly. A mismatch surfaces as a `401` on the signed trigger |
229
+ | `CLIVLY_SYNC_TRIGGER_URL` | Your own deployment | The public URL of your mounted tick route, e.g. `https://app.example.com/api/clivly/tick`. Optional until you deploy — push-only setups can leave it unset |
230
+ | `CLIVLY_PUBLISHABLE_KEY` | Same place as the secret key | Only needed for the chat widget. Safe to expose in client code |
231
+
232
+ Write them into the env file of the package that imports `clivly`, then re-run
233
+ `clivly status` to confirm each step goes green.
234
+
235
+ > **Running your own Clivly backend?** `CLIVLY_TRIGGER_SECRET_WRAPPING_KEY` (a
236
+ > 32-byte hex key) is a **hard production prerequisite** for pairing. The
237
+ > trigger secret is stored encrypted with it, so without the key set on the
238
+ > server, `clivly login` fails with a 500 at the moment it would hand over
239
+ > credentials. Set it before enabling `CLIVLY_CLI_PAIRING_ENABLED`.
240
+
241
+ ---
242
+
243
+ ### `clivly status`
129
244
 
130
245
  ```bash
131
246
  npx clivly status [--verbose] [--config <path>] [--env-file <path>]
132
247
  ```
133
248
 
134
- Walks the setup one rung at a time: credentials, config, schema, sources, and
135
- connectivity. This is the main diagnostic command for integration issues.
249
+ Walks your setup one step at a time and stops at the first thing that isn't
250
+ ready, telling you what to do about it. **This is the command to reach for when
251
+ something isn't working.** It is read-only and writes nothing.
252
+
253
+ The ladder: credential → Clivly reachable → `clivly.config` loads →
254
+ `clivly.config` is edge-safe → setup checks → sync trigger URL.
255
+
256
+ | Flag | Effect |
257
+ | --- | --- |
258
+ | `--verbose` | Show every check, including those that passed |
259
+ | `--config <path>` | Path to `clivly.config.ts` (needed in a monorepo) |
260
+ | `--env-file <path>` | Extra env file to load. Repeatable; later files win |
261
+
262
+ ```bash
263
+ npx clivly status --config ./apps/web/clivly.config.ts
264
+ npx clivly status --env-file ../server/.env
265
+ ```
266
+
267
+ Env precedence, strongest first: **real environment → `--env-file` / the
268
+ `envFiles` array in `clivly.cli.ts` → `.env.local` → `.env`**.
269
+
270
+ ---
136
271
 
137
- ## `clivly dev`
272
+ ### `clivly add widget`
273
+
274
+ ```bash
275
+ npx clivly add widget [--force] [--dry-run]
276
+ ```
277
+
278
+ Writes the route that mints chat-widget sessions, for your detected framework
279
+ (e.g. `app/api/clivly/widget/session/route.ts` on Next.js). Idempotent.
280
+
281
+ It creates **no widget record** — that lives in the Clivly dashboard under
282
+ **Settings → Chat widget**. See
283
+ [Adding the chat widget](https://github.com/amani-joseph/clivly.com/blob/master/docs/guides/adding-the-chat-widget.md)
284
+ for the full flow.
285
+
286
+ | Flag | Effect |
287
+ | --- | --- |
288
+ | `--dry-run` | Show what would be written; write nothing |
289
+ | `--force` | Overwrite the route if it already exists |
290
+
291
+ ---
292
+
293
+ ### `clivly push-schema`
294
+
295
+ ```bash
296
+ npx clivly push-schema [--config <path>]
297
+ ```
298
+
299
+ Sends your discovered tables and columns to Clivly so the dashboard can offer
300
+ them for mapping — without deploying your app. Reads your config; writes nothing
301
+ locally. **Table and column names only; never row data.**
302
+
303
+ ---
304
+
305
+ ### `clivly dev`
138
306
 
139
307
  ```bash
140
308
  npx clivly dev [--port <n>] [--api-url <url>] [--route-path <path>] [-- <cmd>]
141
309
  ```
142
310
 
143
311
  Opens a `cloudflared` tunnel to your local app and registers it with Clivly for
144
- the lifetime of the session, so remote sync can reach `localhost` during local
145
- development. The override is cleared on exit.
312
+ the length of the session, so remote sync can reach `localhost` while you
313
+ develop. Your dev server is detected from `package.json` scripts, or pass it
314
+ explicitly after `--`. Needs `cloudflared` on `PATH` and `CLIVLY_SECRET_KEY`
315
+ set. The override is cleared when the process exits; remote sync is otherwise
316
+ deploy-only.
317
+
318
+ ```bash
319
+ npx clivly dev --port 3000 -- npm run dev
320
+ ```
146
321
 
147
- Requires:
322
+ It also watches `clivly.config.ts` and tells you when it changes. It cannot
323
+ reload it for you — the config belongs to your app's process, not to the CLI.
148
324
 
149
- - `cloudflared` on `PATH`
150
- - `CLIVLY_SECRET_KEY` set
325
+ If registration fails, the warning names the cause: an invalid key, a backend
326
+ without the endpoint enabled, an HTTP status Clivly returned, or a genuine
327
+ network failure. Your app keeps running either way, and outbound sync (your app
328
+ → Clivly) is unaffected — only dashboard-triggered sync needs the tunnel.
151
329
 
152
- ## `clivly push-schema`
330
+ | Flag | Effect |
331
+ | --- | --- |
332
+ | `--port <n>` | Local port your app listens on |
333
+ | `--route-path <path>` | Path to your mounted tick route |
334
+ | `--api-url <url>` | Point at a non-default backend |
335
+
336
+ ---
337
+
338
+ ### Deprecated aliases
339
+
340
+ `ping`, `connect` and `doctor` each run part of the `status` ladder. They still
341
+ work, and are kept because output and docs have recommended them — but prefer
342
+ `clivly status`, which runs the whole ladder and tells you which step you are on.
343
+
344
+ | Alias | Equivalent scope |
345
+ | --- | --- |
346
+ | `clivly ping` | Credential + reachability |
347
+ | `clivly connect` | The above, plus config and trigger URL |
348
+ | `clivly doctor` | Config, setup checks and edge safety |
349
+
350
+ Each prints a deprecation notice, then the steps it covers, renumbered to its own
351
+ scope:
153
352
 
154
353
  ```bash
155
- npx clivly push-schema [--config <path>]
354
+ $ npx clivly ping
355
+ `clivly ping` is a deprecated alias that runs part of `clivly status`. Prefer `clivly status`.
356
+
357
+ Clivly status
358
+ ✅ Step 1 of 2 · API key present — Secret key is set.
359
+ ✅ Step 2 of 2 · Clivly reachable — Reached org: acme (org_1)
156
360
  ```
157
361
 
158
- Reports your discovered schema to Clivly without waiting for a deployed app to
159
- heartbeat it first.
362
+ `clivly doctor` runs the same read-only checks as `await clivly.doctor()` in
363
+ code: API key present, entities configured, sources configured, concept
364
+ coverage, Drizzle cursor/id columns, sample fetch, mapping dry-run,
365
+ custom-object slugs, heartbeat, and namespace reachability.
160
366
 
161
- ## `clivly ping` / `clivly doctor`
367
+ ---
162
368
 
163
- `clivly ping` sends one heartbeat and prints a terse pass/fail result.
369
+ ## Runtime imports
164
370
 
165
- `clivly doctor` loads your config and runs the same read-only checks exposed by
166
- `await clivly.doctor()`.
371
+ `clivly` is the consolidated public package. Application code imports from typed
372
+ subpaths:
167
373
 
168
- ## Telemetry
374
+ ```ts
375
+ import { createClivlySDK, defineClivlyConfig } from "clivly/sdk";
376
+ import { fromDrizzle, discoverFromDrizzle } from "clivly/drizzle";
377
+ import { clivlyVite } from "clivly/vite";
378
+ import { createClerkAuthAdapter } from "clivly/auth/clerk";
379
+ import { createWorkOSAuthAdapter } from "clivly/auth/workos";
380
+ import { createBetterAuthAdapter } from "clivly/auth/better-auth";
381
+ ```
169
382
 
170
- Clivly collects no telemetry. The CLI makes network requests only to the
171
- Clivly API you point it at, and only to do the work you asked for: pairing,
172
- heartbeats, schema pushes, status checks, and tunnel override registration.
383
+ > **Never `import` from the bare `clivly` specifier.** The package root is the
384
+ > CLI binary — it imports `node:fs` and `node:child_process` at module scope, so
385
+ > importing it from app code drags the whole CLI into your bundle and throws at
386
+ > import time on Workers and other edge runtimes.
387
+
388
+ Provider and framework integrations use **optional peer dependencies**. Install
389
+ only what your app actually uses:
390
+
391
+ | Subpath | Peer dependency |
392
+ | --- | --- |
393
+ | `clivly/drizzle` | `drizzle-orm` |
394
+ | `clivly/vite` | `vite` |
395
+ | `clivly/auth/clerk` | `@clerk/backend` |
396
+ | `clivly/auth/workos` | `@workos-inc/node` |
397
+ | `clivly/auth/better-auth` | none — you pass your own `betterAuth({...})` instance |
398
+
399
+ `clivly/core/*` is deprecated in favour of `clivly/internal/*`. It still
400
+ resolves, and emits a one-time `process.emitWarning` naming the replacement (so
401
+ it stays off stdout and cannot corrupt machine-readable CLI output). See
402
+ [STABILITY.md](https://github.com/amani-joseph/clivly.com/blob/master/STABILITY.md).
403
+
404
+ ---
405
+
406
+ ## Common errors
407
+
408
+ | Symptom | Fix |
409
+ | --- | --- |
410
+ | Generated code doesn't match these docs | `npx clivly --version` — an older installed version is the usual cause |
411
+ | `init` refuses to write, "No usable contact entity" | Pass `--contact-table <name>` |
412
+ | `init` cannot find your schema | Pass `--schema <path\|package>` |
413
+ | `init` scaffolded a `db` import that doesn't resolve | The probe cannot evaluate a db module that throws without `DATABASE_URL`. Fix the `TODO(clivly)` import path it left, or pass `--db-import` |
414
+ | `clivly login` exits non-zero, pairing unavailable | The backend has `CLIVLY_CLI_PAIRING_ENABLED` off. Use the manual credential path above |
415
+ | `clivly dev` cannot start | Install `cloudflared`, or set `CLIVLY_SYNC_TRIGGER_URL` to a tunnel you run yourself |
416
+ | Credentials written but the app never loads them | Wrong env file for your framework — see the table under `clivly login` |
417
+
418
+ The full troubleshooting table is in the
419
+ [integration guide](https://github.com/amani-joseph/clivly.com/blob/master/docs/guides/connecting-to-clivly-cloud.md#8-common-errors-and-troubleshooting).
420
+
421
+ ---
422
+
423
+ ## Telemetry
173
424
 
174
- See the telemetry section in:
175
- [SECURITY.md](https://github.com/amani-joseph/clivly.com/blob/master/SECURITY.md#telemetry)
425
+ **Clivly collects no telemetry.** The CLI makes network requests only to the
426
+ Clivly API you point it at, and only to do what you asked: pairing, heartbeats,
427
+ schema pushes, and status checks. No usage analytics, no crash reporting, no
428
+ phone-home. See
429
+ [SECURITY.md](https://github.com/amani-joseph/clivly.com/blob/master/SECURITY.md#telemetry).
176
430
 
177
- ## More docs
431
+ ## Related documentation
178
432
 
179
- - Cloud onboarding guide:
180
- [Connecting to Clivly Cloud](https://github.com/amani-joseph/clivly.com/blob/master/docs/guides/connecting-to-clivly-cloud.md)
181
- - Chat widget guide:
182
- [Adding the chat widget](https://github.com/amani-joseph/clivly.com/blob/master/docs/guides/adding-the-chat-widget.md)
183
- - SDK docs:
184
- [packages/sdk/README.md](https://github.com/amani-joseph/clivly.com/blob/master/packages/sdk/README.md)
185
- - Release notes:
186
- [CHANGELOG.md](https://github.com/amani-joseph/clivly.com/blob/master/CHANGELOG.md)
433
+ - **[Connecting to Clivly Cloud](https://github.com/amani-joseph/clivly.com/blob/master/docs/guides/connecting-to-clivly-cloud.md)** — the canonical end-to-end guide
434
+ - [Adding the chat widget](https://github.com/amani-joseph/clivly.com/blob/master/docs/guides/adding-the-chat-widget.md)
435
+ - [`@clivly/sdk` README](https://github.com/amani-joseph/clivly.com/blob/master/packages/sdk/README.md) — the runtime API surface
436
+ - [STABILITY.md](https://github.com/amani-joseph/clivly.com/blob/master/STABILITY.md) — import tiers
437
+ - [SECURITY.md](https://github.com/amani-joseph/clivly.com/blob/master/SECURITY.md)
438
+ - [CHANGELOG.md](https://github.com/amani-joseph/clivly.com/blob/master/CHANGELOG.md)
187
439
 
188
440
  ## License
189
441
 
package/drizzle.cjs CHANGED
@@ -65,11 +65,15 @@ function fromDrizzle(db, table, opts) {
65
65
  const idColumn = columns[idField];
66
66
  if (!cursorColumn) throw new Error(`fromDrizzle: cursorField "${opts.cursorField}" is not a column on "${(0, drizzle_orm.getTableName)(table)}".`);
67
67
  if (!idColumn) throw new Error(`fromDrizzle: idField "${idField}" is not a column on "${(0, drizzle_orm.getTableName)(table)}".`);
68
+ const projection = {};
69
+ for (const column of Object.values(columns)) projection[column.name] = column;
70
+ const cursorColumnName = cursorColumn.name;
71
+ const idColumnName = idColumn.name;
68
72
  const source = {
69
73
  entity: opts.entity,
70
- cursorField: opts.cursorField,
71
- idField,
72
- fetchPage: ({ since, limit }) => drizzleDb.select().from(table).where(since ? drizzle_orm.sql`(${cursorColumn}, ${idColumn}) > (${since.time}, ${since.id})` : void 0).orderBy((0, drizzle_orm.asc)(cursorColumn), (0, drizzle_orm.asc)(idColumn)).limit(limit)
74
+ cursorField: cursorColumnName,
75
+ idField: idColumnName,
76
+ fetchPage: ({ since, limit }) => drizzleDb.select(projection).from(table).where(since ? drizzle_orm.sql`(${cursorColumn}, ${idColumn}) > (${since.time}, ${since.id})` : void 0).orderBy((0, drizzle_orm.asc)(cursorColumn), (0, drizzle_orm.asc)(idColumn)).limit(limit)
73
77
  };
74
78
  const meta = {
75
79
  columnKeys: Object.keys(columns),
package/drizzle.d.cts CHANGED
@@ -1,14 +1,6 @@
1
1
  import { t as discoverFromDrizzle } from "./drizzle-DmeKSqeF.cjs";
2
- import { n as SyncSource } from "./types-CsL0rFYh.cjs";
2
+ import { m as SyncSource, t as NamespaceIntrospector } from "./namespace-probe-DRzOtaRT.cjs";
3
3
  import { Table } from "drizzle-orm";
4
- //#region ../sdk/src/namespace-probe.d.ts
5
- interface NamespaceIntrospector {
6
- /** All base table names in the reachable schema. */
7
- listTables(): Promise<string[]>;
8
- /** Prove a table is genuinely selectable (SELECT ... LIMIT 1). Throws if unreachable/denied. */
9
- sampleSelect(table: string): Promise<void>;
10
- }
11
- //#endregion
12
4
  //#region ../sdk/src/drizzle.d.ts
13
5
  interface DrizzleDb {
14
6
  select: (...args: any[]) => any;
package/drizzle.d.mts CHANGED
@@ -1,14 +1,6 @@
1
1
  import { t as discoverFromDrizzle } from "./drizzle-CFOr9dU9.mjs";
2
- import { n as SyncSource } from "./types-CsL0rFYh.mjs";
2
+ import { m as SyncSource, t as NamespaceIntrospector } from "./namespace-probe-B4WE45TH.mjs";
3
3
  import { Table } from "drizzle-orm";
4
- //#region ../sdk/src/namespace-probe.d.ts
5
- interface NamespaceIntrospector {
6
- /** All base table names in the reachable schema. */
7
- listTables(): Promise<string[]>;
8
- /** Prove a table is genuinely selectable (SELECT ... LIMIT 1). Throws if unreachable/denied. */
9
- sampleSelect(table: string): Promise<void>;
10
- }
11
- //#endregion
12
4
  //#region ../sdk/src/drizzle.d.ts
13
5
  interface DrizzleDb {
14
6
  select: (...args: any[]) => any;
package/drizzle.mjs CHANGED
@@ -64,11 +64,15 @@ function fromDrizzle(db, table, opts) {
64
64
  const idColumn = columns[idField];
65
65
  if (!cursorColumn) throw new Error(`fromDrizzle: cursorField "${opts.cursorField}" is not a column on "${getTableName(table)}".`);
66
66
  if (!idColumn) throw new Error(`fromDrizzle: idField "${idField}" is not a column on "${getTableName(table)}".`);
67
+ const projection = {};
68
+ for (const column of Object.values(columns)) projection[column.name] = column;
69
+ const cursorColumnName = cursorColumn.name;
70
+ const idColumnName = idColumn.name;
67
71
  const source = {
68
72
  entity: opts.entity,
69
- cursorField: opts.cursorField,
70
- idField,
71
- fetchPage: ({ since, limit }) => drizzleDb.select().from(table).where(since ? sql`(${cursorColumn}, ${idColumn}) > (${since.time}, ${since.id})` : void 0).orderBy(asc(cursorColumn), asc(idColumn)).limit(limit)
73
+ cursorField: cursorColumnName,
74
+ idField: idColumnName,
75
+ fetchPage: ({ since, limit }) => drizzleDb.select(projection).from(table).where(since ? sql`(${cursorColumn}, ${idColumn}) > (${since.time}, ${since.id})` : void 0).orderBy(asc(cursorColumn), asc(idColumn)).limit(limit)
72
76
  };
73
77
  const meta = {
74
78
  columnKeys: Object.keys(columns),