clivly 0.9.2 → 0.9.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.
Files changed (64) hide show
  1. package/README.md +60 -6
  2. package/core-entity-config.cjs +1 -1
  3. package/core-entity-config.mjs +1 -1
  4. package/core-mappings-config.cjs +1 -1
  5. package/core-mappings-config.mjs +1 -1
  6. package/core-sync-engine.cjs +1 -1
  7. package/core-sync-engine.mjs +1 -1
  8. package/core-view-compiler.cjs +1 -1
  9. package/core-view-compiler.mjs +1 -1
  10. package/core.cjs +11 -6
  11. package/core.d.cts +3 -2
  12. package/core.d.mts +3 -2
  13. package/core.mjs +8 -7
  14. package/drizzle.cjs +74 -9
  15. package/drizzle.d.cts +83 -7
  16. package/drizzle.d.mts +83 -7
  17. package/drizzle.mjs +74 -10
  18. package/{entity-config-DtBsl2MQ.cjs → entity-config-7sGCk_nw.cjs} +1 -1
  19. package/{entity-config-kUHri9yA.mjs → entity-config-B74SPOUZ.mjs} +1 -1
  20. package/index.cjs +141 -29
  21. package/index.d.cts +2 -1
  22. package/index.d.mts +2 -1
  23. package/index.mjs +141 -30
  24. package/integration-state-BK-riZnm.d.cts +130 -0
  25. package/integration-state-BK-riZnm.d.mts +130 -0
  26. package/{internal-Df-PtOH_.mjs → internal-CykXpEGk.mjs} +2 -2
  27. package/{internal-CwnEG6Wl.cjs → internal-DqduZU3l.cjs} +2 -2
  28. package/internal-entity-config.cjs +1 -1
  29. package/internal-entity-config.mjs +1 -1
  30. package/internal-entity-heuristics.cjs +1 -1
  31. package/internal-entity-heuristics.mjs +1 -1
  32. package/internal-mappings-config.cjs +1 -1
  33. package/internal-mappings-config.mjs +1 -1
  34. package/{internal-CDY8g9Ic.d.mts → internal-o2M7FGlR.d.mts} +29 -1
  35. package/internal-sync-engine.cjs +1 -1
  36. package/internal-sync-engine.mjs +1 -1
  37. package/internal-view-compiler.cjs +1 -1
  38. package/internal-view-compiler.mjs +1 -1
  39. package/{internal-Da_dlcQ_.d.cts → internal-wCXSELDN.d.cts} +29 -1
  40. package/internal.cjs +11 -6
  41. package/internal.d.cts +3 -2
  42. package/internal.d.mts +3 -2
  43. package/internal.mjs +8 -7
  44. package/{mappings-config-B0RqFLxu.cjs → mappings-config-CLyEg0sf.cjs} +1 -1
  45. package/{mappings-config-B1b1HVBg.mjs → mappings-config-DQS_s4dg.mjs} +1 -1
  46. package/{namespace-probe-C6aHjedO.d.cts → namespace-probe-BoGI18dQ.d.cts} +15 -0
  47. package/{namespace-probe-Dj-0Q3Hb.d.mts → namespace-probe-Cn7voAd0.d.mts} +15 -0
  48. package/package.json +1 -1
  49. package/{projection-D7xmQP4S.mjs → projection-CSGJbZlZ.mjs} +25 -4
  50. package/{projection-C5244vGa.cjs → projection-Dz6_i1-w.cjs} +25 -4
  51. package/projection-sql-BgnfiGqF.mjs +65 -0
  52. package/projection-sql-Cl1qIn8m.cjs +94 -0
  53. package/{sdk-D4Wp_uxb.cjs → sdk-CM1UO-I7.cjs} +59 -15
  54. package/{sdk-DXV8M_Io.mjs → sdk-DYBDR939.mjs} +54 -16
  55. package/sdk.cjs +3 -2
  56. package/sdk.d.cts +5 -2
  57. package/sdk.d.mts +5 -2
  58. package/sdk.mjs +3 -3
  59. package/{sync-engine-abE94g6X.cjs → sync-engine-C4A5jgA4.cjs} +1 -1
  60. package/{sync-engine-SuxSzbZ4.mjs → sync-engine-Dt5QQSKO.mjs} +1 -1
  61. package/{view-compiler-Blu4OXJ7.cjs → view-compiler-B5nDal14.cjs} +21 -74
  62. package/{view-compiler-CQx8TRrW.mjs → view-compiler-CSYwEWf5.mjs} +2 -55
  63. package/vite.d.cts +1 -1
  64. package/vite.d.mts +1 -1
