@reshotdev/screenshot 0.0.1-beta.1 → 0.0.1-beta.10

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 (38) hide show
  1. package/README.md +65 -7
  2. package/package.json +9 -2
  3. package/src/commands/auth.js +108 -26
  4. package/src/commands/certify.js +62 -0
  5. package/src/commands/ci-run.js +57 -2
  6. package/src/commands/ci-setup.js +5 -5
  7. package/src/commands/doctor-release.js +67 -0
  8. package/src/commands/doctor-target.js +49 -0
  9. package/src/commands/drifts.js +5 -70
  10. package/src/commands/import-tests.js +13 -13
  11. package/src/commands/ingest.js +10 -10
  12. package/src/commands/init.js +16 -277
  13. package/src/commands/publish.js +204 -237
  14. package/src/commands/pull.js +253 -23
  15. package/src/commands/run.js +292 -12
  16. package/src/commands/setup-wizard.js +277 -499
  17. package/src/commands/setup.js +41 -13
  18. package/src/commands/status.js +313 -125
  19. package/src/commands/sync.js +28 -236
  20. package/src/commands/ui.js +1 -1
  21. package/src/commands/verify-publish.js +46 -0
  22. package/src/index.js +194 -94
  23. package/src/lib/api-client.js +121 -35
  24. package/src/lib/capture-engine.js +103 -7
  25. package/src/lib/capture-script-runner.js +305 -58
  26. package/src/lib/certification.js +865 -0
  27. package/src/lib/config.js +181 -76
  28. package/src/lib/record-cdp.js +288 -16
  29. package/src/lib/record-config.js +1 -1
  30. package/src/lib/release-doctor.js +313 -0
  31. package/src/lib/run-manifest.js +103 -0
  32. package/src/lib/standalone-mode.js +1 -1
  33. package/src/lib/storage-providers.js +4 -4
  34. package/src/lib/target-contract.js +292 -0
  35. package/src/lib/ui-api.js +6 -7
  36. package/web/manager/dist/assets/{index--ZgioErz.js → index-D2qqcFNN.js} +1 -1
  37. package/web/manager/dist/index.html +1 -1
  38. package/src/commands/validate-docs.js +0 -529
@@ -2,6 +2,7 @@
2
2
  const chalk = require("chalk");
3
3
  const oraModule = require("ora");
4
4
  const ora = oraModule.default || oraModule;
5
+ const { execSync } = require("child_process");
5
6
 
