autokap 1.5.7 → 1.6.1

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.
@@ -686,6 +686,7 @@ async function uploadResults(config, program, result, runId = randomUUID()) {
686
686
  healerPatches: sanitizedHealerPatches,
687
687
  runResult: strippedRunResult,
688
688
  totalDurationMs: result.totalDurationMs,
689
+ detectedAppVersion: result.detectedAppVersion ?? undefined,
689
690
  variantSummaries: result.variantResults.map(v => ({
690
691
  variantId: v.variantId,
691
692
  success: v.success,
package/dist/cli.js CHANGED
@@ -240,12 +240,54 @@ program
240
240
  }
241
241
  process.exit(0);
242
242
  });
243
+ async function runOutdatedPresetsLocally(opts) {
244
+ if (opts.output) {
245
+ fatal('`--output` is not supported with `--outdated`; run an individual preset when local copies are needed.');
246
+ }
247
+ const config = await requireConfig();
248
+ const search = new URLSearchParams({ freshness: 'outdated' });
249
+ const data = await requestJson(config, `/api/cli/projects/${opts.project}/auto-recapture-presets`, { headers: authHeaders(config) }, 'Failed to list outdated presets', search);
250
+ if (data.presets.length === 0) {
251
+ logger.info(`[capture] No outdated presets found for project ${opts.project}`);
252
+ process.exit(0);
253
+ }
254
+ const { runCapture } = await import('./cli-runner.js');
255
+ const failures = [];
256
+ logger.info(`[capture] Running ${data.presets.length} outdated preset(s)`);
257
+ for (const preset of data.presets) {
258
+ const label = preset.name ? `${preset.name} (${preset.id})` : preset.id;
259
+ logger.info(`[capture] Running outdated preset ${label}`);
260
+ const result = await runCapture({
261
+ presetId: preset.id,
262
+ env: opts.env,
263
+ headed: opts.headed,
264
+ allowUploadFailure: opts.allowUploadFailure,
265
+ dryRun: opts.dry,
266
+ regenerateTts: opts.regenerateTts,
267
+ });
268
+ if (!result.success) {
269
+ failures.push({
270
+ id: preset.id,
271
+ name: preset.name,
272
+ error: result.error ?? result.runResult?.error ?? 'capture failed',
273
+ });
274
+ }
275
+ }
276
+ if (failures.length > 0) {
277
+ logger.error(`[capture] ${failures.length}/${data.presets.length} outdated preset(s) failed: ${failures.map((failure) => failure.name ?? failure.id).join(', ')}`);
278
+ process.exit(1);
279
+ }
280
+ logger.success(`[capture] ${data.presets.length} outdated preset(s) captured successfully`);
281
+ process.exit(0);
282
+ }
243
283
  // ── run command (deterministic capture engine) ───────────────────────
244
284
  program
245
- .command('run <preset-id>')
285
+ .command('run [preset-id]')
246
286
  .description('Run a capture using the deterministic opcode engine (local Playwright)')
247
287
  .option('--headed', 'Show browser window for debugging', false)
248
288
  .option('--local', `Use the local AutoKap dev server (${LOCAL_API_BASE_URL})`, false)
289
+ .option('--project <id>', 'Project ID. Required with --outdated.')
290
+ .option('--outdated', 'Run all outdated presets in the project', false)
249
291
  .option('--env <name>', "Project environment to capture against. Falls back to the project's default environment when omitted.")
250
292
  .option('--allow-upload-failure', 'Keep a successful capture exit code even if artifact upload fails', false)
251
293
  .option('--output <dir>', 'Optional output directory for local artifact copies')
@@ -264,6 +306,19 @@ program
264
306
  process.env[WS_URL_ENV_VAR] = LOCAL_WS_URL;
265
307
  logger.info(`Using local AutoKap dev server: ${LOCAL_API_BASE_URL}`);
266
308
  }
309
+ if (opts.outdated) {
310
+ if (!opts.project) {
311
+ fatal('Missing --project <id> for `autokap run --outdated`.');
312
+ }
313
+ if (opts.program) {
314
+ fatal('`--program` cannot be combined with `--outdated`.');
315
+ }
316
+ await runOutdatedPresetsLocally(opts);
317
+ return;
318
+ }
319
+ if (!presetId) {
320
+ fatal('Missing preset ID. Use `autokap run <preset-id>` or `autokap run --outdated --project <project-id>`.');
321
+ }
267
322
  const { runLocal } = await import('./cli-runner-local.js');
268
323
  await runLocal(presetId, opts);
269
324
  });
@@ -279,6 +334,7 @@ program
279
334
  .option('--debug', 'Verbose logging: per-substep timing, opcode dumps, recovery strategy traces', false)
280
335
  .option('--cloud', 'Cloud runner mode: signals 4+ vCPU available, unblocks the conservative Linux FPS default (8 → 30)', false)
281
336
  .option('--preset-ids <ids>', 'Comma-separated preset IDs to capture. When omitted, captures all presets with auto_recapture_enabled=true.')
