@riddledc/riddle-proof 0.5.54 → 0.5.56

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.
@@ -87,12 +87,94 @@ async function deployRiddleStaticPreview(config, directory, label) {
87
87
  raw: published
88
88
  };
89
89
  }
90
+ function createTarball(directory, label, exclude = []) {
91
+ const scratch = mkdtempSync(path.join(tmpdir(), "riddle-upload-"));
92
+ const tarball = path.join(scratch, `${label}.tar.gz`);
93
+ const excludeArgs = exclude.flatMap((item) => ["--exclude", item]);
94
+ try {
95
+ execFileSync("tar", ["czf", tarball, ...excludeArgs, "-C", directory, "."], { stdio: "pipe" });
96
+ return { scratch, tarball };
97
+ } catch (error) {
98
+ rmSync(scratch, { recursive: true, force: true });
99
+ throw error;
100
+ }
101
+ }
90
102
  function parseRiddleViewport(value) {
91
103
  if (!value) return void 0;
92
104
  const match = /^(\d+)x(\d+)$/.exec(value.trim());
93
105
  if (!match) throw new Error("viewport must look like 1280x720");
94
106
  return { width: Number(match[1]), height: Number(match[2]) };
95
107
  }
108
+ function scriptErrorFrom(job) {
109
+ const nested = job?.proof_of_execution && typeof job.proof_of_execution === "object" && !Array.isArray(job.proof_of_execution) ? job.proof_of_execution : null;
110
+ return typeof job?.script_error === "string" && job.script_error.trim() ? job.script_error : typeof nested?.script_error === "string" && nested.script_error.trim() ? nested.script_error : null;
111
+ }
112
+ async function runRiddleServerPreview(config, input) {
113
+ if (!input.directory?.trim()) throw new Error("directory is required");
114
+ if (!input.script?.trim()) throw new Error("script is required");
115
+ const port = input.port || 3e3;
116
+ const routePath = input.path || "/";
117
+ const timeoutSec = input.timeoutSec || 180;
118
+ const created = await riddleRequestJson(config, "/v1/server-preview", {
119
+ method: "POST",
120
+ body: JSON.stringify({
121
+ image: input.image || "node:22-slim",
122
+ command: input.command || "node scripts/riddleSpaPreviewServer.mjs build 3000",
123
+ port,
124
+ path: routePath,
125
+ readiness_path: input.readinessPath || routePath,
126
+ readiness_timeout: input.readinessTimeoutSec || 90,
127
+ wait_until: input.waitUntil || "domcontentloaded",
128
+ wait_for_selector: input.waitForSelector || "body",
129
+ navigation_timeout: input.navigationTimeoutSec || 60,
130
+ viewport: input.viewport,
131
+ timeout: timeoutSec,
132
+ script: input.script
133
+ })
134
+ });
135
+ const jobId = String(created.job_id || "");
136
+ const uploadUrl = String(created.upload_url || "");
137
+ if (!jobId || !uploadUrl) {
138
+ throw new Error(`Riddle server preview create response was missing job_id or upload_url.`);
139
+ }
140
+ const { scratch, tarball } = createTarball(input.directory, jobId, [
141
+ ".git",
142
+ "node_modules",
143
+ "test-results",
144
+ ...input.exclude || []
145
+ ]);
146
+ try {
147
+ const upload = await fetchFor(config)(uploadUrl, {
148
+ method: "PUT",
149
+ headers: { "Content-Type": "application/gzip" },
150
+ body: readFileSync(tarball)
151
+ });
152
+ if (!upload.ok) throw new RiddleApiError(uploadUrl, upload.status, await upload.text());
153
+ } finally {
154
+ rmSync(scratch, { recursive: true, force: true });
155
+ }
156
+ await riddleRequestJson(config, `/v1/server-preview/${jobId}/start`, {
157
+ method: "POST"
158
+ });
159
+ let job = null;
160
+ const attempts = input.pollAttempts || Math.ceil((timeoutSec + 90) / 2);
161
+ const intervalMs = input.pollIntervalMs || 2e3;
162
+ for (let index = 0; index < attempts; index += 1) {
163
+ job = await riddleRequestJson(config, `/v1/server-preview/${jobId}`);
164
+ if (isTerminalRiddleJobStatus(job.status)) break;
165
+ if (index + 1 < attempts) await new Promise((resolve) => setTimeout(resolve, intervalMs));
166
+ }
167
+ const status = job?.status ? String(job.status) : null;
168
+ const scriptError = scriptErrorFrom(job);
169
+ return {
170
+ ok: (status === "completed" || status === "complete") && !scriptError,
171
+ job_id: jobId,
172
+ status,
173
+ terminal: isTerminalRiddleJobStatus(status),
174
+ script_error: scriptError,
175
+ job
176
+ };
177
+ }
96
178
  async function runRiddleScript(config, input) {
97
179
  if (!input.url?.trim()) throw new Error("url is required");
98
180
  if (!input.script?.trim()) throw new Error("script is required");
@@ -144,6 +226,7 @@ function createRiddleApiClient(config = {}) {
144
226
  requestJson: (pathname, init) => riddleRequestJson(config, pathname, init),
145
227
  deployStaticPreview: (directory, label) => deployRiddleStaticPreview(config, directory, label),
146
228
  runScript: (input) => runRiddleScript(config, input),
229
+ runServerPreview: (input) => runRiddleServerPreview(config, input),
147
230
  pollJob: (jobId, options) => pollRiddleJob(config, jobId, options)
148
231
  };
149
232
  }