package/drizzle.mjs CHANGED
@@ -1,6 +1,77 @@
1
+ import { t as BASE_REF } from "./projection-CSGJbZlZ.mjs";
1
2
  import { t as DRIZZLE_META } from "./drizzle-meta-D-5k3n0i.mjs";
3
+ import { a as quoteIdent, n as compileProjectionIdentity, r as compileProjectionJoins, t as compileProjectionFrom } from "./projection-sql-BgnfiGqF.mjs";
2
4
  import { n as buildUniqueConstraints, r as discoverFromDrizzle, t as buildColumnsMeta } from "./drizzle-3kvhkwO7.mjs";
3
5
  import { asc, getTableColumns, getTableName, sql } from "drizzle-orm";
6
+ //#region ../sdk/src/drizzle-projection.ts
7
+ /** `<ref>.<column>` — the token `fields` carries and cloud-ingest reads. */
8
+ function refToken(ref) {
9
+ return `${ref.table}.${ref.column}`;
10
+ }
11
+ function refSql(ref) {
12
+ return `${quoteIdent(ref.table)}.${quoteIdent(ref.column)}`;
13
+ }
14
+ function parseRef(value) {
15
+ if (typeof value !== "string") return value;
16
+ const dot = value.indexOf(".");
17
+ if (dot <= 0 || dot === value.length - 1) throw new Error(`fromDrizzleProjection: cursorField "${value}" must be a ref token like "base.updated_at".`);
18
+ return {
19
+ table: value.slice(0, dot),
20
+ column: value.slice(dot + 1)
21
+ };
22
+ }
23
+ /**
24
+ * Refs address tables symbolically — `base` or a join key — never by table name,
25
+ * which is what stops a projection reaching a table it did not declare. An
26
+ * unresolvable ref would otherwise compile to SQL referencing a missing alias
27
+ * and fail at the database with a message that never mentions the config.
28
+ */
29
+ function assertKnownRef(ref, known, context) {
30
+ if (!known.has(ref.table)) throw new Error(`fromDrizzleProjection: ${context} names unknown ref "${ref.table}" — expected "${[...known].join("\", \"")}".`);
31
+ }
32
+ function normalizeRows(result) {
33
+ if (Array.isArray(result)) return result;
34
+ const rows = result.rows;
35
+ if (Array.isArray(rows)) return rows;
36
+ throw new Error("fromDrizzleProjection: db.execute() returned neither an array of rows nor { rows }.");
37
+ }
38
+ function fromDrizzleProjection(db, projection, opts) {
39
+ const client = db;
40
+ const known = /* @__PURE__ */ new Set([BASE_REF, ...projection.joins.map((j) => j.key)]);
41
+ if (projection.identity.kind === "base_pk" && projection.identity.column === void 0 && opts.basePkColumn === void 0) throw new Error(`fromDrizzleProjection: projection "${projection.entityKey}" has a base_pk identity with no column — pass { basePkColumn }.`);
42
+ const cursorRef = parseRef(opts.cursorField);
43
+ assertKnownRef(cursorRef, known, "cursorField");
44
+ for (const [field, binding] of Object.entries(projection.fieldBindings)) assertKnownRef(binding.from, known, `field "${field}"`);
45
+ const identityExpr = compileProjectionIdentity(projection, opts.basePkColumn ?? "");
46
+ const cursorToken = refToken(cursorRef);
47
+ const cursorExpr = `CAST(${refSql(cursorRef)} AS timestamptz)`;
48
+ const cursorRead = `to_char(${cursorExpr} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`;
49
+ const selectItems = [`${identityExpr} AS ${quoteIdent("id")}`, `${cursorRead} AS ${quoteIdent(cursorToken)}`];
50
+ const emitted = /* @__PURE__ */ new Set([cursorToken]);
51
+ for (const binding of Object.values(projection.fieldBindings)) {
52
+ const token = refToken(binding.from);
53
+ if (emitted.has(token)) continue;
54
+ emitted.add(token);
55
+ selectItems.push(`${refSql(binding.from)} AS ${quoteIdent(token)}`);
56
+ }
57
+ const head = [
58
+ `select ${selectItems.join(", ")}`,
59
+ compileProjectionFrom(projection),
60
+ ...compileProjectionJoins(projection)
61
+ ].join(" ");
62
+ return {
63
+ entity: opts.entity,
64
+ cursorField: cursorToken,
65
+ idField: "id",
66
+ ...opts.objectType === void 0 ? {} : { objectType: opts.objectType },
67
+ fetchPage: async ({ since, limit }) => {
68
+ const where = since ? sql` where (${sql.raw(cursorExpr)}, ${sql.raw(identityExpr)}) > (${since.time.toISOString()}::timestamptz, ${since.id})` : sql``;
69
+ const query = sql`${sql.raw(head)}${where} order by ${sql.raw(cursorExpr)} asc, ${sql.raw(identityExpr)} asc limit ${limit}`;
70
+ return normalizeRows(await client.execute(query));
71
+ }
72
+ };
73
+ }
74
+ //#endregion
4
75
  //#region ../sdk/src/drizzle.ts