337
+ .option('--outdated', 'Capture only outdated auto-recapture presets.', false)
282
338
  .action(async (opts) => {
283
339
  if (opts.debug) {
284
340
  const { setDebugEnabled } = await import('./logger.js');
@@ -393,9 +449,10 @@ program
393
449
  // When `--preset-ids` is provided, restrict the run to that subset
394
450
  // (per-preset recapture launched from the dashboard).
395
451
  const presetIdsArg = opts.presetIds?.trim();
452
+ const freshnessSuffix = opts.outdated ? '?freshness=outdated' : '';
396
453
  const presetsPath = presetIdsArg
397
454
  ? `/api/cli/projects/${opts.project}/auto-recapture-presets?preset_ids=${encodeURIComponent(presetIdsArg)}`
398
- : `/api/cli/projects/${opts.project}/auto-recapture-presets`;
455
+ : `/api/cli/projects/${opts.project}/auto-recapture-presets${freshnessSuffix}`;
399
456
  let data;
400
457
  try {
401
458
  const response = await fetch(buildApiUrl(config, presetsPath), {
@@ -605,6 +605,13 @@ export interface VariantResult {
605
605
  durationMs: number;
606
606
  /** Artifact buffers produced */
607
607
  artifacts: ArtifactResult[];
608
+ /**
609
+ * App version detected on the captured page (meta tag, window global, or
610
+ * data attribute). Sent to the server with telemetry so freshness tracking
611
+ * picks up the version live during the capture instead of waiting for the
612
+ * hourly cron.
613
+ */
614
+ detectedAppVersion?: string | null;
608
615
  error?: string;
609
616
  }
610
617
  export interface ArtifactResult {
@@ -725,6 +732,12 @@ export interface RunResult {
725
732
  * array for `screenshot` and `clip` modes.
726
733
  */
727
734
  opcodeTimings: OpcodeTiming[];
735
+ /**
736
+ * First non-null `detectedAppVersion` from the variants. Used by the server
737
+ * telemetry route to bypass the cron-detected version and stamp the preset
738
+ * with the version actually captured on the page.
739
+ */
740
+ detectedAppVersion?: string | null;
728
741
  error?: string;
729
742
  }
730
743
  export interface WaitCondition {
@@ -842,6 +855,12 @@ export interface RuntimeAdapter {
842
855
  buffer: Buffer;
843
856
  mimeType: string;
844
857
  } | null>;
858
+ /**
859
+ * Read the captured app's version from the live page (meta tag, window
860
+ * global, or data attribute). Mirrors `extractAppVersionFromHtml` server-side
861
+ * and `__AUTOKAP_VERSION__` lookup. Returns null if no marker is present.
862
+ */
863
+ detectAppVersion?(): Promise<string | null>;
845
864
  close(): Promise<void>;
846
865
  /** Click an element by semantic target. Falls back to selector if target not found. */
847
866
  clickByTarget?(opts: ClickByTargetOptions): Promise<void>;
@@ -147,6 +147,7 @@ export async function executeProgram(program, createAdapter, options = {}) {
147
147
  const completedVariantResults = variantResults.filter((result) => Boolean(result));
148
148
  const aborted = options.abortSignal?.aborted && completedVariantResults.length < program.variants.length;
149
149
  const success = !aborted && completedVariantResults.length > 0 && completedVariantResults.every(v => v.success);
150
+ const detectedAppVersion = completedVariantResults.reduce((acc, variantResult) => acc ?? (variantResult.detectedAppVersion ?? null), null);
150
151
  return {
151
152
  programId: program.presetId,
152
153
  success,
@@ -155,6 +156,7 @@ export async function executeProgram(program, createAdapter, options = {}) {
155
156
  healerPatches: success ? healerPatches : [], // Only propagate patches on success
156
157
  opcodeTimings,
157
158
  totalDurationMs: Date.now() - startTime,
159
+ detectedAppVersion,
158
160
  error: aborted ? 'aborted' : (success ? undefined : completedVariantResults.find(v => !v.success)?.error),
159
161
  };
160
162
  }
@@ -234,12 +236,25 @@ async function executeVariant(program, variant, createAdapter, recoveryChain, te
234
236
  };
235
237
  }
236
238
  }
239
+ // Best-effort: read the app version from the live page so the server can
240
+ // stamp the preset with what we actually captured (instead of falling back
241
+ // to whatever the hourly cron last detected).
242
+ let detectedAppVersion = null;
243
+ if (adapter.detectAppVersion) {
244
+ try {
245
+ detectedAppVersion = await adapter.detectAppVersion();
246
+ }
247
+ catch {
248
+ detectedAppVersion = null;
249
+ }
250
+ }
237
251
  return {
238
252
  variantId: variant.id,
239
253
  success: true,
240
254
  opcodeResults,
241
255
  durationMs: Date.now() - startTime,
242
256
  artifacts,
257
+ detectedAppVersion,
243
258
  };
244
259
  }
245
260
  catch (err) {
@@ -1,5 +1,5 @@
1
1
  import type { SupabaseClient } from '@supabase/supabase-js';
2
- export type CreditUsageType = 'screenshot' | 'clip' | 'video' | 'preset_analysis' | 'cloud_recapture' | 'ai_chat' | 'studio_creation' | 'studio_iteration';
2
+ export type CreditUsageType = 'screenshot' | 'clip' | 'video' | 'cloud_recapture' | 'ai_chat' | 'studio_creation' | 'studio_iteration';
3
3
  export declare function recordCreditUsage(supabase: SupabaseClient, params: {
4
4
  userId: string;
5
5
  projectId: string | null;
@@ -27,6 +27,7 @@ export declare class WebPlaywrightLocal implements RuntimeAdapter {
27
27
  constructor(browser: Browser, recordingDir?: string | undefined);
28
28
  navigate(url: string): Promise<void>;
29
29
  getCurrentUrl(): Promise<string>;
30
+ detectAppVersion(): Promise<string | null>;
30
31
  getAKTree(): Promise<AKTree>;
31
32
  getPageSignals(): Promise<VideoPageSignals>;
32
33
  click(selector: string, options?: ClickOptions): Promise<void>;
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autokap",
3
- "version": "1.5.7",
3
+ "version": "1.6.1",
4
4
  "description": "AI-powered CLI tool for capturing clean screenshots of websites",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",