@@ -156,6 +239,7 @@ export {
156
239
  riddleRequestJson,
157
240
  deployRiddleStaticPreview,
158
241
  parseRiddleViewport,
242
+ runRiddleServerPreview,
159
243
  runRiddleScript,
160
244
  isTerminalRiddleJobStatus,
161
245
  pollRiddleJob,
@@ -0,0 +1,276 @@
1
+ // src/basic-gameplay.ts
2
+ var RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION = "riddle-proof.basic-gameplay.v1";
3
+ var RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION = "riddle-proof.basic-gameplay.assessment.v1";
4
+ var RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION = "riddle-proof.basic-gameplay.catch.v1";
5
+ var BASIC_GAMEPLAY_CONTAINER_KEYS = [
6
+ "basic_gameplay",
7
+ "basicGameplay",
8
+ "basic_gameplay_evidence",
9
+ "basicGameplayEvidence",
10
+ "gameplay_proof",
11
+ "gameplayProof"
12
+ ];
13
+ function assessBasicGameplayEvidence(evidence, options = {}) {
14
+ const run = extractBasicGameplayEvidence(evidence);
15
+ if (!run) {
16
+ return {
17
+ version: RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION,
18
+ evidence_present: false,
19
+ passed: false,
20
+ checked_routes: 0,
21
+ passing_routes: 0,
22
+ failing_routes: [],
23
+ failure_counts: {},
24
+ warning_counts: {},
25
+ route_results: []
26
+ };
27
+ }
28
+ const routeResults = (run.results || []).map((route) => assessBasicGameplayRoute(route, options));
29
+ const failingRoutes = routeResults.filter((result) => !result.ok).map((result) => ({
30
+ name: result.name,
31
+ path: result.path,
32
+ failures: result.failures,
33
+ warnings: result.warnings
34
+ }));
35
+ return {
36
+ version: RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION,
37
+ evidence_present: true,
38
+ passed: failingRoutes.length === 0,
39
+ checked_routes: routeResults.length,
40
+ passing_routes: routeResults.filter((result) => result.ok).length,
41
+ failing_routes: failingRoutes,
42
+ failure_counts: countCodes(routeResults.flatMap((result) => result.failures)),
43
+ warning_counts: countCodes(routeResults.flatMap((result) => result.warnings)),
44
+ route_results: routeResults
45
+ };
46
+ }
47
+ function assessBasicGameplayRoute(route, options = {}) {
48
+ const maxMobileOverflowPx = options.maxMobileOverflowPx ?? 4;
49
+ const minBodyTextLength = options.minBodyTextLength ?? 20;
50
+ const minVisibleLargeNodes = options.minVisibleLargeNodes ?? 3;
51
+ const minSurfaceLargeNodes = options.minSurfaceLargeNodes ?? 8;
52
+ const warnOnMissingResetPath = options.warnOnMissingResetPath ?? true;
53
+ const warnOnConsoleError = options.warnOnConsoleError ?? true;
54
+ const failOnConsoleError = options.failOnConsoleError ?? false;
55
+ const failures = [];
56
+ const warnings = [];
57
+ const initial = route.initial || {};
58
+ const timed = route.timed || {};
59
+ const afterAction = route.after_action || route.afterAction || {};
60
+ const mobile = route.mobile || {};
61
+ const timedChange = changed(initial, timed);
62
+ const actionChange = changed(timed, afterAction);
63
+ const surfaceVisible = numberValue(initial.visible_canvas_count) > 0 || numberValue(initial.enabled_clickable_count) > 0 || numberValue(initial.visible_large_node_count) >= minSurfaceLargeNodes;
64
+ const actionResults = listValue(route.action_results || route.actionResults);
65
+ const actionAttempted = actionResults.some((result) => result.ok === true && result.action !== "wait");
66
+ const actionFailed = actionResults.some((result) => result.ok === false && result.action !== "wait");
67
+ const stateChangeObserved = actionChange.changed || timedChange.changed;
68
+ const resetPathPresent = numberValue(initial.reset_control_count) > 0 || numberValue(timed.reset_control_count) > 0 || numberValue(afterAction.reset_control_count) > 0;
69
+ const responseStatus = firstNumber(route.http_status, route.response_status, route.status);
70
+ const pageErrorCount = numberValue(route.page_error_count);
71
+ const consoleErrorCount = numberValue(route.console_error_count);
72
+ const mobileOverflowPx = numberValue(mobile.overflow_px);
73
+ if (responseStatus !== null && responseStatus >= 400) failures.push("route_http_error");
74
+ if (pageErrorCount > 0) failures.push("fatal_page_error");
75
+ if (numberValue(initial.body_text_length) < minBodyTextLength && numberValue(initial.visible_large_node_count) < minVisibleLargeNodes) {
76
+ failures.push("route_blank_or_thin");
77
+ }
78
+ if (!surfaceVisible) failures.push("no_game_surface");
79
+ if (mobileOverflowPx > maxMobileOverflowPx) failures.push("mobile_horizontal_overflow");
80
+ if (!actionAttempted && !timedChange.changed) failures.push("primary_control_missing");
81
+ if (actionAttempted && !stateChangeObserved) failures.push("primary_control_inert");
82
+ if (failOnConsoleError && consoleErrorCount > 0) failures.push("fatal_page_error");
83
+ if (numberValue(initial.visible_canvas_count) > 0 && actionAttempted && !timedChange.canvas_changed && !actionChange.canvas_changed && !actionChange.screenshot_changed) {
84
+ warnings.push("canvas_inert");
85
+ }
86
+ if (actionFailed) warnings.push("some_actions_failed");
87
+ if (warnOnMissingResetPath && !resetPathPresent && route.requires_reset !== false && actionAttempted && stateChangeObserved) {
88
+ warnings.push("missing_reset_path");
89
+ }
90
+ if (warnOnConsoleError && consoleErrorCount > 0) warnings.push("critical_console_error");
91
+ return {
92
+ name: route.name,
93
+ path: route.path,
94
+ ok: failures.length === 0,
95
+ failures,
96
+ warnings,
97
+ signals: {
98
+ route_loaded: !failures.includes("route_http_error") && !failures.includes("route_blank_or_thin"),
99
+ surface_visible: surfaceVisible,
100
+ action_attempted: actionAttempted,
101
+ timed_progression_observed: timedChange.changed,
102
+ first_interaction_observed: actionChange.changed,
103
+ state_change_observed: stateChangeObserved,
104
+ mobile_overflow_absent: mobileOverflowPx <= maxMobileOverflowPx,
105
+ reset_path_present: resetPathPresent,
106
+ fatal_errors_absent: pageErrorCount === 0
107
+ },
108
+ diffs: {
109
+ timed: timedChange,
110
+ after_action: actionChange
111
+ }
112
+ };
113
+ }
114
+ function createBasicGameplayCatchSummary(input, options = {}) {
115
+ const before = summarizeAssessment(assessBasicGameplayEvidence(input.before, options));
116
+ const after = input.after === void 0 ? void 0 : summarizeAssessment(assessBasicGameplayEvidence(input.after, options));
117
+ const fixed = Boolean(after && before.notable_codes.length > 0 && after.passed && after.notable_codes.length === 0);
118
+ const title = input.title || [
119
+ input.site || "Basic gameplay",
120
+ input.route ? `${input.route} proof catch` : "proof catch"
121
+ ].join(" ");
122
+ const beforeCodes = before.notable_codes.length ? before.notable_codes.join(", ") : "no failing or warning codes";
123
+ const afterCodes = after ? after.notable_codes.length ? after.notable_codes.join(", ") : "no failing or warning codes" : "not verified";
124
+ const summaryLines = [
125
+ `Before: ${before.checked_routes} checked, ${before.passing_routes} passing, codes: ${beforeCodes}.`,
126
+ after ? `After: ${after.checked_routes} checked, ${after.passing_routes} passing, codes: ${afterCodes}.` : "After: not provided."
127
+ ];
128
+ if (input.fix?.summary) summaryLines.push(`Fix: ${input.fix.summary}`);
129
+ if (input.artifacts?.length) summaryLines.push(`Artifacts: ${input.artifacts.length} attached.`);
130
+ return {
131
+ version: RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION,
132
+ title,
133
+ site: input.site,
134
+ route: input.route,
135
+ detected_at: input.detected_at || (/* @__PURE__ */ new Date()).toISOString(),
136
+ before,
137
+ after,
138
+ fixed,
139
+ fix: input.fix,
140
+ artifacts: input.artifacts || [],
141
+ summary_lines: summaryLines,
142
+ marketing_summary: fixed ? `${title}: Riddle Proof caught ${beforeCodes}; after the fix, ${afterCodes}.` : `${title}: Riddle Proof caught ${beforeCodes}; after evidence is ${after ? "not yet clean" : "not yet attached"}.`
143
+ };
144
+ }
145
+ function extractBasicGameplayEvidence(...sources) {
146
+ const seen = /* @__PURE__ */ new Set();
147
+ for (const source of sources) {
148
+ const found = findBasicGameplayEvidence(source, seen);
149
+ if (found) return found;
150
+ }
151
+ return null;
152
+ }
153
+ function summarizeAssessment(assessment) {
154
+ return {
155
+ evidence_present: assessment.evidence_present,
156
+ passed: assessment.passed,
157
+ checked_routes: assessment.checked_routes,
158
+ passing_routes: assessment.passing_routes,
159
+ failing_routes: assessment.failing_routes,
160
+ failure_counts: assessment.failure_counts,
161
+ warning_counts: assessment.warning_counts,
162
+ notable_codes: [
163
+ ...Object.keys(assessment.failure_counts),
164
+ ...Object.keys(assessment.warning_counts)
165
+ ].sort()
166
+ };
167
+ }
168
+ function findBasicGameplayEvidence(value, seen, depth = 0) {
169
+ if (depth > 6 || value === null || value === void 0) return null;
170
+ if (typeof value === "string") {
171
+ const parsed = parseJson(value);
172
+ return parsed === null ? null : findBasicGameplayEvidence(parsed, seen, depth + 1);
173
+ }
174
+ if (Array.isArray(value)) {
175
+ if (seen.has(value)) return null;
176
+ seen.add(value);
177
+ if (value.some((item) => hasRouteShape(recordValue(item)))) {
178
+ return { results: value.filter((item) => hasRouteShape(recordValue(item))) };
179
+ }
180
+ for (const item of value) {
181
+ const found = findBasicGameplayEvidence(item, seen, depth + 1);
182
+ if (found) return found;
183
+ }
184
+ return null;
185
+ }
186
+ const record = recordValue(value);
187
+ if (!record || seen.has(record)) return null;
188
+ seen.add(record);
189
+ if (record.version === RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION || Array.isArray(record.results)) {
190
+ return {
191
+ ...record,
192
+ results: listValue(record.results).filter((item) => Boolean(recordValue(item)))
193
+ };
194
+ }
195
+ if (hasRouteShape(record)) {
196
+ return { results: [record] };
197
+ }
198
+ for (const key of BASIC_GAMEPLAY_CONTAINER_KEYS) {
199
+ if (Object.prototype.hasOwnProperty.call(record, key)) {
200
+ const nested = findBasicGameplayEvidence(record[key], seen, depth + 1);
201
+ if (nested) return nested;
202
+ }
203
+ }
204
+ for (const item of Object.values(record)) {
205
+ const found = findBasicGameplayEvidence(item, seen, depth + 1);
206
+ if (found) return found;
207
+ }
208
+ return null;
209
+ }
210
+ function hasRouteShape(record) {
211
+ return Boolean(record && (record.initial || record.after_action || record.afterAction || record.action_results || record.actionResults) && (record.path || record.name));
212
+ }
213
+ function changed(before, after) {
214
+ const bodyTextChanged = Boolean(before.body_text_hash && after.body_text_hash && before.body_text_hash !== after.body_text_hash);
215
+ const screenshotChanged = Boolean(before.screenshot_hash && after.screenshot_hash && before.screenshot_hash !== after.screenshot_hash);
216
+ const beforeCanvasHashes = canvasHashes(before.canvases);
217
+ const afterCanvasHashes = canvasHashes(after.canvases);
218
+ const canvasChanged = Boolean(beforeCanvasHashes && afterCanvasHashes && beforeCanvasHashes !== afterCanvasHashes);
219
+ return {
220
+ body_text_changed: bodyTextChanged,
221
+ screenshot_changed: screenshotChanged,
222
+ canvas_changed: canvasChanged,
223
+ changed: bodyTextChanged || screenshotChanged || canvasChanged
224
+ };
225
+ }
226
+ function canvasHashes(canvases) {
227
+ return (canvases || []).map((canvas) => canvas.hash).filter(Boolean).join("|");
228
+ }
229
+ function countCodes(codes) {
230
+ const counts = {};
231
+ for (const code of codes) counts[code] = (counts[code] || 0) + 1;
232
+ return counts;
233
+ }
234
+ function firstNumber(...values) {
235
+ for (const value of values) {
236
+ const number = numericValue(value);
237
+ if (number !== null) return number;
238
+ }
239
+ return null;
240
+ }
241
+ function numberValue(value) {
242
+ return numericValue(value) ?? 0;
243
+ }
244
+ function numericValue(value) {
245
+ if (typeof value === "number" && Number.isFinite(value)) return value;
246
+ if (typeof value === "string" && value.trim()) {
247
+ const parsed = Number(value);
248
+ return Number.isFinite(parsed) ? parsed : null;
249
+ }
250
+ return null;
251
+ }
252
+ function recordValue(value) {
253
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
254
+ }
255
+ function listValue(value) {
256
+ return Array.isArray(value) ? value : [];
257
+ }
258
+ function parseJson(value) {
259
+ const trimmed = value.trim();
260
+ if (!trimmed || !/^[{[]/.test(trimmed)) return null;
261
+ try {
262
+ return JSON.parse(trimmed);
263
+ } catch {
264
+ return null;
265
+ }
266
+ }
267
+
268
+ export {
269
+ RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION,
270
+ RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION,
271
+ RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION,
272
+ assessBasicGameplayEvidence,
273
+ assessBasicGameplayRoute,
274
+ createBasicGameplayCatchSummary,
275
+ extractBasicGameplayEvidence
276
+ };
package/dist/cli.cjs CHANGED
@@ -6487,12 +6487,94 @@ async function deployRiddleStaticPreview(config, directory, label) {
6487
6487
  raw: published
6488
6488
  };
6489
6489
  }
6490
+ function createTarball(directory, label, exclude = []) {
6491
+ const scratch = (0, import_node_fs5.mkdtempSync)(import_node_path5.default.join((0, import_node_os2.tmpdir)(), "riddle-upload-"));
6492
+ const tarball = import_node_path5.default.join(scratch, `${label}.tar.gz`);
6493
+ const excludeArgs = exclude.flatMap((item) => ["--exclude", item]);
6494
+ try {
6495
+ (0, import_node_child_process4.execFileSync)("tar", ["czf", tarball, ...excludeArgs, "-C", directory, "."], { stdio: "pipe" });
6496
+ return { scratch, tarball };
6497
+ } catch (error) {
6498
+ (0, import_node_fs5.rmSync)(scratch, { recursive: true, force: true });
6499
+ throw error;
6500
+ }
6501
+ }
6490
6502
  function parseRiddleViewport(value) {
6491
6503
  if (!value) return void 0;
6492
6504
  const match = /^(\d+)x(\d+)$/.exec(value.trim());
6493
6505
  if (!match) throw new Error("viewport must look like 1280x720");
6494
6506
  return { width: Number(match[1]), height: Number(match[2]) };
6495
6507
  }
6508
+ function scriptErrorFrom(job) {
6509
+ const nested = job?.proof_of_execution && typeof job.proof_of_execution === "object" && !Array.isArray(job.proof_of_execution) ? job.proof_of_execution : null;
6510
+ return typeof job?.script_error === "string" && job.script_error.trim() ? job.script_error : typeof nested?.script_error === "string" && nested.script_error.trim() ? nested.script_error : null;
6511
+ }
6512
+ async function runRiddleServerPreview(config, input) {
6513
+ if (!input.directory?.trim()) throw new Error("directory is required");
6514
+ if (!input.script?.trim()) throw new Error("script is required");
6515
+ const port = input.port || 3e3;
6516
+ const routePath = input.path || "/";
6517
+ const timeoutSec = input.timeoutSec || 180;
6518
+ const created = await riddleRequestJson(config, "/v1/server-preview", {
6519
+ method: "POST",
6520
+ body: JSON.stringify({
6521
+ image: input.image || "node:22-slim",
6522
+ command: input.command || "node scripts/riddleSpaPreviewServer.mjs build 3000",
6523
+ port,
6524
+ path: routePath,
6525
+ readiness_path: input.readinessPath || routePath,
6526
+ readiness_timeout: input.readinessTimeoutSec || 90,
6527
+ wait_until: input.waitUntil || "domcontentloaded",
6528
+ wait_for_selector: input.waitForSelector || "body",
6529
+ navigation_timeout: input.navigationTimeoutSec || 60,
6530
+ viewport: input.viewport,
6531
+ timeout: timeoutSec,
6532
+ script: input.script
6533
+ })
6534
+ });
6535
+ const jobId = String(created.job_id || "");
6536
+ const uploadUrl = String(created.upload_url || "");
6537
+ if (!jobId || !uploadUrl) {
6538
+ throw new Error(`Riddle server preview create response was missing job_id or upload_url.`);
6539
+ }
6540
+ const { scratch, tarball } = createTarball(input.directory, jobId, [
6541
+ ".git",
6542
+ "node_modules",
6543
+ "test-results",
6544
+ ...input.exclude || []
6545
+ ]);
6546
+ try {
6547
+ const upload = await fetchFor(config)(uploadUrl, {
6548
+ method: "PUT",
6549
+ headers: { "Content-Type": "application/gzip" },
6550
+ body: (0, import_node_fs5.readFileSync)(tarball)
6551
+ });
6552
+ if (!upload.ok) throw new RiddleApiError(uploadUrl, upload.status, await upload.text());
6553
+ } finally {
6554
+ (0, import_node_fs5.rmSync)(scratch, { recursive: true, force: true });
6555
+ }
6556
+ await riddleRequestJson(config, `/v1/server-preview/${jobId}/start`, {
6557
+ method: "POST"
6558
+ });
6559
+ let job = null;
6560
+ const attempts = input.pollAttempts || Math.ceil((timeoutSec + 90) / 2);
6561
+ const intervalMs = input.pollIntervalMs || 2e3;
6562
+ for (let index = 0; index < attempts; index += 1) {
6563
+ job = await riddleRequestJson(config, `/v1/server-preview/${jobId}`);
6564
+ if (isTerminalRiddleJobStatus(job.status)) break;
6565
+ if (index + 1 < attempts) await new Promise((resolve) => setTimeout(resolve, intervalMs));
6566
+ }
6567
+ const status = job?.status ? String(job.status) : null;
6568
+ const scriptError = scriptErrorFrom(job);
6569
+ return {
6570
+ ok: (status === "completed" || status === "complete") && !scriptError,
6571
+ job_id: jobId,
6572
+ status,
6573
+ terminal: isTerminalRiddleJobStatus(status),
6574
+ script_error: scriptError,
6575
+ job
6576
+ };
6577
+ }
6496
6578
  async function runRiddleScript(config, input) {
6497
6579
  if (!input.url?.trim()) throw new Error("url is required");
6498
6580
  if (!input.script?.trim()) throw new Error("script is required");
@@ -6544,6 +6626,7 @@ function createRiddleApiClient(config = {}) {
6544
6626
  requestJson: (pathname, init) => riddleRequestJson(config, pathname, init),
6545
6627
  deployStaticPreview: (directory, label) => deployRiddleStaticPreview(config, directory, label),
6546
6628
  runScript: (input) => runRiddleScript(config, input),
6629
+ runServerPreview: (input) => runRiddleServerPreview(config, input),
6547
6630
  pollJob: (jobId, options) => pollRiddleJob(config, jobId, options)
6548
6631
  };
6549
6632
  }
@@ -6558,6 +6641,7 @@ function usage() {
6558
6641
  " riddle-proof-loop respond --state-path <path> --decision <decision> --summary <text> [--payload-json <file|json|->]",
6559
6642
  " riddle-proof-loop status --state-path <path>",
6560
6643
  " riddle-proof-loop riddle-preview-deploy <build-dir> <label>",
6644
+ " riddle-proof-loop riddle-server-preview <directory> --script-file <file> [--path /route] [--wait-for-selector selector]",
6561
6645
  " riddle-proof-loop riddle-run-script --url <url> --script-file <file> [--viewport 1280x720]",
6562
6646
  " riddle-proof-loop riddle-poll <job-id> [--wait]",
6563
6647
  " riddle-proof-loop doctor local [--codex-command <path>]",
@@ -6801,6 +6885,32 @@ async function main() {
6801
6885
  `);
6802
6886
  return;
6803
6887
  }
6888
+ if (command === "riddle-server-preview") {
6889
+ const directory = positional[1];
6890
+ const scriptFile = optionString(options, "scriptFile");
6891
+ if (!directory || !scriptFile) throw new Error("riddle-server-preview requires <directory> and --script-file.");
6892
+ const result = await createRiddleApiClient(riddleClientConfig(options)).runServerPreview({
6893
+ directory,
6894
+ script: (0, import_node_fs6.readFileSync)(scriptFile, "utf-8"),
6895
+ image: optionString(options, "image"),
6896
+ command: optionString(options, "command"),
6897
+ port: optionString(options, "port") ? Number(optionString(options, "port")) : void 0,
6898
+ path: optionString(options, "path"),
6899
+ readinessPath: optionString(options, "readinessPath"),
6900
+ readinessTimeoutSec: optionString(options, "readinessTimeout") ? Number(optionString(options, "readinessTimeout")) : void 0,
6901
+ waitForSelector: optionString(options, "waitForSelector"),
6902
+ navigationTimeoutSec: optionString(options, "navigationTimeout") ? Number(optionString(options, "navigationTimeout")) : void 0,
6903
+ viewport: parseRiddleViewport(optionString(options, "viewport")),
6904
+ timeoutSec: optionString(options, "timeout") ? Number(optionString(options, "timeout")) : void 0,
6905
+ pollAttempts: optionString(options, "pollAttempts") ? Number(optionString(options, "pollAttempts")) : void 0,
6906
+ pollIntervalMs: optionString(options, "pollIntervalMs") ? Number(optionString(options, "pollIntervalMs")) : void 0,
6907
+ exclude: optionString(options, "exclude")?.split(",").map((item) => item.trim()).filter(Boolean)
6908
+ });
6909
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
6910
+ `);
6911
+ process.exitCode = result.ok ? 0 : 1;
6912
+ return;
6913
+ }
6804
6914
  if (command === "riddle-run-script") {
6805
6915
  const url = optionString(options, "url");
6806
6916
  const scriptFile = optionString(options, "scriptFile");
package/dist/cli.js CHANGED
@@ -2,23 +2,23 @@
2
2
  import {
3
3
  createRiddleApiClient,
4
4
  parseRiddleViewport
5
- } from "./chunk-UR6ADV4Y.js";
5
+ } from "./chunk-3CVGVQTQ.js";
6
6
  import {
7
7
  createDisabledRiddleProofAgentAdapter,
8
8
  readRiddleProofRunStatus,
9
9
  runRiddleProofEngineHarness
10
- } from "./chunk-A2AWRZ5B.js";
10
+ } from "./chunk-2FBF2UDZ.js";
11
+ import "./chunk-MO24D3PY.js";
11
12
  import "./chunk-RFJ5BQF6.js";
13
+ import "./chunk-3UHWI3FO.js";
14
+ import {
15
+ createCheckpointResponseTemplate
16
+ } from "./chunk-33XO42CY.js";
12
17
  import "./chunk-JFQXAJH2.js";
13
18
  import {
14
19
  createCodexExecAgentAdapter,
15
20
  runCodexExecAgentDoctor
16
21
  } from "./chunk-3266V3MO.js";
17
- import "./chunk-MO24D3PY.js";
18
- import "./chunk-3UHWI3FO.js";
19
- import {
20
- createCheckpointResponseTemplate
21
- } from "./chunk-33XO42CY.js";
22
22
  import "./chunk-DUFDZJOF.js";
23
23
 
24
24
  // src/cli.ts
@@ -32,6 +32,7 @@ function usage() {
32
32
  " riddle-proof-loop respond --state-path <path> --decision <decision> --summary <text> [--payload-json <file|json|->]",
33
33
  " riddle-proof-loop status --state-path <path>",
34
34
  " riddle-proof-loop riddle-preview-deploy <build-dir> <label>",
35
+ " riddle-proof-loop riddle-server-preview <directory> --script-file <file> [--path /route] [--wait-for-selector selector]",
35
36
  " riddle-proof-loop riddle-run-script --url <url> --script-file <file> [--viewport 1280x720]",
36
37
  " riddle-proof-loop riddle-poll <job-id> [--wait]",
37
38
  " riddle-proof-loop doctor local [--codex-command <path>]",
@@ -275,6 +276,32 @@ async function main() {
275
276
  `);
276
277
  return;
277
278
  }
279
+ if (command === "riddle-server-preview") {
280
+ const directory = positional[1];
281
+ const scriptFile = optionString(options, "scriptFile");
282
+ if (!directory || !scriptFile) throw new Error("riddle-server-preview requires <directory> and --script-file.");
283
+ const result = await createRiddleApiClient(riddleClientConfig(options)).runServerPreview({
284
+ directory,
285
+ script: readFileSync(scriptFile, "utf-8"),
286
+ image: optionString(options, "image"),
287
+ command: optionString(options, "command"),
288
+ port: optionString(options, "port") ? Number(optionString(options, "port")) : void 0,
289
+ path: optionString(options, "path"),
290
+ readinessPath: optionString(options, "readinessPath"),
291
+ readinessTimeoutSec: optionString(options, "readinessTimeout") ? Number(optionString(options, "readinessTimeout")) : void 0,
292
+ waitForSelector: optionString(options, "waitForSelector"),
293
+ navigationTimeoutSec: optionString(options, "navigationTimeout") ? Number(optionString(options, "navigationTimeout")) : void 0,
294
+ viewport: parseRiddleViewport(optionString(options, "viewport")),
295
+ timeoutSec: optionString(options, "timeout") ? Number(optionString(options, "timeout")) : void 0,
296
+ pollAttempts: optionString(options, "pollAttempts") ? Number(optionString(options, "pollAttempts")) : void 0,
297
+ pollIntervalMs: optionString(options, "pollIntervalMs") ? Number(optionString(options, "pollIntervalMs")) : void 0,
298
+ exclude: optionString(options, "exclude")?.split(",").map((item) => item.trim()).filter(Boolean)
299
+ });
300
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
301
+ `);
302
+ process.exitCode = result.ok ? 0 : 1;
303
+ return;
304
+ }
278
305
  if (command === "riddle-run-script") {
279
306
  const url = optionString(options, "url");
280
307
  const scriptFile = optionString(options, "scriptFile");
@@ -2,9 +2,9 @@ import {
2
2
  createDisabledRiddleProofAgentAdapter,
3
3
  readRiddleProofRunStatus,
4
4
  runRiddleProofEngineHarness
5
- } from "./chunk-A2AWRZ5B.js";
6
- import "./chunk-RFJ5BQF6.js";
5
+ } from "./chunk-2FBF2UDZ.js";
7
6
  import "./chunk-MO24D3PY.js";
7
+ import "./chunk-RFJ5BQF6.js";
8
8
  import "./chunk-3UHWI3FO.js";
9
9
  import "./chunk-33XO42CY.js";
10
10
  import "./chunk-DUFDZJOF.js";