5
76
  function inferIdField(table) {
6
77
  const columns = getTableColumns(table);
@@ -13,14 +84,6 @@ function inferIdField(table) {
13
84
  const kind = primaryKeys.length === 0 ? "no" : "a composite";
14
85
  throw new Error(`fromDrizzle: table "${getTableName(table)}" has ${kind} single-column primary key — pass { idField } explicitly.`);
15
86
  }
16
- /**
17
- * Build a `SyncSource` from a Drizzle table, generating the keyset-paginated
18
- * `fetchPage` the SDK needs. `cursorField`/`idField` are drizzle property keys;
19
- * `idField` defaults to the table's single-column primary key.
20
- *
21
- * `drizzle-orm` is an optional peer dependency — installed only when you import
22
- * `@clivly/sdk/drizzle`.
23
- */
24
87
  function fromDrizzle(db, table, opts) {
25
88
  const drizzleDb = db;
26
89
  const idField = opts.idField ?? inferIdField(table);
@@ -37,7 +100,8 @@ function fromDrizzle(db, table, opts) {
37
100
  entity: opts.entity,
38
101
  cursorField: cursorColumnName,
39
102
  idField: idColumnName,
40
- 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)
103
+ 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),
104
+ ...opts.objectType === void 0 ? {} : { objectType: opts.objectType }
41
105
  };
42
106
  const uniqueConstraints = buildUniqueConstraints(table);
43
107
  const meta = {
@@ -74,4 +138,4 @@ function drizzleIntrospector(db) {
74
138
  };
75
139
  }
76
140
  //#endregion
77
- export { buildUniqueConstraints, discoverFromDrizzle, drizzleIntrospector, fromDrizzle };
141
+ export { buildUniqueConstraints, discoverFromDrizzle, drizzleIntrospector, fromDrizzle, fromDrizzleProjection };
@@ -1,4 +1,4 @@
1
- const require_projection = require("./projection-C5244vGa.cjs");
1
+ const require_projection = require("./projection-Dz6_i1-w.cjs");
2
2
  //#region ../core/src/schema-meta.ts