6
7
  const {
7
8
  writeSettings,
@@ -12,13 +13,13 @@ const {
12
13
  } = require("../lib/config");
13
14
 
14
15
  /**
15
- * Create a default docsync.config.json for platform mode
16
+ * Create a default reshot.config.json for platform mode
16
17
  * @param {Object} settings - Project settings from auth
17
18
  * @returns {Object} The created config
18
19
  */
19
20
  function createDefaultConfig(settings) {
20
21
  const defaultConfig = {
21
- $schema: "https://reshot.dev/schemas/docsync-config.json",
22
+ $schema: "https://reshot.dev/schemas/reshot-config.json",
22
23
  version: "2.0",
23
24
  projectId: settings.projectId,
24
25
  baseUrl: "http://localhost:3000",
@@ -94,22 +95,49 @@ async function setupCommand(options = {}) {
94
95
  console.log(); // Add spacing after auth
95
96
  }
96
97
 
97
- // Step 3: Handle initialization (create docsync.config.json if missing)
98
+ // Step 3: Handle initialization (create reshot.config.json if missing)
98
99
  const hasConfig = configExists();
99
100
 
100
101
  if (hasConfig && !force) {
101
- console.log(chalk.green("✔ Configuration found:"), chalk.cyan("docsync.config.json"));
102
+ console.log(chalk.green("✔ Configuration found:"), chalk.cyan("reshot.config.json"));
102
103
  } else if (hasConfig && force) {
103
- console.log(chalk.yellow("⚠ Overwriting existing docsync.config.json..."));
104
- const newConfig = createDefaultConfig(existingSettings);
105
- writeConfig(newConfig);
106
- console.log(chalk.green("✔ Configuration updated"));
104
+ // Patch projectId in existing config instead of wiping scenarios
105
+ const fs = require("fs");
106
+ const path = require("path");
107
+ const configPath = path.join(process.cwd(), "reshot.config.json");
108
+ const existingConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
109
+ existingConfig.projectId = existingSettings.projectId;
110
+ fs.writeFileSync(configPath, JSON.stringify(existingConfig, null, 2) + "\n");
111
+ console.log(chalk.green("✔ Configuration updated (projectId synced)"));
107
112
  } else {
108
113
  // No config - create it
109
- console.log(chalk.gray("Creating docsync.config.json..."));
114
+ console.log(chalk.gray("Creating reshot.config.json..."));
110
115
  const newConfig = createDefaultConfig(existingSettings);
111
116
  writeConfig(newConfig);
112
- console.log(chalk.green("✔ Configuration created:"), chalk.cyan("docsync.config.json"));
117
+ console.log(chalk.green("✔ Configuration created:"), chalk.cyan("reshot.config.json"));
118
+ }
119
+
120
+ // Step 3.5: Ensure @reshotdev/screenshot is in devDependencies
121
+ const fs = require("fs");
122
+ const path = require("path");
123
+ const pkgJsonPath = path.join(process.cwd(), "package.json");
124
+ if (fs.existsSync(pkgJsonPath)) {
125
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
126
+ const hasDep =
127
+ pkgJson.devDependencies?.["@reshotdev/screenshot"] ||
128
+ pkgJson.dependencies?.["@reshotdev/screenshot"];
129
+ if (!hasDep) {
130
+ console.log(chalk.gray(" Adding @reshotdev/screenshot to devDependencies..."));
131
+ const usePnpm = fs.existsSync(path.join(process.cwd(), "pnpm-lock.yaml"));
132
+ const useYarn = fs.existsSync(path.join(process.cwd(), "yarn.lock"));
133
+ const cmd = usePnpm ? "pnpm add -D" : useYarn ? "yarn add -D" : "npm install -D";
134
+ try {
135
+ execSync(`${cmd} @reshotdev/screenshot`, { stdio: "inherit" });
136
+ console.log(chalk.green(" ✔ Added @reshotdev/screenshot to devDependencies"));
137
+ } catch {
138
+ console.log(chalk.yellow(" ⚠ Could not auto-install. Run manually: " + cmd + " @reshotdev/screenshot"));
139
+ }
140
+ }
113
141
  }
114
142
 
115
143
  // Step 4: Success summary
@@ -128,9 +156,9 @@ async function setupCommand(options = {}) {
128
156
  await uiCommand({ open: true });
129
157
  } else {
130
158
  console.log(chalk.gray("Next steps:"));
131
- console.log(chalk.gray(" • Run"), chalk.cyan("reshot studio"), chalk.gray("to open the management UI"));
132
- console.log(chalk.gray(" • Run"), chalk.cyan("reshot record"), chalk.gray("to capture your first visual"));
133
- console.log(chalk.gray(" • Run"), chalk.cyan("reshot run"), chalk.gray("to execute scenarios\n"));
159
+ console.log(chalk.gray(" • Run"), chalk.cyan("reshot run"), chalk.gray("to generate your first local capture"));
160
+ console.log(chalk.gray(" • Run"), chalk.cyan("reshot publish"), chalk.gray("when you are ready for hosted assets and review workflows"));
161
+ console.log(chalk.gray(" • Run"), chalk.cyan("reshot studio"), chalk.gray("to inspect output locally\n"));
134
162
  }
135
163
  }
136
164
 
@@ -1,20 +1,46 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * status - Check DocSync status, sync jobs, and drift queue
3
+ * status - Check project status, sync jobs, and drift queue
4
4
  *
5
5
  * Displays:
6
6
  * - Current project configuration
7
7
  * - Recent sync jobs with status
8
8
  * - Drift queue summary
9
- * - Pending actions
10
9
  *
11
- * Usage: reshot status [--jobs] [--drifts] [--json]
10
+ * Usage: reshot status [--jobs] [--drifts] [--config] [--json]
12
11
  */
13
12
 
14
13
  const chalk = require("chalk");
15
14
  const config = require("../lib/config");
16
15
  const apiClient = require("../lib/api-client");
17
16
 
17
+ function createIssue(scope, level, message, details = {}) {
18
+ return {
19
+ scope,
20
+ level,
21
+ message,
22
+ ...details,
23
+ };
24
+ }
25
+
26
+ function summarizeConfig(reshotConfig) {
27
+ return {
28
+ baseUrl: reshotConfig.baseUrl || null,
29
+ viewport: reshotConfig.viewport || null,
30
+ assetDir: reshotConfig.assetDir || ".reshot/output",
31
+ scenarioCount: reshotConfig.scenarios?.length || 0,
32
+ traceDir: reshotConfig.visuals?.traceDir || null,
33
+ };
34
+ }
35
+
36
+ function readSettingsSafe() {
37
+ try {
38
+ return config.readSettings();
39
+ } catch (error) {
40
+ return null;
41
+ }
42
+ }
43
+
18
44
  /**
19
45
  * Format timestamp for display
20
46
  */
@@ -86,33 +112,255 @@ function formatDriftType(type) {
86
112
  }
87
113
 
88
114
  /**
89
- * Get and display project configuration
115
+ * Display project configuration summary
90
116
  */
91
- async function displayConfig(docSyncConfig) {
117
+ async function displayConfig(configSummary) {
92
118
  console.log(chalk.bold("\n📋 Project Configuration\n"));
93
119
 
94
- const docConfig = docSyncConfig.documentation;
95
- if (!docConfig) {
96
- console.log(chalk.yellow(" No documentation block configured."));
97
- console.log(chalk.gray(" Add a 'documentation' block to docsync.config.json"));
120
+ console.log(chalk.gray(" Base URL: ") + chalk.white(configSummary.baseUrl || "(not set)"));
121
+ console.log(chalk.gray(" Viewport: ") + chalk.white(
122
+ configSummary.viewport
123
+ ? `${configSummary.viewport.width}×${configSummary.viewport.height}`
124
+ : "(default)"
125
+ ));
126
+ console.log(chalk.gray(" Asset Dir: ") + chalk.white(configSummary.assetDir || ".reshot/output"));
127
+
128
+ console.log(chalk.gray(" Scenarios: ") + chalk.white(`${configSummary.scenarioCount || 0} defined`));
129
+
130
+ if (configSummary.traceDir) {
131
+ console.log(chalk.gray(" Trace Dir: ") + chalk.white(configSummary.traceDir));
132
+ }
133
+ }
134
+
135
+ function renderJobs(jobs) {
136
+ if (jobs.length === 0) {
137
+ console.log(chalk.gray(" No sync jobs found."));
138
+ console.log(chalk.gray(" Run `reshot sync` to upload traces."));
98
139
  return;
99
140
  }
100
141
 
101
- console.log(chalk.gray(" Root: ") + chalk.white(docConfig.root || "./docs"));
102
- console.log(chalk.gray(" Strategy: ") + chalk.white(docConfig.strategy));
103
- console.log(chalk.gray(" Format: ") + chalk.white(docConfig.assetFormat || "markdown"));
104
-
105
- if (docConfig.include?.length) {
106
- console.log(chalk.gray(" Include: ") + chalk.white(docConfig.include.join(", ")));
142
+ for (const job of jobs) {
143
+ console.log(
144
+ ` ${formatJobStatus(job.status)} ` +
145
+ chalk.gray(`${job.id.slice(0, 8)} `) +
146
+ chalk.white(`${job.branch || "unknown"}@${(job.commitHash || "").slice(0, 7)} `) +
147
+ chalk.gray(formatTime(job.createdAt)),
148
+ );
149
+
150
+ if (job.driftCount > 0) {
151
+ console.log(chalk.gray(` └─ ${job.driftCount} drift(s) detected`));
152
+ }
153
+ if (job.errorMessage) {
154
+ console.log(chalk.red(` └─ Error: ${job.errorMessage}`));
155
+ }
156
+ }
157
+ }
158
+
159
+ function renderDrifts(drifts, stats, projectId, limit = 10) {
160
+ if (stats.total > 0) {
161
+ console.log(
162
+ chalk.gray(" Summary: ") +
163
+ chalk.yellow(`${stats.pending || 0} pending`) + ", " +
164
+ chalk.green(`${stats.approved || 0} approved`) + ", " +
165
+ chalk.red(`${stats.rejected || 0} rejected`) + ", " +
166
+ chalk.gray(`${stats.ignored || 0} ignored`),
167
+ );
168
+ console.log();
169
+ }
170
+
171
+ if (drifts.length === 0) {
172
+ console.log(chalk.green(" ✓ No pending drifts!"));
173
+ return;
174
+ }
175
+
176
+ const displayed = drifts.slice(0, limit);
177
+ for (const drift of displayed) {
178
+ console.log(
179
+ ` ${formatDriftType(drift.driftType)} ` +
180
+ chalk.white(drift.journeyKey) + " " +
181
+ chalk.gray(`(${Math.round(drift.confidenceScore * 100)}% confidence)`),
182
+ );
183
+ }
184
+
185
+ if (drifts.length > limit) {
186
+ console.log(chalk.gray(`\n ... and ${drifts.length - limit} more.`));
187
+ }
188
+
189
+ console.log();
190
+ console.log(
191
+ chalk.gray(" View in dashboard: ") +
192
+ chalk.blue(`https://reshot.dev/app/projects/${projectId}/visuals`),
193
+ );
194
+ }
195
+
196
+ async function fetchJobsData(apiKey, projectId, limit = 5) {
197
+ try {
198
+ const response = await apiClient.getSyncJobs(apiKey, projectId, { limit });
199
+ return {
200
+ jobs: response.jobs || response.data?.jobs || [],
201
+ error: null,
202
+ };
203
+ } catch (error) {
204
+ return {
205
+ jobs: [],
206
+ error: {
207
+ message: error.message,
208
+ kind: error.reshot?.kind || null,
209
+ status: error.reshot?.status || error.response?.status || null,
210
+ },
211
+ };
212
+ }
213
+ }
214
+
215
+ async function fetchDriftsData(apiKey, projectId) {
216
+ try {
217
+ const response = await apiClient.getDrifts(apiKey, projectId, {
218
+ status: "PENDING",
219
+ });
220
+ return {
221
+ drifts: response.drifts || [],
222
+ stats: response.stats || {},
223
+ error: null,
224
+ };
225
+ } catch (error) {
226
+ return {
227
+ drifts: [],
228
+ stats: {},
229
+ error: {
230
+ message: error.message,
231
+ kind: error.reshot?.kind || null,
232
+ status: error.reshot?.status || error.response?.status || null,
233
+ },
234
+ };
235
+ }
236
+ }
237
+
238
+ async function buildStatusReport(options = {}) {
239
+ const report = {
240
+ generatedAt: new Date().toISOString(),
241
+ ok: true,
242
+ projectId: null,
243
+ mode: config.getModeInfo(),
244
+ config: {
245
+ exists: false,
246
+ summary: null,
247
+ validation: {
248
+ valid: false,
249
+ errors: [],
250
+ warnings: [],
251
+ },
252
+ },
253
+ auth: {
254
+ hasApiKey: false,
255
+ hasProjectId: false,
256
+ },
257
+ jobs: {
258
+ items: [],
259
+ error: null,
260
+ },
261
+ drifts: {
262
+ items: [],
263
+ stats: {},
264
+ error: null,
265
+ },
266
+ issues: [],
267
+ };
268
+
269
+ let reshotConfig = null;
270
+ try {
271
+ reshotConfig = config.readConfigLenient();
272
+ report.config.exists = true;
273
+ report.config.summary = summarizeConfig(reshotConfig);
274
+ } catch (error) {
275
+ report.issues.push(
276
+ createIssue(
277
+ "config",
278
+ "error",
279
+ "reshot.config.json not found. Run `reshot init` or `reshot setup` first.",
280
+ { detail: error.message },
281
+ ),
282
+ );
283
+ }
284
+
285
+ if (report.config.exists) {
286
+ report.config.validation = config.validateConfig();
287
+ for (const errorMessage of report.config.validation.errors) {
288
+ report.issues.push(createIssue("config", "error", errorMessage));
289
+ }
290
+ for (const warningMessage of report.config.validation.warnings) {
291
+ report.issues.push(createIssue("config", "warning", warningMessage));
292
+ }
293
+ }
294
+
295
+ const settings = readSettingsSafe();
296
+ const apiKey = process.env.RESHOT_API_KEY || settings?.apiKey || null;
297
+ const projectId =
298
+ process.env.RESHOT_PROJECT_ID ||
299
+ settings?.projectId ||
300
+ reshotConfig?._metadata?.projectId ||
301
+ null;
302
+
303
+ report.projectId = projectId;
304
+ report.auth.hasApiKey = Boolean(apiKey);
305
+ report.auth.hasProjectId = Boolean(projectId);
306
+
307
+ if (!apiKey) {
308
+ report.issues.push(
309
+ createIssue(
310
+ "auth",
311
+ "error",
312
+ "API key not found. Set RESHOT_API_KEY or run `reshot auth`.",
313
+ ),
314
+ );
107
315
  }
108
- if (docConfig.exclude?.length) {
109
- console.log(chalk.gray(" Exclude: ") + chalk.white(docConfig.exclude.join(", ")));
316
+
317
+ if (!projectId) {
318
+ report.issues.push(
319
+ createIssue(
320
+ "auth",
321
+ "error",
322
+ "Project ID not found. Set RESHOT_PROJECT_ID or run `reshot setup`.",
323
+ ),
324
+ );
110
325
  }
111
-
112
- const mappingCount = Object.keys(docConfig.mappings || {}).length;
113
- if (mappingCount > 0) {
114
- console.log(chalk.gray(" Mappings: ") + chalk.white(`${mappingCount} explicit binding(s)`));
326
+
327
+ if (apiKey && projectId) {
328
+ const [jobsResult, driftsResult] = await Promise.all([
329
+ fetchJobsData(apiKey, projectId, options.limit || 5),
330
+ fetchDriftsData(apiKey, projectId),
331
+ ]);
332
+
333
+ report.jobs.items = jobsResult.jobs;
334
+ report.jobs.error = jobsResult.error;
335
+ report.drifts.items = driftsResult.drifts;
336
+ report.drifts.stats = driftsResult.stats;
337
+ report.drifts.error = driftsResult.error;
338
+
339
+ if (jobsResult.error) {
340
+ report.issues.push(
341
+ createIssue(
342
+ "jobs",
343
+ "warning",
344
+ `Could not fetch sync jobs: ${jobsResult.error.message}`,
345
+ jobsResult.error,
346
+ ),
347
+ );
348
+ }
349
+
350
+ if (driftsResult.error) {
351
+ report.issues.push(
352
+ createIssue(
353
+ "drifts",
354
+ "warning",
355
+ `Could not fetch drifts: ${driftsResult.error.message}`,
356
+ driftsResult.error,
357
+ ),
358
+ );
359
+ }
115
360
  }
361
+
362
+ report.ok = !report.issues.some((issue) => issue.level === "error");
363
+ return report;
116
364
  }
117
365
 
118
366
  /**
@@ -122,32 +370,9 @@ async function displayJobs(apiKey, projectId, limit = 5) {
122
370
  console.log(chalk.bold("\n📦 Recent Sync Jobs\n"));
123
371
 
124
372
  try {
125
- // Fetch jobs via API
126
373
  const response = await apiClient.getSyncJobs(apiKey, projectId, { limit });
127
-
128
374
  const jobs = response.jobs || response.data?.jobs || [];
129
-
130
- if (jobs.length === 0) {
131
- console.log(chalk.gray(" No sync jobs found."));
132
- console.log(chalk.gray(" Run `reshot ingest` to upload traces and docs."));
133
- return;
134
- }
135
-
136
- for (const job of jobs) {
137
- console.log(
138
- ` ${formatJobStatus(job.status)} ` +
139
- chalk.gray(`${job.id.slice(0, 8)} `) +
140
- chalk.white(`${job.branch || "unknown"}@${(job.commitHash || "").slice(0, 7)} `) +
141
- chalk.gray(formatTime(job.createdAt))
142
- );
143
-
144
- if (job.driftCount > 0) {
145
- console.log(chalk.gray(` └─ ${job.driftCount} drift(s) detected`));
146
- }
147
- if (job.errorMessage) {
148
- console.log(chalk.red(` └─ Error: ${job.errorMessage}`));
149
- }
150
- }
375
+ renderJobs(jobs);
151
376
  } catch (error) {
152
377
  console.log(chalk.yellow(" Could not fetch sync jobs: " + error.message));
153
378
  console.log(chalk.gray(" This endpoint may not be available yet."));
@@ -164,45 +389,7 @@ async function displayDrifts(apiKey, projectId, limit = 10) {
164
389
  const response = await apiClient.getDrifts(apiKey, projectId, { status: "PENDING" });
165
390
  const drifts = response.drifts || [];
166
391
  const stats = response.stats || {};
167
-
168
- // Show stats summary
169
- if (stats.total > 0) {
170
- console.log(
171
- chalk.gray(" Summary: ") +
172
- chalk.yellow(`${stats.pending || 0} pending`) + ", " +
173
- chalk.green(`${stats.approved || 0} approved`) + ", " +
174
- chalk.red(`${stats.rejected || 0} rejected`) + ", " +
175
- chalk.gray(`${stats.ignored || 0} ignored`)
176
- );
177
- console.log();
178
- }
179
-
180
- if (drifts.length === 0) {
181
- console.log(chalk.green(" ✓ No pending drifts!"));
182
- console.log(chalk.gray(" Your documentation is in sync with the application."));
183
- return;
184
- }
185
-
186
- // Show pending drifts
187
- const displayed = drifts.slice(0, limit);
188
- for (const drift of displayed) {
189
- console.log(
190
- ` ${formatDriftType(drift.driftType)} ` +
191
- chalk.white(drift.journeyKey || drift.docPath) + " " +
192
- chalk.gray(`(${Math.round(drift.confidenceScore * 100)}% confidence)`)
193
- );
194
-
195
- if (drift.docPath) {
196
- console.log(chalk.gray(` └─ ${drift.docPath}`));
197
- }
198
- }
199
-
200
- if (drifts.length > limit) {
201
- console.log(chalk.gray(`\n ... and ${drifts.length - limit} more.`));
202
- }
203
-
204
- console.log();
205
- console.log(chalk.gray(" View in dashboard: ") + chalk.blue(`https://reshot.dev/app/projects/${projectId}/docsync`));
392
+ renderDrifts(drifts, stats, projectId, limit);
206
393
  } catch (error) {
207
394
  console.log(chalk.yellow(" Could not fetch drifts: " + error.message));
208
395
  }
@@ -212,64 +399,65 @@ async function displayDrifts(apiKey, projectId, limit = 10) {
212
399
  * Main status command
213
400
  */
214
401
  async function statusCommand(options = {}) {
215
- console.log(chalk.blue("\n📊 Reshot DocSync Status\n"));
402
+ const report = await buildStatusReport(options);
216
403
 
217
- // Read configuration
218
- let docSyncConfig;
219
- try {
220
- docSyncConfig = config.readDocSyncConfig();
221
- } catch (error) {
222
- console.error(chalk.red("Error:"), "docsync.config.json not found. Run `reshot init` first.");
223
- process.exit(1);
404
+ if (options.json) {
405
+ console.log(JSON.stringify(report, null, 2));
406
+ if (!report.ok) {
407
+ process.exitCode = 1;
408
+ }
409
+ return report;
224
410
  }
225
411
 
226
- // Get API key and project ID
227
- const settings = config.readSettings();
228
- const apiKey = process.env.RESHOT_API_KEY || settings?.apiKey;
229
- const projectId =
230
- process.env.RESHOT_PROJECT_ID ||
231
- settings?.projectId ||
232
- docSyncConfig._metadata?.projectId;
412
+ console.log(chalk.blue("\n📊 Reshot Status\n"));
233
413
 
234
- if (!apiKey) {
235
- console.error(chalk.red("Error:"), "API key not found. Set RESHOT_API_KEY or run `reshot auth`.");
236
- process.exit(1);
414
+ if (report.projectId) {
415
+ console.log(chalk.gray(` Project: ${report.projectId}`));
237
416
  }
238
417
 
239
- if (!projectId) {
240
- console.error(chalk.red("Error:"), "Project ID not found. Set RESHOT_PROJECT_ID or run `reshot init`.");
241
- process.exit(1);
418
+ if (report.config.summary && (options.config || (!options.jobs && !options.drifts))) {
419
+ await displayConfig(report.config.summary);
242
420
  }
243
421
 
244
- console.log(chalk.gray(` Project: ${projectId}`));
245
-
246
- // Display configuration
247
- if (options.config || (!options.jobs && !options.drifts)) {
248
- await displayConfig(docSyncConfig);
422
+ if (!options.config && (options.jobs || (!options.config && !options.drifts))) {
423
+ console.log(chalk.bold("\n📦 Recent Sync Jobs\n"));
424
+ if (report.jobs.error) {
425
+ console.log(chalk.yellow(" Could not fetch sync jobs: " + report.jobs.error.message));
426
+ console.log(chalk.gray(" This endpoint may not be available yet."));
427
+ } else {
428
+ renderJobs(report.jobs.items);
429
+ }
249
430
  }
250
431
 
251
- // Display jobs
252
- if (options.jobs || (!options.config && !options.drifts)) {
253
- await displayJobs(apiKey, projectId, options.limit || 5);
432
+ if (!options.jobs && (options.drifts || (!options.config && !options.jobs))) {
433
+ console.log(chalk.bold("\n🔄 Drift Queue\n"));
434
+ if (report.drifts.error) {
435
+ console.log(chalk.yellow(" Could not fetch drifts: " + report.drifts.error.message));
436
+ } else {
437
+ renderDrifts(
438
+ report.drifts.items,
439
+ report.drifts.stats,
440
+ report.projectId,
441
+ options.limit || 10,
442
+ );
443
+ }
254
444
  }
255
445
 
256
- // Display drifts
257
- if (options.drifts || (!options.config && !options.jobs)) {
258
- await displayDrifts(apiKey, projectId, options.limit || 10);
446
+ if (report.issues.length > 0) {
447
+ console.log(chalk.bold("\n⚠ Issues\n"));
448
+ for (const issue of report.issues) {
449
+ const prefix = issue.level === "error" ? chalk.red(" ✖") : chalk.yellow(" ⚠");
450
+ console.log(`${prefix} [${issue.scope}] ${issue.message}`);
451
+ }
259
452
  }
260
453
 
261
- // JSON output mode
262
- if (options.json) {
263
- const response = await apiClient.getDrifts(apiKey, projectId).catch(() => ({}));
264
- console.log(JSON.stringify({
265
- projectId,
266
- config: docSyncConfig.documentation,
267
- drifts: response.drifts || [],
268
- stats: response.stats || {},
269
- }, null, 2));
454
+ if (!report.ok) {
455
+ process.exitCode = 1;
270
456
  }
271
457
 
272
458
  console.log();
459
+ return report;
273
460
  }
274
461
 
275
462
  module.exports = statusCommand;
463
+ module.exports.buildStatusReport = buildStatusReport;