@relyio/cli 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import http from 'http';
2
3
  import * as fs2 from 'fs';
3
- import { existsSync, readdirSync, statSync, mkdirSync, cpSync } from 'fs';
4
- import path3 from 'path';
4
+ import fs2__default, { existsSync, readFileSync, mkdirSync, readdirSync, writeFileSync, statSync, cpSync } from 'fs';
5
+ import path5 from 'path';
5
6
  import * as os from 'os';
6
7
  import os__default from 'os';
7
8
  import * as yaml from 'yaml';
@@ -9,17 +10,18 @@ import axios, { AxiosError } from 'axios';
9
10
  import axiosRetry from 'axios-retry';
10
11
  import { z } from 'zod';
11
12
  import * as fs4 from 'fs/promises';
12
- import { mkdir, writeFile, rm, readFile, stat } from 'fs/promises';
13
+ import { rm, writeFile, readFile, stat, mkdir } from 'fs/promises';
13
14
  import * as yaml2 from 'js-yaml';
14
15
  import { glob } from 'glob';
15
16
  import { createJiti } from 'jiti';
16
17
  import { fileURLToPath } from 'url';
17
18
  import { execFileSync, execSync, spawn } from 'child_process';
19
+ import Handlebars from 'handlebars';
18
20
  import 'object-hash';
19
21
  import 'semver';
20
22
  import { format } from 'prettier';
21
23
  import { structuredPatch, diffWordsWithSpace } from 'diff';
22
- import { randomUUID, createHash } from 'crypto';
24
+ import { randomBytes, randomUUID, createHash } from 'crypto';
23
25
  import React3, { createContext, memo, useState, useEffect, useReducer, useMemo, useCallback, useContext, useRef, useSyncExternalStore } from 'react';
24
26
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
25
27
  import { Box, Text, useApp, Static, useInput, usePaste, useWindowSize } from 'ink';
@@ -35,13 +37,11 @@ import figures from 'figures';
35
37
  import { table as table$1 } from 'table';
36
38
  import readline from 'readline';
37
39
  import open from 'open';
38
- import http from 'http';
39
- import inquirer from 'inquirer';
40
+ import inquirer2 from 'inquirer';
40
41
  import { Listr } from 'listr2';
41
42
  import 'strip-ansi';
42
43
  import { watch } from 'chokidar';
43
44
  import { EventEmitter } from 'events';
44
- import Handlebars from 'handlebars';
45
45
 
46
46
  var __defProp = Object.defineProperty;
47
47
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -52,6 +52,143 @@ var __export = (target, all) => {
52
52
  for (var name in all)
53
53
  __defProp(target, name, { get: all[name], enumerable: true });
54
54
  };
55
+ async function startCallbackServer(options = {}) {
56
+ const host = options.host ?? "127.0.0.1";
57
+ const callbackPath = options.callbackPath ?? "/callback";
58
+ const server = http.createServer();
59
+ await new Promise((resolve, reject) => {
60
+ server.once("error", reject);
61
+ server.listen(0, host, resolve);
62
+ });
63
+ const { port } = server.address();
64
+ const origin = `http://${host}:${port}`;
65
+ const redirectUri = `${origin}${callbackPath === "/" ? "" : callbackPath}`;
66
+ let settled = false;
67
+ let waiter;
68
+ const held = [];
69
+ const close = () => {
70
+ try {
71
+ server.close();
72
+ server.closeAllConnections?.();
73
+ } catch {
74
+ }
75
+ };
76
+ const decide = (query, res, active) => {
77
+ if (query.get("state") !== active.expectedState) {
78
+ settled = true;
79
+ html(res, 400, errorHtml("State mismatch"));
80
+ close();
81
+ active.settle({
82
+ ok: false,
83
+ error: new Error("Sign-in state mismatch \u2014 aborting for safety.")
84
+ });
85
+ return;
86
+ }
87
+ const error2 = query.get("error");
88
+ if (error2 !== null) {
89
+ settled = true;
90
+ html(res, 400, errorHtml(error2));
91
+ close();
92
+ active.settle({ ok: false, error: new Error(`Authorization failed: ${error2}`) });
93
+ return;
94
+ }
95
+ settled = true;
96
+ html(res, 200, SUCCESS_HTML);
97
+ close();
98
+ active.settle({ ok: true, query });
99
+ };
100
+ server.on("request", (req, res) => {
101
+ const url = new URL(req.url ?? "/", origin);
102
+ if (url.pathname !== callbackPath) {
103
+ res.writeHead(404).end();
104
+ return;
105
+ }
106
+ if (settled) {
107
+ html(res, 200, SUCCESS_HTML);
108
+ return;
109
+ }
110
+ if (url.search === "") {
111
+ html(res, 200, WAITING_HTML);
112
+ return;
113
+ }
114
+ if (waiter === void 0) {
115
+ held.push({ query: url.searchParams, res });
116
+ return;
117
+ }
118
+ decide(url.searchParams, res, waiter);
119
+ });
120
+ const waitForCallback = (expectedState, timeoutMs = DEFAULT_CALLBACK_TIMEOUT_MS) => new Promise((resolve, reject) => {
121
+ const timer = setTimeout(() => {
122
+ if (settled) return;
123
+ settled = true;
124
+ close();
125
+ reject(new Error("Timed out waiting for the browser sign-in to complete."));
126
+ }, timeoutMs);
127
+ timer.unref?.();
128
+ const active = {
129
+ expectedState,
130
+ settle: (outcome) => {
131
+ clearTimeout(timer);
132
+ if (outcome.ok) resolve(outcome.query);
133
+ else reject(outcome.error);
134
+ }
135
+ };
136
+ waiter = active;
137
+ const [first, ...rest] = held.splice(0);
138
+ for (const extra of rest) html(extra.res, 200, SUCCESS_HTML);
139
+ if (first) decide(first.query, first.res, active);
140
+ });
141
+ const waitForCode = async (expectedState, timeoutMs) => {
142
+ const query = await waitForCallback(expectedState, timeoutMs);
143
+ const code = query.get("code");
144
+ if (code === null || code === "") {
145
+ throw new Error("The sign-in callback carried no authorization code.");
146
+ }
147
+ return code;
148
+ };
149
+ return { redirectUri, waitForCallback, waitForCode, close };
150
+ }
151
+ function escapeHtml(value) {
152
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
153
+ }
154
+ function writeSecretFile(filePath, data) {
155
+ fs2__default.mkdirSync(path5.dirname(filePath), { recursive: true });
156
+ fs2__default.writeFileSync(filePath, JSON.stringify(data, null, 2), {
157
+ encoding: "utf8",
158
+ mode: SECRET_FILE_MODE
159
+ });
160
+ fs2__default.chmodSync(filePath, SECRET_FILE_MODE);
161
+ }
162
+ function readSecretFile(filePath) {
163
+ try {
164
+ if (!fs2__default.existsSync(filePath)) return null;
165
+ return JSON.parse(fs2__default.readFileSync(filePath, "utf8"));
166
+ } catch {
167
+ return null;
168
+ }
169
+ }
170
+ var DEFAULT_CALLBACK_TIMEOUT_MS, SUCCESS_HTML, WAITING_HTML, errorHtml, html, SECRET_FILE_MODE;
171
+ var init_dist = __esm({
172
+ "../device-auth/dist/index.js"() {
173
+ DEFAULT_CALLBACK_TIMEOUT_MS = 5 * 60 * 1e3;
174
+ SUCCESS_HTML = `<!doctype html><html><head><meta charset="utf-8"><title>RelyIO</title>
175
+ <style>body{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;text-align:center;padding:3rem;color:#111}h1{color:#16a34a}</style>
176
+ </head><body><h1>Connected to RelyIO</h1><p>Authentication succeeded. You can close this tab and return to your terminal.</p></body></html>`;
177
+ WAITING_HTML = `<!doctype html><html><head><meta charset="utf-8"><title>RelyIO</title>
178
+ <style>body{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;text-align:center;padding:3rem;color:#111}</style>
179
+ </head><body><h1>Waiting for RelyIO</h1><p>Finish signing in in the tab that opened, then come back here.</p></body></html>`;
180
+ errorHtml = (message) => `<!doctype html><html><head><meta charset="utf-8"><title>RelyIO</title>
181
+ <style>body{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;text-align:center;padding:3rem;color:#111}h1{color:#dc2626}</style>
182
+ </head><body><h1>Authentication failed</h1><p>${escapeHtml(message)}</p></body></html>`;
183
+ html = (res, status, body) => {
184
+ res.writeHead(status, {
185
+ "content-type": "text/html; charset=utf-8",
186
+ "cache-control": "no-store"
187
+ }).end(body);
188
+ };
189
+ SECRET_FILE_MODE = 384;
190
+ }
191
+ });
55
192
 
56
193
  // src/config/secret-store.ts
57
194
  var secret_store_exports = {};
@@ -91,19 +228,10 @@ function emitPlaintextWarning() {
91
228
  );
92
229
  }
93
230
  function readLegacyFile() {
94
- try {
95
- if (!fs2.existsSync(LEGACY_CONFIG_FILE)) return {};
96
- const raw = fs2.readFileSync(LEGACY_CONFIG_FILE, "utf8");
97
- return JSON.parse(raw);
98
- } catch {
99
- return {};
100
- }
231
+ return readSecretFile(LEGACY_CONFIG_FILE) ?? {};
101
232
  }
102
233
  function writeLegacyFile(data) {
103
- if (!fs2.existsSync(LEGACY_CONFIG_DIR)) {
104
- fs2.mkdirSync(LEGACY_CONFIG_DIR, { recursive: true });
105
- }
106
- fs2.writeFileSync(LEGACY_CONFIG_FILE, JSON.stringify(data, null, 2), "utf8");
234
+ writeSecretFile(LEGACY_CONFIG_FILE, data);
107
235
  }