3
3
  /**
4
4
  * Accessors over a discovered table's schema-v2 metadata.
@@ -1,4 +1,4 @@
1
- import { a as _enum, c as literal, d as strictObject, f as string, i as projectionSchema, l as number, o as array, p as union, r as identityRefs, s as boolean, t as BASE_REF, u as record } from "./projection-D7xmQP4S.mjs";
1
+ import { a as _enum, c as literal, d as strictObject, f as string, i as projectionSchema, l as number, o as array, p as union, r as identityRefs, s as boolean, t as BASE_REF, u as record } from "./projection-CSGJbZlZ.mjs";
2
2
  //#region ../core/src/schema-meta.ts
3
3
  /**
4
4
  * Accessors over a discovered table's schema-v2 metadata.
package/index.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_entity_config = require("./entity-config-DtBsl2MQ.cjs");
3
+ const require_entity_config = require("./entity-config-7sGCk_nw.cjs");
4
4
  require("./mapping-score-CaB3F4Uu.cjs");
5
5
  const require_internal_entity_heuristics = require("./internal-entity-heuristics.cjs");
6
- const require_sdk = require("./sdk-D4Wp_uxb.cjs");
6
+ const require_sdk = require("./sdk-CM1UO-I7.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");
@@ -292,10 +292,37 @@ function addCommand(pm, packages) {
292
292
  };
293
293
  }
294
294
  //#endregion
295
+ //#region src/tick-route.ts
296
+ /**
297
+ * The tick route's public URL path, and the template `clivly login` writes for
298
+ * it.
299
+ *
300
+ * Every framework the CLI scaffolds mounts the tick handler at the same URL —
301
+ * the *files* differ (`app/api/clivly/tick/route.ts`,
302
+ * `src/routes/api/clivly/tick.ts`, `app/routes/api.clivly.tick.ts`, …) but each
303
+ * one resolves to `/api/clivly/tick`. So this is a constant, not a per-framework
304
+ * lookup, and `clivly dev`, the generated config and the `.env` template can all
305
+ * read from one place instead of repeating the string and drifting.
306
+ */
307
+ const CLIVLY_TICK_PATH = "/api/clivly/tick";
308
+ /**
309
+ * What `clivly login` writes into `.env` for `CLIVLY_SYNC_TRIGGER_URL`.
310
+ *
311
+ * A visible template beats an empty line: the developer sees the shape they
312
+ * eventually need — a public origin plus the tick path — instead of an unhelpful
313
+ * `CLIVLY_SYNC_TRIGGER_URL=` they have to go and look up.
314
+ *
315
+ * It is safe to ship a truthy non-address here only because the SDK refuses to
316
+ * treat a bracketed value as configured (`isPlaceholderUrl` in
317
+ * `@clivly/sdk/trigger-url`). Change the brackets here and that guard stops
318
+ * matching, so the two must stay in step.
319
+ */
320
+ const TRIGGER_URL_PLACEHOLDER = `https://<yourDomain.com>${CLIVLY_TICK_PATH}`;
321
+ //#endregion
295
322
  //#region src/dev-tunnel.ts
296
323
  const CLOUDFLARE_QUICK_TUNNEL_REGEX = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
297
324
  const DEFAULT_PORT = "3000";
