autokap 1.1.0 → 1.1.3
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/assets/skill/OPCODE-REFERENCE.md +1 -41
- package/assets/skill/README.md +0 -1
- package/assets/skill/SKILL.md +9 -32
- package/assets/skill/references/examples.md +1 -17
- package/dist/billing-operation-logging.d.ts +1 -3
- package/dist/billing-operation-logging.js +0 -4
- package/dist/browser.js +164 -1
- package/dist/capture-strategy.d.ts +2 -8
- package/dist/capture-strategy.js +2 -30
- package/dist/cli-config.d.ts +2 -1
- package/dist/cli-config.js +18 -2
- package/dist/cli-contract.d.ts +1 -0
- package/dist/cli-contract.js +8 -2
- package/dist/cli-runner-local.d.ts +2 -0
- package/dist/cli-runner-local.js +12 -21
- package/dist/cli-runner.d.ts +4 -0
- package/dist/cli-runner.js +45 -53
- package/dist/cli.js +89 -44
- package/dist/execution-schema.d.ts +143 -331
- package/dist/execution-schema.js +43 -28
- package/dist/execution-types.d.ts +6 -151
- package/dist/execution-types.js +1 -3
- package/dist/logger.js +1 -1
- package/dist/mockup-html.d.ts +2 -0
- package/dist/mockup-html.js +13 -10
- package/dist/mockup.js +2 -2
- package/dist/opcode-actions.js +0 -2
- package/dist/opcode-runner.js +0 -121
- package/dist/program-signing.d.ts +50 -72
- package/dist/security.js +2 -2
- package/dist/server-capture-runtime.d.ts +0 -1
- package/dist/server-capture-runtime.js +0 -3
- package/dist/server-credit-usage.d.ts +1 -1
- package/dist/skill-packaging.d.ts +1 -1
- package/dist/skill-packaging.js +1 -11
- package/dist/types.d.ts +2 -2
- package/dist/web-playwright-local.d.ts +0 -14
- package/dist/web-playwright-local.js +2 -194
- package/package.json +2 -19
- package/readme.md +13 -0
- package/assets/skill/STUDIO-SKILL.md +0 -476
- package/assets/skill/references/interactive-demo.md +0 -225
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AutoKap Opcode Reference
|
|
2
2
|
|
|
3
|
-
Detailed parameter documentation for all
|
|
3
|
+
Detailed parameter documentation for all 23 opcodes. For workflow, rules, and examples, see [SKILL.md](SKILL.md).
|
|
4
4
|
|
|
5
5
|
## Common Fields (all opcodes)
|
|
6
6
|
|
|
@@ -375,46 +375,6 @@ Capture a screenshot of the viewport or a specific element.
|
|
|
375
375
|
{ "kind": "CAPTURE_SCREENSHOT", "captureId": "dashboard-main", "captureName": "Dashboard", "elementSelector": "[data-ak=\"main-content\"]", "postcondition": { "type": "always" } }
|
|
376
376
|
```
|
|
377
377
|
|
|
378
|
-
## CAPTURE_DOM
|
|
379
|
-
|
|
380
|
-
Capture a DOM snapshot for an interactive demo state.
|
|
381
|
-
|
|
382
|
-
| Param | Type | Required | Description |
|
|
383
|
-
|-------|------|----------|-------------|
|
|
384
|
-
| `stateName` | string | yes | Stable state identifier used by the interactive demo player |
|
|
385
|
-
| `selector` | string | no | CSS selector of a focused subtree to serialize instead of the whole page |
|
|
386
|
-
|
|
387
|
-
**Can:** Capture the full page DOM for a state, or a focused subtree when only part of the page matters. Feed the interactive demo player with a state that can later host fragments and transitions.
|
|
388
|
-
**Cannot:** Produce screenshots or clips. Use only when `mediaMode` is `dom`.
|
|
389
|
-
|
|
390
|
-
**Tip:** Omit `selector` for page-level states. Add it when the interactive demo should ignore surrounding chrome and serialize only a mounted area.
|
|
391
|
-
|
|
392
|
-
```json
|
|
393
|
-
{ "kind": "CAPTURE_DOM", "stateName": "dashboard-default", "selector": "[data-ak=\"dashboard-shell\"]", "postcondition": { "type": "always" } }
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
## CAPTURE_FRAGMENT
|
|
397
|
-
|
|
398
|
-
Capture a local DOM fragment that mounts on top of a previously captured state.
|
|
399
|
-
|
|
400
|
-
| Param | Type | Required | Description |
|
|
401
|
-
|-------|------|----------|-------------|
|
|
402
|
-
| `fragmentName` | string | yes | Stable fragment identifier |
|
|
403
|
-
| `parentState` | string | yes | Name of the CAPTURE_DOM state this fragment belongs to |
|
|
404
|
-
| `selector` | string | yes | CSS selector of the fragment root to serialize |
|
|
405
|
-
| `variantName` | string | no | Optional named fragment variant for in-place swaps |
|
|
406
|
-
| `triggerSelector` | string | no | Selector of the element that should trigger this fragment in the player |
|
|
407
|
-
| `mountStrategy` | `"replace"` \| `"append"` | no | How the player should mount the fragment relative to the parent state |
|
|
408
|
-
|
|
409
|
-
**Can:** Capture modals, popovers, dropdowns, drawers, or any local subtree that should be replayed on top of a parent state. Capture multiple variants of the same fragment to enable in-place swaps in the player.
|
|
410
|
-
**Cannot:** Stand alone without a `parentState`. Replace full-page routing states; use CAPTURE_DOM for those.
|
|
411
|
-
|
|
412
|
-
**Tip:** Use `variantName` when the same fragment can appear in multiple visual forms, such as color/theme swaps or alternate content blocks on the same background state.
|
|
413
|
-
|
|
414
|
-
```json
|
|
415
|
-
{ "kind": "CAPTURE_FRAGMENT", "fragmentName": "pricing-modal", "parentState": "dashboard-default", "selector": "[data-ak=\"pricing-modal\"]", "triggerSelector": "[data-ak=\"open-pricing\"]", "mountStrategy": "append", "postcondition": { "type": "always" } }
|
|
416
|
-
```
|
|
417
|
-
|
|
418
378
|
## BEGIN_CLIP
|
|
419
379
|
|
|
420
380
|
Start recording a clip. All interactions between BEGIN_CLIP and END_CLIP are recorded.
|
package/assets/skill/README.md
CHANGED
|
@@ -8,7 +8,6 @@ Edit files here first.
|
|
|
8
8
|
- `SKILL.md` is the core preset contract and must stay short enough to remain
|
|
9
9
|
readable.
|
|
10
10
|
- `references/*.md` holds longer mode-specific guidance and examples.
|
|
11
|
-
- `STUDIO-SKILL.md` is the separate studio/composition skill.
|
|
12
11
|
- `src/skill-packaging.ts` compiles these sources into the outputs each agent
|
|
13
12
|
expects.
|
|
14
13
|
|
package/assets/skill/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: autokap-preset
|
|
3
3
|
description: >
|
|
4
4
|
Generate AutoKap capture programs — deterministic opcode sequences for automated
|
|
5
|
-
screenshot
|
|
5
|
+
screenshot and clip capture of web apps. Use when: creating or updating presets,
|
|
6
6
|
adding data-ak attributes for capture automation, or debugging failed capture programs.
|
|
7
7
|
metadata:
|
|
8
8
|
author: AutoKap
|
|
@@ -21,7 +21,7 @@ This installed skill is the **source of truth** for the AutoKap contract: opcode
|
|
|
21
21
|
|
|
22
22
|
## When To Use This Skill
|
|
23
23
|
|
|
24
|
-
- User wants to capture screenshots
|
|
24
|
+
- User wants to capture screenshots or clips of their web app
|
|
25
25
|
- User asks to create or update an AutoKap preset
|
|
26
26
|
- User needs `data-ak` attributes added to UI elements for capture
|
|
27
27
|
- User is debugging a failed capture program
|
|
@@ -38,7 +38,7 @@ This installed skill is the **source of truth** for the AutoKap contract: opcode
|
|
|
38
38
|
|
|
39
39
|
## Quick Workflow
|
|
40
40
|
|
|
41
|
-
1. **Understand the capture goal** — What pages/states should be captured? Screenshot
|
|
41
|
+
1. **Understand the capture goal** — What pages/states should be captured? Screenshot or clip? Which viewports, locales, themes, and mockups matter?
|
|
42
42
|
2. **Inspect the implementation** — Confirm routes, auth, theme, locale, and any dynamic UI state in the codebase.
|
|
43
43
|
3. **Add `data-ak` attributes** — Tag every element the opcodes must interact with using stable selectors.
|
|
44
44
|
4. **Choose media mode and variants** — Set `mediaMode` and define the exact viewport/locale/theme combinations.
|
|
@@ -52,7 +52,6 @@ This installed skill is the **source of truth** for the AutoKap contract: opcode
|
|
|
52
52
|
Load these only when the request actually needs them:
|
|
53
53
|
|
|
54
54
|
- **Opcode parameters** — [OPCODE-REFERENCE.md](OPCODE-REFERENCE.md)
|
|
55
|
-
- **Interactive demos** — [references/interactive-demo.md](references/interactive-demo.md)
|
|
56
55
|
- **Mock data** — [references/mock-data.md](references/mock-data.md)
|
|
57
56
|
- **Complete examples** — [references/examples.md](references/examples.md)
|
|
58
57
|
|
|
@@ -91,7 +90,7 @@ For every element you interact with in the opcodes, add a `data-ak="descriptive-
|
|
|
91
90
|
interface ExecutionProgram {
|
|
92
91
|
presetId: string; // Unique slug (e.g. "homepage-hero")
|
|
93
92
|
programVersion: number; // Always 1 for new programs
|
|
94
|
-
mediaMode: 'screenshot' | 'clip'
|
|
93
|
+
mediaMode: 'screenshot' | 'clip';
|
|
95
94
|
baseUrl: string; // Root URL of the application
|
|
96
95
|
variants: VariantSpec[]; // Viewport/locale/theme combinations
|
|
97
96
|
preconditions: {
|
|
@@ -105,7 +104,7 @@ interface ExecutionProgram {
|
|
|
105
104
|
};
|
|
106
105
|
steps: ExecutionOpcode[];
|
|
107
106
|
artifactPlan: {
|
|
108
|
-
mediaMode: 'screenshot' | 'clip'
|
|
107
|
+
mediaMode: 'screenshot' | 'clip';
|
|
109
108
|
cursorTheme?: 'minimal' | 'macos' | 'windows'; // Clip only. Default: 'minimal'
|
|
110
109
|
format?: {
|
|
111
110
|
clipFormat?: 'gif' | 'mp4' | 'both'; // Default: 'gif'
|
|
@@ -134,7 +133,7 @@ interface VariantSpec {
|
|
|
134
133
|
|
|
135
134
|
## Opcode Quick Reference
|
|
136
135
|
|
|
137
|
-
|
|
136
|
+
23 opcodes available. For full parameter documentation, see [OPCODE-REFERENCE.md](OPCODE-REFERENCE.md).
|
|
138
137
|
|
|
139
138
|
| Kind | Selector? | Key Params | Typical Postcondition | Notes |
|
|
140
139
|
|------|-----------|-----------|----------------------|-------|
|
|
@@ -155,8 +154,6 @@ interface VariantSpec {
|
|
|
155
154
|
| `ASSERT_ROUTE` | no | `urlPattern` | `route_matches` | Validation checkpoint |
|
|
156
155
|
| `ASSERT_SURFACE` | no | `selectors[]`, `matchAll` | `always` | Validation checkpoint |
|
|
157
156
|
| `CAPTURE_SCREENSHOT` | no | `captureId`, `captureName`, `elementSelector?` | `always` | `elementSelector` for element-level crop |
|
|
158
|
-
| `CAPTURE_DOM` | no | `stateName`, `selector?` | `always` | Interactive Demo state — see [Interactive Demo Workflow](#interactive-demo-workflow). `mediaMode: "dom"` only. Add `selector` to capture a focused subtree instead of the whole page. |
|
|
159
|
-
| `CAPTURE_FRAGMENT` | yes | `fragmentName`, `parentState`, `selector`, `variantName?`, `triggerSelector?`, `mountStrategy?` | `always` | Interactive Demo fragment (modal/popover/dropdown/local subtree) — see [Fragments](#fragments-and-local-interactions). Mounted on top of `parentState` by the player. Capture the same fragment under multiple `variantName`s to enable in-place swap (e.g. background colour change). |
|
|
160
157
|
| `BEGIN_CLIP` | no | `clipId`, `clipName` | `always` | Start recording |
|
|
161
158
|
| `END_CLIP` | no | `clipId`, `clipName` | `always` | Stop recording. Same `clipId` as BEGIN_CLIP |
|
|
162
159
|
| `CLONE_ELEMENT` | yes | `sourceSelector`, `containerSelector`, `count` | `always` | **Non-blocking.** Duplicate a template element N times |
|
|
@@ -284,25 +281,6 @@ Place this step **after** DISMISS_OVERLAYS + any content WAIT_FOR, and **immedia
|
|
|
284
281
|
}
|
|
285
282
|
```
|
|
286
283
|
|
|
287
|
-
## Interactive Demo Workflow
|
|
288
|
-
|
|
289
|
-
Interactive demos are advanced and should only be used when the user wants a
|
|
290
|
-
clickable DOM-based experience, not static screenshots or a clip.
|
|
291
|
-
|
|
292
|
-
Key rules:
|
|
293
|
-
|
|
294
|
-
- center the capture around the feature loop, not the whole app
|
|
295
|
-
- use `CAPTURE_DOM` for base states
|
|
296
|
-
- use `CAPTURE_FRAGMENT` for local overlays and subtree swaps
|
|
297
|
-
- add authored markers such as `data-ak-interact`, `data-ak-fragment`,
|
|
298
|
-
`data-ak-model`, and `data-ak-template`
|
|
299
|
-
- prefer fragments / bindings / model-driven reconstruction before custom
|
|
300
|
-
interaction `code`
|
|
301
|
-
|
|
302
|
-
Read the full reference before generating an interactive demo:
|
|
303
|
-
|
|
304
|
-
- [references/interactive-demo.md](references/interactive-demo.md)
|
|
305
|
-
|
|
306
284
|
## Recovery System Overview
|
|
307
285
|
|
|
308
286
|
When an opcode fails, AutoKap tries 5 recovery strategies in order:
|
|
@@ -358,7 +336,7 @@ Read the full reference before adding mock data:
|
|
|
358
336
|
4. **CLICK postconditions describe the result**, not the action (what changed, not what was clicked)
|
|
359
337
|
5. **Add `WAIT_FOR` after page transitions** (login, route change, modal open)
|
|
360
338
|
6. **Set `waitMs: 10000`** on postconditions involving async transitions
|
|
361
|
-
7. **Persist the preset via the CLI — the program lives INSIDE `config.program`**, not in a separate file. After generating the program JSON, write the full config to a temp file and run `autokap preset create` or `autokap preset update` so the saved preset contains `program: { ...the full ExecutionProgram... }` plus any `
|
|
339
|
+
7. **Persist the preset via the CLI — the program lives INSIDE `config.program`**, not in a separate file. After generating the program JSON, write the full config to a temp file and run `autokap preset create` or `autokap preset update` so the saved preset contains `program: { ...the full ExecutionProgram... }` plus any `mockDataInjection` blocks. The user MUST be able to run `autokap run <preset-id>` afterwards with no `--program` flag and no extra files. **Never tell the user to save the JSON to a local file**, never suggest `--program <file>` as the normal run path. The CLI fetches the program from the server.
|
|
362
340
|
8. **Use `captureId`/`clipId`** on CAPTURE_SCREENSHOT/BEGIN_CLIP for Studio and dev links to work. Always set `"devLinksEnabled": true` in the preset config when creating via API so endpoints are visible on the dashboard
|
|
363
341
|
9. **Mock data opcodes are non-blocking** — they log a warning and continue if selectors miss; always pair them with `recovery: { retries: 0, ... }` and `postcondition: { type: "always" }`
|
|
364
342
|
10. **Hardcode the login URL — never use `{{loginUrl}}`.** The login URL is just another navigation. You generate the entire program, so you know which path the app's login lives at — write it directly (`https://app.example.com/login`, `http://localhost:3000/login`, etc.). The `{{loginUrl}}` placeholder is **deprecated** and produces broken navigations when the user hasn't filled the optional `loginUrl` credential field. `{{email}}` and `{{password}}` placeholders for `TYPE` opcodes are still correct and required (those are sensitive secrets the user fills in).
|
|
@@ -408,7 +386,7 @@ Use the AutoKap CLI commands to create, update, and query presets. The CLI reads
|
|
|
408
386
|
|
|
409
387
|
### Step 1: Write the config JSON to a temp file
|
|
410
388
|
|
|
411
|
-
Build the full config object (with `program`, `pages`, and optionally `
|
|
389
|
+
Build the full config object (with `program`, `pages`, and optionally `mockDataInjection`) and write it to a temporary file:
|
|
412
390
|
|
|
413
391
|
```bash
|
|
414
392
|
cat > /tmp/autokap-preset.json << 'EOF'
|
|
@@ -441,7 +419,7 @@ EOF
|
|
|
441
419
|
- **`pages`** — one entry per `CAPTURE_SCREENSHOT` opcode (`{ "id": "<captureId>", "name": "<captureName>" }`). Drives endpoint (dev link) creation. **Screenshot presets only.**
|
|
442
420
|
- **`clips`** — one entry per `BEGIN_CLIP`/`END_CLIP` pair (`{ "id": "<clipId>", "name": "<clipName>" }`). Drives endpoint and Studio slot creation. **Clip presets only.** Do NOT put clip entries in `pages` — the server treats `pages` as screenshots.
|
|
443
421
|
- **`program`** — the full `ExecutionProgram` with `steps`, `variants`, `artifactPlan`, etc.
|
|
444
|
-
- **`captureMode`** — `"screenshot"
|
|
422
|
+
- **`captureMode`** — `"screenshot"` or `"clip"`.
|
|
445
423
|
- **`devLinksEnabled: true`** — enables endpoints on the dashboard.
|
|
446
424
|
|
|
447
425
|
The server auto-syncs `langs`, `themes`, `targets`, and `viewports` from `program.variants` — you do NOT need to set these manually.
|
|
@@ -553,7 +531,6 @@ Use the examples reference for ready-made shapes of:
|
|
|
553
531
|
- anonymous screenshot presets
|
|
554
532
|
- authenticated screenshot presets
|
|
555
533
|
- clip presets
|
|
556
|
-
- interactive demo presets
|
|
557
534
|
- mock-data-enabled presets
|
|
558
535
|
|
|
559
536
|
Read:
|
|
@@ -59,23 +59,7 @@ Pattern:
|
|
|
59
59
|
|
|
60
60
|
Prefer short, deterministic flows.
|
|
61
61
|
|
|
62
|
-
## Example 4 —
|
|
63
|
-
|
|
64
|
-
Good for:
|
|
65
|
-
|
|
66
|
-
- embeddable product demos
|
|
67
|
-
- focused feature loops
|
|
68
|
-
- local UI swaps and fragment-driven demos
|
|
69
|
-
|
|
70
|
-
Pattern:
|
|
71
|
-
|
|
72
|
-
1. `mediaMode: "dom"`
|
|
73
|
-
2. capture a small number of base states with `CAPTURE_DOM`
|
|
74
|
-
3. capture overlays/local subtrees with `CAPTURE_FRAGMENT`
|
|
75
|
-
4. wire `interactiveDemo.script`
|
|
76
|
-
5. use fragments, model bindings, and small `code` blocks for reconstruction
|
|
77
|
-
|
|
78
|
-
## Example 5 — Mock data preset
|
|
62
|
+
## Example 4 — Mock data preset
|
|
79
63
|
|
|
80
64
|
Good for:
|
|
81
65
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
2
2
|
import type { StepUsage } from './types.js';
|
|
3
|
-
export type BillingOperationType = 'screenshot' | 'clip'
|
|
3
|
+
export type BillingOperationType = 'screenshot' | 'clip';
|
|
4
4
|
type BillingOperationOutcome = 'succeeded' | 'failed' | 'cancelled';
|
|
5
5
|
interface BillingOperationContext {
|
|
6
6
|
runId: string;
|
|
@@ -10,7 +10,6 @@ interface BillingOperationContext {
|
|
|
10
10
|
captureId?: string | null;
|
|
11
11
|
videoId?: string | null;
|
|
12
12
|
clipRecordId?: string | null;
|
|
13
|
-
interactiveDemoStateId?: string | null;
|
|
14
13
|
operationType: BillingOperationType;
|
|
15
14
|
captureType?: 'fullpage' | 'element' | null;
|
|
16
15
|
lang?: string | null;
|
|
@@ -33,7 +32,6 @@ interface BillingOperationParams {
|
|
|
33
32
|
export declare function insertBillingOperationLog(supabase: SupabaseClient, ctx: BillingOperationContext, params: BillingOperationParams): Promise<string | null>;
|
|
34
33
|
export declare function insertScreenshotOperationLog(supabase: SupabaseClient, ctx: Omit<BillingOperationContext, 'operationType'>, params: BillingOperationParams): Promise<string | null>;
|
|
35
34
|
export declare function insertClipOperationLog(supabase: SupabaseClient, ctx: Omit<BillingOperationContext, 'operationType'>, params: BillingOperationParams): Promise<string | null>;
|
|
36
|
-
export declare function insertInteractiveDemoOperationLog(supabase: SupabaseClient, ctx: Omit<BillingOperationContext, 'operationType'>, params: BillingOperationParams): Promise<string | null>;
|
|
37
35
|
export declare function cancelScreenshotOperationLogsForCapture(supabase: SupabaseClient, captureId: string, reason: string): Promise<void>;
|
|
38
36
|
export declare function reconcilePendingBillingOperationCosts(supabase: SupabaseClient, operationIds?: string[]): Promise<void>;
|
|
39
37
|
export {};
|
|
@@ -131,7 +131,6 @@ export async function insertBillingOperationLog(supabase, ctx, params) {
|
|
|
131
131
|
capture_id: ctx.captureId ?? null,
|
|
132
132
|
video_id: ctx.videoId ?? null,
|
|
133
133
|
clip_record_id: ctx.clipRecordId ?? null,
|
|
134
|
-
interactive_demo_state_id: ctx.interactiveDemoStateId ?? null,
|
|
135
134
|
operation_type: ctx.operationType,
|
|
136
135
|
operation_outcome: params.outcome,
|
|
137
136
|
outcome_reason: params.outcomeReason ?? null,
|
|
@@ -178,9 +177,6 @@ export async function insertScreenshotOperationLog(supabase, ctx, params) {
|
|
|
178
177
|
export async function insertClipOperationLog(supabase, ctx, params) {
|
|
179
178
|
return insertBillingOperationLog(supabase, { ...ctx, operationType: 'clip' }, params);
|
|
180
179
|
}
|
|
181
|
-
export async function insertInteractiveDemoOperationLog(supabase, ctx, params) {
|
|
182
|
-
return insertBillingOperationLog(supabase, { ...ctx, operationType: 'interactive_demo' }, params);
|
|
183
|
-
}
|
|
184
180
|
export async function cancelScreenshotOperationLogsForCapture(supabase, captureId, reason) {
|
|
185
181
|
try {
|
|
186
182
|
const { error } = await supabase
|
package/dist/browser.js
CHANGED
|
@@ -98,7 +98,7 @@ function resolveEffectivePadding(config, bbox) {
|
|
|
98
98
|
}
|
|
99
99
|
import { dismissCookiesAndWidgets, ensureCaptureHideStyles } from './cookie-dismiss.js';
|
|
100
100
|
import { CHROMIUM_ARGS, browserPool } from './browser-pool.js';
|
|
101
|
-
import { logger } from './logger.js';
|
|
101
|
+
import { isDebugEnabled, logger } from './logger.js';
|
|
102
102
|
async function withHelperTimeout(label, timeoutMs, work) {
|
|
103
103
|
if (!timeoutMs || timeoutMs <= 0) {
|
|
104
104
|
return work();
|
|
@@ -117,6 +117,82 @@ async function withHelperTimeout(label, timeoutMs, work) {
|
|
|
117
117
|
clearTimeout(timer);
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
|
+
function isLikelyFontUrl(url) {
|
|
121
|
+
return /\.(?:woff2?|ttf|otf)(?:[?#]|$)/i.test(url);
|
|
122
|
+
}
|
|
123
|
+
async function logFontDiagnostics(page, stage) {
|
|
124
|
+
if (!isDebugEnabled())
|
|
125
|
+
return;
|
|
126
|
+
try {
|
|
127
|
+
const diagnostics = await page.evaluate(async () => {
|
|
128
|
+
const fontPreloads = Array.from(document.querySelectorAll('link[rel="preload"][as="font"], link[as="font"]')).map((link) => ({
|
|
129
|
+
href: link.href,
|
|
130
|
+
type: link.type || null,
|
|
131
|
+
crossOrigin: link.crossOrigin || null,
|
|
132
|
+
}));
|
|
133
|
+
const fetches = [];
|
|
134
|
+
for (const preload of fontPreloads) {
|
|
135
|
+
const startedAt = performance.now();
|
|
136
|
+
try {
|
|
137
|
+
const response = await fetch(preload.href, {
|
|
138
|
+
cache: 'no-store',
|
|
139
|
+
credentials: 'include',
|
|
140
|
+
});
|
|
141
|
+
fetches.push({
|
|
142
|
+
url: preload.href,
|
|
143
|
+
ok: response.ok,
|
|
144
|
+
status: response.status,
|
|
145
|
+
contentType: response.headers.get('content-type'),
|
|
146
|
+
contentLength: response.headers.get('content-length'),
|
|
147
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
fetches.push({
|
|
152
|
+
url: preload.href,
|
|
153
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
154
|
+
error: error instanceof Error ? error.message : String(error),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const normalizeFamily = (family) => family.trim().replace(/^['"]|['"]$/g, '');
|
|
159
|
+
const computedFamilies = Array.from(new Set([document.body, ...Array.from(document.querySelectorAll('h1,h2,h3,h4,h5,h6,p,a,button,input,textarea,label,span,[data-ak]')).slice(0, 40)]
|
|
160
|
+
.filter(Boolean)
|
|
161
|
+
.flatMap((node) => getComputedStyle(node).fontFamily
|
|
162
|
+
.split(',')
|
|
163
|
+
.map(normalizeFamily)
|
|
164
|
+
.filter(Boolean))));
|
|
165
|
+
const fontChecks = computedFamilies.map((family) => ({
|
|
166
|
+
family,
|
|
167
|
+
weight400: document.fonts.check(`400 16px "${family}"`, 'AutoKap Aa 0123456789'),
|
|
168
|
+
weight600: document.fonts.check(`600 16px "${family}"`, 'AutoKap Aa 0123456789'),
|
|
169
|
+
}));
|
|
170
|
+
return {
|
|
171
|
+
url: location.href,
|
|
172
|
+
count: document.fonts.size,
|
|
173
|
+
status: document.fonts.status,
|
|
174
|
+
faces: Array.from(document.fonts).map((font) => ({
|
|
175
|
+
family: font.family,
|
|
176
|
+
weight: font.weight,
|
|
177
|
+
style: font.style,
|
|
178
|
+
stretch: font.stretch,
|
|
179
|
+
status: font.status,
|
|
180
|
+
})),
|
|
181
|
+
bodyComputed: getComputedStyle(document.body).fontFamily,
|
|
182
|
+
documentElementClass: document.documentElement.className,
|
|
183
|
+
bodyClass: document.body.className,
|
|
184
|
+
computedFamilies,
|
|
185
|
+
fontChecks,
|
|
186
|
+
preloads: fontPreloads,
|
|
187
|
+
fetches,
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
logger.debug(`[capture] font diagnostics ${stage}: ${JSON.stringify(diagnostics)}`);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
logger.debug(`[capture] font diagnostics ${stage} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
120
196
|
/**
|
|
121
197
|
* Map a BCP-47 language tag to a Playwright-compatible locale string.
|
|
122
198
|
* Playwright accepts both "fr" and "fr-FR". We normalize 2-char codes to their
|
|
@@ -401,6 +477,8 @@ export class Browser {
|
|
|
401
477
|
attachDebugLifecycleListeners() {
|
|
402
478
|
if (!this.page || !this.context)
|
|
403
479
|
return;
|
|
480
|
+
if (!isDebugEnabled())
|
|
481
|
+
return;
|
|
404
482
|
const page = this.page;
|
|
405
483
|
const context = this.context;
|
|
406
484
|
page.on('crash', () => {
|
|
@@ -423,6 +501,21 @@ export class Browser {
|
|
|
423
501
|
logger.debug(`[page] console.${type}: ${msg.text().slice(0, 200)}`);
|
|
424
502
|
}
|
|
425
503
|
});
|
|
504
|
+
page.on('requestfailed', (request) => {
|
|
505
|
+
const url = request.url();
|
|
506
|
+
if (request.resourceType() !== 'font' && !isLikelyFontUrl(url))
|
|
507
|
+
return;
|
|
508
|
+
logger.debug(`[page] font request failed: ${url} — ${request.failure()?.errorText ?? 'unknown error'}`);
|
|
509
|
+
});
|
|
510
|
+
page.on('response', (response) => {
|
|
511
|
+
const request = response.request();
|
|
512
|
+
const url = response.url();
|
|
513
|
+
if (request.resourceType() !== 'font' && !isLikelyFontUrl(url))
|
|
514
|
+
return;
|
|
515
|
+
const headers = response.headers();
|
|
516
|
+
logger.debug(`[page] font response: status=${response.status()} type=${headers['content-type'] ?? 'unknown'} ` +
|
|
517
|
+
`length=${headers['content-length'] ?? 'unknown'} url=${url}`);
|
|
518
|
+
});
|
|
426
519
|
context.on('close', () => {
|
|
427
520
|
logger.debug(`[context] CLOSE event`);
|
|
428
521
|
});
|
|
@@ -466,6 +559,7 @@ export class Browser {
|
|
|
466
559
|
this.context = await this.browser.newContext(this.buildContextOptions());
|
|
467
560
|
this.page = await this.context.newPage();
|
|
468
561
|
this.elementMap.clear();
|
|
562
|
+
this.attachDebugLifecycleListeners();
|
|
469
563
|
}
|
|
470
564
|
async setDeviceScaleFactor(deviceScaleFactor) {
|
|
471
565
|
const normalizedScale = normalizeDeviceScaleFactor(deviceScaleFactor);
|
|
@@ -600,6 +694,75 @@ export class Browser {
|
|
|
600
694
|
// Move cursor off-screen to avoid hover effects in screenshots
|
|
601
695
|
await page.mouse.move(0, 0);
|
|
602
696
|
await ensureCaptureHideStyles(page);
|
|
697
|
+
// Wait for web fonts to be loaded AND applied to the rendered page.
|
|
698
|
+
// next/font and other `font-display: swap` setups can report the
|
|
699
|
+
// FontFaceSet as "ready" while visible text is still painted with fallback.
|
|
700
|
+
// We force-load declared and currently computed families, verify the
|
|
701
|
+
// primary rendered families with document.fonts.check(), then wait for a
|
|
702
|
+
// committed repaint.
|
|
703
|
+
await page.evaluate(() => Promise.race([
|
|
704
|
+
(async () => {
|
|
705
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
706
|
+
const nextPaint = () => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
|
|
707
|
+
const normalizeFamily = (family) => family.trim().replace(/^['"]|['"]$/g, '');
|
|
708
|
+
const computedFamilies = () => {
|
|
709
|
+
const families = new Set();
|
|
710
|
+
const nodes = [
|
|
711
|
+
document.body,
|
|
712
|
+
...Array.from(document.querySelectorAll('h1,h2,h3,h4,h5,h6,p,a,button,input,textarea,label,span,[data-ak]')).slice(0, 80),
|
|
713
|
+
].filter(Boolean);
|
|
714
|
+
for (const node of nodes) {
|
|
715
|
+
const stack = getComputedStyle(node).fontFamily;
|
|
716
|
+
for (const family of stack.split(',')) {
|
|
717
|
+
const normalized = normalizeFamily(family);
|
|
718
|
+
if (normalized
|
|
719
|
+
&& !/^(serif|sans-serif|monospace|system-ui|ui-sans-serif|ui-monospace)$/i.test(normalized)) {
|
|
720
|
+
families.add(normalized);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return families;
|
|
725
|
+
};
|
|
726
|
+
const allFamilies = () => new Set([
|
|
727
|
+
...Array.from(document.fonts).map((font) => font.family),
|
|
728
|
+
...computedFamilies(),
|
|
729
|
+
]);
|
|
730
|
+
const probe = document.createElement('div');
|
|
731
|
+
probe.setAttribute('aria-hidden', 'true');
|
|
732
|
+
probe.style.cssText = 'position:fixed;left:-9999px;top:-9999px;visibility:hidden;pointer-events:none;';
|
|
733
|
+
for (const family of allFamilies()) {
|
|
734
|
+
const span = document.createElement('span');
|
|
735
|
+
span.style.fontFamily = `"${family}"`;
|
|
736
|
+
span.textContent = 'AutoKap Aa 0123456789';
|
|
737
|
+
probe.appendChild(span);
|
|
738
|
+
}
|
|
739
|
+
document.body.appendChild(probe);
|
|
740
|
+
void probe.offsetHeight;
|
|
741
|
+
const sample = 'AutoKap Aa 0123456789';
|
|
742
|
+
await Promise.all([
|
|
743
|
+
...Array.from(document.fonts).map((font) => font.load().catch(() => null)),
|
|
744
|
+
...Array.from(allFamilies()).flatMap((family) => [
|
|
745
|
+
document.fonts.load(`400 16px "${family}"`, sample).catch(() => null),
|
|
746
|
+
document.fonts.load(`600 16px "${family}"`, sample).catch(() => null),
|
|
747
|
+
]),
|
|
748
|
+
]);
|
|
749
|
+
await document.fonts.ready;
|
|
750
|
+
const deadline = performance.now() + 7000;
|
|
751
|
+
while (performance.now() < deadline) {
|
|
752
|
+
const renderedFamilies = Array.from(computedFamilies()).filter((family) => !/\bFallback\b/i.test(family));
|
|
753
|
+
const loaded = renderedFamilies.length === 0
|
|
754
|
+
|| renderedFamilies.every((family) => document.fonts.check(`400 16px "${family}"`, sample)
|
|
755
|
+
|| document.fonts.check(`600 16px "${family}"`, sample));
|
|
756
|
+
if (document.fonts.status === 'loaded' && loaded)
|
|
757
|
+
break;
|
|
758
|
+
await sleep(100);
|
|
759
|
+
}
|
|
760
|
+
probe.remove();
|
|
761
|
+
await nextPaint();
|
|
762
|
+
})(),
|
|
763
|
+
new Promise((resolve) => setTimeout(resolve, 8000)),
|
|
764
|
+
])).catch(() => { });
|
|
765
|
+
await logFontDiagnostics(page, 'before screenshot');
|
|
603
766
|
return Buffer.from(await page.screenshot({ type: 'png', fullPage: false }));
|
|
604
767
|
}
|
|
605
768
|
async takeScreenshotForAI(options = {}) {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Capture Agent — Capture Strategy
|
|
3
3
|
*
|
|
4
4
|
* Abstraction over the supported media modes: screenshot and clip.
|
|
5
|
-
* The opcode runner is identical for
|
|
6
|
-
*
|
|
5
|
+
* The opcode runner is identical for both — only the capture opcodes dispatch
|
|
6
|
+
* to different strategies.
|
|
7
7
|
*/
|
|
8
8
|
import type { ArtifactSpec, ArtifactResult, RuntimeAdapter, MediaMode } from './execution-types.js';
|
|
9
9
|
export interface CaptureStrategy {
|
|
@@ -27,10 +27,4 @@ export declare class ClipStrategy implements CaptureStrategy {
|
|
|
27
27
|
capture(adapter: RuntimeAdapter, _spec: ArtifactSpec): Promise<ArtifactResult>;
|
|
28
28
|
postProcess(artifact: ArtifactResult, spec: ArtifactSpec): Promise<ArtifactResult>;
|
|
29
29
|
}
|
|
30
|
-
export declare class DomStrategy implements CaptureStrategy {
|
|
31
|
-
readonly mediaMode: "dom";
|
|
32
|
-
prepare(_adapter: RuntimeAdapter, _spec: ArtifactSpec): Promise<void>;
|
|
33
|
-
capture(adapter: RuntimeAdapter, _spec: ArtifactSpec): Promise<ArtifactResult>;
|
|
34
|
-
postProcess(artifact: ArtifactResult, _spec: ArtifactSpec): Promise<ArtifactResult>;
|
|
35
|
-
}
|
|
36
30
|
export declare function createCaptureStrategy(mediaMode: MediaMode): CaptureStrategy;
|
package/dist/capture-strategy.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Capture Agent — Capture Strategy
|
|
3
3
|
*
|
|
4
4
|
* Abstraction over the supported media modes: screenshot and clip.
|
|
5
|
-
* The opcode runner is identical for
|
|
6
|
-
*
|
|
5
|
+
* The opcode runner is identical for both — only the capture opcodes dispatch
|
|
6
|
+
* to different strategies.
|
|
7
7
|
*/
|
|
8
8
|
// ── Screenshot strategy ─────────────────────────────────────────────
|
|
9
9
|
export class ScreenshotStrategy {
|
|
@@ -58,39 +58,11 @@ export class ClipStrategy {
|
|
|
58
58
|
return artifact;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
// ── DOM strategy (Interactive Demos — AUT-121) ──────────────────────
|
|
62
|
-
export class DomStrategy {
|
|
63
|
-
mediaMode = 'dom';
|
|
64
|
-
async prepare(_adapter, _spec) {
|
|
65
|
-
// Nothing to prepare — DOM serialization happens inline in the
|
|
66
|
-
// CAPTURE_DOM opcode handler in opcode-runner.ts.
|
|
67
|
-
}
|
|
68
|
-
async capture(adapter, _spec) {
|
|
69
|
-
if (!adapter.serializeDom) {
|
|
70
|
-
throw new Error('DOM capture requires an adapter that implements serializeDom()');
|
|
71
|
-
}
|
|
72
|
-
const result = await adapter.serializeDom();
|
|
73
|
-
return {
|
|
74
|
-
mediaMode: 'dom',
|
|
75
|
-
buffer: Buffer.from(result.html, 'utf8'),
|
|
76
|
-
mimeType: 'text/html; charset=utf-8',
|
|
77
|
-
domHtml: result.html,
|
|
78
|
-
domAssetUrls: result.assetUrls,
|
|
79
|
-
domHtmlBytes: result.html.length,
|
|
80
|
-
dimensions: result.viewport,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
async postProcess(artifact, _spec) {
|
|
84
|
-
// Phase 3 will add asset extraction (CAS), PurgeCSS and Brotli here.
|
|
85
|
-
return artifact;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
61
|
// ── Factory ─────────────────────────────────────────────────────────
|
|
89
62
|
export function createCaptureStrategy(mediaMode) {
|
|
90
63
|
switch (mediaMode) {
|
|
91
64
|
case 'screenshot': return new ScreenshotStrategy();
|
|
92
65
|
case 'clip': return new ClipStrategy();
|
|
93
|
-
case 'dom': return new DomStrategy();
|
|
94
66
|
}
|
|
95
67
|
}
|
|
96
68
|
//# sourceMappingURL=capture-strategy.js.map
|
package/dist/cli-config.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ declare const DEFAULT_API_BASE_URL = "https://autokap.app";
|
|
|
7
7
|
declare const DEFAULT_WS_URL = "wss://autokap.app/ws";
|
|
8
8
|
declare const LOCAL_API_BASE_URL = "http://localhost:3000";
|
|
9
9
|
declare const LOCAL_WS_URL = "ws://localhost:3000/ws";
|
|
10
|
+
declare const API_KEY_ENV_VAR = "AUTOKAP_API_KEY";
|
|
10
11
|
declare const API_BASE_URL_ENV_VAR = "AUTOKAP_API_BASE_URL";
|
|
11
12
|
declare const WS_URL_ENV_VAR = "AUTOKAP_WS_URL";
|
|
12
13
|
declare const ALLOW_UNSAFE_SERVER_ORIGIN_ENV_VAR = "AUTOKAP_ALLOW_UNSAFE_SERVER_ORIGIN";
|
|
@@ -18,4 +19,4 @@ export declare function readConfig(): Promise<AutokapConfig | null>;
|
|
|
18
19
|
export declare function writeConfig(config: AutokapConfig): Promise<void>;
|
|
19
20
|
export declare function deleteConfig(): Promise<void>;
|
|
20
21
|
export declare function requireConfig(): Promise<AutokapConfig>;
|
|
21
|
-
export { DEFAULT_API_BASE_URL, DEFAULT_WS_URL, LOCAL_API_BASE_URL, LOCAL_WS_URL, API_BASE_URL_ENV_VAR, WS_URL_ENV_VAR, ALLOW_UNSAFE_SERVER_ORIGIN_ENV_VAR, };
|
|
22
|
+
export { DEFAULT_API_BASE_URL, DEFAULT_WS_URL, LOCAL_API_BASE_URL, LOCAL_WS_URL, API_KEY_ENV_VAR, API_BASE_URL_ENV_VAR, WS_URL_ENV_VAR, ALLOW_UNSAFE_SERVER_ORIGIN_ENV_VAR, };
|
package/dist/cli-config.js
CHANGED
|
@@ -6,6 +6,7 @@ const DEFAULT_API_BASE_URL = 'https://autokap.app';
|
|
|
6
6
|
const DEFAULT_WS_URL = 'wss://autokap.app/ws';
|
|
7
7
|
const LOCAL_API_BASE_URL = 'http://localhost:3000';
|
|
8
8
|
const LOCAL_WS_URL = 'ws://localhost:3000/ws';
|
|
9
|
+
const API_KEY_ENV_VAR = 'AUTOKAP_API_KEY';
|
|
9
10
|
const API_BASE_URL_ENV_VAR = 'AUTOKAP_API_BASE_URL';
|
|
10
11
|
const WS_URL_ENV_VAR = 'AUTOKAP_WS_URL';
|
|
11
12
|
const ALLOW_UNSAFE_SERVER_ORIGIN_ENV_VAR = 'AUTOKAP_ALLOW_UNSAFE_SERVER_ORIGIN';
|
|
@@ -22,6 +23,17 @@ export function getDefaultWsUrl(apiBaseUrl = getDefaultApiBaseUrl()) {
|
|
|
22
23
|
return normalizeUrl(process.env[WS_URL_ENV_VAR]) ?? deriveWsUrl(apiBaseUrl);
|
|
23
24
|
}
|
|
24
25
|
export async function readConfig() {
|
|
26
|
+
const envApiKey = normalizeApiKey(process.env[API_KEY_ENV_VAR]);
|
|
27
|
+
if (envApiKey) {
|
|
28
|
+
const apiBaseUrl = getDefaultApiBaseUrl();
|
|
29
|
+
const wsUrl = getDefaultWsUrl(apiBaseUrl);
|
|
30
|
+
assertAllowedApiOrigin(apiBaseUrl, DEFAULT_API_BASE_URL, API_BASE_URL_ENV_VAR);
|
|
31
|
+
return {
|
|
32
|
+
apiKey: envApiKey,
|
|
33
|
+
apiBaseUrl,
|
|
34
|
+
wsUrl,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
25
37
|
try {
|
|
26
38
|
const raw = await fs.readFile(getConfigPath(), 'utf-8');
|
|
27
39
|
const parsed = JSON.parse(raw);
|
|
@@ -81,11 +93,15 @@ export async function requireConfig() {
|
|
|
81
93
|
process.exit(1);
|
|
82
94
|
}
|
|
83
95
|
if (!config) {
|
|
84
|
-
logger.error(
|
|
96
|
+
logger.error(`Not authenticated. Run \`npx autokap@latest init --cli-key <key>\` first, or set ${API_KEY_ENV_VAR} in CI.`);
|
|
85
97
|
process.exit(1);
|
|
86
98
|
}
|
|
87
99
|
return config;
|
|
88
100
|
}
|
|
101
|
+
function normalizeApiKey(value) {
|
|
102
|
+
const trimmed = value?.trim();
|
|
103
|
+
return trimmed || null;
|
|
104
|
+
}
|
|
89
105
|
function normalizeUrl(value) {
|
|
90
106
|
const trimmed = value?.trim();
|
|
91
107
|
if (!trimmed)
|
|
@@ -143,5 +159,5 @@ function assertAllowedApiOrigin(candidateUrl, baselineUrl, envVar) {
|
|
|
143
159
|
}
|
|
144
160
|
throw new Error(`Refusing unsafe server override to ${candidateOrigin}. Set ${ALLOW_UNSAFE_SERVER_ORIGIN_ENV_VAR}=1 to allow ${envVar ?? 'this override'} explicitly.`);
|
|
145
161
|
}
|
|
146
|
-
export { DEFAULT_API_BASE_URL, DEFAULT_WS_URL, LOCAL_API_BASE_URL, LOCAL_WS_URL, API_BASE_URL_ENV_VAR, WS_URL_ENV_VAR, ALLOW_UNSAFE_SERVER_ORIGIN_ENV_VAR, };
|
|
162
|
+
export { DEFAULT_API_BASE_URL, DEFAULT_WS_URL, LOCAL_API_BASE_URL, LOCAL_WS_URL, API_KEY_ENV_VAR, API_BASE_URL_ENV_VAR, WS_URL_ENV_VAR, ALLOW_UNSAFE_SERVER_ORIGIN_ENV_VAR, };
|
|
147
163
|
//# sourceMappingURL=cli-config.js.map
|
package/dist/cli-contract.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare const CLI_ADVANCED_SKILL_COMMAND = "autokap skill --agent <agent>
|
|
|
14
14
|
export declare const CLI_FALLBACK_PROGRAM_COMMAND = "autokap run <preset-id> --program <file>";
|
|
15
15
|
export declare function buildCliRunCommand(presetId: string, options?: {
|
|
16
16
|
local?: boolean;
|
|
17
|
+
env?: string;
|
|
17
18
|
}): string;
|
|
18
19
|
export declare function buildCliInstalledSetupCommand(cliKey: string): string;
|
|
19
20
|
export declare const CLI_PUBLIC_COMMANDS: CliPublicCommandDescriptor[];
|
package/dist/cli-contract.js
CHANGED
|
@@ -7,7 +7,7 @@ export const CLI_DEFAULT_SETUP_COMMAND = "npx autokap@latest init --cli-key <you
|
|
|
7
7
|
export const CLI_ADVANCED_SKILL_COMMAND = "autokap skill --agent <agent>";
|
|
8
8
|
export const CLI_FALLBACK_PROGRAM_COMMAND = "autokap run <preset-id> --program <file>";
|
|
9
9
|
export function buildCliRunCommand(presetId, options = {}) {
|
|
10
|
-
return `autokap run${options.local ? " --local" : ""} ${presetId}`;
|
|
10
|
+
return `autokap run${options.local ? " --local" : ""}${options.env ? ` --env ${options.env}` : ""} ${presetId}`;
|
|
11
11
|
}
|
|
12
12
|
export function buildCliInstalledSetupCommand(cliKey) {
|
|
13
13
|
return `autokap init --cli-key ${cliKey}`;
|
|
@@ -45,10 +45,16 @@ export const CLI_PUBLIC_COMMANDS = [
|
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
id: "run",
|
|
48
|
-
command: "autokap run <preset-id>",
|
|
48
|
+
command: "autokap run <preset-id> --env local",
|
|
49
49
|
summary: "Run a preset capture using local Playwright",
|
|
50
50
|
docsDescriptionKey: "cliCmdRun",
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
id: "auto-recapture",
|
|
54
|
+
command: "autokap auto-recapture --project <project-id> --env local",
|
|
55
|
+
summary: "Run every preset enabled for CI auto-recapture in a project",
|
|
56
|
+
docsDescriptionKey: "cliCmdAutoRecapture",
|
|
57
|
+
},
|
|
52
58
|
{
|
|
53
59
|
id: "run-headed",
|
|
54
60
|
command: "autokap run <preset-id> --headed",
|