@runtypelabs/cli 2.59.0 → 2.59.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/chunk-0-T9NWjH.js +3899 -0
  2. package/dist/chunk-0x7UKC1U.js +17 -0
  3. package/dist/chunk-2ySxXtcg.js +418 -0
  4. package/dist/chunk-3An_BRHv.js +501 -0
  5. package/dist/chunk-6uTxgdiY.js +16218 -0
  6. package/dist/chunk-87nRnNRG.js +1363 -0
  7. package/dist/chunk-B3wo8uRx.js +322 -0
  8. package/dist/chunk-BGp3ZdBy.js +560 -0
  9. package/dist/chunk-BJQv9Elp.js +107 -0
  10. package/dist/chunk-BOpO6xE-.js +6053 -0
  11. package/dist/chunk-BQXXCL8B.js +102 -0
  12. package/dist/chunk-BV-8isOd.js +34 -0
  13. package/dist/chunk-BVMhjGVe.js +826 -0
  14. package/dist/chunk-BW2fg46T.js +141 -0
  15. package/dist/chunk-BWJdY5Ag.js +36 -0
  16. package/dist/chunk-BYJOrLhh.js +302 -0
  17. package/dist/chunk-BYab8EVL.js +127 -0
  18. package/dist/chunk-BfEKvp7h.js +82 -0
  19. package/dist/chunk-BhgLSfH6.js +1123 -0
  20. package/dist/chunk-BirUxTtH.js +296 -0
  21. package/dist/chunk-BmB6UVZ9.js +1559 -0
  22. package/dist/chunk-BnWhIqW1.js +109 -0
  23. package/dist/chunk-Bpfnfk0t.js +123 -0
  24. package/dist/chunk-BrhgdnM3.js +108 -0
  25. package/dist/chunk-Bt0XAgX5.js +327 -0
  26. package/dist/chunk-Bu0FJr6S.js +84 -0
  27. package/dist/chunk-BvEoKpN3.js +255 -0
  28. package/dist/chunk-BxNJgYbE.js +28 -0
  29. package/dist/chunk-C1OFhd7F.js +6131 -0
  30. package/dist/chunk-C7e7WwbU.js +331 -0
  31. package/dist/chunk-CDTYP6bN.js +558 -0
  32. package/dist/chunk-CGDQoqDC.js +24 -0
  33. package/dist/chunk-CJraEBVd.js +127 -0
  34. package/dist/chunk-CQrNhOtv.js +40 -0
  35. package/dist/chunk-CW6AzBH6.js +128 -0
  36. package/dist/chunk-CXgL4w-9.js +317 -0
  37. package/dist/chunk-CZrqoPyf.js +233 -0
  38. package/dist/chunk-CcFCyqbB.js +83 -0
  39. package/dist/chunk-CdaIvO-y.js +485 -0
  40. package/dist/chunk-CehSXtgK.js +624 -0
  41. package/dist/chunk-Ciqkop65.js +218 -0
  42. package/dist/chunk-CoAWdSiE.js +43 -0
  43. package/dist/chunk-Crb4y7t4.js +830 -0
  44. package/dist/chunk-CrlqIfmb.js +337 -0
  45. package/dist/chunk-Cs4pwCQ-.js +324 -0
  46. package/dist/chunk-CwM_9y01.js +519 -0
  47. package/dist/chunk-CyRbIJg8.js +440 -0
  48. package/dist/chunk-D0OHsyMy.js +212 -0
  49. package/dist/chunk-D3y2bURs.js +1347 -0
  50. package/dist/chunk-D4_mJk03.js +2338 -0
  51. package/dist/chunk-D6C8rned.js +578 -0
  52. package/dist/chunk-D9ZbUnks.js +11 -0
  53. package/dist/chunk-DEPmLshX.js +834 -0
  54. package/dist/chunk-DFj1UPB2.js +330 -0
  55. package/dist/chunk-DGdFdZVf.js +197 -0
  56. package/dist/chunk-DGv7X9S_.js +63 -0
  57. package/dist/chunk-DJQ47WYZ.js +664 -0
  58. package/dist/chunk-DJw3O61j.js +1974 -0
  59. package/dist/chunk-DK4FbWVK.js +100 -0
  60. package/dist/chunk-DSRYhUus.js +993 -0
  61. package/dist/chunk-DajZ9Eg4.js +299 -0
  62. package/dist/chunk-Df4dunvU.js +195 -0
  63. package/dist/chunk-DjNTPhfO.js +481 -0
  64. package/dist/chunk-Dn8eaJuu.js +1042 -0
  65. package/dist/chunk-DwDWqSxM.js +637 -0
  66. package/dist/chunk-Dz7cEtxl.js +84 -0
  67. package/dist/chunk-E_0bgjrS.js +359 -0
  68. package/dist/chunk-KPYN2vUp.js +776 -0
  69. package/dist/chunk-KcqPQJBi.js +86 -0
  70. package/dist/chunk-N9H0JnoG.js +349 -0
  71. package/dist/chunk-NONFcXkn.js +728 -0
  72. package/dist/chunk-PrFjcpmH.js +256 -0
  73. package/dist/chunk-QSfNYiw9.js +11 -0
  74. package/dist/chunk-R265bzj9.js +226 -0
  75. package/dist/chunk-X9V_r6jM.js +166 -0
  76. package/dist/chunk-ZBtPUtyA.js +55 -0
  77. package/dist/chunk-ZCTfmOs7.js +4939 -0
  78. package/dist/chunk-h6oavj0t.js +97 -0
  79. package/dist/chunk-i7DwkvvD.js +42 -0
  80. package/dist/chunk-mG-4eZ6S.js +203 -0
  81. package/dist/chunk-o0ygUbCf.js +313 -0
  82. package/dist/chunk-ucu5lrDZ.js +5715 -0
  83. package/dist/index.js +11367 -73764
  84. package/package.json +6 -6
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ // src/lib/local-harness/types.ts
3
+ const HARNESS_LIMITS = {
4
+ maxSessions: 8,
5
+ maxQueuedTurnsPerSession: 1,
6
+ maxEventBytes: 131072,
7
+ maxBatchBytes: 524288,
8
+ maxTurnBytes: 2097152,
9
+ maxSpoolBytes: 16777216,
10
+ maxControlReplyBytes: 131072,
11
+ deliveryTimeoutMs: 1e4,
12
+ spoolRetentionMs: 864e5,
13
+ cancelGraceMs: 1e3,
14
+ maxTurnTimeoutMs: 3e5
15
+ };
16
+ export { HARNESS_LIMITS as t };
17
+
@@ -0,0 +1,418 @@
1
+ #!/usr/bin/env node
2
+ import { t as isTTY } from "./index.js";
3
+ import { ConfirmPrompt, DataList, EntityCard, MutationResult, React, render, useEffect, useState } from "./chunk-CoAWdSiE.js";
4
+ import { r as createCliClient } from "./chunk-BW2fg46T.js";
5
+ import { n as ensureAuth } from "./chunk-CJraEBVd.js";
6
+ import { a as printJson, s as printNextCursorHint } from "./chunk-CcFCyqbB.js";
7
+ import { t as readJsonFile } from "./chunk-DGv7X9S_.js";
8
+ import { Command } from "commander";
9
+ import chalk from "chalk";
10
+ // src/commands/collections.ts
11
+ /**
12
+ * Record collections: a collection registers a record type (slug =
13
+ * records.type) and optionally owns a metadata schema, validated at every
14
+ * record write seam according to its validation mode (off | warn | enforce).
15
+ */
16
+ function parseSchemaOption(options) {
17
+ if (options.schemaFile) return readJsonFile(options.schemaFile);
18
+ if (options.schema !== void 0) try {
19
+ return JSON.parse(options.schema);
20
+ } catch {
21
+ console.error(chalk.red("Invalid JSON in --schema"));
22
+ process.exit(1);
23
+ }
24
+ }
25
+ function collectionFields(collection) {
26
+ return [
27
+ {
28
+ label: "Slug",
29
+ value: collection.slug,
30
+ color: "green"
31
+ },
32
+ {
33
+ label: "Name",
34
+ value: collection.displayName
35
+ },
36
+ {
37
+ label: "Mode",
38
+ value: collection.validationMode
39
+ },
40
+ {
41
+ label: "Schema",
42
+ value: collection.schema ? `v${collection.schemaVersion}` : "none"
43
+ },
44
+ {
45
+ label: "Records",
46
+ value: collection.recordCount !== void 0 ? String(collection.recordCount) : null
47
+ },
48
+ {
49
+ label: "Description",
50
+ value: collection.description ?? null,
51
+ color: "gray"
52
+ }
53
+ ];
54
+ }
55
+ function printCollectionDetail(collection) {
56
+ console.log(` Slug: ${chalk.green(collection.slug)}`);
57
+ console.log(` Name: ${collection.displayName}`);
58
+ console.log(` Mode: ${collection.validationMode}`);
59
+ if (collection.description) console.log(` Description: ${collection.description}`);
60
+ if (collection.recordCount !== void 0) console.log(` Records: ${collection.recordCount}`);
61
+ if (collection.schema) {
62
+ console.log(` Schema (v${collection.schemaVersion}):`);
63
+ console.log(JSON.stringify(collection.schema, null, 2).split("\n").map((line) => ` ${line}`).join("\n"));
64
+ } else console.log(" Schema: none");
65
+ }
66
+ const collectionsCommand = new Command("collections").description("Manage record collections (registered record types with optional metadata schemas)");
67
+ collectionsCommand.command("list").description("List collections").option("--limit <n>", "Limit number of results", "25").option("--cursor <cursor>", "Pagination cursor for the next page").option("--count", "Include per-collection record counts").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
68
+ const apiKey = await ensureAuth();
69
+ if (!apiKey) return;
70
+ const client = createCliClient(apiKey);
71
+ const params = {
72
+ limit: parseInt(options.limit, 10),
73
+ ...options.cursor ? { cursor: options.cursor } : {},
74
+ ...options.count ? { includeCount: true } : {}
75
+ };
76
+ if (!isTTY(options) || options.json) {
77
+ try {
78
+ const data = await client.collections.list(params);
79
+ if (options.json) {
80
+ printJson(data);
81
+ return;
82
+ }
83
+ const collections = data.data ?? [];
84
+ if (collections.length === 0) {
85
+ console.log("No collections found");
86
+ return;
87
+ }
88
+ console.log("Your Collections:");
89
+ for (const collection of collections) {
90
+ const schemaTag = collection.schema ? chalk.gray(` schema v${collection.schemaVersion}`) : chalk.gray(" schemaless");
91
+ const countTag = collection.recordCount !== void 0 ? ` (${collection.recordCount} records)` : "";
92
+ console.log(` ${chalk.green(collection.slug)} - ${collection.displayName} [${collection.validationMode}]${schemaTag}${countTag}`);
93
+ }
94
+ printNextCursorHint("runtype collections list", data.pagination);
95
+ } catch (error) {
96
+ const message = error instanceof Error ? error.message : "Unknown error";
97
+ console.error(chalk.red(`Failed to fetch collections: ${message}`));
98
+ process.exit(1);
99
+ }
100
+ return;
101
+ }
102
+ const App = () => {
103
+ const [items, setItems] = useState(null);
104
+ const [error, setError] = useState(null);
105
+ useEffect(() => {
106
+ client.collections.list(params).then((res) => setItems(res.data ?? [])).catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
107
+ }, []);
108
+ return React.createElement(DataList, {
109
+ title: "Your Collections",
110
+ items,
111
+ error,
112
+ loading: items === null && error === null,
113
+ renderCard: (item) => React.createElement(EntityCard, { fields: collectionFields(item) }),
114
+ emptyMessage: "No collections found"
115
+ });
116
+ };
117
+ const { waitUntilExit } = render(React.createElement(App));
118
+ await waitUntilExit();
119
+ });
120
+ collectionsCommand.command("get <slug>").description("Get collection details").option("--history", "Include the schema version history").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (slug, options) => {
121
+ const apiKey = await ensureAuth();
122
+ if (!apiKey) return;
123
+ const client = createCliClient(apiKey);
124
+ const params = options.history ? { includeHistory: true } : void 0;
125
+ if (!isTTY(options) || options.json) {
126
+ try {
127
+ const data = await client.collections.get(slug, params);
128
+ if (options.json) {
129
+ printJson(data);
130
+ return;
131
+ }
132
+ printCollectionDetail(data);
133
+ const history = data.history;
134
+ if (history && history.length > 0) {
135
+ console.log(chalk.cyan(`\nSchema history (${history.length}):`));
136
+ for (const entry of history) console.log(` v${entry.version} ${entry.changeKind} ${chalk.gray(String(entry.createdAt))}`);
137
+ }
138
+ } catch (error) {
139
+ const message = error instanceof Error ? error.message : "Unknown error";
140
+ console.error(chalk.red(`Failed to fetch collection: ${message}`));
141
+ process.exit(1);
142
+ }
143
+ return;
144
+ }
145
+ const App = () => {
146
+ const [items, setItems] = useState(null);
147
+ const [error, setError] = useState(null);
148
+ useEffect(() => {
149
+ client.collections.get(slug, params).then((res) => setItems([res])).catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
150
+ }, []);
151
+ return React.createElement(DataList, {
152
+ title: "Collection",
153
+ items,
154
+ error,
155
+ loading: items === null && error === null,
156
+ renderCard: (item) => React.createElement(EntityCard, { fields: collectionFields(item) })
157
+ });
158
+ };
159
+ const { waitUntilExit } = render(React.createElement(App));
160
+ await waitUntilExit();
161
+ });
162
+ collectionsCommand.command("create").description("Register a record type as a collection").requiredOption("-s, --slug <slug>", "Record type to register (matches records.type)").requiredOption("-n, --name <name>", "Display name").option("-d, --description <text>", "Description").option("--schema <json>", "Metadata schema as a JSON string (constrained dialect)").option("--schema-file <path>", "Read the metadata schema from a JSON file").option("--mode <mode>", "Validation mode: off | warn | enforce (default off)").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
163
+ const apiKey = await ensureAuth();
164
+ if (!apiKey) return;
165
+ const client = createCliClient(apiKey);
166
+ const schema = parseSchemaOption(options);
167
+ const body = {
168
+ slug: options.slug,
169
+ displayName: options.name,
170
+ ...options.description !== void 0 ? { description: options.description } : {},
171
+ ...schema !== void 0 ? { schema } : {},
172
+ ...options.mode !== void 0 ? { validationMode: options.mode } : {}
173
+ };
174
+ if (!isTTY(options) || options.json) {
175
+ try {
176
+ const data = await client.collections.create(body);
177
+ if (options.json) {
178
+ printJson(data);
179
+ return;
180
+ }
181
+ console.log(chalk.green("Collection created"));
182
+ printCollectionDetail(data);
183
+ if (!data.schema) console.log(chalk.gray(`\nPropose a schema from existing data: runtype collections infer ${data.slug}`));
184
+ } catch (error) {
185
+ const message = error instanceof Error ? error.message : "Unknown error";
186
+ console.error(chalk.red(`Failed to create collection: ${message}`));
187
+ process.exit(1);
188
+ }
189
+ return;
190
+ }
191
+ const App = () => {
192
+ const [loading, setLoading] = useState(true);
193
+ const [success, setSuccess] = useState(null);
194
+ const [error, setError] = useState(null);
195
+ const [result, setResult] = useState(null);
196
+ useEffect(() => {
197
+ client.collections.create(body).then((data) => {
198
+ setResult(data);
199
+ setSuccess(true);
200
+ setLoading(false);
201
+ }).catch((err) => {
202
+ setError(err instanceof Error ? err : new Error(String(err)));
203
+ setSuccess(false);
204
+ setLoading(false);
205
+ });
206
+ }, []);
207
+ return React.createElement(MutationResult, {
208
+ loading,
209
+ loadingLabel: "Creating collection...",
210
+ success,
211
+ successMessage: "Collection created",
212
+ error,
213
+ result: result ? React.createElement(EntityCard, { fields: collectionFields(result) }) : void 0
214
+ });
215
+ };
216
+ const { waitUntilExit } = render(React.createElement(App));
217
+ await waitUntilExit();
218
+ });
219
+ collectionsCommand.command("update <slug>").description("Update a collection (display fields, schema, and/or validation mode)").option("-n, --name <name>", "New display name").option("-d, --description <text>", "New description").option("--schema <json>", "New metadata schema as a JSON string").option("--schema-file <path>", "Read the new metadata schema from a JSON file").option("--mode <mode>", "New validation mode: off | warn | enforce").option("--from-file <path>", "Read the full update payload from a JSON file").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (slug, options) => {
220
+ const apiKey = await ensureAuth();
221
+ if (!apiKey) return;
222
+ const client = createCliClient(apiKey);
223
+ let body = {};
224
+ if (options.fromFile) body = readJsonFile(options.fromFile);
225
+ if (options.name !== void 0) body.displayName = options.name;
226
+ if (options.description !== void 0) body.description = options.description;
227
+ const schema = parseSchemaOption(options);
228
+ if (schema !== void 0) body.schema = schema;
229
+ if (options.mode !== void 0) body.validationMode = options.mode;
230
+ if (Object.keys(body).length === 0) {
231
+ console.error(chalk.red("Error: provide at least one of --name, --description, --schema, --schema-file, --mode, or --from-file"));
232
+ process.exit(1);
233
+ }
234
+ const printUpdateResult = (data) => {
235
+ console.log(chalk.green("Collection updated"));
236
+ printCollectionDetail(data.collection);
237
+ if (data.schemaChange) {
238
+ console.log(` Schema change: ${data.schemaChange.kind}`);
239
+ for (const change of data.schemaChange.breakingChanges ?? []) console.log(chalk.yellow(` ${change}`));
240
+ }
241
+ if (data.enforceCheck) {
242
+ const { checked, failed, truncated } = data.enforceCheck;
243
+ const line = ` Enforce check: ${failed} of ${checked} existing records would fail${truncated ? " (truncated)" : ""}`;
244
+ console.log(failed > 0 ? chalk.yellow(line) : chalk.green(line));
245
+ }
246
+ };
247
+ if (!isTTY(options) || options.json) {
248
+ try {
249
+ const data = await client.collections.update(slug, body);
250
+ if (options.json) {
251
+ printJson(data);
252
+ return;
253
+ }
254
+ printUpdateResult(data);
255
+ } catch (error) {
256
+ const message = error instanceof Error ? error.message : "Unknown error";
257
+ console.error(chalk.red(`Failed to update collection: ${message}`));
258
+ process.exit(1);
259
+ }
260
+ return;
261
+ }
262
+ const App = () => {
263
+ const [loading, setLoading] = useState(true);
264
+ const [success, setSuccess] = useState(null);
265
+ const [error, setError] = useState(null);
266
+ const [resultNode, setResultNode] = useState(void 0);
267
+ useEffect(() => {
268
+ client.collections.update(slug, body).then((data) => {
269
+ const fields = collectionFields(data.collection);
270
+ if (data.schemaChange) fields.push({
271
+ label: "Schema change",
272
+ value: data.schemaChange.kind
273
+ });
274
+ if (data.enforceCheck) fields.push({
275
+ label: "Enforce check",
276
+ value: `${data.enforceCheck.failed} of ${data.enforceCheck.checked} existing records would fail`,
277
+ color: data.enforceCheck.failed > 0 ? "yellow" : "green"
278
+ });
279
+ setResultNode(React.createElement(EntityCard, { fields }));
280
+ setSuccess(true);
281
+ setLoading(false);
282
+ }).catch((err) => {
283
+ setError(err instanceof Error ? err : new Error(String(err)));
284
+ setSuccess(false);
285
+ setLoading(false);
286
+ });
287
+ }, []);
288
+ return React.createElement(MutationResult, {
289
+ loading,
290
+ loadingLabel: "Updating collection...",
291
+ success,
292
+ successMessage: "Collection updated",
293
+ error,
294
+ result: resultNode
295
+ });
296
+ };
297
+ const { waitUntilExit } = render(React.createElement(App));
298
+ await waitUntilExit();
299
+ });
300
+ collectionsCommand.command("delete <slug>").description("Delete a collection registration (records of the type are not touched)").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (slug, options) => {
301
+ const apiKey = await ensureAuth();
302
+ if (!apiKey) return;
303
+ const client = createCliClient(apiKey);
304
+ if (!isTTY(options)) {
305
+ try {
306
+ await client.collections.delete(slug);
307
+ console.log(JSON.stringify({
308
+ status: "deleted",
309
+ slug
310
+ }, null, 2));
311
+ } catch (error) {
312
+ const message = error instanceof Error ? error.message : "Unknown error";
313
+ console.error(chalk.red(`Failed to delete collection: ${message}`));
314
+ process.exit(1);
315
+ }
316
+ return;
317
+ }
318
+ const App = () => {
319
+ const [confirmed, setConfirmed] = useState(null);
320
+ const [loading, setLoading] = useState(false);
321
+ const [success, setSuccess] = useState(null);
322
+ const [error, setError] = useState(null);
323
+ useEffect(() => {
324
+ if (confirmed !== true) return void 0;
325
+ setLoading(true);
326
+ client.collections.delete(slug).then(() => {
327
+ setSuccess(true);
328
+ setLoading(false);
329
+ }).catch((err) => {
330
+ setError(err instanceof Error ? err : new Error(String(err)));
331
+ setSuccess(false);
332
+ setLoading(false);
333
+ });
334
+ }, [confirmed]);
335
+ if (confirmed === null) return React.createElement(ConfirmPrompt, {
336
+ message: `Delete collection ${slug}? Records of this type are not touched.`,
337
+ onConfirm: (val) => setConfirmed(val)
338
+ });
339
+ if (confirmed === false) return React.createElement(MutationResult, {
340
+ loading: false,
341
+ success: true,
342
+ successMessage: "Cancelled",
343
+ error: null
344
+ });
345
+ return React.createElement(MutationResult, {
346
+ loading,
347
+ loadingLabel: "Deleting collection...",
348
+ success,
349
+ successMessage: "Collection deleted",
350
+ error
351
+ });
352
+ };
353
+ const { waitUntilExit } = render(React.createElement(App));
354
+ await waitUntilExit();
355
+ });
356
+ collectionsCommand.command("infer <slug>").description("Propose a metadata schema from existing records (saves nothing)").option("--sample <n>", "How many of the most recently updated records to sample (1-1000)").option("-o, --output <file>", "Write the proposed schema JSON to a file").option("--json", "Output the full proposal as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (slug, options) => {
357
+ const apiKey = await ensureAuth();
358
+ if (!apiKey) return;
359
+ const client = createCliClient(apiKey);
360
+ const params = options.sample ? { sample: parseInt(options.sample, 10) } : void 0;
361
+ try {
362
+ const proposal = await client.collections.inferSchema(slug, params);
363
+ if (options.output) {
364
+ const { writeFileSync } = await import("fs");
365
+ writeFileSync(options.output, JSON.stringify(proposal.schema, null, 2));
366
+ console.log(chalk.green(`Proposed schema written to ${options.output}`));
367
+ }
368
+ if (options.json) {
369
+ printJson(proposal);
370
+ return;
371
+ }
372
+ console.log(`Sampled ${proposal.sampledRecords} of ${proposal.totalRecords} "${slug}" records`);
373
+ if (!proposal.schema) {
374
+ console.log(chalk.yellow("No schema could be inferred (no records or no stable fields)"));
375
+ return;
376
+ }
377
+ console.log(chalk.cyan("\nProposed fields:"));
378
+ for (const field of proposal.fields) {
379
+ const enumTag = field.enum ? ` enum(${field.enum.join("|")})` : "";
380
+ const format = field.format ? `:${field.format}` : "";
381
+ console.log(` ${field.name}: ${field.type}${format}${enumTag} ${chalk.gray(`confidence ${Math.round(field.confidence * 100)}%, present in ${Math.round(field.presence * 100)}%`)}`);
382
+ }
383
+ if (proposal.skippedKeys.length > 0) console.log(chalk.gray(`\nSkipped keys: ${proposal.skippedKeys.join(", ")}`));
384
+ console.log(chalk.cyan("\nProposed schema:"));
385
+ console.log(JSON.stringify(proposal.schema, null, 2));
386
+ console.log(chalk.gray(`\nNothing was saved. Persist it with: runtype collections update ${slug} --schema-file <file> --mode warn`));
387
+ } catch (error) {
388
+ const message = error instanceof Error ? error.message : "Unknown error";
389
+ console.error(chalk.red(`Failed to infer schema: ${message}`));
390
+ process.exit(1);
391
+ }
392
+ });
393
+ collectionsCommand.command("validate <slug>").description("Dry-run existing records against a schema (writes nothing)").option("--schema <json>", "Proposed schema as a JSON string (defaults to the saved schema)").option("--schema-file <path>", "Read the proposed schema from a JSON file").option("--json", "Output the full result as JSON").action(async (slug, options) => {
394
+ const apiKey = await ensureAuth();
395
+ if (!apiKey) return;
396
+ const client = createCliClient(apiKey);
397
+ const schema = parseSchemaOption(options);
398
+ try {
399
+ const result = await client.collections.validateExisting(slug, schema !== void 0 ? { schema } : void 0);
400
+ if (options.json) {
401
+ printJson(result);
402
+ return;
403
+ }
404
+ const summary = `${result.failed} of ${result.checked} existing "${slug}" records would fail${result.truncated ? " (truncated at the check cap)" : ""}`;
405
+ console.log(result.failed > 0 ? chalk.yellow(summary) : chalk.green(summary));
406
+ for (const example of result.examples) {
407
+ console.log(` ${chalk.green(example.recordId)} ${example.name}`);
408
+ for (const err of example.errors) console.log(chalk.yellow(` ${err.field || "(document)"}: [${err.code}] ${err.message}`));
409
+ }
410
+ if (result.failed === 0) console.log(chalk.gray(`\nSafe to enforce: runtype collections update ${slug} --mode enforce`));
411
+ } catch (error) {
412
+ const message = error instanceof Error ? error.message : "Unknown error";
413
+ console.error(chalk.red(`Failed to validate records: ${message}`));
414
+ process.exit(1);
415
+ }
416
+ });
417
+ export { collectionsCommand };
418
+