298
- const DEFAULT_ROUTE_PATH = "/api/clivly/tick";
325
+ const DEFAULT_ROUTE_PATH = CLIVLY_TICK_PATH;
299
326
  function parseTunnelUrl(chunk) {
300
327
  const match = chunk.match(CLOUDFLARE_QUICK_TUNNEL_REGEX);
301
328
  return match ? match[0] : null;
@@ -1002,7 +1029,7 @@ Usage:
1002
1029
  clivly init [options] Scaffold Clivly (config, routes, env file) for your app
1003
1030
  clivly add widget [options] Scaffold the chat-session route
1004
1031
  clivly status [options] Check your setup, step by step
1005
- clivly push-schema [options] Report your schema to Clivly without deploying
1032
+ clivly push-schema [options] Push schema, custom objects and mappings to Clivly
1006
1033
  clivly dev [options] Tunnel localhost so remote sync can reach you
1007
1034
  clivly login [options] Pair this machine and write credentials to your env file
1008
1035
  clivly --version Print the installed clivly version
@@ -1058,7 +1085,9 @@ Options:
1058
1085
  Usage:
1059
1086
  clivly status [--verbose] [--config <path>] [--env-file <path>]
1060
1087
 
1061
- Runs the setup ladder: credential, reachability, config load, edge-safety,
1088
+ Prints two sections. "Workspace" is how far setup has got on Clivly's side,
1089
+ computed by the server so the terminal and the dashboard agree. "This project"
1090
+ runs the local ladder: credential, reachability, config load, edge-safety,
1062
1091
  setup checks, sync trigger. Read-only — writes nothing.
1063
1092
 
1064
1093
  Options:
@@ -1066,13 +1095,23 @@ Options:
1066
1095
  --config <path> Path to clivly.config.ts
1067
1096
  --env-file <path> Extra env file to load
1068
1097
  `,
1069
- "push-schema": `clivly push-schema — report your schema without deploying
1098
+ "push-schema": `clivly push-schema — push everything your config declares
1070
1099
 
1071
1100
  Usage:
1072
1101
  clivly push-schema [--config <path>]
1073
1102
 
1074
- Sends your discovered tables to Clivly so the dashboard can offer them for
1075
- mapping. Reads your config; writes nothing locally.
1103
+ Pushes three things to Clivly, without deploying your app:
1104
+ 1. Your table and column NAMES, so the dashboard can offer them for mapping.
1105
+ Never row data — discovery is names only.
1106
+ 2. The custom object types you declared with \`objectType\`.
1107
+ 3. The mappings that make those objects actually receive rows.
1108
+
1109
+ Reads your config; writes nothing locally. Safe to re-run — it reports what it
1110
+ created, updated, or left alone.
1111
+
1112
+ Exits 1 if Clivly refuses your config, most often because an entity would
1113
+ overwrite a mapping edited in the dashboard. That makes it usable in CI as a
1114
+ schema gate.
1076
1115
  `,
1077
1116
  dev: `clivly dev — tunnel localhost so remote sync can reach you
1078
1117
 
@@ -1388,15 +1427,23 @@ async function confirmWorkspace(deps, credentials) {
1388
1427
  return await deps.confirm("Write this workspace's credentials to .env? [y/N] ");
1389
1428
  }
1390
1429
  function credentialEntries(credentials) {
1391
- return [{
1392
- key: SECRET_KEY_ENV,
1393
- value: credentials.apiKey,
1394
- comment: `Org secret key — ${CREDENTIALS_LOCATION}`
1395
- }, {
1396
- key: "CLIVLY_SYNC_TRIGGER_SECRET",
1397
- value: credentials.triggerSecret,
1398
- comment: `Signing secret — ${SYNC_SECRET_LOCATION}`
1399
- }];
1430
+ return [
1431
+ {
1432
+ key: SECRET_KEY_ENV,
1433
+ value: credentials.apiKey,
1434
+ comment: `Org secret key — ${CREDENTIALS_LOCATION}`
1435
+ },
1436
+ {
1437
+ key: "CLIVLY_SYNC_TRIGGER_SECRET",
1438
+ value: credentials.triggerSecret,
1439
+ comment: `Signing secret — ${SYNC_SECRET_LOCATION}`
1440
+ },
1441
+ {
1442
+ key: "CLIVLY_SYNC_TRIGGER_URL",
1443
+ value: TRIGGER_URL_PLACEHOLDER,
1444
+ comment: "Public URL of your tick route — replace <yourDomain.com> once deployed. Local dev: run `clivly dev` instead."
1445
+ }
1446
+ ];
1400
1447
  }
1401
1448
  function writeCredentials(options, credentials) {
1402
1449
  const { deps } = options;
@@ -1861,7 +1908,13 @@ export default createClivlySDK({
1861
1908
  ${sources.join(",\n")},
1862
1909
  },
1863
1910
  syncTrigger: {
1864
- // The public URL Clivly Cloud calls to trigger a sync — your mounted tick route.
1911
+ // Where Clivly Cloud calls to start a sync — your mounted tick route.
1912
+ //
1913
+ // \`path\` lets the SDK build its own public URL from the hosting platform's
1914
+ // environment (VERCEL_URL, CF_PAGES_URL, RAILWAY_PUBLIC_DOMAIN,
1915
+ // RENDER_EXTERNAL_URL, FLY_APP_NAME), so on those platforms you never have
1916
+ // to set the URL by hand. \`url\` overrides that when you do.
1917
+ path: "${CLIVLY_TICK_PATH}",
1865
1918
  url: process.env.CLIVLY_SYNC_TRIGGER_URL,
1866
1919
  secret: process.env.CLIVLY_SYNC_TRIGGER_SECRET,
1867
1920
  },
@@ -2861,6 +2914,17 @@ function defaultReadConfigSource(path) {
2861
2914
  return null;
2862
2915
  }
2863
2916
  }
2917
+ /**
2918
+ * Ping Clivly at most once per command, memoised on the context.
2919
+ *
2920
+ * `clivly status` asks two questions of one response — "did this call reach
2921
+ * Clivly" and "how far has this workspace got" — and they must come from the
2922
+ * same answer, not two calls that could disagree.
2923
+ */
2924
+ function pingOnce(ctx) {
2925
+ ctx.pingResult ??= ctx.connect(ctx.apiKey, ctx.apiUrl);
2926
+ return ctx.pingResult;
2927
+ }
2864
2928
  const REASON_HINT = {
2865
2929
  ok: "",
2866
2930
  invalid_api_key: "The key was rejected (401). Check CLIVLY_SECRET_KEY (or legacy CLIVLY_API_KEY).",
@@ -2872,7 +2936,7 @@ const REASON_HINT = {
2872
2936
  network_error: "Could not reach Clivly. Check your network or --api-url."
2873
2937
  };
2874
2938
  const apiKeyStage = {
2875
- id: "credential",
2939
+ id: "api-key",
2876
2940
  label: "API key present",
2877
2941
  run(ctx) {
2878
2942
  if (ctx.apiKey) return Promise.resolve({
@@ -2887,11 +2951,11 @@ const apiKeyStage = {
2887
2951
  }
2888
2952
  };
2889
2953
  const reachabilityStage = {
2890
- id: "application",
2954
+ id: "reachable",
2891
2955
  label: "Clivly reachable",
2892
2956
  needs: [apiKeyStage.id],
2893
2957
  async run(ctx) {
2894
- const result = await ctx.connect(ctx.apiKey, ctx.apiUrl);
2958
+ const result = await pingOnce(ctx);
2895
2959
  if (result.ok) {
2896
2960
  const name = result.org?.name ?? "(unnamed org)";
2897
2961
  const id = result.org?.id ? ` (${result.org.id})` : "";
@@ -2963,14 +3027,15 @@ const triggerUrlStage = {
2963
3027
  needs: [configStage.id],
2964
3028
  run(ctx) {
2965
3029
  const url = ctx.sdk?.syncTrigger?.url;
2966
- if (url) return Promise.resolve({
3030
+ if (url && !require_sdk.isPlaceholderUrl(url)) return Promise.resolve({
2967
3031
  state: "pass",
2968
3032
  detail: url
2969
3033
  });
3034
+ const isPlaceholder = url !== void 0;
2970
3035
  return Promise.resolve({
2971
3036
  state: "warn",
2972
- detail: "Not set — remote sync cannot call back into this app.",
2973
- hint: "Set CLIVLY_SYNC_TRIGGER_URL once the app is deployed. For local development run `clivly dev` — it opens a tunnel and registers the URL for you. Push-only setups can ignore this."
3037
+ detail: isPlaceholder ? `Still the placeholder (${url}) — remote sync cannot call back into this app.` : "Not set — remote sync cannot call back into this app.",
3038
+ hint: "Replace <yourDomain.com> with your deployed origin, or delete the line — on Vercel, Cloudflare Pages, Railway, Render and Fly the SDK derives the URL from the platform. For local development run `clivly dev`: it opens a tunnel and registers the URL for you, outranking whatever is in .env. Push-only setups can ignore this."
2974
3039
  });
2975
3040
  }
2976
3041
  };
@@ -3025,19 +3090,57 @@ function defaultLoadSdk(path) {
3025
3090
  return loadConfigSdk(path, "doctor");
3026
3091
  }
3027
3092
  //#endregion
3093
+ //#region src/workspace-section.ts
3094
+ const MARK$1 = {
3095
+ active: "⏳",
3096
+ blocked: "🔒",
3097
+ done: "✅",
3098
+ failed: "❌",
3099
+ skipped: "⏭️ "
3100
+ };
3101
+ /**
3102
+ * The workspace half of `clivly status`, rendered from what the server sent.
3103
+ *
3104
+ * This module decides nothing about a stage's state — it prints the verdict the
3105
+ * server computed. A CLI that computed its own would be a second ladder, which
3106
+ * is the drift this whole change removes.
3107
+ *
3108
+ * "Unavailable" and "nothing is set up" are deliberately different outcomes. An
3109
+ * old server, a rejected key and a dead network all mean we could not ask; none
3110
+ * of them means the workspace is empty.
3111
+ */
3112
+ function workspaceSection(result) {
3113
+ if (!result) return {
3114
+ kind: "unavailable",
3115
+ reason: "Could not reach Clivly."
3116
+ };
3117
+ if (!result.ok) return {
3118
+ kind: "unavailable",
3119
+ reason: result.status === null ? "Could not reach Clivly (no response)." : `Clivly rejected the request — ${result.reason} (HTTP ${result.status}).`
3120
+ };
3121
+ if (!result.workspace) return {
3122
+ kind: "unavailable",
3123
+ reason: "This Clivly server does not report setup progress. Upgrade the server to see it here."
3124
+ };
3125
+ return {
3126
+ kind: "ladder",
3127
+ lines: result.workspace.steps.map((step) => ` ${MARK$1[step.state]} ${step.label}${step.evidence ? ` ${step.evidence}` : ""}`)
3128
+ };
3129
+ }
3130
+ //#endregion
3028
3131
  //#region src/status.ts
3029
3132
  /** Stage ids each legacy command maps onto. */
3030
3133
  const ALIAS_STAGE_IDS = {
3031
3134
  status: STATUS_STAGES.map((stage) => stage.id),
3032
- ping: ["credential", "application"],
3135
+ ping: ["api-key", "reachable"],
3033
3136
  doctor: [
3034
3137
  "config",
3035
3138
  "runtime",
3036
3139
  "setup-checks"
3037
3140
  ],
3038
3141
  connect: [
3039
- "credential",
3040
- "application",
3142
+ "api-key",
3143
+ "reachable",
3041
3144
  "config",
3042
3145
  "trigger-url"
3043
3146
  ]
@@ -3093,6 +3196,13 @@ async function runStatus(options) {
3093
3196
  };
3094
3197
  out("\nClivly status\n");
3095
3198
  const results = await runLadder(stages, ctx);
3199
+ if (selected.has("reachable")) {
3200
+ const section = workspaceSection(ctx.pingResult ? await ctx.pingResult : null);
3201
+ out("\nWorkspace\n");
3202
+ if (section.kind === "ladder") for (const line of section.lines) out(`${line}\n`);
3203
+ else out(` ⚠️ ${section.reason}\n`);
3204
+ }
3205
+ out("\nThis project\n");
3096
3206
  for (const result of results) renderStage(result, options.verbose ?? false, out);
3097
3207
  const code = ladderExitCode(results);
3098
3208
  if (code !== 0 && results.some((result) => result.state === "pending")) out("\nSetup is incomplete — finish the steps above and re-run.\n");
@@ -3122,6 +3232,7 @@ const KNOWN_COMMANDS = [
3122
3232
  "init",
3123
3233
  "status",
3124
3234
  "push-schema",
3235
+ "add",
3125
3236
  "dev",
3126
3237
  "login",
3127
3238
  "help",
@@ -3478,8 +3589,8 @@ function previewMutations(deps, mutations, options) {
3478
3589
  function printNextSteps(out, authComplete, loginRan, envFile) {
3479
3590
  out("\nNext steps:\n");
3480
3591
  out(loginRan ? " 1. Credentials written by `clivly login`\n" : ` 1. Add CLIVLY_SECRET_KEY to ${envFile} — create one at\n ${CREDENTIALS_LOCATION}\n (the legacy CLIVLY_API_KEY is still accepted)\n`);
3481
- out(" 2. Set CLIVLY_SYNC_TRIGGER_URL to your public tick route\n");
3482
- out(" Local dev? Run `clivly dev` to open a tunnel, or skip this step until you deploy (remote sync is deploy-only locally).\n");
3592
+ out(" 2. Local dev: run `clivly dev` for dashboard-triggered sync\n");
3593
+ out(" It tunnels your app and registers the URL for you — remote sync is\n deploy-only otherwise, so nothing is broken if you skip it. Once deployed\n to Vercel/Cloudflare/Railway/Render/Fly the URL is detected automatically;\n set CLIVLY_SYNC_TRIGGER_URL by hand only for a custom domain.\n");
3483
3594
  const step = authComplete ? 3 : 4;
3484
3595
  if (!authComplete) out(" 3. Finish resolveUser in the auth/verify route\n");
3485
3596
  out(` ${step}. Run \`clivly status\` to see where you are\n`);