108
236
  async function setSecret(key, value) {
109
237
  cache.set(key, value);
@@ -183,9 +311,10 @@ function __resetForTests() {
183
311
  var SERVICE_NAME, LEGACY_CONFIG_DIR, LEGACY_CONFIG_FILE, keytarPromise, keychainAvailable, plaintextWarningShown, cache;
184
312
  var init_secret_store = __esm({
185
313
  "src/config/secret-store.ts"() {
314
+ init_dist();
186
315
  SERVICE_NAME = "relyio-cli";
187
- LEGACY_CONFIG_DIR = path3.join(os.homedir(), ".relyio");
188
- LEGACY_CONFIG_FILE = path3.join(LEGACY_CONFIG_DIR, "config.json");
316
+ LEGACY_CONFIG_DIR = path5.join(os.homedir(), ".relyio");
317
+ LEGACY_CONFIG_FILE = path5.join(LEGACY_CONFIG_DIR, "config.json");
189
318
  plaintextWarningShown = false;
190
319
  cache = /* @__PURE__ */ new Map();
191
320
  }
@@ -233,28 +362,19 @@ function ensureDirectory(dir) {
233
362
  }
234
363
  }
235
364
  function readGlobalConfig() {
236
- try {
237
- ensureDirectory(GLOBAL_CONFIG_DIR);
238
- if (fs2.existsSync(GLOBAL_CONFIG_FILE)) {
239
- const configData = fs2.readFileSync(GLOBAL_CONFIG_FILE, "utf8");
240
- return JSON.parse(configData);
241
- }
242
- } catch (error2) {
243
- debugError("Error reading global config", error2);
244
- }
245
- return { ...defaultGlobalConfig };
365
+ const stored = readSecretFile(GLOBAL_CONFIG_FILE);
366
+ return stored ?? { ...defaultGlobalConfig };
246
367
  }
247
368
  function writeGlobalConfig(config) {
248
369
  try {
249
- ensureDirectory(GLOBAL_CONFIG_DIR);
250
- fs2.writeFileSync(GLOBAL_CONFIG_FILE, JSON.stringify(config, null, 2), "utf8");
370
+ writeSecretFile(GLOBAL_CONFIG_FILE, config);
251
371
  } catch (error2) {
252
372
  debugError("Error writing global config", error2);
253
373
  throw new Error("Failed to write global configuration");
254
374
  }
255
375
  }
256
376
  function getProjectConfigPath() {
257
- return path3.join(process.cwd(), PROJECT_CONFIG_FILE);
377
+ return path5.join(process.cwd(), PROJECT_CONFIG_FILE);
258
378
  }
259
379
  function readProjectConfig() {
260
380
  try {
@@ -278,7 +398,7 @@ function writeProjectConfig(config) {
278
398
  }
279
399
  }
280
400
  function getLocalConfigPath() {
281
- return path3.join(process.cwd(), LOCAL_CONFIG_FILE);
401
+ return path5.join(process.cwd(), LOCAL_CONFIG_FILE);
282
402
  }
283
403
  function readLocalConfig() {
284
404
  try {
@@ -294,7 +414,7 @@ function readLocalConfig() {
294
414
  }
295
415
  function writeLocalConfig(config) {
296
416
  try {
297
- const configDir = path3.join(process.cwd(), LOCAL_CONFIG_DIR);
417
+ const configDir = path5.join(process.cwd(), LOCAL_CONFIG_DIR);
298
418
  ensureDirectory(configDir);
299
419
  const configPath = getLocalConfigPath();
300
420
  fs2.writeFileSync(configPath, yaml.stringify(config), "utf8");
@@ -428,13 +548,14 @@ async function isAuthenticated() {
428
548
  var isDebug, GLOBAL_CONFIG_DIR, GLOBAL_CONFIG_FILE, PROJECT_CONFIG_FILE, LOCAL_CONFIG_DIR, LOCAL_CONFIG_FILE, PROD_API_URL, PROD_DASHBOARD_URL, defaultGlobalConfig, defaultProjectConfig, defaultLocalConfig, getAuthTokenAsync, getApiKeyAsync, config_manager_default;
429
549
  var init_config_manager = __esm({
430
550
  "src/config/config-manager.ts"() {
551
+ init_dist();
431
552
  init_secret_store();
432
553
  isDebug = () => process.env.DEBUG === "true" || process.env.DEBUG === "1";
433
- GLOBAL_CONFIG_DIR = path3.join(os.homedir(), ".relyio");
434
- GLOBAL_CONFIG_FILE = path3.join(GLOBAL_CONFIG_DIR, "config.json");
554
+ GLOBAL_CONFIG_DIR = path5.join(os.homedir(), ".relyio");
555
+ GLOBAL_CONFIG_FILE = path5.join(GLOBAL_CONFIG_DIR, "config.json");
435
556
  PROJECT_CONFIG_FILE = ".relyio-context.json";
436
557
  LOCAL_CONFIG_DIR = "relyio";
437
- LOCAL_CONFIG_FILE = path3.join(LOCAL_CONFIG_DIR, "config.yaml");
558
+ LOCAL_CONFIG_FILE = path5.join(LOCAL_CONFIG_DIR, "config.yaml");
438
559
  PROD_API_URL = "https://api.relyio.com";
439
560
  PROD_DASHBOARD_URL = "https://dashboard.relyio.com";
440
561
  defaultGlobalConfig = {};
@@ -542,12 +663,32 @@ var init_config = __esm({
542
663
  config_default = config_manager_default;
543
664
  }
544
665
  });
545
- var DEFAULT_TIMEOUT_MS, isDebug2, ApiClient, apiClient, client_default;
666
+ function redactForLog(data) {
667
+ if (typeof data === "string") {
668
+ try {
669
+ return redactForLog(JSON.parse(data));
670
+ } catch {
671
+ return data;
672
+ }
673
+ }
674
+ if (Array.isArray(data)) return data.map((item) => redactForLog(item));
675
+ if (typeof data === "object" && data !== null) {
676
+ return Object.fromEntries(
677
+ Object.entries(data).map(([key, value]) => [
678
+ key,
679
+ SECRET_FIELD.test(key) ? "***" : redactForLog(value)
680
+ ])
681
+ );
682
+ }
683
+ return data;
684
+ }
685
+ var DEFAULT_TIMEOUT_MS, isDebug2, SECRET_FIELD, ApiClient, apiClient, client_default;
546
686
  var init_client = __esm({
547
687
  "src/api/client.ts"() {
548
688
  init_config();
549
689
  DEFAULT_TIMEOUT_MS = 3e4;
550
690
  isDebug2 = () => process.env.DEBUG === "true" || process.env.DEBUG === "1";
691
+ SECRET_FIELD = /secret|token|password/i;
551
692
  ApiClient = class {
552
693
  client;
553
694
  constructor() {
@@ -635,9 +776,8 @@ var init_client = __esm({
635
776
  }
636
777
  }
637
778
  if (reqConfig.data) {
638
- const data = typeof reqConfig.data === "string" ? reqConfig.data : JSON.stringify(reqConfig.data);
639
779
  curlCommand += ` \\
640
- -d '${data}'`;
780
+ -d '${JSON.stringify(redactForLog(reqConfig.data))}'`;
641
781
  }
642
782
  console.error(`[relyio-cli][debug] ${curlCommand}`);
643
783
  }
@@ -682,21 +822,21 @@ var init_client = __esm({
682
822
  }
683
823
  }
684
824
  // Helper methods for common HTTP methods
685
- async get(path23, options) {
825
+ async get(path25, options) {
686
826
  return this.request({
687
827
  method: "GET",
688
- url: path23,
828
+ url: path25,
689
829
  params: options?.params,
690
830
  headers: options?.headers
691
831
  });
692
832
  }
693
- async post(path23, data, headers) {
694
- return this.request({ method: "POST", url: path23, data, headers });
833
+ async post(path25, data, headers) {
834
+ return this.request({ method: "POST", url: path25, data, headers });
695
835
  }
696
- async put(path23, data, headers) {
697
- return this.request({ method: "PUT", url: path23, data, headers });
836
+ async put(path25, data, headers) {
837
+ return this.request({ method: "PUT", url: path25, data, headers });
698
838
  }
699
- async delete(path23, headers) {
839
+ async delete(path25, headers) {
700
840
  const customHeaders = {
701
841
  ...headers,
702
842
  // Remove Content-Type for DELETE requests as they typically don't have a body
@@ -704,7 +844,7 @@ var init_client = __esm({
704
844
  };
705
845
  return this.request({
706
846
  method: "DELETE",
707
- url: path23,
847
+ url: path25,
708
848
  headers: customHeaders
709
849
  });
710
850
  }
@@ -714,38 +854,10 @@ var init_client = __esm({
714
854
  }
715
855
  });
716
856
 
717
- // src/api/schema.ts
718
- async function getSchemaStatus(projectId, branchId, localSchema) {
719
- const response = await client_default.post(
720
- `/projects/${projectId}/branches/${branchId}/schema/status`,
721
- localSchema || {}
722
- );
723
- return response.data;
724
- }
725
- async function deploySchema(projectId, branchId, localSchema, options) {
726
- const response = await client_default.post(
727
- `/projects/${projectId}/branches/${branchId}/schema/deploy`,
728
- {
729
- localSchema,
730
- ...options
731
- }
732
- );
733
- const data = response.data;
734
- if ("data" in data && typeof data.data === "object") {
735
- return data.data;
736
- }
737
- return data;
738
- }
739
- var init_schema = __esm({
740
- "src/api/schema.ts"() {
741
- init_client();
742
- }
743
- });
744
-
745
- // ../../platform/api-contract/dist/chunk-QKXHDVSP.js
857
+ // ../../platform/api-contract/dist/chunk-MFO7NR2F.js
746
858
  var ERROR_CODES;
747
- var init_chunk_QKXHDVSP = __esm({
748
- "../../platform/api-contract/dist/chunk-QKXHDVSP.js"() {
859
+ var init_chunk_MFO7NR2F = __esm({
860
+ "../../platform/api-contract/dist/chunk-MFO7NR2F.js"() {
749
861
  ERROR_CODES = {
750
862
  MISSING_API_KEY: { status: 401 },
751
863
  INVALID_API_KEY: { status: 401 },
@@ -757,7 +869,7 @@ var init_chunk_QKXHDVSP = __esm({
757
869
  REQUEST_INVALID: { status: 400 },
758
870
  CONTENT_INVALID: { status: 400 },
759
871
  /**
760
- * Added in API3a, and the only name added to §4's approved seventeen.
872
+ * Added in API3a, the first name added to §4's approved seventeen.
761
873
  *
762
874
  * `apps/edge/cdn` answers any method other than `GET`, `HEAD` or `OPTIONS`
763
875
  * with `405`, and no code in §4 carries that status. The two alternatives
@@ -775,6 +887,37 @@ var init_chunk_QKXHDVSP = __esm({
775
887
  * approves those. Approved 2026-09-06 (plan §4, decision record in §16 API3a).
776
888
  */
777
889
  METHOD_NOT_ALLOWED: { status: 405 },
890
+ /**
891
+ * Added by REL-657, the second name added to §4's approved seventeen.
892
+ *
893
+ * A session that verified a factor too long ago may not hand authority out:
894
+ * mint an Access Token, approve an app, delete the account. `ACCESS_DENIED`
895
+ * is what a subject may never do; this is what a subject may do once they
896
+ * have proved the session is still theirs, and a client does something
897
+ * different on reading it — it raises a Reverification and retries — which
898
+ * is Decision 3's test. `details.level` names the proof the client asks for.
899
+ *
900
+ * A **customer-facing naming decision**, approved by the product owner on
901
+ * 2026-09-20 (REL-657, spec 016).
902
+ */
903
+ REVERIFICATION_REQUIRED: { status: 403 },
904
+ /**
905
+ * Added by REL-1080, the third name added to §4's approved seventeen.
906
+ *
907
+ * A downgrade (or cancel) that would leave the Organization keeping more than
908
+ * the target Plan allows — more Projects, Members or stored bytes than its
909
+ * Stock Limits — is refused, because the lower Plan would otherwise keep
910
+ * serving what it refuses to admit. It is not `ACCESS_DENIED` (the subject may
911
+ * downgrade) and not `RESOURCE_CONFLICT` (no version raced): a client reading
912
+ * it does something no other code tells it — shows `details.stockLimits`, the
913
+ * same list the downgrade preview reads, and asks the customer to reduce what
914
+ * they keep before retrying. 409 because the request conflicts with the
915
+ * Organization's current state and succeeds unchanged once that state does.
916
+ *
917
+ * A **customer-facing naming decision**, approved by the product owner on
918
+ * 2026-09-24 (REL-1080).
919
+ */
920
+ PLAN_DOWNGRADE_BLOCKED: { status: 409 },
778
921
  RESOURCE_NOT_FOUND: { status: 404 },
779
922
  RESOURCE_CONFLICT: { status: 409 },
780
923
  RESOURCE_EXPIRED: { status: 410 },
@@ -821,7 +964,7 @@ function readError(body) {
821
964
  var ContractBodyError;
822
965
  var init_client2 = __esm({
823
966
  "../../platform/api-contract/dist/client/index.js"() {
824
- init_chunk_QKXHDVSP();
967
+ init_chunk_MFO7NR2F();
825
968
  ContractBodyError = class extends Error {
826
969
  body;
827
970
  constructor(message, body) {
@@ -868,9 +1011,11 @@ async function getProject(projectId) {
868
1011
  }
869
1012
  async function getProjectBranches(projectId, filter) {
870
1013
  const params = new URLSearchParams();
871
- if (filter?.name) params.append("name", filter.name);
872
- if (filter?.sortField) params.append("sortField", filter.sortField);
873
- if (filter?.sortDirection) params.append("sortDirection", filter.sortDirection);
1014
+ if (filter?.name) params.append("name", `eq:${filter.name}`);
1015
+ if (filter?.sortField) {
1016
+ params.append("sort", filter.sortField);
1017
+ if (filter.sortDirection) params.append("order", filter.sortDirection);
1018
+ }
874
1019
  params.append("limit", "100");
875
1020
  const queryString = params.toString();
876
1021
  const url = `/projects/${projectId}/branches${queryString ? `?${queryString}` : ""}`;
@@ -916,6 +1061,26 @@ async function compareBranchSchemas(projectId, sourceBranchId, targetBranchId) {
916
1061
  )
917
1062
  );
918
1063
  }
1064
+ async function createProjectApiKey(projectId, body) {
1065
+ return readPayload(
1066
+ await client_default.post(`/projects/${projectId}/api-keys`, body)
1067
+ );
1068
+ }
1069
+ async function getProjectConnections(projectId) {
1070
+ return readPayload(
1071
+ await client_default.get(`/projects/${projectId}/connections`)
1072
+ );
1073
+ }
1074
+ async function createConnection(projectId, body) {
1075
+ return readPayload(
1076
+ await client_default.post(`/projects/${projectId}/connections`, body)
1077
+ );
1078
+ }
1079
+ async function createAutomation(projectId, body) {
1080
+ return readPayload(
1081
+ await client_default.post(`/projects/${projectId}/automations`, body)
1082
+ );
1083
+ }
919
1084
  var manage_default;
920
1085
  var init_manage = __esm({
921
1086
  "src/api/manage.ts"() {
@@ -932,11 +1097,43 @@ var init_manage = __esm({
932
1097
  createBranchWithParentDetection,
933
1098
  deleteBranch,
934
1099
  getBranchDivergence,
935
- compareBranchSchemas
1100
+ compareBranchSchemas,
1101
+ createProjectApiKey,
1102
+ getProjectConnections,
1103
+ createConnection,
1104
+ createAutomation
936
1105
  };
937
1106
  }
938
1107
  });
939
1108
 
1109
+ // src/api/schema.ts
1110
+ async function getSchemaStatus(projectId, branchId, localSchema) {
1111
+ const response = await client_default.post(
1112
+ `/projects/${projectId}/branches/${branchId}/schema/status`,
1113
+ localSchema || {}
1114
+ );
1115
+ return response.data;
1116
+ }
1117
+ async function deploySchema(projectId, branchId, localSchema, options) {
1118
+ const response = await client_default.post(
1119
+ `/projects/${projectId}/branches/${branchId}/schema/deploy`,
1120
+ {
1121
+ localSchema,
1122
+ ...options
1123
+ }
1124
+ );
1125
+ const data = response.data;
1126
+ if ("data" in data && typeof data.data === "object") {
1127
+ return data.data;
1128
+ }
1129
+ return data;
1130
+ }
1131
+ var init_schema = __esm({
1132
+ "src/api/schema.ts"() {
1133
+ init_client();
1134
+ }
1135
+ });
1136
+
940
1137
  // src/api/manage-error.ts
941
1138
  function manageErrorBody(error2) {
942
1139
  const candidate = error2;
@@ -977,24 +1174,24 @@ var init_manage_error = __esm({
977
1174
  });
978
1175
  function detectCircularFallback(startCode, locales) {
979
1176
  const visited = /* @__PURE__ */ new Set();
980
- const path23 = [];
1177
+ const path25 = [];
981
1178
  const localeMap = new Map(locales.map((l) => [l.code, l]));
982
1179
  function visit(code) {
983
1180
  if (visited.has(code)) {
984
- const cycleStart = path23.indexOf(code);
985
- return [...path23.slice(cycleStart), code];
1181
+ const cycleStart = path25.indexOf(code);
1182
+ return [...path25.slice(cycleStart), code];
986
1183
  }
987
1184
  const locale = localeMap.get(code);
988
1185
  if (!locale || !locale.fallback || locale.fallback.length === 0) {
989
1186
  return null;
990
1187
  }
991
1188
  visited.add(code);
992
- path23.push(code);
1189
+ path25.push(code);
993
1190
  for (const fallbackCode of locale.fallback) {
994
1191
  const cycle = visit(fallbackCode);
995
1192
  if (cycle) return cycle;
996
1193
  }
997
- path23.pop();
1194
+ path25.pop();
998
1195
  visited.delete(code);
999
1196
  return null;
1000
1197
  }
@@ -1086,6 +1283,14 @@ var init_chunk_VGBOPF6G = __esm({
1086
1283
  });
1087
1284
  }
1088
1285
  });
1286
+
1287
+ // ../../domain/schema/dist/chunk-JH6U6ENN.js
1288
+ var TIME_UNITS;
1289
+ var init_chunk_JH6U6ENN = __esm({
1290
+ "../../domain/schema/dist/chunk-JH6U6ENN.js"() {
1291
+ TIME_UNITS = ["minutes", "hours", "days", "weeks", "months", "years"];
1292
+ }
1293
+ });
1089
1294
  function sortDependencyLevels(nodes, dependencies) {
1090
1295
  const nodeSet = new Set(nodes);
1091
1296
  const dependents = /* @__PURE__ */ new Map();
@@ -1127,21 +1332,21 @@ function sortDependencyLevels(nodes, dependencies) {
1127
1332
  function extractCycle(leftover, dependencies) {
1128
1333
  const leftoverSet = new Set(leftover);
1129
1334
  const visiting = /* @__PURE__ */ new Set();
1130
- const path23 = [];
1335
+ const path25 = [];
1131
1336
  const dfs = (node) => {
1132
1337
  if (visiting.has(node)) {
1133
- const start = path23.indexOf(node);
1134
- return [...path23.slice(start), node];
1338
+ const start = path25.indexOf(node);
1339
+ return [...path25.slice(start), node];
1135
1340
  }
1136
1341
  if (!leftoverSet.has(node)) return null;
1137
1342
  visiting.add(node);
1138
- path23.push(node);
1343
+ path25.push(node);
1139
1344
  for (const dep of dependencies.get(node) ?? []) {
1140
1345
  if (!leftoverSet.has(dep)) continue;
1141
1346
  const cycle = dfs(dep);
1142
1347
  if (cycle) return cycle;
1143
1348
  }
1144
- path23.pop();
1349
+ path25.pop();
1145
1350
  visiting.delete(node);
1146
1351
  leftoverSet.delete(node);
1147
1352
  return null;
@@ -1171,7 +1376,7 @@ function fieldsFromDirectOrFieldsets(type) {
1171
1376
  function refineDeclaredReadSets(fields, ctx) {
1172
1377
  const knownIds = new Set(fields.map((entry) => entry.field.id));
1173
1378
  const dependencies = /* @__PURE__ */ new Map();
1174
- for (const { field, path: path23 } of fields) {
1379
+ for (const { field, path: path25 } of fields) {
1175
1380
  const inputs = field.ai?.inputs;
1176
1381
  if (!inputs || inputs.length === 0) continue;
1177
1382
  const valid2 = [];
@@ -1180,7 +1385,7 @@ function refineDeclaredReadSets(fields, ctx) {
1180
1385
  ctx.addIssue({
1181
1386
  code: z.ZodIssueCode.custom,
1182
1387
  message: UNKNOWN_INPUT_MESSAGE(field.id, inputId),
1183
- path: [...path23, "ai", "inputs"]
1388
+ path: [...path25, "ai", "inputs"]
1184
1389
  });
1185
1390
  continue;
1186
1391
  }
@@ -1200,9 +1405,10 @@ function refineDeclaredReadSets(fields, ctx) {
1200
1405
  });
1201
1406
  }
1202
1407
  }
1203
- var AI_INSTRUCTIONS_MAX_CHARS, AIConfigSchema, FieldAIConfigSchema, AssetAIConfigSchema, BaseUIConfigSchema, TextUIOptionsSchema, UIConfigSchema, RuleConditionSchema, RuleConditionsSchema, VisibilityRuleSchema, RequirementRuleSchema, ValidationRuleSchema, ReadOnlyRuleSchema, FieldRuleSchema, LocalizationConfigSchema, FieldSchema, FieldsetSchema, BUILT_IN_ROLES, ROLE_ACTIONS, RoleActionSchema, BuiltInRoleSchema, roleIdPattern, RoleIdSchema, accessTagPattern, AccessTagSchema, AccessTagsArraySchema, ResourcePermissionSchema, FieldVisibilitySchema, RolePermissionsSchema, RoleSchema, RolesArraySchema, stageIdPattern, WorkflowEscalationSchema, WorkflowAutoAssignSchema, WorkflowStageSchema, WorkflowConfigSchema, ContentTypeWorkflowSchema, FILTER_OPERATORS, FilterOperatorSchema, TEXT_OPERATORS, LONG_TEXT_OPERATORS, NUMERIC_OPERATORS, BOOLEAN_OPERATORS, ENUM_OPERATORS, DATE_OPERATORS, REFERENCE_OPERATORS, TAGS_OPERATORS, FIELD_TYPE_OPERATORS, TIME_UNITS, TimeUnitSchema, RelativeDateValueSchema, COLUMN_RENDERERS, ColumnRendererSchema, ASSET_KINDS, FORBIDDEN_ASSET_TYPE_FIELD_TYPES, IMAGE_OUTPUT_FORMATS, IMAGE_FIT_MODES, UNKNOWN_INPUT_MESSAGE, INPUT_CYCLE_MESSAGE, FitSchema, OutputFormatSchema, ImageTransformSchema, ImageUploadConfigSchema, VideoUploadConfigSchema, FileUploadConfigSchema, ImageExifConfigSchema, VariantDefinitionSchema, ImageSourceConfigSchema, ImageKindConfigSchema, VideoPosterConfigSchema, VideoCaptionsConfigSchema, VideoDownloadConfigSchema, VideoWatermarkConfigSchema, VideoKindConfigSchema, FileScanConfigSchema, FileKindConfigSchema, AssetTypeSchema;
1204
- var init_chunk_NB2NTUXG = __esm({
1205
- "../../domain/schema/dist/chunk-NB2NTUXG.js"() {
1408
+ var AI_INSTRUCTIONS_MAX_CHARS, AIConfigSchema, FieldAIConfigSchema, AssetAIConfigSchema, BaseUIConfigSchema, TextUIOptionsSchema, UIConfigSchema, RuleConditionSchema, RuleConditionsSchema, VisibilityRuleSchema, RequirementRuleSchema, ValidationRuleSchema, ReadOnlyRuleSchema, FieldRuleSchema, LocalizationConfigSchema, FieldSchema, FieldsetSchema, BUILT_IN_ROLES, ROLE_ACTIONS, RoleActionSchema, BuiltInRoleSchema, roleIdPattern, RoleIdSchema, accessTagPattern, AccessTagSchema, AccessTagsArraySchema, ResourcePermissionSchema, FieldVisibilitySchema, RolePermissionsSchema, RoleSchema, RolesArraySchema, stageIdPattern, WorkflowEscalationSchema, WorkflowAutoAssignSchema, WorkflowStageSchema, WorkflowConfigSchema, ContentTypeWorkflowSchema, FILTER_OPERATORS, FilterOperatorSchema, TEXT_OPERATORS, LONG_TEXT_OPERATORS, NUMERIC_OPERATORS, BOOLEAN_OPERATORS, ENUM_OPERATORS, DATE_OPERATORS, REFERENCE_OPERATORS, TAGS_OPERATORS, FIELD_TYPE_OPERATORS, TimeUnitSchema, RelativeDateValueSchema, COLUMN_RENDERERS, ColumnRendererSchema, ASSET_KINDS, FORBIDDEN_ASSET_TYPE_FIELD_TYPES, IMAGE_OUTPUT_FORMATS, IMAGE_FIT_MODES, UNKNOWN_INPUT_MESSAGE, INPUT_CYCLE_MESSAGE, FitSchema, OutputFormatSchema, ImageTransformSchema, ImageUploadConfigSchema, VideoUploadConfigSchema, FileUploadConfigSchema, ImageExifConfigSchema, VariantDefinitionSchema, ImageSourceConfigSchema, ImageKindConfigSchema, VideoPosterConfigSchema, VideoCaptionsConfigSchema, VideoDownloadConfigSchema, VideoWatermarkConfigSchema, VideoKindConfigSchema, FileScanConfigSchema, FileKindConfigSchema, AssetTypeSchema;
1409
+ var init_chunk_PCG6XHFD = __esm({
1410
+ "../../domain/schema/dist/chunk-PCG6XHFD.js"() {
1411
+ init_chunk_JH6U6ENN();
1206
1412
  AI_INSTRUCTIONS_MAX_CHARS = 1e3;
1207
1413
  AIConfigSchema = z.object({
1208
1414
  /**
@@ -1669,7 +1875,6 @@ var init_chunk_NB2NTUXG = __esm({
1669
1875
  list: []
1670
1876
  // Lists are complex, use nested filters
1671
1877
  };
1672
- TIME_UNITS = ["minutes", "hours", "days", "weeks", "months", "years"];
1673
1878
  TimeUnitSchema = z.enum(TIME_UNITS);
1674
1879
  RelativeDateValueSchema = z.object({
1675
1880
  amount: z.number().int().positive(),
@@ -2057,11 +2262,42 @@ var init_chunk_NB2NTUXG = __esm({
2057
2262
  });
2058
2263
  }
2059
2264
  });
2060
- var StringOperationSchema, StringOperationWithParamsSchema, WhereValueSchema, WhereConditionSchema, SortConditionSchema, EnhancedIncludeObjectSchema, IncludeDefinitionSchema, SelectMappingSchema, FilterDefinitionSchema, FullTextSearchSchema, ContentTypesSchema, SearchBlockSchema, SimilarBlockSchema, AggregationMetricSchema, QueryCachePolicySchema, FetchBlockSchema, AggregateBlockSchema, IncludeSubQueriesSchema, QueryDefinitionSchema, FilterValueSchema, FilterConditionSchema, LogicalOperatorSchema, FilterGroupSchema, ColumnConfigSchema, MetadataFieldConfigSchema, MetadataLineConfigSchema, TableSettingsSchema, ViewScopeSchema, ViewDSLSchema, ViewVisibilitySchema, ViewSourceSchema, SchemaViewDefinitionSchema, getComponentIdFromType, ComponentSchema, ContentTypeSchema, SettingsGroupSchema, SchemaDefinitionSchema;
2061
- var init_chunk_OOMXKJZA = __esm({
2062
- "../../domain/schema/dist/chunk-OOMXKJZA.js"() {
2265
+ var PARAMETER_EXPRESSION_SOURCE, ParameterExpressionStringSchema, QUERY_PARAMETER_TYPES, QueryParameterTypeSchema, QueryParameterScalarSchema, QueryParameterSchema, StringOperationSchema, StringOperationWithParamsSchema, ParameterizedLimitSchema, ParameterizedOffsetSchema, WhereValueSchema, WhereConditionSchema, SortConditionSchema, EnhancedIncludeObjectSchema, IncludeDefinitionSchema, SelectMappingSchema, FilterDefinitionSchema, FullTextSearchSchema, ContentTypesSchema, SearchBlockSchema, SimilarBlockSchema, AggregationMetricSchema, QueryCardinalitySchema, QueryCachePolicySchema, FetchBlockSchema, AggregateBlockSchema, IncludeSubQueriesSchema, QueryDefinitionSchema, VIEW_SLUG_PATTERN, VIEW_SLUG_MESSAGE, FilterValueSchema, FilterConditionSchema, LogicalOperatorSchema, FilterGroupSchema, ColumnConfigSchema, MetadataFieldConfigSchema, MetadataLineConfigSchema, TableSettingsSchema, ViewScopeSchema, ViewDSLSchema, ViewVisibilitySchema, ViewSourceSchema, SchemaViewDefinitionSchema, getComponentIdFromType, ComponentSchema, ContentTypeSchema, SettingsGroupSchema, SchemaDefinitionSchema;
2266
+ var init_chunk_2326XWZE = __esm({
2267
+ "../../domain/schema/dist/chunk-2326XWZE.js"() {
2063
2268
  init_chunk_VGBOPF6G();
2064
- init_chunk_NB2NTUXG();
2269
+ init_chunk_PCG6XHFD();
2270
+ PARAMETER_EXPRESSION_SOURCE = String.raw`\$\{([a-zA-Z][a-zA-Z0-9_]*)(\?([^}]*))?\}`;
2271
+ ParameterExpressionStringSchema = z.string().regex(
2272
+ new RegExp(`^${PARAMETER_EXPRESSION_SOURCE}$`),
2273
+ "Must be a parameter expression like ${limit?20}"
2274
+ );
2275
+ QUERY_PARAMETER_TYPES = [
2276
+ "string",
2277
+ "number",
2278
+ "integer",
2279
+ "boolean",
2280
+ "array",
2281
+ "object"
2282
+ ];
2283
+ QueryParameterTypeSchema = z.enum(QUERY_PARAMETER_TYPES);
2284
+ QueryParameterScalarSchema = z.union([z.string(), z.number(), z.boolean()]);
2285
+ QueryParameterSchema = z.object({
2286
+ name: z.string().regex(
2287
+ /^[a-zA-Z][a-zA-Z0-9_]*$/,
2288
+ "Parameter name must start with a letter and contain only letters, numbers, and underscores"
2289
+ ),
2290
+ type: QueryParameterTypeSchema.optional(),
2291
+ /** Absent means "required unless every `${…}` occurrence says otherwise". */
2292
+ required: z.boolean().optional(),
2293
+ default: z.union([QueryParameterScalarSchema, z.array(QueryParameterScalarSchema)]).optional(),
2294
+ minimum: z.number().optional(),
2295
+ maximum: z.number().optional(),
2296
+ minLength: z.number().int().min(0).optional(),
2297
+ maxLength: z.number().int().min(0).optional(),
2298
+ pattern: z.string().min(1).optional(),
2299
+ enum: z.array(QueryParameterScalarSchema).min(1).optional()
2300
+ });
2065
2301
  StringOperationSchema = z.enum([
2066
2302
  "first",
2067
2303
  // Get first character
@@ -2085,6 +2321,14 @@ var init_chunk_OOMXKJZA = __esm({
2085
2321
  name: z.string().min(1),
2086
2322
  defaultValue: z.union([z.string(), z.number(), z.boolean()]).optional()
2087
2323
  });
2324
+ ParameterizedLimitSchema = z.union([
2325
+ z.number().int().positive(),
2326
+ ParameterExpressionStringSchema
2327
+ ]);
2328
+ ParameterizedOffsetSchema = z.union([
2329
+ z.number().int().min(0),
2330
+ ParameterExpressionStringSchema
2331
+ ]);
2088
2332
  WhereValueSchema = z.union([
2089
2333
  z.string(),
2090
2334
  z.number(),
@@ -2232,8 +2476,8 @@ var init_chunk_OOMXKJZA = __esm({
2232
2476
  ]).optional(),
2233
2477
  // Sorting and pagination
2234
2478
  sort: SortConditionSchema.optional(),
2235
- limit: z.number().int().positive().optional(),
2236
- offset: z.number().int().min(0).optional()
2479
+ limit: ParameterizedLimitSchema.optional(),
2480
+ offset: ParameterizedOffsetSchema.optional()
2237
2481
  }).refine((data) => data.contentType || data.contentTypes, {
2238
2482
  message: "Either contentType or contentTypes is required for search queries"
2239
2483
  });
@@ -2250,7 +2494,7 @@ var init_chunk_OOMXKJZA = __esm({
2250
2494
  /** Fields to return */
2251
2495
  fields: SelectMappingSchema.optional(),
2252
2496
  /** Maximum results to return */
2253
- limit: z.number().int().positive().optional()
2497
+ limit: ParameterizedLimitSchema.optional()
2254
2498
  }).refine((data) => data.contentType || data.contentTypes, {
2255
2499
  message: "Either contentType or contentTypes is required for similar queries"
2256
2500
  });
@@ -2260,6 +2504,7 @@ var init_chunk_OOMXKJZA = __esm({
2260
2504
  z.string()
2261
2505
  // field expression like "items.quantity.sum" or "id.count"
2262
2506
  );
2507
+ QueryCardinalitySchema = z.enum(["one", "many"]);
2263
2508
  QueryCachePolicySchema = z.union([
2264
2509
  // Simple: "public 300s" or "private" or "public 300s swr 60s"
2265
2510
  z.string(),
@@ -2277,8 +2522,8 @@ var init_chunk_OOMXKJZA = __esm({
2277
2522
  where: WhereConditionSchema.optional(),
2278
2523
  include: IncludeDefinitionSchema.optional(),
2279
2524
  sort: SortConditionSchema.optional(),
2280
- limit: z.number().int().positive().optional(),
2281
- offset: z.number().int().min(0).optional()
2525
+ limit: ParameterizedLimitSchema.optional(),
2526
+ offset: ParameterizedOffsetSchema.optional()
2282
2527
  }).refine((data) => data.contentType || data.singleton, {
2283
2528
  message: "Either contentType or singleton must be specified"
2284
2529
  }).refine((data) => !(data.contentType && data.singleton), {
@@ -2323,7 +2568,7 @@ var init_chunk_OOMXKJZA = __esm({
2323
2568
  ])
2324
2569
  ).optional(),
2325
2570
  sort: SortConditionSchema.optional(),
2326
- limit: z.number().int().positive().optional()
2571
+ limit: ParameterizedLimitSchema.optional()
2327
2572
  });
2328
2573
  IncludeSubQueriesSchema = z.record(
2329
2574
  z.string(),
@@ -2331,7 +2576,7 @@ var init_chunk_OOMXKJZA = __esm({
2331
2576
  source: z.string(),
2332
2577
  where: WhereConditionSchema.optional(),
2333
2578
  sort: SortConditionSchema.optional(),
2334
- limit: z.number().int().positive().optional(),
2579
+ limit: ParameterizedLimitSchema.optional(),
2335
2580
  select: SelectMappingSchema.optional()
2336
2581
  })
2337
2582
  );
@@ -2359,7 +2604,15 @@ var init_chunk_OOMXKJZA = __esm({
2359
2604
  filters: FilterDefinitionSchema.optional(),
2360
2605
  fullText: FullTextSearchSchema.optional(),
2361
2606
  select: SelectMappingSchema.optional(),
2362
- cache: QueryCachePolicySchema.optional()
2607
+ cache: QueryCachePolicySchema.optional(),
2608
+ /**
2609
+ * What the query accepts. Additive and optional: a query that declares nothing still has the
2610
+ * parameters its own `${…}` expressions and endpoint segments declare — `declareQueryParameters`
2611
+ * is the one reader, and this block is authoritative where it speaks (type and constraints).
2612
+ */
2613
+ parameters: z.array(QueryParameterSchema).optional(),
2614
+ /** `one` or `many`. Absent means `many`, unless the query fetches a singleton. */
2615
+ cardinality: QueryCardinalitySchema.optional()
2363
2616
  }).refine((data) => data.fetch || data.aggregate || data.search || data.similar, {
2364
2617
  message: "Query must contain at least one of: fetch, aggregate, search, or similar block"
2365
2618
  }).refine(
@@ -2373,6 +2626,8 @@ var init_chunk_OOMXKJZA = __esm({
2373
2626
  message: "Aggregate block must have a source when not used with fetch"
2374
2627
  }
2375
2628
  );
2629
+ VIEW_SLUG_PATTERN = /^[a-z][a-z0-9-]*$/;
2630
+ VIEW_SLUG_MESSAGE = "Slug must be lowercase, start with a letter, and contain only letters, numbers, and hyphens";
2376
2631
  FilterValueSchema = z.union([
2377
2632
  // Simple values
2378
2633
  z.string(),
@@ -2620,7 +2875,7 @@ var init_chunk_OOMXKJZA = __esm({
2620
2875
  * URL-friendly slug for the view (unique per dataset)
2621
2876
  * Used in Studio URLs like /studio/views/:slug
2622
2877
  */
2623
- slug: z.string().regex(/^[a-z][a-z0-9-]*$/, "Slug must be lowercase, start with a letter"),
2878
+ slug: z.string().regex(VIEW_SLUG_PATTERN, VIEW_SLUG_MESSAGE),
2624
2879
  /**
2625
2880
  * Optional description
2626
2881
  */
@@ -2676,7 +2931,7 @@ var init_chunk_OOMXKJZA = __esm({
2676
2931
  * URL-friendly slug for the view (optional, auto-generated from id if not provided)
2677
2932
  * Used in Studio URLs like /studio/views/:slug
2678
2933
  */
2679
- slug: z.string().regex(/^[a-z][a-z0-9-]*$/, "Slug must be lowercase, start with a letter").optional(),
2934
+ slug: z.string().regex(VIEW_SLUG_PATTERN, VIEW_SLUG_MESSAGE).optional(),
2680
2935
  /**
2681
2936
  * View name
2682
2937
  */
@@ -3138,18 +3393,19 @@ var init_chunk_OOMXKJZA = __esm({
3138
3393
  // ../../domain/schema/dist/parsed-type.js
3139
3394
  var init_parsed_type = __esm({
3140
3395
  "../../domain/schema/dist/parsed-type.js"() {
3141
- init_chunk_OOMXKJZA();
3396
+ init_chunk_2326XWZE();
3142
3397
  init_chunk_VGBOPF6G();
3143
- init_chunk_NB2NTUXG();
3398
+ init_chunk_PCG6XHFD();
3399
+ init_chunk_JH6U6ENN();
3144
3400
  }
3145
3401
  });
3146
3402
  async function loadRelyIOConfig(baseDir) {
3147
3403
  for (const configPath of CONFIG_FILE_PATHS) {
3148
- const fullPath = path3.join(baseDir, configPath);
3404
+ const fullPath = path5.join(baseDir, configPath);
3149
3405
  if (existsSync(fullPath)) {
3150
3406
  try {
3151
- const __filename2 = fileURLToPath(import.meta.url);
3152
- const jiti = createJiti(__filename2, {
3407
+ const __filename = fileURLToPath(import.meta.url);
3408
+ const jiti = createJiti(__filename, {
3153
3409
  interopDefault: true,
3154
3410
  // Must stay OFF: jiti's module cache is process-global, so in a long-running process
3155
3411
  // (`relyio dev`) a cached config would be re-served after edits. fsCache (content-hashed
@@ -3172,7 +3428,7 @@ async function loadRelyIOConfig(baseDir) {
3172
3428
  }
3173
3429
  async function findTypeScriptSchema(relyioDir) {
3174
3430
  for (const tsPath of TS_SCHEMA_PATHS) {
3175
- const fullPath = path3.join(relyioDir, tsPath);
3431
+ const fullPath = path5.join(relyioDir, tsPath);
3176
3432
  if (existsSync(fullPath)) {
3177
3433
  return {
3178
3434
  found: true,
@@ -3192,7 +3448,7 @@ async function findYamlSchemas(relyioDir) {
3192
3448
  if (yamlFiles.length > 0) {
3193
3449
  return {
3194
3450
  found: true,
3195
- paths: yamlFiles.map((f) => path3.join(relyioDir, f)),
3451
+ paths: yamlFiles.map((f) => path5.join(relyioDir, f)),
3196
3452
  description: `YAML schema (${yamlFiles.length} file${yamlFiles.length === 1 ? "" : "s"})`
3197
3453
  };
3198
3454
  }
@@ -3201,7 +3457,7 @@ async function findYamlSchemas(relyioDir) {
3201
3457
  return { found: false };
3202
3458
  }
3203
3459
  async function detectSchemaSource(baseDir = process.cwd()) {
3204
- const relyioDir = path3.join(baseDir, RELYIO_DIR);
3460
+ const relyioDir = path5.join(baseDir, RELYIO_DIR);
3205
3461
  if (!existsSync(relyioDir)) {
3206
3462
  return {
3207
3463
  sourceType: null,
@@ -3218,7 +3474,7 @@ async function detectSchemaSource(baseDir = process.cwd()) {
3218
3474
  if (config?.schemaSource) {
3219
3475
  if (config.schemaSource === "typescript") {
3220
3476
  if (config.schemaPath) {
3221
- const customPath = path3.join(baseDir, config.schemaPath);
3477
+ const customPath = path5.join(baseDir, config.schemaPath);
3222
3478
  if (existsSync(customPath)) {
3223
3479
  return {
3224
3480
  sourceType: "typescript",
@@ -3359,8 +3615,8 @@ async function loadTypeScriptSchema(schemaPath, detection) {
3359
3615
  });
3360
3616
  }
3361
3617
  try {
3362
- const __filename2 = fileURLToPath(import.meta.url);
3363
- const jiti = createJiti(__filename2, {
3618
+ const __filename = fileURLToPath(import.meta.url);
3619
+ const jiti = createJiti(__filename, {
3364
3620
  // Enable ESM interop
3365
3621
  interopDefault: true,
3366
3622
  // Must stay OFF. jiti's module cache is process-global and integrates with Node's require
@@ -3637,7 +3893,7 @@ function isIgnoredWatchPath(filePath) {
3637
3893
  return IGNORED_WATCH_DIR_RE.test(filePath);
3638
3894
  }
3639
3895
  async function getSchemaWatchPaths(baseDir = process.cwd()) {
3640
- const relyioDir = path3.join(baseDir, RELYIO_DIR);
3896
+ const relyioDir = path5.join(baseDir, RELYIO_DIR);
3641
3897
  return existsSync(relyioDir) ? [relyioDir] : [];
3642
3898
  }
3643
3899
  var RELYIO_DIR, TS_SCHEMA_PATHS, CONFIG_FILE_PATHS, SCHEMA_FILE_RE, DECLARATION_FILE_RE, IGNORED_WATCH_DIR_RE, SchemaNotFoundError;
@@ -3652,7 +3908,7 @@ var init_schema_loader = __esm({
3652
3908
  IGNORED_WATCH_DIR_RE = /(^|[/\\])(node_modules|\.git|dist|\.next|\.turbo|coverage)([/\\]|$)/;
3653
3909
  SchemaNotFoundError = class extends Error {
3654
3910
  constructor(baseDir) {
3655
- const relyioDir = path3.join(baseDir, RELYIO_DIR);
3911
+ const relyioDir = path5.join(baseDir, RELYIO_DIR);
3656
3912
  const message = `
3657
3913
  No schema found in ${relyioDir}
3658
3914
 
@@ -4061,9 +4317,204 @@ var init_deploy_service = __esm({
4061
4317
  }
4062
4318
  });
4063
4319
 
4064
- // ../../domain/schema/dist/chunk-QSWR2YJN.js
4065
- var init_chunk_QSWR2YJN = __esm({
4066
- "../../domain/schema/dist/chunk-QSWR2YJN.js"() {
4320
+ // ../sdk/dist/chunk-XQ4QHWZE.js
4321
+ var __defProp2, __name;
4322
+ var init_chunk_XQ4QHWZE = __esm({
4323
+ "../sdk/dist/chunk-XQ4QHWZE.js"() {
4324
+ __defProp2 = Object.defineProperty;
4325
+ __name = (target, value) => __defProp2(target, "name", { value, configurable: true });
4326
+ }
4327
+ });
4328
+
4329
+ // ../sdk/dist/chunk-BBUB7C2Z.js
4330
+ function readDeadline(raw, now) {
4331
+ if (raw === void 0 || raw === null || raw === "") return { state: "none" };
4332
+ if (typeof raw !== "string") return { state: "unreadable" };
4333
+ const deadline = new Date(raw).getTime();
4334
+ if (!Number.isFinite(deadline)) return { state: "unreadable" };
4335
+ return deadline <= now ? { state: "passed", expiredAt: raw } : { state: "live", expiresAt: raw };
4336
+ }
4337
+ function parseDevContextFile(raw, now) {
4338
+ let parsed;
4339
+ try {
4340
+ parsed = JSON.parse(raw);
4341
+ } catch {
4342
+ return { state: "unreadable", reason: "not-json" };
4343
+ }
4344
+ if (typeof parsed !== "object" || parsed === null) {
4345
+ return { state: "unreadable", reason: "not-json" };
4346
+ }
4347
+ const record = parsed;
4348
+ const { datasetId, previewApiKey, branchId, expiresAt } = record;
4349
+ if (typeof datasetId !== "string" || datasetId === "" || typeof previewApiKey !== "string" || previewApiKey === "" || typeof branchId !== "string" || branchId === "") {
4350
+ return { state: "unreadable", reason: "missing-fields" };
4351
+ }
4352
+ const deadline = readDeadline(expiresAt, now);
4353
+ if (deadline.state === "unreadable") return { state: "unreadable", reason: "bad-expiry" };
4354
+ const context = {
4355
+ datasetId,
4356
+ previewApiKey,
4357
+ branchId,
4358
+ expiresAt: deadline.state === "none" ? null : expiresAt
4359
+ };
4360
+ return deadline.state === "passed" ? { state: "expired", context, expiredAt: deadline.expiredAt } : { state: "active", context };
4361
+ }
4362
+ function buildDevEnv(context) {
4363
+ const env = {
4364
+ [DEV_ENV.dataset]: context.datasetId,
4365
+ [DEV_ENV.apiKey]: context.previewApiKey
4366
+ };
4367
+ if (context.expiresAt != null && context.expiresAt !== "") {
4368
+ env[DEV_ENV.expiresAt] = context.expiresAt;
4369
+ }
4370
+ return env;
4371
+ }
4372
+ function parseDevEnv(env, now) {
4373
+ const dataset = env?.[DEV_ENV.dataset];
4374
+ const apiKey = env?.[DEV_ENV.apiKey];
4375
+ if (dataset == null || dataset === "" || apiKey == null || apiKey === "") {
4376
+ return { state: "absent" };
4377
+ }
4378
+ const deadline = readDeadline(env?.[DEV_ENV.expiresAt], now);
4379
+ switch (deadline.state) {
4380
+ case "unreadable":
4381
+ return { state: "unreadable", reason: "bad-expiry" };
4382
+ case "passed":
4383
+ return { state: "expired", expiredAt: deadline.expiredAt };
4384
+ case "live":
4385
+ return { state: "active", credentials: { dataset, apiKey }, expiresAt: deadline.expiresAt };
4386
+ case "none":
4387
+ return { state: "active", credentials: { dataset, apiKey }, expiresAt: null };
4388
+ }
4389
+ }
4390
+ var DEV_CONTEXT_DIR, DEV_CONTEXT_FILENAME, DEV_CONTEXT_FILE, DEV_ENV;
4391
+ var init_chunk_BBUB7C2Z = __esm({
4392
+ "../sdk/dist/chunk-BBUB7C2Z.js"() {
4393
+ init_chunk_XQ4QHWZE();
4394
+ DEV_CONTEXT_DIR = ".relyio";
4395
+ DEV_CONTEXT_FILENAME = "dev.json";
4396
+ DEV_CONTEXT_FILE = `${DEV_CONTEXT_DIR}/${DEV_CONTEXT_FILENAME}`;
4397
+ DEV_ENV = {
4398
+ dataset: "RELYIO_DEV_DATASET",
4399
+ apiKey: "RELYIO_DEV_API_KEY",
4400
+ expiresAt: "RELYIO_DEV_EXPIRES_AT"
4401
+ };
4402
+ __name(readDeadline, "readDeadline");
4403
+ __name(parseDevContextFile, "parseDevContextFile");
4404
+ __name(buildDevEnv, "buildDevEnv");
4405
+ __name(parseDevEnv, "parseDevEnv");
4406
+ }
4407
+ });
4408
+
4409
+ // ../sdk/dist/dev-handshake/index.js
4410
+ var init_dev_handshake = __esm({
4411
+ "../sdk/dist/dev-handshake/index.js"() {
4412
+ init_chunk_BBUB7C2Z();
4413
+ }
4414
+ });
4415
+ function registerPlugin(language, load3) {
4416
+ registeredPlugins.set(language, load3);
4417
+ }
4418
+ function registeredLanguages() {
4419
+ return [...registeredPlugins.keys()].sort((a, b) => a.localeCompare(b));
4420
+ }
4421
+ function asPlugin(module_, describe3) {
4422
+ const candidate = module_?.default ?? module_;
4423
+ const plugin3 = candidate;
4424
+ if (!plugin3 || typeof plugin3.generate !== "function") {
4425
+ throw new Error(`Invalid plugin: ${describe3}`);
4426
+ }
4427
+ return plugin3;
4428
+ }
4429
+ async function loadPlugin(language) {
4430
+ const registered = registeredPlugins.get(language);
4431
+ if (!registered) {
4432
+ const available = registeredLanguages();
4433
+ throw new Error(
4434
+ `No generator is registered for "${language}". ` + (available.length > 0 ? `Registered generators: ${available.join(", ")}.` : `No generator is registered at all \u2014 the host registers them at startup.`)
4435
+ );
4436
+ }
4437
+ return asPlugin(await registered(), `registered ${language}`);
4438
+ }
4439
+ async function writeFiles(files, outputDir) {
4440
+ await fs4.mkdir(outputDir, { recursive: true });
4441
+ await Promise.all(
4442
+ files.map(async (file) => {
4443
+ const fullPath = path5.join(outputDir, file.path);
4444
+ const dir = path5.dirname(fullPath);
4445
+ await fs4.mkdir(dir, { recursive: true });
4446
+ await fs4.writeFile(fullPath, file.content, "utf8");
4447
+ })
4448
+ );
4449
+ }
4450
+ function words(identifier) {
4451
+ return identifier.split(/[-_\s]+/).filter((word) => word.length > 0);
4452
+ }
4453
+ var BUILT_IN_LANGUAGES, registeredPlugins, TemplateEngine, naming;
4454
+ var init_dist2 = __esm({
4455
+ "../codegen-core/dist/index.js"() {
4456
+ BUILT_IN_LANGUAGES = ["typescript", "python"];
4457
+ registeredPlugins = /* @__PURE__ */ new Map();
4458
+ TemplateEngine = class {
4459
+ handlebars;
4460
+ constructor() {
4461
+ this.handlebars = Handlebars.create();
4462
+ this.registerHelpers();
4463
+ }
4464
+ registerHelpers() {
4465
+ this.handlebars.registerHelper("pascalCase", (str) => {
4466
+ if (!str) return "";
4467
+ return str.replaceAll(/(^|_)(\w)/g, (_, __, char) => char.toUpperCase());
4468
+ });
4469
+ this.handlebars.registerHelper("camelCase", (str) => {
4470
+ if (!str) return "";
4471
+ const pascal = str.replaceAll(/(^|_)(\w)/g, (_, __, char) => char.toUpperCase());
4472
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
4473
+ });
4474
+ this.handlebars.registerHelper("snakeCase", (str) => {
4475
+ if (!str) return "";
4476
+ return str.replaceAll(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
4477
+ });
4478
+ this.handlebars.registerHelper("kebabCase", (str) => {
4479
+ if (!str) return "";
4480
+ return str.replaceAll(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
4481
+ });
4482
+ this.handlebars.registerHelper("plural", (str) => {
4483
+ if (!str) return "";
4484
+ if (str.endsWith("y")) {
4485
+ return str.slice(0, -1) + "ies";
4486
+ }
4487
+ if (str.endsWith("s") || str.endsWith("sh") || str.endsWith("ch")) {
4488
+ return str + "es";
4489
+ }
4490
+ return str + "s";
4491
+ });
4492
+ }
4493
+ compile(template) {
4494
+ const compiledTemplate = this.handlebars.compile(template);
4495
+ return (context) => compiledTemplate(context);
4496
+ }
4497
+ render(template, context) {
4498
+ const compiledTemplate = this.compile(template);
4499
+ return compiledTemplate(context);
4500
+ }
4501
+ };
4502
+ new TemplateEngine();
4503
+ naming = {
4504
+ /** `blogPost` → `BlogPost`. The name of a generated type, class or struct. */
4505
+ pascal(identifier) {
4506
+ return words(identifier).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
4507
+ },
4508
+ /** `blog_post` → `blogPost`. The name of a generated value or field. */
4509
+ camel(identifier) {
4510
+ const pascal = naming.pascal(identifier);
4511
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
4512
+ },
4513
+ /** `blogPost` → `blog_post`. Python module paths and field names. */
4514
+ snake(identifier) {
4515
+ return identifier.replaceAll(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "").replaceAll(/[-\s]+/g, "_");
4516
+ }
4517
+ };
4067
4518
  }
4068
4519
  });
4069
4520
  function normalizeReferenceTargets(to) {
@@ -4071,8 +4522,8 @@ function normalizeReferenceTargets(to) {
4071
4522
  return Array.isArray(to) ? [...to] : [to];
4072
4523
  }
4073
4524
  var BaseFieldConfigSchema, ReferenceStrengthSchema, OnTargetDeleteSchema, ReferenceTargetSchema;
4074
- var init_chunk_YL47KAP2 = __esm({
4075
- "../../domain/schema/dist/chunk-YL47KAP2.js"() {
4525
+ var init_chunk_RIJEP5Z6 = __esm({
4526
+ "../../domain/schema/dist/chunk-RIJEP5Z6.js"() {
4076
4527
  BaseFieldConfigSchema = z.object({
4077
4528
  required: z.boolean().optional(),
4078
4529
  unique: z.boolean().optional(),
@@ -4126,19 +4577,11 @@ var init_chunk_YL47KAP2 = __esm({
4126
4577
  });
4127
4578
  }
4128
4579
  });
4129
-
4130
- // ../../domain/schema/dist/chunk-ILN7T4KB.js
4131
- var init_chunk_ILN7T4KB = __esm({
4132
- "../../domain/schema/dist/chunk-ILN7T4KB.js"() {
4133
- init_chunk_QSWR2YJN();
4134
- init_chunk_YL47KAP2();
4135
- init_chunk_NB2NTUXG();
4136
- }
4137
- });
4138
- var init_chunk_KWUTF26B = __esm({
4139
- "../../domain/schema/dist/chunk-KWUTF26B.js"() {
4140
- init_chunk_YL47KAP2();
4141
- init_chunk_NB2NTUXG();
4580
+ var init_chunk_3UOSTM65 = __esm({
4581
+ "../../domain/schema/dist/chunk-3UOSTM65.js"() {
4582
+ init_chunk_2326XWZE();
4583
+ init_chunk_RIJEP5Z6();
4584
+ init_chunk_PCG6XHFD();
4142
4585
  BaseFieldConfigSchema.extend({
4143
4586
  maxLength: z.number().min(1).optional(),
4144
4587
  pattern: z.string().optional(),
@@ -4369,15 +4812,67 @@ var init_chunk_KWUTF26B = __esm({
4369
4812
  z.record(z.any());
4370
4813
  }
4371
4814
  });
4372
- var init_dist = __esm({
4373
- "../../domain/schema/dist/index.js"() {
4374
- init_chunk_ILN7T4KB();
4815
+ var init_chunk_XTMEVLMJ = __esm({
4816
+ "../../domain/schema/dist/chunk-XTMEVLMJ.js"() {
4817
+ }
4818
+ });
4819
+
4820
+ // ../../domain/schema/dist/chunk-73FJJM22.js
4821
+ var TABLE_SYSTEM_FIELD_KINDS;
4822
+ var init_chunk_73FJJM22 = __esm({
4823
+ "../../domain/schema/dist/chunk-73FJJM22.js"() {
4824
+ init_chunk_RIJEP5Z6();
4825
+ init_chunk_PCG6XHFD();
4826
+ TABLE_SYSTEM_FIELD_KINDS = {
4827
+ id: "text",
4828
+ contentType: "choice",
4829
+ status: "text",
4830
+ createdAt: "date",
4831
+ updatedAt: "date",
4832
+ publishedAt: "date",
4833
+ createdBy: "reference",
4834
+ updatedBy: "reference"
4835
+ };
4836
+ ({
4837
+ ...TABLE_SYSTEM_FIELD_KINDS,
4838
+ "file.status": "text",
4839
+ "file.backend": "text",
4840
+ "file.mimeType": "text",
4841
+ "file.fileName": "text",
4842
+ "file.size": "number",
4843
+ "file.extracted.width": "number",
4844
+ "file.extracted.height": "number",
4845
+ "file.extracted.duration": "number"
4846
+ });
4847
+ }
4848
+ });
4849
+
4850
+ // ../../domain/schema/dist/chunk-QSWR2YJN.js
4851
+ var init_chunk_QSWR2YJN = __esm({
4852
+ "../../domain/schema/dist/chunk-QSWR2YJN.js"() {
4853
+ }
4854
+ });
4855
+
4856
+ // ../../domain/schema/dist/chunk-2ABFISBQ.js
4857
+ var init_chunk_2ABFISBQ = __esm({
4858
+ "../../domain/schema/dist/chunk-2ABFISBQ.js"() {
4859
+ init_chunk_RIJEP5Z6();
4375
4860
  init_chunk_QSWR2YJN();
4376
- init_chunk_KWUTF26B();
4377
- init_chunk_OOMXKJZA();
4861
+ init_chunk_PCG6XHFD();
4862
+ }
4863
+ });
4864
+ var init_dist3 = __esm({
4865
+ "../../domain/schema/dist/index.js"() {
4866
+ init_chunk_3UOSTM65();
4867
+ init_chunk_2326XWZE();
4868
+ init_chunk_XTMEVLMJ();
4378
4869
  init_chunk_VGBOPF6G();
4379
- init_chunk_YL47KAP2();
4380
- init_chunk_NB2NTUXG();
4870
+ init_chunk_73FJJM22();
4871
+ init_chunk_2ABFISBQ();
4872
+ init_chunk_RIJEP5Z6();
4873
+ init_chunk_QSWR2YJN();
4874
+ init_chunk_PCG6XHFD();
4875
+ init_chunk_JH6U6ENN();
4381
4876
  new Set(FORBIDDEN_ASSET_TYPE_FIELD_TYPES);
4382
4877
  }
4383
4878
  });
@@ -4422,24 +4917,17 @@ function validateSchema(schema) {
4422
4917
  formatted: SchemaValidator.formatErrors(errors)
4423
4918
  };
4424
4919
  }
4425
- function toPascalCase(str) {
4426
- const spaced = str.replaceAll(/([a-z])([A-Z])/g, "$1 $2");
4427
- return spaced.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
4428
- }
4429
- function toCamelCase(str) {
4430
- const pascal = toPascalCase(str);
4431
- return pascal.charAt(0).toLowerCase() + pascal.slice(1);
4432
- }
4433
4920
  async function generateTypeScriptFromSchema(rawSchema, options) {
4434
4921
  const parsedSchema = parseSchema(rawSchema);
4435
4922
  const generator = new TypeScriptGenerator(parsedSchema, options);
4436
4923
  return generator.generate();
4437
4924
  }
4438
4925
  var ASSET_KINDS2, SchemaParser, SchemaValidator, TypeScriptGenerator, plugin, index_default;
4439
- var init_dist2 = __esm({
4926
+ var init_dist4 = __esm({
4440
4927
  "../codegen-typescript/dist/index.js"() {
4441
- init_dist();
4442
- init_dist();
4928
+ init_dist3();
4929
+ init_dist2();
4930
+ init_dist3();
4443
4931
  ASSET_KINDS2 = ["image", "video", "file"];
4444
4932
  SchemaParser = class {
4445
4933
  /**
@@ -4906,16 +5394,16 @@ var init_dist2 = __esm({
4906
5394
  * Validate a content type
4907
5395
  */
4908
5396
  validateContentType(id, contentType) {
4909
- const path23 = `contentTypes.${id}`;
4910
- this.validateFields(contentType.fields, path23);
5397
+ const path25 = `contentTypes.${id}`;
5398
+ this.validateFields(contentType.fields, path25);
4911
5399
  if (contentType.fields.length === 0) {
4912
- this.addWarning(path23, "Content type has no fields defined");
5400
+ this.addWarning(path25, "Content type has no fields defined");
4913
5401
  }
4914
5402
  const reservedFields = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "_id"]);
4915
5403
  for (const field of contentType.fields) {
4916
5404
  if (reservedFields.has(field.id)) {
4917
5405
  this.addError(
4918
- `${path23}.fields.${field.id}`,
5406
+ `${path25}.fields.${field.id}`,
4919
5407
  `Field name '${field.id}' is reserved by BaseContent`,
4920
5408
  `Use a different field name`
4921
5409
  );
@@ -4942,20 +5430,20 @@ var init_dist2 = __esm({
4942
5430
  /**
4943
5431
  * Validate field type
4944
5432
  */
4945
- validateFieldType(field, path23) {
5433
+ validateFieldType(field, path25) {
4946
5434
  if (field.type === "enum" && (!field.enumValues || field.enumValues.length === 0)) {
4947
- this.addError(path23, "Enum field has no values defined");
5435
+ this.addError(path25, "Enum field has no values defined");
4948
5436
  }
4949
5437
  if (field.type === "reference" && !field.referenceType) {
4950
5438
  this.addError(
4951
- path23,
5439
+ path25,
4952
5440
  "Reference field missing referenceType",
4953
5441
  "Ensure the reference target (to) is specified in the schema"
4954
5442
  );
4955
5443
  }
4956
5444
  if (field.type === "component" && !field.componentType) {
4957
5445
  this.addError(
4958
- path23,
5446
+ path25,
4959
5447
  "Component field missing componentType",
4960
5448
  "Ensure component type is specified as component(componentName)"
4961
5449
  );
@@ -4965,11 +5453,11 @@ var init_dist2 = __esm({
4965
5453
  * Check for circular references
4966
5454
  */
4967
5455
  checkCircularReferences(schema) {
4968
- const checkType = (typeName, visited, path23) => {
5456
+ const checkType = (typeName, visited, path25) => {
4969
5457
  if (visited.has(typeName)) {
4970
5458
  this.addError(
4971
- path23.join(" -> "),
4972
- `Circular reference detected: ${path23.join(" -> ")} -> ${typeName}`
5459
+ path25.join(" -> "),
5460
+ `Circular reference detected: ${path25.join(" -> ")} -> ${typeName}`
4973
5461
  );
4974
5462
  return;
4975
5463
  }
@@ -4978,7 +5466,7 @@ var init_dist2 = __esm({
4978
5466
  visited.add(typeName);
4979
5467
  for (const field of contentType.fields) {
4980
5468
  if (field.isReference && field.referenceType) {
4981
- checkType(field.referenceType, new Set(visited), [...path23, field.referenceType]);
5469
+ checkType(field.referenceType, new Set(visited), [...path25, field.referenceType]);
4982
5470
  }
4983
5471
  }
4984
5472
  };
@@ -4989,10 +5477,10 @@ var init_dist2 = __esm({
4989
5477
  /**
4990
5478
  * Add an error
4991
5479
  */
4992
- addError(path23, message, suggestion) {
5480
+ addError(path25, message, suggestion) {
4993
5481
  this.errors.push({
4994
5482
  type: "error",
4995
- path: path23,
5483
+ path: path25,
4996
5484
  message,
4997
5485
  suggestion
4998
5486
  });
@@ -5000,10 +5488,10 @@ var init_dist2 = __esm({
5000
5488
  /**
5001
5489
  * Add a warning
5002
5490
  */
5003
- addWarning(path23, message, suggestion) {
5491
+ addWarning(path25, message, suggestion) {
5004
5492
  this.warnings.push({
5005
5493
  type: "warning",
5006
- path: path23,
5494
+ path: path25,
5007
5495
  message,
5008
5496
  suggestion
5009
5497
  });
@@ -5148,6 +5636,9 @@ ${prefix}: ${error2.path}`, ` ${error2.message}`);
5148
5636
  import type {
5149
5637
  ${typeImports.join(",\n ")},
5150
5638
  } from '@relyio/sdk/schema'
5639
+
5640
+ // Puts '@relyio/sdk' in the program for the \`Register\` augmentation at the end of this file
5641
+ import type {} from '@relyio/sdk'
5151
5642
  `);
5152
5643
  }
5153
5644
  /**
@@ -5178,7 +5669,7 @@ import type {
5178
5669
  }
5179
5670
  lines.push(" */");
5180
5671
  }
5181
- const varName = toCamelCase(component.id) + "Component";
5672
+ const varName = naming.camel(component.id) + "Component";
5182
5673
  lines.push(`export const ${varName} = defineComponent('${component.id}', {`);
5183
5674
  if (component.displayName) {
5184
5675
  lines.push(` displayName: '${this.escapeString(component.displayName)}',`);
@@ -5221,7 +5712,7 @@ import type {
5221
5712
  }
5222
5713
  lines.push(" */");
5223
5714
  }
5224
- const varName = toCamelCase(contentType.id);
5715
+ const varName = naming.camel(contentType.id);
5225
5716
  lines.push(`export const ${varName} = defineContentType('${contentType.id}', {`);
5226
5717
  if (contentType.displayName) {
5227
5718
  lines.push(` displayName: '${this.escapeString(contentType.displayName)}',`);
@@ -5278,7 +5769,7 @@ import type {
5278
5769
  }
5279
5770
  lines.push(" */");
5280
5771
  }
5281
- const varName = toCamelCase(assetType.id);
5772
+ const varName = naming.camel(assetType.id);
5282
5773
  lines.push(`export const ${varName} = defineAssetType('${assetType.id}', {`);
5283
5774
  if (assetType.displayName) {
5284
5775
  lines.push(` displayName: '${this.escapeString(assetType.displayName)}',`);
@@ -5553,7 +6044,7 @@ import type {
5553
6044
  }
5554
6045
  lines.push(" */");
5555
6046
  }
5556
- const varName = toCamelCase(singleton5.id);
6047
+ const varName = naming.camel(singleton5.id);
5557
6048
  lines.push(`export const ${varName} = defineSingleton('${singleton5.id}', {`);
5558
6049
  if (singleton5.displayName) {
5559
6050
  lines.push(` displayName: '${this.escapeString(singleton5.displayName)}',`);
@@ -5605,7 +6096,7 @@ import type {
5605
6096
  }
5606
6097
  lines.push(" */");
5607
6098
  }
5608
- const varName = toCamelCase(group.id) + "Settings";
6099
+ const varName = naming.camel(group.id) + "Settings";
5609
6100
  lines.push(`export const ${varName} = defineSettingsGroup('${group.id}', {`);
5610
6101
  if (group.displayName) {
5611
6102
  lines.push(` displayName: '${this.escapeString(group.displayName)}',`);
@@ -5638,7 +6129,7 @@ import type {
5638
6129
  */
5639
6130
  generateQuery(query) {
5640
6131
  const lines = [];
5641
- const varName = toCamelCase(query.id) + "Query";
6132
+ const varName = naming.camel(query.id) + "Query";
5642
6133
  lines.push(`export const ${varName} = defineQuery('${query.id}', {`);
5643
6134
  if (query.endpoint) {
5644
6135
  lines.push(` endpoint: '${query.endpoint}',`);
@@ -5697,7 +6188,7 @@ import type {
5697
6188
  if (this.schema.contentTypes.size > 0) {
5698
6189
  lines.push(" contentTypes: {");
5699
6190
  for (const [id] of this.schema.contentTypes) {
5700
- const varName = toCamelCase(id);
6191
+ const varName = naming.camel(id);
5701
6192
  lines.push(` ${id}: ${varName},`);
5702
6193
  }
5703
6194
  lines.push(" },");
@@ -5705,7 +6196,7 @@ import type {
5705
6196
  if (this.schema.components.size > 0) {
5706
6197
  lines.push(" components: {");
5707
6198
  for (const [id] of this.schema.components) {
5708
- const varName = toCamelCase(id) + "Component";
6199
+ const varName = naming.camel(id) + "Component";
5709
6200
  lines.push(` ${id}: ${varName},`);
5710
6201
  }
5711
6202
  lines.push(" },");
@@ -5713,7 +6204,7 @@ import type {
5713
6204
  if (this.schema.assetTypes.size > 0) {
5714
6205
  lines.push(" assetTypes: {");
5715
6206
  for (const [id] of this.schema.assetTypes) {
5716
- const varName = toCamelCase(id);
6207
+ const varName = naming.camel(id);
5717
6208
  lines.push(` ${id}: ${varName},`);
5718
6209
  }
5719
6210
  lines.push(" },");
@@ -5721,7 +6212,7 @@ import type {
5721
6212
  if (this.schema.singletons.size > 0) {
5722
6213
  lines.push(" singletonContentTypes: {");
5723
6214
  for (const [id] of this.schema.singletons) {
5724
- const varName = toCamelCase(id);
6215
+ const varName = naming.camel(id);
5725
6216
  lines.push(` ${id}: ${varName},`);
5726
6217
  }
5727
6218
  lines.push(" },");
@@ -5729,7 +6220,7 @@ import type {
5729
6220
  if (this.schema.queries.size > 0) {
5730
6221
  lines.push(" queries: {");
5731
6222
  for (const [id] of this.schema.queries) {
5732
- const varName = toCamelCase(id) + "Query";
6223
+ const varName = naming.camel(id) + "Query";
5733
6224
  lines.push(` ${id}: ${varName},`);
5734
6225
  }
5735
6226
  lines.push(" },");
@@ -5737,7 +6228,7 @@ import type {
5737
6228
  if (this.schema.settingsGroups.size > 0) {
5738
6229
  lines.push(" settingsGroups: {");
5739
6230
  for (const [id] of this.schema.settingsGroups) {
5740
- const varName = toCamelCase(id) + "Settings";
6231
+ const varName = naming.camel(id) + "Settings";
5741
6232
  lines.push(` ${id}: ${varName},`);
5742
6233
  }
5743
6234
  lines.push(" },");
@@ -5755,13 +6246,13 @@ import type {
5755
6246
  `);
5756
6247
  const lines = [];
5757
6248
  for (const [id] of this.schema.contentTypes) {
5758
- const varName = toCamelCase(id);
5759
- const typeName = toPascalCase(id);
6249
+ const varName = naming.camel(id);
6250
+ const typeName = naming.pascal(id);
5760
6251
  lines.push(`export type ${typeName} = InferContentType<typeof ${varName}>`);
5761
6252
  }
5762
6253
  for (const [, assetType] of this.schema.assetTypes) {
5763
- const varName = toCamelCase(assetType.id);
5764
- const typeName = toPascalCase(assetType.id);
6254
+ const varName = naming.camel(assetType.id);
6255
+ const typeName = naming.pascal(assetType.id);
5765
6256
  lines.push(`export type ${typeName} = InferAssetType<typeof ${varName}>`);
5766
6257
  if (assetType.variantIds && assetType.variantIds.length > 0) {
5767
6258
  const union = assetType.variantIds.map((vid) => `'${this.escapeString(vid)}'`).join(" | ");
@@ -5769,8 +6260,8 @@ import type {
5769
6260
  }
5770
6261
  }
5771
6262
  for (const [id] of this.schema.singletons) {
5772
- const varName = toCamelCase(id);
5773
- const typeName = toPascalCase(id);
6263
+ const varName = naming.camel(id);
6264
+ const typeName = naming.pascal(id);
5774
6265
  lines.push(`export type ${typeName} = InferSingleton<typeof ${varName}>`);
5775
6266
  }
5776
6267
  this.output.push(
@@ -5780,6 +6271,27 @@ import type {
5780
6271
  export type SchemaTypes = typeof schema
5781
6272
  `
5782
6273
  );
6274
+ this.addRegisterAugmentation();
6275
+ }
6276
+ /**
6277
+ * Register this schema with the SDK, so `createClient({ apiKey })` is typed with no generic and no
6278
+ * `schema:` (`INV-SS-TYPES-INFERRED`, `sdk-surfaces-spec.md` decision 7).
6279
+ *
6280
+ * It is emitted into this same file rather than a second one: the augmentation takes effect
6281
+ * wherever `schema.ts` is part of the program. It needs `'@relyio/sdk'` itself in the program too,
6282
+ * which is what the empty `import type {} from '@relyio/sdk'` in `addImports` is for — measured,
6283
+ * a program whose only SDK import is `@relyio/sdk/client` fails this block with TS2664 ("module
6284
+ * '@relyio/sdk' cannot be found") even though the name resolves. Two generated files augmenting
6285
+ * `Register` in one program conflict on `schema` (TS2717); one program registers one schema.
6286
+ */
6287
+ addRegisterAugmentation() {
6288
+ this.output.push(`// Registers this schema with the SDK: \`createClient({ apiKey })\` is typed by it
6289
+ declare module '@relyio/sdk' {
6290
+ interface Register {
6291
+ schema: typeof schema
6292
+ }
6293
+ }
6294
+ `);
5783
6295
  }
5784
6296
  /**
5785
6297
  * Escape string for use in generated code
@@ -5913,18 +6425,12 @@ function mapFieldTypeToPython(fieldType, config) {
5913
6425
  default: {
5914
6426
  const componentMatch = fieldType.match(/^component\(([^)]+)\)$/);
5915
6427
  if (componentMatch) {
5916
- return toPascalCase2(componentMatch[1]);
6428
+ return naming.pascal(componentMatch[1]);
5917
6429
  }
5918
6430
  return "Any";
5919
6431
  }
5920
6432
  }
5921
6433
  }
5922
- function toPascalCase2(str) {
5923
- return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
5924
- }
5925
- function toSnakeCase(str) {
5926
- return str.replaceAll(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "").replaceAll(/[-\s]+/g, "_");
5927
- }
5928
6434
  function isRecord2(value) {
5929
6435
  return value !== null && typeof value === "object" && !Array.isArray(value);
5930
6436
  }
@@ -5963,7 +6469,7 @@ async function generatePython(ir, options) {
5963
6469
  for (const [id, contentType] of recordEntries(ir.contentTypes)) {
5964
6470
  const content = generateContentType(id, asSchemaType(contentType), options);
5965
6471
  files.push({
5966
- path: `content_types/${toSnakeCase(id)}.py`,
6472
+ path: `content_types/${naming.snake(id)}.py`,
5967
6473
  content
5968
6474
  });
5969
6475
  }
@@ -5977,7 +6483,7 @@ async function generatePython(ir, options) {
5977
6483
  for (const [id, component] of recordEntries(ir.components)) {
5978
6484
  const content = generateComponent(id, asSchemaType(component), options);
5979
6485
  files.push({
5980
- path: `components/${toSnakeCase(id)}.py`,
6486
+ path: `components/${naming.snake(id)}.py`,
5981
6487
  content
5982
6488
  });
5983
6489
  }
@@ -5991,7 +6497,7 @@ async function generatePython(ir, options) {
5991
6497
  for (const [id, singleton5] of recordEntries(ir.singletons)) {
5992
6498
  const content = generateSingleton(id, asSchemaType(singleton5), options);
5993
6499
  files.push({
5994
- path: `singletons/${toSnakeCase(id)}.py`,
6500
+ path: `singletons/${naming.snake(id)}.py`,
5995
6501
  content
5996
6502
  });
5997
6503
  }
@@ -6104,7 +6610,7 @@ __all__ = ['RichTextContent', 'ImageField', 'FileField', 'Reference']
6104
6610
  `;
6105
6611
  }
6106
6612
  function generateContentType(id, contentType, options) {
6107
- const className = toPascalCase2(id);
6613
+ const className = naming.pascal(id);
6108
6614
  const fields = generateFields(contentType.fields || [], options);
6109
6615
  const imports = ["from ..base_types import RichTextContent, ImageField, FileField, Reference"];
6110
6616
  if (options.baseModel === "pydantic") {
@@ -6142,7 +6648,7 @@ __all__ = ['${className}', 'Create${className}Input', 'Update${className}Input']
6142
6648
  }
6143
6649
  }
6144
6650
  function generateComponent(id, component, options) {
6145
- const className = toPascalCase2(id);
6651
+ const className = naming.pascal(id);
6146
6652
  const fields = generateFields(component.fields || [], options);
6147
6653
  const imports = ["from ..base_types import RichTextContent, ImageField, FileField, Reference"];
6148
6654
  if (options.baseModel === "pydantic") {
@@ -6173,7 +6679,7 @@ __all__ = ['${className}']
6173
6679
  }
6174
6680
  }
6175
6681
  function generateSingleton(id, singleton5, options) {
6176
- const className = toPascalCase2(id);
6682
+ const className = naming.pascal(id);
6177
6683
  const fields = generateFields(singleton5.fields || [], options);
6178
6684
  const imports = ["from ..base_types import RichTextContent, ImageField, FileField, Reference"];
6179
6685
  if (options.baseModel === "pydantic") {
@@ -6209,12 +6715,12 @@ __all__ = ['${className}', 'Update${className}Input']
6209
6715
  }
6210
6716
  function generateFields(fields, options) {
6211
6717
  return fields.map((field) => {
6212
- const fieldName = toSnakeCase(field.id);
6718
+ const fieldName = naming.snake(field.id);
6213
6719
  const pythonType = mapFieldTypeToPython(field.type, field.config);
6214
6720
  const isOptional = !field.required;
6215
6721
  const fieldType = isOptional ? `Optional[${pythonType}]` : pythonType;
6216
6722
  const defaultValue = isOptional ? " = None" : "";
6217
- if (options.baseModel === "pydantic" && field.id !== toSnakeCase(field.id)) {
6723
+ if (options.baseModel === "pydantic" && field.id !== naming.snake(field.id)) {
6218
6724
  return `${fieldName}: ${fieldType} = Field(${defaultValue.slice(3)}, alias='${field.id}')`;
6219
6725
  }
6220
6726
  return `${fieldName}: ${fieldType}${defaultValue}`;
@@ -6334,8 +6840,8 @@ class TenantContext(BaseModel):
6334
6840
  `);
6335
6841
  if (isRecord2(ir.contentTypes)) {
6336
6842
  for (const id of Object.keys(ir.contentTypes)) {
6337
- const className = toPascalCase2(id);
6338
- imports.push(`from .content_types.${toSnakeCase(id)} import ${className}`);
6843
+ const className = naming.pascal(id);
6844
+ imports.push(`from .content_types.${naming.snake(id)} import ${className}`);
6339
6845
  tenantTypes.push(`
6340
6846
  class Tenant${className}(${className}):
6341
6847
  """Tenant-scoped ${className}"""
@@ -6354,8 +6860,8 @@ class TenantContext:
6354
6860
  `);
6355
6861
  if (isRecord2(ir.contentTypes)) {
6356
6862
  for (const id of Object.keys(ir.contentTypes)) {
6357
- const className = toPascalCase2(id);
6358
- imports.push(`from .content_types.${toSnakeCase(id)} import ${className}`);
6863
+ const className = naming.pascal(id);
6864
+ imports.push(`from .content_types.${naming.snake(id)} import ${className}`);
6359
6865
  tenantTypes.push(`
6360
6866
  @dataclass
6361
6867
  class Tenant${className}(${className}):
@@ -6368,7 +6874,7 @@ class Tenant${className}(${className}):
6368
6874
  const exportList = ["TenantContext"];
6369
6875
  if (isRecord2(ir.contentTypes)) {
6370
6876
  for (const id of Object.keys(ir.contentTypes)) {
6371
- exportList.push(`Tenant${toPascalCase2(id)}`);
6877
+ exportList.push(`Tenant${naming.pascal(id)}`);
6372
6878
  }
6373
6879
  }
6374
6880
  return `${imports.join("\n")}
@@ -6411,10 +6917,10 @@ __all__ = ['validate_data', 'to_dict', 'to_json']
6411
6917
  }
6412
6918
  function generateContentTypesIndex(contentTypeIds, _options) {
6413
6919
  const imports = contentTypeIds.map(
6414
- (id) => `from .${toSnakeCase(id)} import ${toPascalCase2(id)}, Create${toPascalCase2(id)}Input, Update${toPascalCase2(id)}Input`
6920
+ (id) => `from .${naming.snake(id)} import ${naming.pascal(id)}, Create${naming.pascal(id)}Input, Update${naming.pascal(id)}Input`
6415
6921
  );
6416
6922
  const exports = contentTypeIds.flatMap((id) => {
6417
- const className = toPascalCase2(id);
6923
+ const className = naming.pascal(id);
6418
6924
  return [`'${className}'`, `'Create${className}Input'`, `'Update${className}Input'`];
6419
6925
  });
6420
6926
  return `${imports.join("\n")}
@@ -6423,8 +6929,8 @@ __all__ = [${exports.join(", ")}]
6423
6929
  `;
6424
6930
  }
6425
6931
  function generateComponentsIndex(componentIds, _options) {
6426
- const imports = componentIds.map((id) => `from .${toSnakeCase(id)} import ${toPascalCase2(id)}`);
6427
- const exports = componentIds.map((id) => `'${toPascalCase2(id)}'`);
6932
+ const imports = componentIds.map((id) => `from .${naming.snake(id)} import ${naming.pascal(id)}`);
6933
+ const exports = componentIds.map((id) => `'${naming.pascal(id)}'`);
6428
6934
  return `${imports.join("\n")}
6429
6935
 
6430
6936
  __all__ = [${exports.join(", ")}]
@@ -6432,10 +6938,10 @@ __all__ = [${exports.join(", ")}]
6432
6938
  }
6433
6939
  function generateSingletonsIndex(singletonIds, _options) {
6434
6940
  const imports = singletonIds.map(
6435
- (id) => `from .${toSnakeCase(id)} import ${toPascalCase2(id)}, Update${toPascalCase2(id)}Input`
6941
+ (id) => `from .${naming.snake(id)} import ${naming.pascal(id)}, Update${naming.pascal(id)}Input`
6436
6942
  );
6437
6943
  const exports = singletonIds.flatMap((id) => {
6438
- const className = toPascalCase2(id);
6944
+ const className = naming.pascal(id);
6439
6945
  return [`'${className}'`, `'Update${className}Input'`];
6440
6946
  });
6441
6947
  return `${imports.join("\n")}
@@ -6444,8 +6950,10 @@ __all__ = [${exports.join(", ")}]
6444
6950
  `;
6445
6951
  }
6446
6952
  var plugin2, index_default2;
6447
- var init_dist3 = __esm({
6953
+ var init_dist5 = __esm({
6448
6954
  "../codegen-python/dist/index.js"() {
6955
+ init_dist2();
6956
+ init_dist2();
6449
6957
  plugin2 = {
6450
6958
  language: "python",
6451
6959
  async generate(ir, options) {
@@ -6659,16 +7167,23 @@ var init_diff_service = __esm({
6659
7167
  }
6660
7168
  });
6661
7169
 
6662
- // src/ai/core/stream-client.ts
7170
+ // src/ai/core/gateway-url.ts
6663
7171
  function getGatewayUrl() {
6664
7172
  if (process.env.RELYIO_AI_GATEWAY_URL) {
6665
7173
  return process.env.RELYIO_AI_GATEWAY_URL.replaceAll("http://localhost", "http://127.0.0.1");
6666
7174
  }
6667
- if (process.env.RELYIO_API_URL) {
6668
- return "http://127.0.0.1:3600";
6669
- }
6670
- return "https://ai-gateway.relyio.com";
7175
+ if (process.env.RELYIO_API_URL) return LOCAL_GATEWAY_URL;
7176
+ return PRODUCTION_GATEWAY_URL;
6671
7177
  }
7178
+ var PRODUCTION_GATEWAY_URL, LOCAL_GATEWAY_URL;
7179
+ var init_gateway_url = __esm({
7180
+ "src/ai/core/gateway-url.ts"() {
7181
+ PRODUCTION_GATEWAY_URL = "https://ai.relyio.com";
7182
+ LOCAL_GATEWAY_URL = "http://127.0.0.1:3600";
7183
+ }
7184
+ });
7185
+
7186
+ // src/ai/core/stream-client.ts
6672
7187
  function getAuthToken3() {
6673
7188
  const token = config_default.getAuthToken();
6674
7189
  if (token) return token;
@@ -6680,6 +7195,7 @@ var AI_GATEWAY_PATH, MAX_RETRIES, RETRY_DELAY_MS, REQUEST_TIMEOUT_MS, GatewayStr
6680
7195
  var init_stream_client = __esm({
6681
7196
  "src/ai/core/stream-client.ts"() {
6682
7197
  init_config();
7198
+ init_gateway_url();
6683
7199
  AI_GATEWAY_PATH = "/ai/cli";
6684
7200
  MAX_RETRIES = 2;
6685
7201
  RETRY_DELAY_MS = 1e3;
@@ -6830,13 +7346,6 @@ var init_stream_client = __esm({
6830
7346
  });
6831
7347
 
6832
7348
  // src/ai/services/handoff-client.ts
6833
- function getGatewayUrl2() {
6834
- if (process.env.RELYIO_AI_GATEWAY_URL) {
6835
- return process.env.RELYIO_AI_GATEWAY_URL.replaceAll("http://localhost", "http://127.0.0.1");
6836
- }
6837
- if (process.env.RELYIO_API_URL) return "http://127.0.0.1:3600";
6838
- return "https://ai-gateway.relyio.com";
6839
- }
6840
7349
  function getAuthToken4() {
6841
7350
  const token = config_default.getAuthToken();
6842
7351
  return token ?? null;
@@ -6847,7 +7356,7 @@ async function fetchHandoffSummary(req) {
6847
7356
  const controller = new AbortController();
6848
7357
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
6849
7358
  try {
6850
- const response = await fetch(`${getGatewayUrl2()}${AI_GATEWAY_HANDOFF_PATH}`, {
7359
+ const response = await fetch(`${getGatewayUrl()}${AI_GATEWAY_HANDOFF_PATH}`, {
6851
7360
  method: "POST",
6852
7361
  headers: {
6853
7362
  "Content-Type": "application/json",
@@ -6856,7 +7365,8 @@ async function fetchHandoffSummary(req) {
6856
7365
  body: JSON.stringify({
6857
7366
  fromAgent: req.fromAgent,
6858
7367
  toAgent: req.toAgent,
6859
- messages: req.messages
7368
+ messages: req.messages,
7369
+ projectId: req.projectId
6860
7370
  }),
6861
7371
  signal: controller.signal
6862
7372
  });
@@ -6873,6 +7383,7 @@ var init_handoff_client = __esm({
6873
7383
  "src/ai/services/handoff-client.ts"() {
6874
7384
  init_wire();
6875
7385
  init_config();
7386
+ init_gateway_url();
6876
7387
  AI_GATEWAY_HANDOFF_PATH = "/ai/cli/handoff/summary";
6877
7388
  REQUEST_TIMEOUT_MS2 = 15e3;
6878
7389
  }
@@ -8038,7 +8549,7 @@ var init_agent_loop = __esm({
8038
8549
  async tryReadFile(relPath) {
8039
8550
  if (!this.projectRoot) return void 0;
8040
8551
  try {
8041
- const fullPath = path3.resolve(this.projectRoot, relPath);
8552
+ const fullPath = path5.resolve(this.projectRoot, relPath);
8042
8553
  if (!fullPath.startsWith(this.projectRoot)) return void 0;
8043
8554
  return await readFile(fullPath, "utf8");
8044
8555
  } catch {
@@ -8199,7 +8710,9 @@ var init_agent_loop = __esm({
8199
8710
  const result = await fetchHandoffSummary({
8200
8711
  fromAgent,
8201
8712
  toAgent,
8202
- messages: this.messages
8713
+ messages: this.messages,
8714
+ // The gateway needs it to attribute the summary's tokens and cost to an org.
8715
+ projectId: this.options.context.projectId
8203
8716
  });
8204
8717
  if (result) {
8205
8718
  this.messages = [
@@ -8295,10 +8808,10 @@ var init_permission_gate = __esm({
8295
8808
  return PROTECTED_PATHS.some((p) => normalized.startsWith(p) || normalized.includes(`/${p}`));
8296
8809
  }
8297
8810
  isOutsideProjectRoot(filePath) {
8298
- if (path3.isAbsolute(filePath)) {
8811
+ if (path5.isAbsolute(filePath)) {
8299
8812
  return !filePath.startsWith(this.projectRoot);
8300
8813
  }
8301
- const resolved = path3.resolve(this.projectRoot, filePath);
8814
+ const resolved = path5.resolve(this.projectRoot, filePath);
8302
8815
  return !resolved.startsWith(this.projectRoot);
8303
8816
  }
8304
8817
  };
@@ -8447,7 +8960,7 @@ var init_context_provider = __esm({
8447
8960
  });
8448
8961
  const schemaFiles = [];
8449
8962
  for (const filePath of files) {
8450
- const fullPath = path3.join(projectRoot, filePath);
8963
+ const fullPath = path5.join(projectRoot, filePath);
8451
8964
  try {
8452
8965
  const stats = await stat2(fullPath);
8453
8966
  const contentType = this.inferContentType(filePath);
@@ -8515,7 +9028,7 @@ var init_read_file_handler = __esm({
8515
9028
  }
8516
9029
  async execute(input) {
8517
9030
  const filePath = input.path;
8518
- const fullPath = path3.resolve(this.projectRoot, filePath);
9031
+ const fullPath = path5.resolve(this.projectRoot, filePath);
8519
9032
  if (!fullPath.startsWith(this.projectRoot)) {
8520
9033
  return { error: "Path is outside project root" };
8521
9034
  }
@@ -8598,12 +9111,12 @@ var init_write_file_handler = __esm({
8598
9111
  async execute(input) {
8599
9112
  const filePath = input.path;
8600
9113
  const content = input.content;
8601
- const fullPath = path3.resolve(this.projectRoot, filePath);
9114
+ const fullPath = path5.resolve(this.projectRoot, filePath);
8602
9115
  if (!fullPath.startsWith(this.projectRoot)) {
8603
9116
  return { error: "Path is outside project root" };
8604
9117
  }
8605
9118
  try {
8606
- await mkdir(path3.dirname(fullPath), { recursive: true });
9119
+ await mkdir(path5.dirname(fullPath), { recursive: true });
8607
9120
  await writeFile(fullPath, content, "utf8");
8608
9121
  return { success: true, path: filePath, bytesWritten: content.length };
8609
9122
  } catch (error2) {
@@ -8627,7 +9140,7 @@ var init_apply_patch_handler = __esm({
8627
9140
  const filePath = input.path;
8628
9141
  const searchBlock = input.searchBlock;
8629
9142
  const replaceBlock = input.replaceBlock;
8630
- const fullPath = path3.resolve(this.projectRoot, filePath);
9143
+ const fullPath = path5.resolve(this.projectRoot, filePath);
8631
9144
  if (!fullPath.startsWith(this.projectRoot)) {
8632
9145
  return { error: "Path is outside project root" };
8633
9146
  }
@@ -10135,7 +10648,7 @@ function Banner() {
10135
10648
  label: "App",
10136
10649
  value: dev.sandbox.wiredEnv ? "wired" : "manual",
10137
10650
  valueColor: dev.sandbox.wiredEnv ? "green" : void 0,
10138
- suffix: dev.sandbox.wiredEnv ? void 0 : ".relyio/dev.json"
10651
+ suffix: dev.sandbox.wiredEnv ? void 0 : DEV_CONTEXT_FILE
10139
10652
  }
10140
10653
  ];
10141
10654
  } else {
@@ -10201,11 +10714,12 @@ function FieldRow({ field }) {
10201
10714
  var CLI_VERSION2, BRAND_GLYPH, BRAND_NAME, LABEL_WIDTH, TWO_COLUMN_MIN_COLS;
10202
10715
  var init_banner = __esm({
10203
10716
  "src/ai/repl/components/banner.tsx"() {
10717
+ init_dev_handshake();
10204
10718
  init_runtime_context();
10205
10719
  init_dev_cockpit_context();
10206
10720
  init_dev_cockpit_format();
10207
10721
  init_startup_info();
10208
- CLI_VERSION2 = "0.2.1" ;
10722
+ CLI_VERSION2 = "0.3.0" ;
10209
10723
  BRAND_GLYPH = "\u25B6";
10210
10724
  BRAND_NAME = "RelyIO";
10211
10725
  LABEL_WIDTH = 9;
@@ -10465,8 +10979,8 @@ function entryLabel(fields, contentType) {
10465
10979
  return title || contentType || "";
10466
10980
  }
10467
10981
  function itemTarget(tc) {
10468
- const path23 = tc.input.path;
10469
- if (path23) return path23;
10982
+ const path25 = tc.input.path;
10983
+ if (path25) return path25;
10470
10984
  switch (tc.toolName) {
10471
10985
  case "getSchemaReference": {
10472
10986
  const topic = tc.input.topic;
@@ -11194,8 +11708,8 @@ var init_input_preprocessor = __esm({
11194
11708
  var HISTORY_DIR, HISTORY_FILE, MAX_HISTORY_ENTRIES, PersistentHistory;
11195
11709
  var init_input_history = __esm({
11196
11710
  "src/ai/services/input-history.ts"() {
11197
- HISTORY_DIR = path3.join(os.homedir(), ".relyio");
11198
- HISTORY_FILE = path3.join(HISTORY_DIR, "ai-history");
11711
+ HISTORY_DIR = path5.join(os.homedir(), ".relyio");
11712
+ HISTORY_FILE = path5.join(HISTORY_DIR, "ai-history");
11199
11713
  MAX_HISTORY_ENTRIES = 1e3;
11200
11714
  PersistentHistory = class {
11201
11715
  entries = [];
@@ -11206,7 +11720,7 @@ var init_input_history = __esm({
11206
11720
  }
11207
11721
  load() {
11208
11722
  try {
11209
- fs2.mkdirSync(path3.dirname(this.filePath), { recursive: true });
11723
+ fs2.mkdirSync(path5.dirname(this.filePath), { recursive: true });
11210
11724
  if (fs2.existsSync(this.filePath)) {
11211
11725
  const content = fs2.readFileSync(this.filePath, "utf8");
11212
11726
  this.entries = content.split("\n").filter(Boolean);
@@ -11236,7 +11750,7 @@ var init_input_history = __esm({
11236
11750
  }
11237
11751
  save() {
11238
11752
  try {
11239
- fs2.mkdirSync(path3.dirname(this.filePath), { recursive: true });
11753
+ fs2.mkdirSync(path5.dirname(this.filePath), { recursive: true });
11240
11754
  fs2.writeFileSync(this.filePath, this.entries.join("\n") + "\n");
11241
11755
  } catch {
11242
11756
  }
@@ -11245,18 +11759,18 @@ var init_input_history = __esm({
11245
11759
  }
11246
11760
  });
11247
11761
  function readMentionedFile(filePath, projectRoot) {
11248
- const resolved = path3.isAbsolute(filePath) ? filePath : path3.resolve(projectRoot, filePath);
11762
+ const resolved = path5.isAbsolute(filePath) ? filePath : path5.resolve(projectRoot, filePath);
11249
11763
  try {
11250
11764
  const stat2 = fs2.statSync(resolved);
11251
11765
  if (stat2.size > MAX_FILE_SIZE) {
11252
11766
  const content2 = fs2.readFileSync(resolved, "utf8").slice(0, MAX_FILE_SIZE);
11253
11767
  const lines2 = content2.split("\n").length;
11254
- const relativePath2 = path3.relative(projectRoot, resolved);
11768
+ const relativePath2 = path5.relative(projectRoot, resolved);
11255
11769
  return { filePath: relativePath2, content: content2, lines: lines2, truncated: true };
11256
11770
  }
11257
11771
  const content = fs2.readFileSync(resolved, "utf8");
11258
11772
  const lines = content.split("\n").length;
11259
- const relativePath = path3.relative(projectRoot, resolved);
11773
+ const relativePath = path5.relative(projectRoot, resolved);
11260
11774
  return { filePath: relativePath, content, lines, truncated: false };
11261
11775
  } catch {
11262
11776
  return null;
@@ -12128,11 +12642,11 @@ var init_autocomplete = __esm({
12128
12642
  const schemaMatches = this.schemaFiles.filter((f) => f.toLowerCase().includes(query.toLowerCase())).slice(0, 15).map((f) => ({ value: f, label: f, kind: "file" }));
12129
12643
  if (schemaMatches.length > 0) return schemaMatches;
12130
12644
  try {
12131
- const dir = query.includes("/") ? path3.join(this.projectRoot, path3.dirname(query)) : this.projectRoot;
12132
- const prefix = query.includes("/") ? path3.basename(query) : query;
12645
+ const dir = query.includes("/") ? path5.join(this.projectRoot, path5.dirname(query)) : this.projectRoot;
12646
+ const prefix = query.includes("/") ? path5.basename(query) : query;
12133
12647
  const entries = fs2.readdirSync(dir, { withFileTypes: true });
12134
12648
  return entries.filter((e) => e.name.startsWith(prefix) && !e.name.startsWith(".")).map((e) => {
12135
- const rel = path3.relative(this.projectRoot, path3.join(dir, e.name));
12649
+ const rel = path5.relative(this.projectRoot, path5.join(dir, e.name));
12136
12650
  const value = e.isDirectory() ? `${rel}/` : rel;
12137
12651
  return { value, label: value, kind: "file" };
12138
12652
  }).slice(0, 15);
@@ -12459,13 +12973,6 @@ var init_agent_picker = __esm({
12459
12973
  });
12460
12974
 
12461
12975
  // src/ai/services/models-client.ts
12462
- function getGatewayUrl3() {
12463
- if (process.env.RELYIO_AI_GATEWAY_URL) {
12464
- return process.env.RELYIO_AI_GATEWAY_URL.replaceAll("http://localhost", "http://127.0.0.1");
12465
- }
12466
- if (process.env.RELYIO_API_URL) return "http://127.0.0.1:3600";
12467
- return "https://ai-gateway.relyio.com";
12468
- }
12469
12976
  function getAuthToken5() {
12470
12977
  const token = config_default.getAuthToken();
12471
12978
  if (token) return token;
@@ -12500,6 +13007,7 @@ var init_models_client = __esm({
12500
13007
  "src/ai/services/models-client.ts"() {
12501
13008
  init_wire();
12502
13009
  init_config();
13010
+ init_gateway_url();
12503
13011
  AI_GATEWAY_MODELS_PATH = "/models";
12504
13012
  ModelsClient = class {
12505
13013
  // In-process cache. Lifetime = CLI process. See file header for rationale.
@@ -12520,7 +13028,7 @@ var init_models_client = __esm({
12520
13028
  this.cached = null;
12521
13029
  }
12522
13030
  async fetchModels() {
12523
- const url = `${getGatewayUrl3()}${AI_GATEWAY_MODELS_PATH}`;
13031
+ const url = `${getGatewayUrl()}${AI_GATEWAY_MODELS_PATH}`;
12524
13032
  const response = await fetch(url, {
12525
13033
  method: "GET",
12526
13034
  headers: {
@@ -12599,8 +13107,7 @@ function ModelPicker() {
12599
13107
  description = `${description} \xB7 now routing to ${resolvedModelId}`;
12600
13108
  }
12601
13109
  if (!m.available) {
12602
- const reason = m.disabledReason === "paused" ? "AI is globally paused" : "admin-disabled for this workspace";
12603
- description = `${description} \xB7 ${reason}`;
13110
+ description = `${description} \xB7 admin-disabled for this workspace`;
12604
13111
  }
12605
13112
  return {
12606
13113
  id: m.id,
@@ -13990,7 +14497,7 @@ var init_use_confirmation = __esm({
13990
14497
  var SESSIONS_DIR, SESSION_VERSION, SessionManager;
13991
14498
  var init_session_manager = __esm({
13992
14499
  "src/ai/services/session-manager.ts"() {
13993
- SESSIONS_DIR = path3.join(os.homedir(), ".relyio", "ai-sessions");
14500
+ SESSIONS_DIR = path5.join(os.homedir(), ".relyio", "ai-sessions");
13994
14501
  SESSION_VERSION = 4;
13995
14502
  SessionManager = class {
13996
14503
  sessionsDir;
@@ -13999,13 +14506,13 @@ var init_session_manager = __esm({
13999
14506
  fs2.mkdirSync(this.sessionsDir, { recursive: true });
14000
14507
  }
14001
14508
  save(session) {
14002
- const filePath = path3.join(this.sessionsDir, `${session.id}.json`);
14509
+ const filePath = path5.join(this.sessionsDir, `${session.id}.json`);
14003
14510
  session.version = SESSION_VERSION;
14004
14511
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
14005
14512
  fs2.writeFileSync(filePath, JSON.stringify(session, null, 2));
14006
14513
  }
14007
14514
  load(sessionId) {
14008
- const filePath = path3.join(this.sessionsDir, `${sessionId}.json`);
14515
+ const filePath = path5.join(this.sessionsDir, `${sessionId}.json`);
14009
14516
  try {
14010
14517
  const content = fs2.readFileSync(filePath, "utf8");
14011
14518
  const parsed = JSON.parse(content);
@@ -14062,7 +14569,7 @@ var init_session_manager = __esm({
14062
14569
  const files = fs2.readdirSync(this.sessionsDir).filter((f) => f.endsWith(".json"));
14063
14570
  return files.map((f) => {
14064
14571
  try {
14065
- const content = fs2.readFileSync(path3.join(this.sessionsDir, f), "utf8");
14572
+ const content = fs2.readFileSync(path5.join(this.sessionsDir, f), "utf8");
14066
14573
  const parsed = JSON.parse(content);
14067
14574
  if (parsed.version !== SESSION_VERSION && parsed.version !== 3 && parsed.version !== 2) {
14068
14575
  return null;
@@ -14080,7 +14587,7 @@ var init_session_manager = __esm({
14080
14587
  return this.listAll().slice(0, count);
14081
14588
  }
14082
14589
  delete(sessionId) {
14083
- const filePath = path3.join(this.sessionsDir, `${sessionId}.json`);
14590
+ const filePath = path5.join(this.sessionsDir, `${sessionId}.json`);
14084
14591
  try {
14085
14592
  fs2.unlinkSync(filePath);
14086
14593
  return true;
@@ -14118,7 +14625,7 @@ var init_snapshot_store = __esm({
14118
14625
  SnapshotStore = class {
14119
14626
  dir;
14120
14627
  constructor(sessionDir) {
14121
- this.dir = path3.join(sessionDir, "snapshots");
14628
+ this.dir = path5.join(sessionDir, "snapshots");
14122
14629
  fs2.mkdirSync(this.dir, { recursive: true });
14123
14630
  }
14124
14631
  /**
@@ -14179,7 +14686,7 @@ var init_snapshot_store = __esm({
14179
14686
  const hash = entry.slice(0, -4);
14180
14687
  if (keepHashes.has(hash)) continue;
14181
14688
  try {
14182
- fs2.unlinkSync(path3.join(this.dir, entry));
14689
+ fs2.unlinkSync(path5.join(this.dir, entry));
14183
14690
  removed++;
14184
14691
  } catch {
14185
14692
  }
@@ -14187,7 +14694,7 @@ var init_snapshot_store = __esm({
14187
14694
  return removed;
14188
14695
  }
14189
14696
  pathFor(hash) {
14190
- return path3.join(this.dir, `${hash}.bin`);
14697
+ return path5.join(this.dir, `${hash}.bin`);
14191
14698
  }
14192
14699
  };
14193
14700
  }
@@ -14202,7 +14709,7 @@ var init_decision_log = __esm({
14202
14709
  DecisionLog = class {
14203
14710
  filePath;
14204
14711
  constructor(sessionDir) {
14205
- this.filePath = path3.join(sessionDir, "decisions.jsonl");
14712
+ this.filePath = path5.join(sessionDir, "decisions.jsonl");
14206
14713
  if (!fs2.existsSync(this.filePath)) {
14207
14714
  fs2.writeFileSync(this.filePath, "", "utf8");
14208
14715
  }
@@ -14327,7 +14834,7 @@ var init_session_history = __esm({
14327
14834
  "src/ai/sessions/session-history.ts"() {
14328
14835
  init_snapshot_store();
14329
14836
  init_decision_log();
14330
- SESSIONS_ROOT = path3.join(os.homedir(), ".relyio", "ai-sessions");
14837
+ SESSIONS_ROOT = path5.join(os.homedir(), ".relyio", "ai-sessions");
14331
14838
  MAX_UNDOABLE_EVENTS = 200;
14332
14839
  SessionHistory = class {
14333
14840
  sessionDir;
@@ -14336,7 +14843,7 @@ var init_session_history = __esm({
14336
14843
  projectRoot;
14337
14844
  constructor(args) {
14338
14845
  const root = args.sessionsRoot ?? SESSIONS_ROOT;
14339
- this.sessionDir = path3.join(root, args.sessionId);
14846
+ this.sessionDir = path5.join(root, args.sessionId);
14340
14847
  fs2.mkdirSync(this.sessionDir, { recursive: true });
14341
14848
  this.snapshots = new SnapshotStore(this.sessionDir);
14342
14849
  this.log = new DecisionLog(this.sessionDir);
@@ -14497,11 +15004,11 @@ var init_session_history = __esm({
14497
15004
  * files via a malformed log entry.
14498
15005
  */
14499
15006
  async writeFileSafe(relPath, content) {
14500
- const fullPath = path3.resolve(this.projectRoot, relPath);
15007
+ const fullPath = path5.resolve(this.projectRoot, relPath);
14501
15008
  if (!fullPath.startsWith(this.projectRoot)) {
14502
15009
  throw new Error(`Refusing to write outside project root: ${relPath}`);
14503
15010
  }
14504
- await fs2.promises.mkdir(path3.dirname(fullPath), { recursive: true });
15011
+ await fs2.promises.mkdir(path5.dirname(fullPath), { recursive: true });
14505
15012
  await fs2.promises.writeFile(fullPath, content, "utf8");
14506
15013
  }
14507
15014
  };
@@ -14509,10 +15016,10 @@ var init_session_history = __esm({
14509
15016
  });
14510
15017
  function renameLocalSessionArtifacts(oldId, newId) {
14511
15018
  if (oldId === newId) return;
14512
- const oldTranscript = path3.join(SESSIONS_DIR2, `${oldId}.json`);
14513
- const newTranscript = path3.join(SESSIONS_DIR2, `${newId}.json`);
14514
- const oldHistoryDir = path3.join(SESSIONS_DIR2, oldId);
14515
- const newHistoryDir = path3.join(SESSIONS_DIR2, newId);
15019
+ const oldTranscript = path5.join(SESSIONS_DIR2, `${oldId}.json`);
15020
+ const newTranscript = path5.join(SESSIONS_DIR2, `${newId}.json`);
15021
+ const oldHistoryDir = path5.join(SESSIONS_DIR2, oldId);
15022
+ const newHistoryDir = path5.join(SESSIONS_DIR2, newId);
14516
15023
  try {
14517
15024
  if (fs2.existsSync(oldTranscript) && !fs2.existsSync(newTranscript)) {
14518
15025
  fs2.renameSync(oldTranscript, newTranscript);
@@ -14616,18 +15123,11 @@ var init_use_session_persistence = __esm({
14616
15123
  init_runtime_context();
14617
15124
  init_session_manager();
14618
15125
  init_session_history();
14619
- SESSIONS_DIR2 = path3.join(os.homedir(), ".relyio", "ai-sessions");
15126
+ SESSIONS_DIR2 = path5.join(os.homedir(), ".relyio", "ai-sessions");
14620
15127
  }
14621
15128
  });
14622
15129
 
14623
15130
  // src/ai/services/proposals-client.ts
14624
- function getGatewayUrl4() {
14625
- if (process.env.RELYIO_AI_GATEWAY_URL) {
14626
- return process.env.RELYIO_AI_GATEWAY_URL.replaceAll("http://localhost", "http://127.0.0.1");
14627
- }
14628
- if (process.env.RELYIO_API_URL) return "http://127.0.0.1:3600";
14629
- return "https://ai-gateway.relyio.com";
14630
- }
14631
15131
  function getAuthToken6() {
14632
15132
  const token = config_default.getAuthToken();
14633
15133
  if (token) return token;
@@ -14642,10 +15142,11 @@ var init_proposals_client = __esm({
14642
15142
  "src/ai/services/proposals-client.ts"() {
14643
15143
  init_wire();
14644
15144
  init_config();
15145
+ init_gateway_url();
14645
15146
  AI_GATEWAY_PROPOSALS_PATH = "/ai/proposals";
14646
15147
  ProposalsClient = class {
14647
- async request(method, path23, init = {}) {
14648
- const url = new URL(`${getGatewayUrl4()}${AI_GATEWAY_PROPOSALS_PATH}${path23}`);
15148
+ async request(method, path25, init = {}) {
15149
+ const url = new URL(`${getGatewayUrl()}${AI_GATEWAY_PROPOSALS_PATH}${path25}`);
14649
15150
  if (init.query) {
14650
15151
  for (const [k, v] of Object.entries(init.query)) url.searchParams.set(k, v);
14651
15152
  }
@@ -14661,7 +15162,7 @@ var init_proposals_client = __esm({
14661
15162
  const raw = await response.text().catch(() => "");
14662
15163
  const refusal = readError(parseJson(raw));
14663
15164
  throw new Error(
14664
- refusal === void 0 ? `Proposals API ${method} ${path23} \u2192 ${response.status}: ${raw}` : `Proposals API ${method} ${path23} \u2192 ${response.status} ${refusal.code}: ${refusal.messages.join(" ")}`
15165
+ refusal === void 0 ? `Proposals API ${method} ${path25} \u2192 ${response.status}: ${raw}` : `Proposals API ${method} ${path25} \u2192 ${response.status} ${refusal.code}: ${refusal.messages.join(" ")}`
14665
15166
  );
14666
15167
  }
14667
15168
  return readPayload(await response.json());
@@ -15439,8 +15940,8 @@ function formatHistoryEntries(entries, status, limit) {
15439
15940
  const rows = entries.map((e) => {
15440
15941
  const time = new Date(e.timestamp).toLocaleTimeString();
15441
15942
  const glyph = decisionGlyph(e.kind);
15442
- const path23 = e.path || "\u2014";
15443
- return ` ${time} ${glyph} ${path23}`;
15943
+ const path25 = e.path || "\u2014";
15944
+ return ` ${time} ${glyph} ${path25}`;
15444
15945
  });
15445
15946
  return [header, "", ...rows, "", footer].join("\n");
15446
15947
  }
@@ -15678,11 +16179,10 @@ Tip: just type \`/model\` (no args) to pick interactively.`
15678
16179
  return;
15679
16180
  }
15680
16181
  if (!match.available) {
15681
- const reason = match.disabledReason === "paused" ? "AI is paused globally" : "this model is currently disabled by the workspace admin";
15682
16182
  ctx.dispatch({
15683
16183
  type: "SYSTEM_MESSAGE_APPENDED",
15684
16184
  label: "/model",
15685
- text: `Cannot switch to ${match.name} \u2014 ${reason}.`
16185
+ text: `Cannot switch to ${match.name} \u2014 this model is currently disabled by the workspace admin.`
15686
16186
  });
15687
16187
  return;
15688
16188
  }
@@ -16068,7 +16568,7 @@ async function resolveContext(spec, flags, pins = {}) {
16068
16568
  acc.datasetId = pins.datasetId;
16069
16569
  acc.datasetName = await safeDatasetName(projectId, acc.branchId, pins.datasetId);
16070
16570
  } else {
16071
- const datasetOutcome = await resolveDataset(projectId, acc.branchId, flags.dataset);
16571
+ const datasetOutcome = await resolveDataset2(projectId, acc.branchId, flags.dataset);
16072
16572
  if (datasetOutcome.kind === "needs-pick") {
16073
16573
  return { ...datasetOutcome, partial: acc };
16074
16574
  }
@@ -16142,7 +16642,7 @@ async function safeBranchName(projectId, branchId) {
16142
16642
  return void 0;
16143
16643
  }
16144
16644
  }
16145
- async function resolveDataset(projectId, branchId, flag) {
16645
+ async function resolveDataset2(projectId, branchId, flag) {
16146
16646
  let datasets;
16147
16647
  try {
16148
16648
  datasets = await manage_default.getProjectDatasets(projectId, { branchId });
@@ -16722,223 +17222,55 @@ function confirm(message) {
16722
17222
  // src/api/auth.ts
16723
17223
  init_client();
16724
17224
  init_config();
17225
+ init_dist();
16725
17226
  var isDebug3 = () => process.env.DEBUG === "true" || process.env.DEBUG === "1";
16726
- var HTML_TEMPLATES = {
16727
- success: (tokenName) => `
16728
- <html>
16729
- <head>
16730
- <title>RelyIO Authentication</title>
16731
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
16732
- <style>
16733
- body {
16734
- font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16735
- text-align: center;
16736
- padding: 2rem;
16737
- max-width: 600px;
16738
- margin: 0 auto;
16739
- line-height: 1.5;
16740
- color: #333;
16741
- }
16742
- h1 { color: #38a169; margin-bottom: 1rem; }
16743
- p { margin-bottom: 1rem; }
16744
- </style>
16745
- </head>
16746
- <body>
16747
- <h1>Authentication Successful</h1>
16748
- <p>${tokenName ? `Personal access token "${tokenName}" created successfully.` : "You have successfully authenticated."}</p>
16749
- <p>You can close this window and return to the CLI.</p>
16750
- </body>
16751
- </html>
16752
- `,
16753
- error: (message) => `
16754
- <html>
16755
- <head>
16756
- <title>RelyIO Authentication Error</title>
16757
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
16758
- <style>
16759
- body {
16760
- font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16761
- text-align: center;
16762
- padding: 2rem;
16763
- max-width: 600px;
16764
- margin: 0 auto;
16765
- line-height: 1.5;
16766
- color: #333;
16767
- }
16768
- h1 { color: #e53e3e; margin-bottom: 1rem; }
16769
- p { margin-bottom: 1rem; }
16770
- </style>
16771
- </head>
16772
- <body>
16773
- <h1>Authentication Failed</h1>
16774
- <p>${message}</p>
16775
- <p>Please try again or contact support if the issue persists.</p>
16776
- </body>
16777
- </html>
16778
- `,
16779
- serverError: (message) => `
16780
- <html>
16781
- <head>
16782
- <title>RelyIO Server Error</title>
16783
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
16784
- <style>
16785
- body {
16786
- font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16787
- text-align: center;
16788
- padding: 2rem;
16789
- max-width: 600px;
16790
- margin: 0 auto;
16791
- line-height: 1.5;
16792
- color: #333;
16793
- }
16794
- h1 { color: #e53e3e; margin-bottom: 1rem; }
16795
- p { margin-bottom: 1rem; }
16796
- </style>
16797
- </head>
16798
- <body>
16799
- <h1>Internal Error</h1>
16800
- <p>An unexpected error occurred during authentication:</p>
16801
- <p>${message}</p>
16802
- <p>Please try again or contact support if the issue persists.</p>
16803
- </body>
16804
- </html>
16805
- `
16806
- };
17227
+ var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
16807
17228
  async function login(email, password) {
16808
17229
  const response = await client_default.post("/auth/login", { email, password });
16809
17230
  await config_default.storeAuthToken(response.token);
16810
17231
  return response;
16811
17232
  }
16812
17233
  async function browserLogin() {
16813
- const server = http.createServer();
16814
- await new Promise((resolve) => server.listen(0, resolve));
16815
- const { port } = server.address();
16816
- const callbackUrl = `http://localhost:${port}`;
16817
- const deviceName = os__default.hostname();
16818
17234
  const dashboardUrl = config_default.getDashboardUrl();
16819
17235
  if (!dashboardUrl) {
16820
17236
  throw new Error(
16821
17237
  "Dashboard URL is not configured. Please run the command with --dashboard-url option."
16822
17238
  );
16823
17239
  }
16824
- const authUrl = `${dashboardUrl}/auth/cli-login?callback=${encodeURIComponent(callbackUrl)}&device_name=${encodeURIComponent(deviceName)}`;
17240
+ const state = randomBytes(32).toString("base64url");
17241
+ const server = await startCallbackServer({ callbackPath: "/" });
16825
17242
  try {
16826
- await open(authUrl);
16827
- console.log(success("Browser opened successfully"));
16828
- } catch (error2) {
16829
- console.error(error(`Failed to open browser: ${error2.message}`));
16830
- throw new Error(`Failed to open browser: ${error2.message}`);
16831
- }
16832
- console.log(info("Waiting for authentication to complete..."));
16833
- const tokenData = await new Promise((resolve, reject) => {
16834
- const timeoutId = setTimeout(
16835
- () => {
16836
- cleanupServer(server);
16837
- reject(new Error("Authentication timed out. Please try again."));
16838
- },
16839
- 5 * 60 * 1e3
16840
- );
16841
- let authProcessed = false;
16842
- server.on("request", async (req, res) => {
16843
- try {
16844
- const reqUrl = new URL(req.url || "", `http://localhost:${port}`);
16845
- const params = reqUrl.searchParams;
16846
- const token = params.get("token");
16847
- const expires_at = params.get("expires_at");
16848
- const token_name = params.get("token_name");
16849
- if (!authProcessed && token) {
16850
- authProcessed = true;
16851
- clearTimeout(timeoutId);
16852
- try {
16853
- await (token_name && expires_at ? config_default.storePAT(token, token_name, expires_at) : config_default.storeAuthToken(token));
16854
- sendHtmlResponse(res, 200, HTML_TEMPLATES.success(token_name || void 0));
16855
- let user;
16856
- try {
16857
- user = await getCurrentUser();
16858
- } catch (userError) {
16859
- if (isDebug3()) console.error("[relyio-cli][debug] fetch user failed:", userError);
16860
- user = {
16861
- id: "unknown",
16862
- email: "unknown",
16863
- name: "Unknown User"
16864
- };
16865
- }
16866
- cleanupServer(server);
16867
- resolve({
16868
- token,
16869
- user
16870
- });
16871
- } catch (storageError) {
16872
- if (isDebug3()) console.error("[relyio-cli][debug] token storage:", storageError);
16873
- sendHtmlResponse(
16874
- res,
16875
- 500,
16876
- HTML_TEMPLATES.serverError("Failed to store authentication token")
16877
- );
16878
- cleanupServer(server);
16879
- reject(storageError);
16880
- }
16881
- } else if (!authProcessed && params.has("error")) {
16882
- authProcessed = true;
16883
- const errorMsg = params.get("error") || "Authentication failed or was canceled.";
16884
- sendHtmlResponse(res, 400, HTML_TEMPLATES.error(errorMsg));
16885
- cleanupServer(server);
16886
- reject(new Error(errorMsg));
16887
- } else {
16888
- if (!token && !authProcessed) {
16889
- if (!res.headersSent) {
16890
- res.writeHead(200, { "Content-Type": "text/plain" });
16891
- res.end("Waiting for authentication to complete...");
16892
- }
16893
- } else if (authProcessed && !res.headersSent) {
16894
- res.writeHead(200, { "Content-Type": "text/plain" });
16895
- res.end("Authentication already processed. You can close this window.");
16896
- }
16897
- }
16898
- } catch (error2) {
16899
- if (isDebug3()) console.error("[relyio-cli][debug] callback processing:", error2);
16900
- if (authProcessed) {
16901
- if (!res.headersSent) {
16902
- res.writeHead(500, { "Content-Type": "text/plain" });
16903
- res.end("Error occurred, but authentication was already processed.");
16904
- }
16905
- } else {
16906
- authProcessed = true;
16907
- if (!res.headersSent) {
16908
- sendHtmlResponse(
16909
- res,
16910
- 500,
16911
- HTML_TEMPLATES.serverError(error2.message || "Unknown error")
16912
- );
16913
- }
16914
- cleanupServer(server);
16915
- reject(error2);
16916
- }
16917
- }
16918
- });
16919
- });
16920
- return tokenData;
16921
- }
16922
- function sendHtmlResponse(res, statusCode, html) {
16923
- if (!res.headersSent) {
16924
- res.writeHead(statusCode, {
16925
- "Content-Type": "text/html; charset=utf-8",
16926
- "Cache-Control": "no-store, no-cache, must-revalidate"
16927
- });
16928
- res.end(html);
17243
+ const authUrl = new URL("/auth/cli-login", dashboardUrl);
17244
+ authUrl.searchParams.set("callback", server.redirectUri);
17245
+ authUrl.searchParams.set("device_name", os__default.hostname());
17246
+ authUrl.searchParams.set("state", state);
17247
+ try {
17248
+ await open(authUrl.toString());
17249
+ console.log(success("Browser opened successfully"));
17250
+ } catch (error2) {
17251
+ console.error(error(`Failed to open browser: ${error2.message}`));
17252
+ throw new Error(`Failed to open browser: ${error2.message}`);
17253
+ }
17254
+ console.log(info("Waiting for authentication to complete..."));
17255
+ const handOff = await server.waitForCallback(state, LOGIN_TIMEOUT_MS);
17256
+ const token = handOff.get("token");
17257
+ const tokenName = handOff.get("token_name");
17258
+ const expiresAt = handOff.get("expires_at");
17259
+ if (token === null || token === "") {
17260
+ throw new Error("The sign-in callback carried no token.");
17261
+ }
17262
+ await (tokenName && expiresAt ? config_default.storePAT(token, tokenName, expiresAt) : config_default.storeAuthToken(token));
17263
+ return { token, user: await currentUserOrStub() };
17264
+ } finally {
17265
+ server.close();
16929
17266
  }
16930
17267
  }
16931
- function cleanupServer(server) {
17268
+ async function currentUserOrStub() {
16932
17269
  try {
16933
- server.close();
16934
- if (typeof server.closeAllConnections === "function") {
16935
- server.closeAllConnections();
16936
- }
16937
- const exitTimeout = setTimeout(() => {
16938
- }, 100);
16939
- exitTimeout.unref();
16940
- } catch (error2) {
16941
- if (isDebug3()) console.error("[relyio-cli][debug] server close:", error2);
17270
+ return await getCurrentUser();
17271
+ } catch (userError) {
17272
+ if (isDebug3()) console.error("[relyio-cli][debug] fetch user failed:", userError);
17273
+ return { id: "unknown", email: "unknown", name: "Unknown User" };
16942
17274
  }
16943
17275
  }
16944
17276
  async function logout() {
@@ -17025,18 +17357,17 @@ init_client();
17025
17357
  async function getProjects() {
17026
17358
  return await client_default.get("/projects");
17027
17359
  }
17028
- var __filename = fileURLToPath(import.meta.url);
17029
- var __dirname = path3.dirname(__filename);
17360
+ var __dirname = path5.dirname(fileURLToPath(import.meta.url));
17030
17361
  function getTemplatesDir() {
17031
17362
  const override = process.env.RELYIO_CLI_TEMPLATES_DIR;
17032
17363
  const candidates = [
17033
- override ? path3.resolve(override) : null,
17364
+ override ? path5.resolve(override) : null,
17034
17365
  // dist/index.js → ../templates (built layout)
17035
- path3.resolve(__dirname, "..", "templates"),
17036
- // src/commands/init.ts → ../../templates (source layout)
17037
- path3.resolve(__dirname, "..", "..", "templates"),
17038
- // src/commands/init.ts → ../../../templates (just in case dist gets nested)
17039
- path3.resolve(__dirname, "..", "..", "..", "templates")
17366
+ path5.resolve(__dirname, "..", "templates"),
17367
+ // src/utils/templates-dir.ts → ../../templates (source layout)
17368
+ path5.resolve(__dirname, "..", "..", "templates"),
17369
+ // src/utils/templates-dir.ts → ../../../templates (just in case dist gets nested)
17370
+ path5.resolve(__dirname, "..", "..", "..", "templates")
17040
17371
  ].filter((p) => p !== null);
17041
17372
  for (const candidate of candidates) {
17042
17373
  if (existsSync(candidate)) return candidate;
@@ -17047,10 +17378,402 @@ function getTemplatesDir() {
17047
17378
  Set RELYIO_CLI_TEMPLATES_DIR to point at the templates folder, or reinstall the CLI.`
17048
17379
  );
17049
17380
  }
17381
+ function schemaTemplateNames(templatesDir) {
17382
+ return readdirSync(templatesDir).filter((item) => {
17383
+ const fullPath = path5.join(templatesDir, item);
17384
+ return statSync(fullPath).isDirectory() && item !== "node_modules" && !item.startsWith(".") && existsSync(path5.join(fullPath, "schemas"));
17385
+ }).sort();
17386
+ }
17387
+
17388
+ // src/commands/init-next.ts
17389
+ init_config();
17390
+ init_manage();
17391
+ var NEXT_ENV = {
17392
+ apiKey: "RELYIO_API_KEY",
17393
+ previewApiKey: "RELYIO_PREVIEW_API_KEY",
17394
+ webhookSecret: "RELYIO_WEBHOOK_SECRET"
17395
+ };
17396
+ var REVALIDATE_PATH = "/api/relyio/revalidate";
17397
+ var REVALIDATION_EVENTS = [
17398
+ "entry.published",
17399
+ "entry.unpublished",
17400
+ "entry.deleted",
17401
+ "entry.archived",
17402
+ "entry.restored"
17403
+ ];
17404
+ var WEBHOOK_ACTION_SLUG = "http-post";
17405
+ var ENV_FILE = ".env.local";
17406
+ var FRAMEWORK_DIR = path5.join("frameworks", "next");
17407
+ var TEMPLATE_SUFFIX = ".tmpl";
17408
+ var DATASET_PLACEHOLDER = "{{DATASET}}";
17409
+ var NEXT_CONFIG = "next.config.ts";
17410
+ var NEXT_CONFIG_NAMES = [
17411
+ "next.config.ts",
17412
+ "next.config.mts",
17413
+ "next.config.js",
17414
+ "next.config.mjs",
17415
+ "next.config.cjs"
17416
+ ];
17417
+ function revalidationUrl(siteUrl) {
17418
+ let parsed;
17419
+ try {
17420
+ parsed = new URL(siteUrl);
17421
+ } catch {
17422
+ throw new Error(`Outbound URL is not a valid URL: ${siteUrl}`);
17423
+ }
17424
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
17425
+ throw new Error(
17426
+ `Outbound URL protocol "${parsed.protocol}" is not allowed (only http: and https:)`
17427
+ );
17428
+ }
17429
+ const hostname = parsed.hostname.toLowerCase();
17430
+ if (["localhost", "broadcasthost", "ip6-localhost", "ip6-loopback"].includes(hostname) || hostname.endsWith(".localhost") || hostname.endsWith(".local")) {
17431
+ throw new Error(
17432
+ `Outbound URL hostname "${hostname}" is not allowed. The webhook is sent to your deployed site; pass its public URL as --site-url.`
17433
+ );
17434
+ }
17435
+ const address = hostname.replaceAll(/^\[|\]$/g, "");
17436
+ if (isLocalAddress(address)) {
17437
+ throw new Error(
17438
+ `Outbound URL IP "${address}" is in a private/loopback range. The webhook is sent to your deployed site; pass its public URL as --site-url.`
17439
+ );
17440
+ }
17441
+ const base = `${parsed.origin}${parsed.pathname.replace(/\/+$/, "")}`;
17442
+ return `${base}${REVALIDATE_PATH}`;
17443
+ }
17444
+ function isLocalAddress(address) {
17445
+ const v4 = /^(\d{1,3})\.(\d{1,3})\.\d{1,3}\.\d{1,3}$/.exec(address);
17446
+ if (v4) {
17447
+ const a = Number(v4[1]);
17448
+ const b = Number(v4[2]);
17449
+ return a === 0 || a === 127 || a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
17450
+ }
17451
+ if (!address.includes(":")) return false;
17452
+ return address === "::" || address === "::1" || /^f[cd][0-9a-f]{2}:/.test(address) || /^fe[89ab][0-9a-f]:/.test(address);
17453
+ }
17454
+ function assignmentOf(name) {
17455
+ return new RegExp(`^(\\s*(?:export\\s+)?)${name}(?:\\s*=|:\\s)(.*)$`);
17456
+ }
17457
+ function parseEnvValue(raw) {
17458
+ const value = raw.trim();
17459
+ const quote = value[0];
17460
+ if (quote === '"' || quote === "'" || quote === "`") {
17461
+ const end = value.indexOf(quote, 1);
17462
+ return end === -1 ? value.slice(1) : value.slice(1, end);
17463
+ }
17464
+ return (value.split("#")[0] ?? "").trim();
17465
+ }
17466
+ function readEnvValue(content, name) {
17467
+ if (content === null) return void 0;
17468
+ const pattern = assignmentOf(name);
17469
+ let value;
17470
+ for (const line of content.split(/\r?\n/)) {
17471
+ const match = pattern.exec(line);
17472
+ if (match) value = parseEnvValue(match[2] ?? "");
17473
+ }
17474
+ return value === void 0 || value === "" ? void 0 : value;
17475
+ }
17476
+ function upsertEnv(content, values) {
17477
+ const source = content ?? "";
17478
+ const eol = source.includes("\r\n") ? "\r\n" : "\n";
17479
+ const lines = source.split(/\r?\n/);
17480
+ if (lines.at(-1) === "") lines.pop();
17481
+ const written = [];
17482
+ const kept = [];
17483
+ for (const [name, value] of Object.entries(values)) {
17484
+ if (readEnvValue(source, name) !== void 0) {
17485
+ kept.push(name);
17486
+ continue;
17487
+ }
17488
+ const pattern = assignmentOf(name);
17489
+ let last = -1;
17490
+ for (const [index, line] of lines.entries()) {
17491
+ if (pattern.test(line)) last = index;
17492
+ }
17493
+ if (last === -1) {
17494
+ lines.push(`${name}=${value}`);
17495
+ } else {
17496
+ const prefix = pattern.exec(lines[last] ?? "")?.[1] ?? "";
17497
+ lines[last] = `${prefix}${name}=${value}`;
17498
+ }
17499
+ written.push(name);
17500
+ }
17501
+ if (written.length === 0) return { content: source, written, kept };
17502
+ return { content: `${lines.join(eol)}${eol}`, written, kept };
17503
+ }
17504
+ function renderNextTemplate(source, dataset) {
17505
+ const literal = dataset.replaceAll("\\", "\\\\").replaceAll("'", String.raw`\'`);
17506
+ return source.replaceAll(DATASET_PLACEHOLDER, literal);
17507
+ }
17508
+ function nextTemplateFiles(templatesDir = getTemplatesDir()) {
17509
+ const root = path5.join(templatesDir, FRAMEWORK_DIR);
17510
+ const found = [];
17511
+ const walk2 = (directory) => {
17512
+ for (const name of readdirSync(directory)) {
17513
+ const full = path5.join(directory, name);
17514
+ if (statSync(full).isDirectory()) walk2(full);
17515
+ else if (name.endsWith(TEMPLATE_SUFFIX)) found.push(path5.relative(root, full));
17516
+ }
17517
+ };
17518
+ walk2(root);
17519
+ return found.sort();
17520
+ }
17521
+ function writeNextScaffold(cwd, dataset, templatesDir = getTemplatesDir()) {
17522
+ const useSrc = existsSync(path5.join(cwd, "src", "app")) && !existsSync(path5.join(cwd, "app"));
17523
+ const files = [];
17524
+ let nextConfigToMerge;
17525
+ for (const template of nextTemplateFiles(templatesDir)) {
17526
+ const text = renderNextTemplate(
17527
+ readFileSync(path5.join(templatesDir, FRAMEWORK_DIR, template), "utf8"),
17528
+ dataset
17529
+ );
17530
+ let target = template.slice(0, -TEMPLATE_SUFFIX.length).split(path5.sep).join("/");
17531
+ if (target === NEXT_CONFIG) {
17532
+ const existing = NEXT_CONFIG_NAMES.find((name) => existsSync(path5.join(cwd, name)));
17533
+ if (existing !== void 0) {
17534
+ nextConfigToMerge = { file: existing, fragment: text };
17535
+ continue;
17536
+ }
17537
+ }
17538
+ if (useSrc && (target.startsWith("app/") || target.startsWith("lib/"))) {
17539
+ target = `src/${target}`;
17540
+ }
17541
+ const destination = path5.join(cwd, target);
17542
+ if (existsSync(destination)) {
17543
+ const current = readFileSync(destination, "utf8");
17544
+ files.push({ path: target, outcome: current === text ? "unchanged" : "kept" });
17545
+ continue;
17546
+ }
17547
+ mkdirSync(path5.dirname(destination), { recursive: true });
17548
+ writeFileSync(destination, text);
17549
+ files.push({ path: target, outcome: "written" });
17550
+ }
17551
+ return { files, nextConfigToMerge };
17552
+ }
17553
+ var defaultIo = () => ({
17554
+ cwd: process.cwd(),
17555
+ log: (line) => console.log(line),
17556
+ interactive: process.stdin.isTTY === true
17557
+ });
17558
+ function named(text, max) {
17559
+ return text.trim().slice(0, max);
17560
+ }
17561
+ async function runInitNext(options, io = defaultIo()) {
17562
+ const { cwd, log } = io;
17563
+ const projectId = options.project ?? config_default.getProjectId();
17564
+ if (projectId === void 0 || projectId === "") {
17565
+ throw new Error("No project. Run 'relyio link' in this directory, or pass --project <id>.");
17566
+ }
17567
+ const url = revalidationUrl(await requireSiteUrl(options.siteUrl, io.interactive));
17568
+ const site = new URL(url).host;
17569
+ const dataset = await resolveDataset(projectId, options.dataset, io.interactive);
17570
+ const connections = await getProjectConnections(projectId);
17571
+ const conflicting = connections.find(
17572
+ (connection) => connection.type === "webhook" && connection.config.url === url
17573
+ );
17574
+ if (conflicting !== void 0) {
17575
+ throw new Error(
17576
+ `A webhook Connection to ${url} already exists: "${conflicting.name}" (${conflicting.id}). Nothing was created. Delete it and its Automation in the dashboard, or pass another --site-url, and run again.`
17577
+ );
17578
+ }
17579
+ const envPath = path5.join(cwd, ENV_FILE);
17580
+ const envBefore = existsSync(envPath) ? readFileSync(envPath, "utf8") : null;
17581
+ let envNow = envBefore;
17582
+ const written = [];
17583
+ const kept = Object.values(NEXT_ENV).filter((name) => readEnvValue(envBefore, name) !== void 0);
17584
+ const persist = (name, value) => {
17585
+ const update = upsertEnv(envNow, { [name]: value });
17586
+ if (update.written.length === 0) return;
17587
+ writeFileSync(envPath, update.content, { mode: 384 });
17588
+ envNow = update.content;
17589
+ written.push(...update.written);
17590
+ };
17591
+ const created = [];
17592
+ try {
17593
+ let publishedKey;
17594
+ let previewKey;
17595
+ const signingSecret = readEnvValue(envBefore, NEXT_ENV.webhookSecret) ?? randomBytes(32).toString("hex");
17596
+ persist(NEXT_ENV.webhookSecret, signingSecret);
17597
+ if (readEnvValue(envBefore, NEXT_ENV.apiKey) === void 0) {
17598
+ const key = await createProjectApiKey(projectId, {
17599
+ name: named(`Next.js ${site} published`, 80),
17600
+ keyType: "production",
17601
+ readOnly: true,
17602
+ defaultDatasetId: dataset.id,
17603
+ allowedDatasetIds: [dataset.id]
17604
+ });
17605
+ publishedKey = { id: key.id, name: key.name };
17606
+ created.push(`API key "${key.name}" (${key.id})`);
17607
+ persist(NEXT_ENV.apiKey, requireToken(key.token, "published"));
17608
+ }
17609
+ if (readEnvValue(envBefore, NEXT_ENV.previewApiKey) === void 0) {
17610
+ const key = await createProjectApiKey(projectId, {
17611
+ name: named(`Next.js ${site} preview`, 80),
17612
+ keyType: "preview",
17613
+ previewScope: "drafts",
17614
+ defaultDatasetId: dataset.id,
17615
+ allowedDatasetIds: [dataset.id]
17616
+ });
17617
+ previewKey = { id: key.id, name: key.name };
17618
+ created.push(`API key "${key.name}" (${key.id})`);
17619
+ persist(NEXT_ENV.previewApiKey, requireToken(key.token, "preview"));
17620
+ }
17621
+ const connection = await createConnection(projectId, {
17622
+ name: named(`Next.js revalidation ${site}`, 100),
17623
+ type: "webhook",
17624
+ config: { url, signingSecret }
17625
+ });
17626
+ created.push(`Connection "${connection.name}" (${connection.id})`);
17627
+ const automation = await createAutomation(projectId, {
17628
+ name: named(`Revalidate ${site} on entry changes`, 100),
17629
+ events: [...REVALIDATION_EVENTS],
17630
+ connectionId: connection.id,
17631
+ actionSlug: WEBHOOK_ACTION_SLUG,
17632
+ filterDatasets: [dataset.id]
17633
+ });
17634
+ created.push(`Automation "${automation.name}" (${automation.id})`);
17635
+ const scaffold = writeNextScaffold(cwd, dataset.name);
17636
+ const result = {
17637
+ dataset: { id: dataset.id, name: dataset.name },
17638
+ publishedKey,
17639
+ previewKey,
17640
+ connection: { id: connection.id, name: connection.name, url },
17641
+ automation: { id: automation.id, name: automation.name },
17642
+ env: { written, kept },
17643
+ scaffold
17644
+ };
17645
+ report(result, log);
17646
+ return result;
17647
+ } catch (error2) {
17648
+ const message = error2 instanceof Error ? error2.message : String(error2);
17649
+ const partial = created.length === 0 ? "" : `
17650
+ Created before the failure: ${created.join("; ")}. Keys and the secret are in ${ENV_FILE}; run again to finish.`;
17651
+ throw new Error(`${message}${partial}`);
17652
+ }
17653
+ }
17654
+ function requireToken(token, kind) {
17655
+ if (typeof token !== "string" || token === "") {
17656
+ throw new Error(`manage minted the ${kind} key but served no token for it`);
17657
+ }
17658
+ return token;
17659
+ }
17660
+ async function requireSiteUrl(siteUrl, interactive) {
17661
+ if (siteUrl !== void 0) return siteUrl;
17662
+ if (!interactive) {
17663
+ throw new Error(
17664
+ "Pass --site-url <url>: your deployed site's URL, which the revalidation webhook targets."
17665
+ );
17666
+ }
17667
+ const { answer } = await inquirer2.prompt([
17668
+ {
17669
+ type: "input",
17670
+ name: "answer",
17671
+ message: "Your deployed site's URL (the revalidation webhook targets it):",
17672
+ validate: (input) => {
17673
+ try {
17674
+ revalidationUrl(input);
17675
+ return true;
17676
+ } catch (error2) {
17677
+ return error2.message;
17678
+ }
17679
+ }
17680
+ }
17681
+ ]);
17682
+ return answer;
17683
+ }
17684
+ async function resolveDataset(projectId, name, interactive) {
17685
+ if (name !== void 0) {
17686
+ const found = await getProjectDatasets(projectId, { q: name });
17687
+ const match = found.find((d) => d.name === name);
17688
+ if (match === void 0) {
17689
+ throw new Error(
17690
+ `No dataset named "${name}" in project ${projectId}. Run without --dataset to pick one.`
17691
+ );
17692
+ }
17693
+ return match;
17694
+ }
17695
+ const datasets = await getProjectDatasets(projectId);
17696
+ const names = datasets.map((d) => d.name).join(", ");
17697
+ if (!interactive) {
17698
+ throw new Error(`Pass --dataset <name>. This project's datasets: ${names}`);
17699
+ }
17700
+ const { answer } = await inquirer2.prompt([
17701
+ {
17702
+ type: "list",
17703
+ name: "answer",
17704
+ message: "Dataset the site reads:",
17705
+ choices: datasets.map((d) => ({ name: d.name, value: d.id })),
17706
+ default: datasets.find((d) => d.isDefault)?.id
17707
+ }
17708
+ ]);
17709
+ const chosen = datasets.find((d) => d.id === answer);
17710
+ if (chosen === void 0) throw new Error(`No dataset with id ${answer}`);
17711
+ return chosen;
17712
+ }
17713
+ function report(result, log) {
17714
+ const row = (label, value) => log(` ${label.padEnd(15)}${value}`);
17715
+ const kept = (name) => `kept the ${name} already in ${ENV_FILE}; none minted`;
17716
+ log(success("Provisioned RelyIO for this Next.js app"));
17717
+ row("Dataset", `${result.dataset.name} (${result.dataset.id})`);
17718
+ row(
17719
+ "Published key",
17720
+ result.publishedKey ? `${result.publishedKey.name} (${result.publishedKey.id})` : kept(NEXT_ENV.apiKey)
17721
+ );
17722
+ row(
17723
+ "Preview key",
17724
+ result.previewKey ? `${result.previewKey.name} (${result.previewKey.id})` : kept(NEXT_ENV.previewApiKey)
17725
+ );
17726
+ row(
17727
+ "Connection",
17728
+ `${result.connection.name} (${result.connection.id}) \u2192 ${result.connection.url}`
17729
+ );
17730
+ row("Automation", `${result.automation.name} (${result.automation.id})`);
17731
+ row(
17732
+ ENV_FILE,
17733
+ [
17734
+ result.env.written.length > 0 ? `wrote ${result.env.written.join(", ")}` : "",
17735
+ result.env.kept.length > 0 ? `kept ${result.env.kept.join(", ")}` : ""
17736
+ ].filter(Boolean).join("; ")
17737
+ );
17738
+ for (const file of result.scaffold.files) {
17739
+ row(file.outcome, file.outcome === "kept" ? `${file.path} (differs; left as is)` : file.path);
17740
+ }
17741
+ if (result.scaffold.nextConfigToMerge) {
17742
+ log(
17743
+ warning(
17744
+ `${result.scaffold.nextConfigToMerge.file} exists and was not edited. Merge its images block:`
17745
+ )
17746
+ );
17747
+ log(result.scaffold.nextConfigToMerge.fragment);
17748
+ }
17749
+ log(
17750
+ info(
17751
+ `Set ${Object.values(NEXT_ENV).join(", ")} in your host's environment too: the webhook reaches the deployed site, which does not read ${ENV_FILE}.`
17752
+ )
17753
+ );
17754
+ }
17755
+
17756
+ // src/commands/init.ts
17757
+ var FRAMEWORK_TEMPLATES = ["next"];
17050
17758
  async function handleInit(options) {
17759
+ if (options.template !== void 0) {
17760
+ if (options.example !== void 0) {
17761
+ throw new Error(
17762
+ "--template scaffolds a framework app and --example picks a schema template; pass one."
17763
+ );
17764
+ }
17765
+ if (options.template !== "next") {
17766
+ throw new Error(
17767
+ `Unknown --template '${options.template}'. Available: ${FRAMEWORK_TEMPLATES.join(", ")}`
17768
+ );
17769
+ }
17770
+ await requireAuth();
17771
+ await runInitNext(options);
17772
+ return;
17773
+ }
17051
17774
  console.log(heading("Initialize a new RelyIO project"));
17052
17775
  if (config_default.isProjectInitialized()) {
17053
- const { shouldReinitialize } = await inquirer.prompt([
17776
+ const { shouldReinitialize } = await inquirer2.prompt([
17054
17777
  {
17055
17778
  type: "confirm",
17056
17779
  name: "shouldReinitialize",
@@ -17084,7 +17807,7 @@ async function handleInit(options) {
17084
17807
  }
17085
17808
  }
17086
17809
  async function collectProjectInfo(options) {
17087
- const { directory } = await inquirer.prompt([
17810
+ const { directory } = await inquirer2.prompt([
17088
17811
  {
17089
17812
  type: "input",
17090
17813
  name: "directory",
@@ -17092,7 +17815,7 @@ async function collectProjectInfo(options) {
17092
17815
  default: process.cwd()
17093
17816
  }
17094
17817
  ]);
17095
- const { projectName } = await inquirer.prompt([
17818
+ const { projectName } = await inquirer2.prompt([
17096
17819
  {
17097
17820
  type: "input",
17098
17821
  name: "projectName",
@@ -17106,12 +17829,7 @@ async function collectProjectInfo(options) {
17106
17829
  console.error(error(`Templates directory not found at: ${templatesDir}`));
17107
17830
  process.exit(1);
17108
17831
  }
17109
- const dirContents = readdirSync(templatesDir);
17110
- const templates = dirContents.filter((item) => {
17111
- const fullPath = path3.join(templatesDir, item);
17112
- const stats = statSync(fullPath);
17113
- return stats.isDirectory() && item !== "node_modules" && !item.startsWith(".");
17114
- }).map((template) => {
17832
+ const templates = schemaTemplateNames(templatesDir).map((template) => {
17115
17833
  const description = (() => {
17116
17834
  switch (template) {
17117
17835
  case "bare": {
@@ -17139,7 +17857,7 @@ async function collectProjectInfo(options) {
17139
17857
  }
17140
17858
  console.log(heading("\nSelect a template for your project"));
17141
17859
  console.log(info("Use \u2191 and \u2193 arrow keys to select, then press Enter\n"));
17142
- const { selectedExample } = await inquirer.prompt([
17860
+ const { selectedExample } = await inquirer2.prompt([
17143
17861
  {
17144
17862
  type: "list",
17145
17863
  name: "selectedExample",
@@ -17164,7 +17882,7 @@ async function collectProjectInfo(options) {
17164
17882
  try {
17165
17883
  existingProjects = await getProjects();
17166
17884
  if (existingProjects.length > 0) {
17167
- const { linkType } = await inquirer.prompt([
17885
+ const { linkType } = await inquirer2.prompt([
17168
17886
  {
17169
17887
  type: "list",
17170
17888
  name: "linkType",
@@ -17178,7 +17896,7 @@ async function collectProjectInfo(options) {
17178
17896
  ]);
17179
17897
  if (linkType === "existing") {
17180
17898
  createProject = false;
17181
- const { selectedProject } = await inquirer.prompt([
17899
+ const { selectedProject } = await inquirer2.prompt([
17182
17900
  {
17183
17901
  type: "list",
17184
17902
  name: "selectedProject",
@@ -17213,12 +17931,12 @@ async function collectProjectInfo(options) {
17213
17931
  }
17214
17932
  function createProjectStructure(context) {
17215
17933
  const { directory, example = "bare" } = context;
17216
- const relyioDir = path3.resolve(directory, "relyio");
17934
+ const relyioDir = path5.resolve(directory, "relyio");
17217
17935
  if (!existsSync(relyioDir)) {
17218
17936
  mkdirSync(relyioDir, { recursive: true });
17219
17937
  }
17220
17938
  const templatesDir = getTemplatesDir();
17221
- const templateDir = path3.resolve(templatesDir, example);
17939
+ const templateDir = path5.resolve(templatesDir, example);
17222
17940
  if (!existsSync(templateDir)) {
17223
17941
  throw new Error(
17224
17942
  `Template '${example}' not found. Available templates: ${existsSync(templatesDir) ? readdirSync(templatesDir).join(", ") : "none"}`
@@ -17231,7 +17949,7 @@ function createProjectStructure(context) {
17231
17949
  if (isDirectory) {
17232
17950
  if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
17233
17951
  for (const childItemName of readdirSync(src)) {
17234
- copyRecursive(path3.join(src, childItemName), path3.join(dest, childItemName));
17952
+ copyRecursive(path5.join(src, childItemName), path5.join(dest, childItemName));
17235
17953
  }
17236
17954
  } else {
17237
17955
  cpSync(src, dest);
@@ -17247,7 +17965,13 @@ function initCommand(program2) {
17247
17965
  (options) => handleInit(options),
17248
17966
  false
17249
17967
  // Don't require auth for init command
17250
- ).option("--example <type>", "Initialize with an example template (bare or blog)");
17968
+ ).option("--example <type>", "Initialize with an example template (bare or blog)").option(
17969
+ "--template <framework>",
17970
+ "Scaffold a framework app and provision its keys, webhook and env file (next)"
17971
+ ).option(
17972
+ "--site-url <url>",
17973
+ "With --template next: your deployed site's URL; the revalidation webhook targets it"
17974
+ ).option("--dataset <name>", "With --template next: the dataset the site reads").option("--project <id>", "With --template next: the project id (default: the linked project)");
17251
17975
  }
17252
17976
  init_config();
17253
17977
  async function handleLogin(options) {
@@ -17273,7 +17997,7 @@ async function handleLogin(options) {
17273
17997
  try {
17274
17998
  const user = await auth_default.getCurrentUser();
17275
17999
  console.log(warning(`You are already logged in as ${colors.highlight(user.email)}`));
17276
- const { shouldLogout } = await inquirer.prompt([
18000
+ const { shouldLogout } = await inquirer2.prompt([
17277
18001
  {
17278
18002
  type: "confirm",
17279
18003
  name: "shouldLogout",
@@ -18583,60 +19307,51 @@ async function pollTaskUntilDone(projectId, taskId, options = {}) {
18583
19307
 
18584
19308
  // src/commands/promote.ts
18585
19309
  init_config();
18586
- var DEV_CONTEXT_DIR = ".relyio";
18587
- var DEV_CONTEXT_FILE = path3.join(DEV_CONTEXT_DIR, "dev.json");
18588
- var GITIGNORE_ENTRY = `${DEV_CONTEXT_DIR}/dev.json`;
19310
+
19311
+ // src/dev/dev-context.ts
19312
+ init_dist();
19313
+ init_dev_handshake();
18589
19314
  function devContextPath(projectRoot) {
18590
- return path3.join(projectRoot, DEV_CONTEXT_FILE);
19315
+ return path5.join(projectRoot, DEV_CONTEXT_FILE);
18591
19316
  }
18592
19317
  async function writeDevContext(projectRoot, context) {
18593
- await mkdir(path3.join(projectRoot, DEV_CONTEXT_DIR), { recursive: true });
18594
- await writeFile(devContextPath(projectRoot), `${JSON.stringify(context, null, 2)}
18595
- `, "utf8");
19318
+ writeSecretFile(devContextPath(projectRoot), context);
18596
19319
  await ensureGitignore(projectRoot);
18597
19320
  }
18598
19321
  async function readDevContext(projectRoot) {
19322
+ let raw;
18599
19323
  try {
18600
- const raw = await readFile(devContextPath(projectRoot), "utf8");
18601
- const parsed = JSON.parse(raw);
18602
- if (typeof parsed.datasetId === "string" && typeof parsed.previewApiKey === "string" && typeof parsed.branchId === "string") {
18603
- return {
18604
- datasetId: parsed.datasetId,
18605
- previewApiKey: parsed.previewApiKey,
18606
- branchId: parsed.branchId,
18607
- expiresAt: typeof parsed.expiresAt === "string" ? parsed.expiresAt : null
18608
- };
18609
- }
18610
- return null;
19324
+ raw = await readFile(devContextPath(projectRoot), "utf8");
18611
19325
  } catch {
18612
- return null;
19326
+ return { state: "absent" };
18613
19327
  }
19328
+ return parseDevContextFile(raw, Date.now());
18614
19329
  }
18615
19330
  async function clearDevContext(projectRoot) {
18616
19331
  await rm(devContextPath(projectRoot), { force: true }).catch(() => {
18617
19332
  });
18618
19333
  }
18619
19334
  async function ensureGitignore(projectRoot) {
18620
- const gitignorePath = path3.join(projectRoot, ".gitignore");
19335
+ const gitignorePath = path5.join(projectRoot, ".gitignore");
18621
19336
  if (!existsSync(gitignorePath)) {
18622
19337
  await writeFile(
18623
19338
  gitignorePath,
18624
19339
  `# RelyIO sandbox dev artifact (contains a scoped preview key \u2014 never commit)
18625
- ${GITIGNORE_ENTRY}
19340
+ ${DEV_CONTEXT_FILE}
18626
19341
  `,
18627
19342
  "utf8"
18628
19343
  );
18629
19344
  return;
18630
19345
  }
18631
19346
  const current = await readFile(gitignorePath, "utf8");
18632
- const alreadyIgnored = current.split(/\r?\n/).map((line) => line.trim()).some((line) => line === GITIGNORE_ENTRY || line === `/${GITIGNORE_ENTRY}`);
19347
+ const alreadyIgnored = current.split(/\r?\n/).map((line) => line.trim()).some((line) => line === DEV_CONTEXT_FILE || line === `/${DEV_CONTEXT_FILE}`);
18633
19348
  if (alreadyIgnored) return;
18634
19349
  const prefix = current.endsWith("\n") || current.length === 0 ? "" : "\n";
18635
19350
  await writeFile(
18636
19351
  gitignorePath,
18637
19352
  `${current}${prefix}
18638
19353
  # RelyIO sandbox dev artifact (contains a scoped preview key \u2014 never commit)
18639
- ${GITIGNORE_ENTRY}
19354
+ ${DEV_CONTEXT_FILE}
18640
19355
  `,
18641
19356
  "utf8"
18642
19357
  );
@@ -18653,12 +19368,13 @@ function requireProjectId() {
18653
19368
  return projectId;
18654
19369
  }
18655
19370
  async function resolveActiveSandbox(projectId) {
19371
+ const artifact = await readDevContext(process.cwd());
18656
19372
  const sandboxes = await sandbox_default.listSandboxes(projectId);
18657
- if (sandboxes.length === 0) return null;
18658
- const devContext = await readDevContext(process.cwd());
18659
- if (devContext) {
18660
- const byArtifact = sandboxes.find((s) => s.branch.id === devContext.branchId);
18661
- if (byArtifact) return byArtifact;
19373
+ if (sandboxes.length === 0) return { sandbox: null, artifact };
19374
+ if (artifact.state === "expired") return { sandbox: null, artifact };
19375
+ if (artifact.state === "active") {
19376
+ const byArtifact = sandboxes.find((s) => s.branch.id === artifact.context.branchId);
19377
+ if (byArtifact) return { sandbox: byArtifact, artifact };
18662
19378
  }
18663
19379
  const gitBranch = getCurrentGitBranch();
18664
19380
  if (gitBranch) {
@@ -18667,12 +19383,25 @@ async function resolveActiveSandbox(projectId) {
18667
19383
  const parent = branches.find((b) => b.name === gitBranch);
18668
19384
  if (parent) {
18669
19385
  const byParent = sandboxes.find((s) => s.branch.parentBranchId === parent.id);
18670
- if (byParent) return byParent;
19386
+ if (byParent) return { sandbox: byParent, artifact };
18671
19387
  }
18672
19388
  } catch {
18673
19389
  }
18674
19390
  }
18675
- return sandboxes.length === 1 ? sandboxes[0] : null;
19391
+ return { sandbox: sandboxes.length === 1 ? sandboxes[0] : null, artifact };
19392
+ }
19393
+ function reportArtifact(artifact) {
19394
+ if (artifact.state === "expired") {
19395
+ displayWarning(
19396
+ `Your sandbox's local dev artifact expired at ${artifact.expiredAt}.`,
19397
+ "It can no longer identify which sandbox to promote, and guessing from your git branch could promote a different one. Run `relyio dev` to refresh it, then promote."
19398
+ );
19399
+ } else if (artifact.state === "unreadable") {
19400
+ displayWarning(
19401
+ `Your \`.relyio/dev.json\` could not be read (${artifact.reason}).`,
19402
+ "Your app is not reading the sandbox. Delete the file and run `relyio dev` to rewrite it."
19403
+ );
19404
+ }
18676
19405
  }
18677
19406
  async function resolveTargetBranch(projectId, requested, defaultParentBranchId) {
18678
19407
  const branches = await manage_default.getProjectBranches(projectId);
@@ -18693,14 +19422,16 @@ async function handlePromote(options) {
18693
19422
  displayHeader("RelyIO Promote", "Flush your sandbox into a real branch");
18694
19423
  const projectId = requireProjectId();
18695
19424
  const spinner2 = createSpinner("Resolving your active sandbox...");
18696
- const sandbox = await resolveActiveSandbox(projectId);
19425
+ const { sandbox, artifact } = await resolveActiveSandbox(projectId);
18697
19426
  if (!sandbox) {
18698
19427
  spinner2.fail("No active sandbox found");
19428
+ reportArtifact(artifact);
18699
19429
  displayInfo("Start one with `relyio dev`, then run `relyio promote` again.");
18700
19430
  process.exit(1);
18701
19431
  return;
18702
19432
  }
18703
19433
  spinner2.succeed(`Sandbox: ${sandbox.branch.name}`);
19434
+ reportArtifact(artifact);
18704
19435
  const target = await resolveTargetBranch(projectId, options.branch, sandbox.branch.parentBranchId);
18705
19436
  if (!target) {
18706
19437
  displayError(
@@ -18815,7 +19546,8 @@ async function offerPromoteWhenSandboxActive() {
18815
19546
  if (!projectId) return false;
18816
19547
  let sandbox;
18817
19548
  try {
18818
- sandbox = await resolveActiveSandbox(projectId);
19549
+ ;
19550
+ ({ sandbox } = await resolveActiveSandbox(projectId));
18819
19551
  } catch {
18820
19552
  return false;
18821
19553
  }
@@ -18988,7 +19720,7 @@ async function handleDeploy(options) {
18988
19720
  let notes;
18989
19721
  if (!json && !options.yes) {
18990
19722
  console.log();
18991
- const { confirmed } = await inquirer.prompt([
19723
+ const { confirmed } = await inquirer2.prompt([
18992
19724
  {
18993
19725
  type: "confirm",
18994
19726
  name: "confirmed",
@@ -19001,7 +19733,7 @@ async function handleDeploy(options) {
19001
19733
  return;
19002
19734
  }
19003
19735
  if (!options.skipNotes) {
19004
- const answer = await inquirer.prompt([
19736
+ const answer = await inquirer2.prompt([
19005
19737
  {
19006
19738
  type: "input",
19007
19739
  name: "notes",
@@ -19708,7 +20440,7 @@ function sandboxCommands(program2) {
19708
20440
  }
19709
20441
 
19710
20442
  // src/commands/version.ts
19711
- var CLI_VERSION = "0.2.1" ;
20443
+ var CLI_VERSION = "0.3.0" ;
19712
20444
  async function handleVersion(_options) {
19713
20445
  console.log(heading("RelyIO CLI Version Information"));
19714
20446
  const versionInfo = [
@@ -19791,13 +20523,13 @@ var TerminalWatcher = class extends EventEmitter {
19791
20523
  ignoreInitial: true,
19792
20524
  persistent: true
19793
20525
  });
19794
- this.watcher.on("raw", (event, path23) => {
20526
+ this.watcher.on("raw", (event, path25) => {
19795
20527
  if (event === "change" || event === "rename") {
19796
- return this.handleFileChange(path23);
20528
+ return this.handleFileChange(path25);
19797
20529
  }
19798
20530
  });
19799
- this.watcher.on("add", (path23) => this.handleFileChange(path23));
19800
- this.watcher.on("unlink", (path23) => this.handleFileChange(path23));
20531
+ this.watcher.on("add", (path25) => this.handleFileChange(path25));
20532
+ this.watcher.on("unlink", (path25) => this.handleFileChange(path25));
19801
20533
  this.watcher.on("error", (error2) => {
19802
20534
  const errorMessage = error2 instanceof Error ? error2.message : String(error2);
19803
20535
  if (this.options.onError && error2 instanceof Error) {
@@ -20481,7 +21213,7 @@ async function handleLink(options) {
20481
21213
  console.log(info("Please create an organization from the dashboard."));
20482
21214
  process.exit(1);
20483
21215
  }
20484
- const { organizationId } = await inquirer.prompt([
21216
+ const { organizationId } = await inquirer2.prompt([
20485
21217
  {
20486
21218
  type: "list",
20487
21219
  name: "organizationId",
@@ -20501,7 +21233,7 @@ async function handleLink(options) {
20501
21233
  console.log(info("Please create a project first."));
20502
21234
  process.exit(1);
20503
21235
  }
20504
- const { projectId } = await inquirer.prompt([
21236
+ const { projectId } = await inquirer2.prompt([
20505
21237
  {
20506
21238
  type: "list",
20507
21239
  name: "projectId",
@@ -20551,172 +21283,13 @@ init_schema_loader();
20551
21283
  init_schema();
20552
21284
  init_manage();
20553
21285
  init_manage_error();
20554
- var BUILT_IN_LANGUAGES = ["typescript", "python"];
20555
- var registeredPlugins = /* @__PURE__ */ new Map();
20556
- function registerPlugin(language, load3) {
20557
- registeredPlugins.set(language, load3);
20558
- }
20559
- function asPlugin(module_, describe3) {
20560
- const candidate = module_?.default ?? module_;
20561
- const plugin3 = candidate;
20562
- if (!plugin3 || typeof plugin3.generate !== "function") {
20563
- throw new Error(`Invalid plugin: ${describe3}`);
20564
- }
20565
- return plugin3;
20566
- }
20567
- async function loadPlugin(language) {
20568
- const registered = registeredPlugins.get(language);
20569
- if (registered) {
20570
- return asPlugin(await registered(), `registered ${language}`);
20571
- }
20572
- if (language === "typescript" || language === "python") {
20573
- try {
20574
- const pluginName2 = `@relyio/codegen-${language}`;
20575
- const pluginModule = await import(
20576
- /* webpackIgnore: true */
20577
- pluginName2
20578
- );
20579
- const plugin3 = pluginModule.default || pluginModule;
20580
- if (!plugin3 || typeof plugin3.generate !== "function") {
20581
- throw new Error(`Invalid built-in plugin: ${language}`);
20582
- }
20583
- return plugin3;
20584
- } catch {
20585
- try {
20586
- const relativePath = path3.resolve(
20587
- path3.dirname(import.meta.url.replace("file://", "")),
20588
- `../../codegen-${language}/dist/index.js`
20589
- );
20590
- const pluginModule = await import(
20591
- /* webpackIgnore: true */
20592
- relativePath
20593
- );
20594
- const plugin3 = pluginModule.default || pluginModule;
20595
- if (!plugin3 || typeof plugin3.generate !== "function") {
20596
- throw new Error(`Invalid built-in plugin: ${language}`);
20597
- }
20598
- return plugin3;
20599
- } catch {
20600
- throw new Error(
20601
- `Built-in ${language} generator not found. Please ensure @relyio/codegen-${language} is built.`
20602
- );
20603
- }
20604
- }
20605
- }
20606
- const pluginName = `@relyio/codegen-${language}`;
20607
- try {
20608
- const pluginModule = await import(
20609
- /* webpackIgnore: true */
20610
- pluginName
20611
- );
20612
- const plugin3 = pluginModule.default || pluginModule;
20613
- if (!plugin3 || typeof plugin3.generate !== "function") {
20614
- throw new Error(`Invalid plugin: ${pluginName}`);
20615
- }
20616
- return plugin3;
20617
- } catch (error2) {
20618
- const code = error2 !== null && typeof error2 === "object" && "code" in error2 ? String(error2.code) : void 0;
20619
- if (code === "MODULE_NOT_FOUND") {
20620
- throw new Error(`Generator "${language}" not installed.
20621
- Run: pnpm add -D ${pluginName}`);
20622
- }
20623
- throw error2;
20624
- }
20625
- }
20626
- async function getAvailablePlugins() {
20627
- try {
20628
- const packageJsonPath = path3.join(process.cwd(), "package.json");
20629
- const packageJsonModule = await import(packageJsonPath);
20630
- const packageJson = packageJsonModule.default;
20631
- const dependencies = {
20632
- ...packageJson.dependencies,
20633
- ...packageJson.devDependencies
20634
- };
20635
- const plugins = Object.keys(dependencies).filter((name) => name.startsWith("@relyio/codegen-")).map((name) => name.replace("@relyio/codegen-", ""));
20636
- if (!plugins.includes("typescript")) {
20637
- plugins.unshift("typescript");
20638
- }
20639
- if (!plugins.includes("python")) {
20640
- plugins.unshift("python");
20641
- }
20642
- return plugins;
20643
- } catch {
20644
- return ["typescript", "python"];
20645
- }
20646
- }
20647
- function getSuggestedPlugins() {
20648
- return {
20649
- python: "@relyio/codegen-python",
20650
- go: "@relyio/codegen-go",
20651
- rust: "@relyio/codegen-rust",
20652
- java: "@relyio/codegen-java",
20653
- csharp: "@relyio/codegen-csharp",
20654
- php: "@relyio/codegen-php",
20655
- swift: "@relyio/codegen-swift",
20656
- kotlin: "@relyio/codegen-kotlin"
20657
- };
20658
- }
20659
- async function writeFiles(files, outputDir) {
20660
- await fs4.mkdir(outputDir, { recursive: true });
20661
- await Promise.all(
20662
- files.map(async (file) => {
20663
- const fullPath = path3.join(outputDir, file.path);
20664
- const dir = path3.dirname(fullPath);
20665
- await fs4.mkdir(dir, { recursive: true });
20666
- await fs4.writeFile(fullPath, file.content, "utf8");
20667
- })
20668
- );
20669
- }
20670
- var TemplateEngine = class {
20671
- handlebars;
20672
- constructor() {
20673
- this.handlebars = Handlebars.create();
20674
- this.registerHelpers();
20675
- }
20676
- registerHelpers() {
20677
- this.handlebars.registerHelper("pascalCase", (str) => {
20678
- if (!str) return "";
20679
- return str.replaceAll(/(^|_)(\w)/g, (_, __, char) => char.toUpperCase());
20680
- });
20681
- this.handlebars.registerHelper("camelCase", (str) => {
20682
- if (!str) return "";
20683
- const pascal = str.replaceAll(/(^|_)(\w)/g, (_, __, char) => char.toUpperCase());
20684
- return pascal.charAt(0).toLowerCase() + pascal.slice(1);
20685
- });
20686
- this.handlebars.registerHelper("snakeCase", (str) => {
20687
- if (!str) return "";
20688
- return str.replaceAll(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
20689
- });
20690
- this.handlebars.registerHelper("kebabCase", (str) => {
20691
- if (!str) return "";
20692
- return str.replaceAll(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
20693
- });
20694
- this.handlebars.registerHelper("plural", (str) => {
20695
- if (!str) return "";
20696
- if (str.endsWith("y")) {
20697
- return str.slice(0, -1) + "ies";
20698
- }
20699
- if (str.endsWith("s") || str.endsWith("sh") || str.endsWith("ch")) {
20700
- return str + "es";
20701
- }
20702
- return str + "s";
20703
- });
20704
- }
20705
- compile(template) {
20706
- const compiledTemplate = this.handlebars.compile(template);
20707
- return (context) => compiledTemplate(context);
20708
- }
20709
- render(template, context) {
20710
- const compiledTemplate = this.compile(template);
20711
- return compiledTemplate(context);
20712
- }
20713
- };
20714
- new TemplateEngine();
21286
+ init_dist2();
20715
21287
 
20716
21288
  // src/codegen/built-in-generators.ts
21289
+ init_dist2();
20717
21290
  var BUILT_IN_GENERATOR_LOADERS = Object.freeze({
20718
- typescript: () => Promise.resolve().then(() => (init_dist2(), dist_exports)),
20719
- python: () => Promise.resolve().then(() => (init_dist3(), dist_exports2))
21291
+ typescript: () => Promise.resolve().then(() => (init_dist4(), dist_exports)),
21292
+ python: () => Promise.resolve().then(() => (init_dist5(), dist_exports2))
20720
21293
  });
20721
21294
  function registerBuiltInGenerators() {
20722
21295
  for (const language of BUILT_IN_LANGUAGES) {
@@ -20727,6 +21300,9 @@ registerBuiltInGenerators();
20727
21300
 
20728
21301
  // src/commands/generate-types.ts
20729
21302
  init_git();
21303
+
21304
+ // src/utils/codegen-config.ts
21305
+ init_dist2();
20730
21306
  var CodegenConfigSchema = z.object({
20731
21307
  output: z.string().optional(),
20732
21308
  languages: z.array(z.string()).optional(),
@@ -20741,16 +21317,11 @@ var CodegenConfigSchema = z.object({
20741
21317
  output: z.string().optional(),
20742
21318
  baseModel: z.enum(["pydantic", "dataclass"]).optional(),
20743
21319
  includeValidators: z.boolean().optional()
20744
- }).optional(),
20745
- go: z.object({
20746
- output: z.string().optional(),
20747
- packageName: z.string().optional(),
20748
- includeValidators: z.boolean().optional()
20749
21320
  }).optional()
20750
21321
  });
20751
21322
  async function loadCodegenConfig(configPath) {
20752
- const defaultPath = path3.join(process.cwd(), "relyio.codegen.yaml");
20753
- const alternatePath = path3.join(process.cwd(), "relyio.codegen.yml");
21323
+ const defaultPath = path5.join(process.cwd(), "relyio.codegen.yaml");
21324
+ const alternatePath = path5.join(process.cwd(), "relyio.codegen.yml");
20754
21325
  let targetPath = configPath;
20755
21326
  if (!targetPath) {
20756
21327
  try {
@@ -20794,12 +21365,11 @@ function mergeConfigWithOptions(config, options, language) {
20794
21365
  moduleFormat: options.moduleFormat || languageConfig.moduleFormat || "esm"
20795
21366
  };
20796
21367
  }
20797
- function validateLanguages(languages) {
20798
- const supportedLanguages = ["typescript", "python", "go", "rust", "csharp", "java"];
21368
+ function assertSupportedLanguages(languages) {
20799
21369
  for (const language of languages) {
20800
- if (!supportedLanguages.includes(language)) {
21370
+ if (!BUILT_IN_LANGUAGES.includes(language)) {
20801
21371
  throw new Error(
20802
- `Unsupported language: ${language}. Supported languages: ${supportedLanguages.join(", ")}`
21372
+ `Unsupported language: ${language}. Supported languages: ${BUILT_IN_LANGUAGES.join(", ")}`
20803
21373
  );
20804
21374
  }
20805
21375
  }
@@ -20815,24 +21385,8 @@ function formatSchemaSource3(result) {
20815
21385
  const count = files.length;
20816
21386
  return `YAML (${count} file${count === 1 ? "" : "s"})`;
20817
21387
  }
20818
- async function handleGenerationError(error2) {
21388
+ function handleGenerationError(error2) {
20819
21389
  if (error2 instanceof Error) {
20820
- if (error2.message.includes("not installed")) {
20821
- const availablePlugins = await getAvailablePlugins();
20822
- const suggestedPlugins = getSuggestedPlugins();
20823
- displayInfo("\u{1F4CB} Available generators:");
20824
- for (const plugin3 of availablePlugins) {
20825
- const status = plugin3 === "typescript" || plugin3 === "python" ? " (built-in)" : "";
20826
- displayInfo(` \u2705 ${plugin3}${status}`);
20827
- }
20828
- displayInfo("\n\u{1F4BE} Install additional generators with:");
20829
- for (const [language, packageName] of Object.entries(suggestedPlugins)) {
20830
- if (!availablePlugins.includes(language)) {
20831
- displayInfo(` pnpm add -D ${packageName}`);
20832
- }
20833
- }
20834
- return;
20835
- }
20836
21390
  if (error2.message.includes("Schema validation failed") || error2.message.includes("validation error")) {
20837
21391
  displayInfo("\n\u{1F4A1} Troubleshooting steps:");
20838
21392
  displayInfo("1. Run 'relyio status' to see validation details");
@@ -20901,14 +21455,16 @@ async function performTypeGeneration(branchName, options, watchMode = false) {
20901
21455
  `Failed to load configuration: ${error2 instanceof Error ? error2.message : "Unknown error"}`
20902
21456
  );
20903
21457
  }
20904
- if (options.languages || options.language) {
20905
- const requestedLanguages = options.languages ? options.languages.split(",") : [options.language || "typescript"];
20906
- ctx.languages = [...new Set(requestedLanguages)];
20907
- } else {
20908
- const configLanguages = ctx.config.languages || ["typescript"];
20909
- ctx.languages = [...new Set(configLanguages)];
20910
- }
20911
- validateLanguages(ctx.languages);
21458
+ const requested = options.languages || options.language ? [
21459
+ ...new Set(
21460
+ options.languages ? options.languages.split(",") : [options.language || "typescript"]
21461
+ )
21462
+ ] : (
21463
+ // Use config file languages if no CLI language specified
21464
+ [...new Set(ctx.config.languages || ["typescript"])]
21465
+ );
21466
+ assertSupportedLanguages(requested);
21467
+ ctx.languages = requested;
20912
21468
  }
20913
21469
  },
20914
21470
  {
@@ -21046,7 +21602,7 @@ async function performTypeGeneration(branchName, options, watchMode = false) {
21046
21602
  "Type generation failed",
21047
21603
  error2 instanceof Error ? error2.message : "Unknown error"
21048
21604
  );
21049
- await handleGenerationError(error2);
21605
+ handleGenerationError(error2);
21050
21606
  process.exit(1);
21051
21607
  }
21052
21608
  }
@@ -21116,18 +21672,17 @@ function generateTypesCommand(program2) {
21116
21672
  "after",
21117
21673
  `
21118
21674
  Examples:
21119
- $ relyio generate-types # Generate TypeScript types
21120
- $ relyio generate-types -o ./src/types # Custom output directory
21121
- $ relyio generate-types --watch # Watch mode
21122
- $ relyio generate-types -l python # Generate Python types
21123
- $ relyio generate-types --languages ts,py # Multiple languages
21675
+ $ relyio generate-types # Generate TypeScript types
21676
+ $ relyio generate-types -o ./src/types # Custom output directory
21677
+ $ relyio generate-types --watch # Watch mode
21678
+ $ relyio generate-types -l python # Generate Python types
21679
+ $ relyio generate-types --languages typescript,python # Multiple languages
21124
21680
 
21125
- Supported Languages:
21126
- typescript (ts) TypeScript type definitions
21127
- python (py) Python type hints (dataclasses)
21681
+ Supported languages:
21682
+ ${BUILT_IN_LANGUAGES.join(", ")}
21128
21683
 
21129
21684
  Configuration:
21130
- Create codegen.config.ts or codegen.config.json for advanced options.
21685
+ Create relyio.codegen.yaml (or .yml) for advanced options.
21131
21686
  `
21132
21687
  );
21133
21688
  }
@@ -21304,7 +21859,6 @@ function displayObjectDiff(obj, indent, mode) {
21304
21859
  const color = THEME3.added ;
21305
21860
  const prefix = "+" ;
21306
21861
  for (const [key, value] of Object.entries(obj)) {
21307
- if (key === "_tag") continue;
21308
21862
  if (Array.isArray(value)) {
21309
21863
  console.log(color(`${indent}${prefix} ${key}:`));
21310
21864
  for (const item of value) {
@@ -21902,7 +22456,7 @@ Configuration:
21902
22456
  relyio config set dashboardUrl <url>
21903
22457
 
21904
22458
  For local development:
21905
- relyio config set dashboardUrl http://localhost:3010
22459
+ relyio config set dashboardUrl http://localhost:3920
21906
22460
 
21907
22461
  For production:
21908
22462
  relyio config set dashboardUrl https://dashboard.relyio.com
@@ -22292,7 +22846,7 @@ var THEME7 = {
22292
22846
  alias: chalk12.cyan,
22293
22847
  command: chalk12.yellow
22294
22848
  };
22295
- var ALIAS_FILE = path3.join(os.homedir(), ".relyio", "aliases.json");
22849
+ var ALIAS_FILE = path5.join(os.homedir(), ".relyio", "aliases.json");
22296
22850
  function readAliases() {
22297
22851
  try {
22298
22852
  if (fs2.existsSync(ALIAS_FILE)) {
@@ -22303,7 +22857,7 @@ function readAliases() {
22303
22857
  return {};
22304
22858
  }
22305
22859
  function writeAliases(aliases) {
22306
- const dir = path3.dirname(ALIAS_FILE);
22860
+ const dir = path5.dirname(ALIAS_FILE);
22307
22861
  if (!fs2.existsSync(dir)) {
22308
22862
  fs2.mkdirSync(dir, { recursive: true });
22309
22863
  }
@@ -22443,6 +22997,7 @@ function runPreflight() {
22443
22997
  }
22444
22998
 
22445
22999
  // src/dev/cockpit.tsx
23000
+ init_dev_handshake();
22446
23001
  init_app();
22447
23002
  init_dev_cockpit_context();
22448
23003
  var WrappedChildProcess = class {
@@ -22457,8 +23012,7 @@ var WrappedChildProcess = class {
22457
23012
  cwd: this.options.cwd,
22458
23013
  env: {
22459
23014
  ...process.env,
22460
- RELYIO_DEV_DATASET: this.options.env.RELYIO_DEV_DATASET,
22461
- RELYIO_DEV_API_KEY: this.options.env.RELYIO_DEV_API_KEY
23015
+ ...this.options.env
22462
23016
  },
22463
23017
  stdio: ["ignore", "pipe", "pipe"]
22464
23018
  });
@@ -22683,10 +23237,14 @@ function DevCockpit({
22683
23237
  command: childCommand.command,
22684
23238
  args: childCommand.args,
22685
23239
  cwd: projectRoot,
22686
- env: {
22687
- RELYIO_DEV_DATASET: sandbox.datasetId,
22688
- RELYIO_DEV_API_KEY: sandbox.previewApiKey
22689
- },
23240
+ // The wrapped child and the standalone `.relyio/dev.json` reader must see the same
23241
+ // sandbox, deadline included — before REL-647 the env carrier had no deadline at all, so a
23242
+ // lapsed sandbox kept working here for the life of the process.
23243
+ env: buildDevEnv({
23244
+ datasetId: sandbox.datasetId,
23245
+ previewApiKey: sandbox.previewApiKey,
23246
+ expiresAt: sandbox.expiresAt
23247
+ }),
22690
23248
  onOutput: (chunk) => setChildLines((previous) => appendChildLines(previous, chunk)),
22691
23249
  onExit: () => setChildRunning(false)
22692
23250
  });
@@ -22932,22 +23490,16 @@ init_session_manager();
22932
23490
  // src/ai/services/conversations-client.ts
22933
23491
  init_wire();
22934
23492
  init_config();
23493
+ init_gateway_url();
22935
23494
  var AI_GATEWAY_CONVERSATIONS_PATH = "/ai/conversations";
22936
- function getGatewayUrl5() {
22937
- if (process.env.RELYIO_AI_GATEWAY_URL) {
22938
- return process.env.RELYIO_AI_GATEWAY_URL.replaceAll("http://localhost", "http://127.0.0.1");
22939
- }
22940
- if (process.env.RELYIO_API_URL) return "http://127.0.0.1:3600";
22941
- return "https://ai-gateway.relyio.com";
22942
- }
22943
23495
  function getAuthToken7() {
22944
23496
  const token = config_default.getAuthToken();
22945
23497
  if (token) return token;
22946
23498
  throw new Error("Not authenticated. Run `relyio login` first.");
22947
23499
  }
22948
23500
  var ConversationsClient = class {
22949
- async request(method, path23, init = {}) {
22950
- const url = new URL(`${getGatewayUrl5()}${AI_GATEWAY_CONVERSATIONS_PATH}${path23}`);
23501
+ async request(method, path25, init = {}) {
23502
+ const url = new URL(`${getGatewayUrl()}${AI_GATEWAY_CONVERSATIONS_PATH}${path25}`);
22951
23503
  if (init.query) {
22952
23504
  for (const [k, v] of Object.entries(init.query)) {
22953
23505
  if (v === void 0) continue;
@@ -22966,7 +23518,7 @@ var ConversationsClient = class {
22966
23518
  const raw = await response.text().catch(() => "");
22967
23519
  const refusal = readError(parseJson(raw));
22968
23520
  throw new Error(
22969
- refusal === void 0 ? `Conversations API ${method} ${path23} \u2192 ${response.status}: ${raw}` : `Conversations API ${method} ${path23} \u2192 ${response.status} ${refusal.code}: ${refusal.messages.join(" ")}`
23521
+ refusal === void 0 ? `Conversations API ${method} ${path25} \u2192 ${response.status}: ${raw}` : `Conversations API ${method} ${path25} \u2192 ${response.status} ${refusal.code}: ${refusal.messages.join(" ")}`
22970
23522
  );
22971
23523
  }
22972
23524
  const text = await response.text();
@@ -23216,10 +23768,13 @@ async function resolveModelFlag(rawRef) {
23216
23768
  return "invalid";
23217
23769
  }
23218
23770
  if (!match.available) {
23219
- const reason = match.disabledReason === "paused" ? "AI is paused globally" : "this model is currently disabled by the workspace admin";
23220
- console.log(chalk12.red(`
23221
- Cannot use ${match.name} \u2014 ${reason}.
23222
- `));
23771
+ console.log(
23772
+ chalk12.red(
23773
+ `
23774
+ Cannot use ${match.name} \u2014 this model is currently disabled by the workspace admin.
23775
+ `
23776
+ )
23777
+ );
23223
23778
  return "invalid";
23224
23779
  }
23225
23780
  return match.id;
@@ -24048,7 +24603,7 @@ Examples:
24048
24603
  }
24049
24604
 
24050
24605
  // src/index.ts
24051
- var CLI_VERSION3 = "0.2.1" ;
24606
+ var CLI_VERSION3 = "0.3.0" ;
24052
24607
  var program = new Command();
24053
24608
  program.name("relyio").description("RelyIO CLI - Content Management, Simplified").version(CLI_VERSION3);
24054
24609
  registerGlobalOptions(program);