@@ -3889,6 +4000,7 @@ if (isEntrypoint(process.argv[1], require("url").pathToFileURL(__filename).href)
3889
4000
  process.exitCode = code;
3890
4001
  });
3891
4002
  //#endregion
4003
+ exports.KNOWN_COMMANDS = KNOWN_COMMANDS;
3892
4004
  exports.credentialsMissing = credentialsMissing;
3893
4005
  exports.main = main;
3894
4006
  exports.nearestCommand = nearestCommand;
package/index.d.cts CHANGED
@@ -58,6 +58,7 @@ interface ProbeHit {
58
58
  }
59
59
  //#endregion
60
60
  //#region src/index.d.ts
61
+ declare const KNOWN_COMMANDS: readonly ["init", "status", "push-schema", "add", "dev", "login", "help", "ping", "doctor", "connect"];
61
62
  declare function nearestCommand(input: string): string | undefined;
62
63
  interface AddDeps {
63
64
  fileExists: (path: string) => boolean;
@@ -117,4 +118,4 @@ declare function runInit(options: RunInitOptions): Promise<number>;
117
118
  declare function runInitFromArgv(argv: string[], cwd: string): Promise<number>;
118
119
  declare function main(argv: string[]): Promise<number>;
119
120
  //#endregion
120
- export { AddDeps, RunInitOptions, credentialsMissing, main, nearestCommand, runAdd, runInit, runInitFromArgv };
121
+ export { AddDeps, KNOWN_COMMANDS, RunInitOptions, credentialsMissing, main, nearestCommand, runAdd, runInit, runInitFromArgv };
package/index.d.mts CHANGED
@@ -58,6 +58,7 @@ interface ProbeHit {
58
58
  }
59
59
  //#endregion
60
60
  //#region src/index.d.ts
61
+ declare const KNOWN_COMMANDS: readonly ["init", "status", "push-schema", "add", "dev", "login", "help", "ping", "doctor", "connect"];
61
62
  declare function nearestCommand(input: string): string | undefined;
62
63
  interface AddDeps {
63
64
  fileExists: (path: string) => boolean;
@@ -117,4 +118,4 @@ declare function runInit(options: RunInitOptions): Promise<number>;
117
118
  declare function runInitFromArgv(argv: string[], cwd: string): Promise<number>;
118
119
  declare function main(argv: string[]): Promise<number>;
119
120
  //#endregion
120
- export { AddDeps, RunInitOptions, credentialsMissing, main, nearestCommand, runAdd, runInit, runInitFromArgv };
121
+ export { AddDeps, KNOWN_COMMANDS, RunInitOptions, credentialsMissing, main, nearestCommand, runAdd, runInit, runInitFromArgv };