autokap 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/assets/skill/OPCODE-REFERENCE.md +1 -41
  2. package/assets/skill/README.md +0 -1
  3. package/assets/skill/SKILL.md +9 -32
  4. package/assets/skill/references/examples.md +1 -17
  5. package/dist/billing-operation-logging.d.ts +1 -3
  6. package/dist/billing-operation-logging.js +0 -4
  7. package/dist/capture-strategy.d.ts +2 -8
  8. package/dist/capture-strategy.js +2 -30
  9. package/dist/cli-config.d.ts +2 -1
  10. package/dist/cli-config.js +18 -2
  11. package/dist/cli-contract.d.ts +1 -0
  12. package/dist/cli-contract.js +8 -2
  13. package/dist/cli-runner-local.d.ts +2 -0
  14. package/dist/cli-runner-local.js +12 -21
  15. package/dist/cli-runner.d.ts +4 -0
  16. package/dist/cli-runner.js +30 -50
  17. package/dist/cli.js +89 -44
  18. package/dist/execution-schema.d.ts +143 -331
  19. package/dist/execution-schema.js +43 -28
  20. package/dist/execution-types.d.ts +6 -151
  21. package/dist/execution-types.js +1 -3
  22. package/dist/logger.js +1 -1
  23. package/dist/mockup-html.d.ts +2 -0
  24. package/dist/mockup-html.js +13 -10
  25. package/dist/mockup.js +2 -2
  26. package/dist/opcode-actions.js +0 -2
  27. package/dist/opcode-runner.js +0 -121
  28. package/dist/program-signing.d.ts +50 -72
  29. package/dist/security.js +2 -2
  30. package/dist/server-capture-runtime.d.ts +0 -1
  31. package/dist/server-capture-runtime.js +0 -3
  32. package/dist/server-credit-usage.d.ts +1 -1
  33. package/dist/skill-packaging.d.ts +1 -1
  34. package/dist/skill-packaging.js +1 -11
  35. package/dist/types.d.ts +2 -2
  36. package/dist/web-playwright-local.d.ts +0 -14
  37. package/dist/web-playwright-local.js +0 -194
  38. package/package.json +1 -18
  39. package/readme.md +13 -0
  40. package/assets/skill/STUDIO-SKILL.md +0 -476
  41. 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 25 opcodes. For workflow, rules, and examples, see [SKILL.md](SKILL.md).
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.
@@ -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
 
@@ -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, clip, and interactive demo capture of web apps. Use when: creating or updating presets,
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, clips, or interactive demos of their web app
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, clip, or interactive demo? Which viewports, locales, themes, and mockups matter?
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' | 'dom';
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' | 'dom';
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
- 25 opcodes available. For full parameter documentation, see [OPCODE-REFERENCE.md](OPCODE-REFERENCE.md).
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 `interactiveDemo` / `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.
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 `interactiveDemo`/`mockDataInjection`) and write it to a temporary file:
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"`, `"clip"`, or `"interactive_demo"`.
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 — Interactive demo preset
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' | 'interactive_demo';
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
@@ -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 all three — only the capture
6
- * opcodes dispatch to different strategies.
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;
@@ -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 all three — only the capture
6
- * opcodes dispatch to different strategies.
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
@@ -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, };
@@ -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('Not authenticated. Run `npx autokap@latest init --cli-key <key>` first.');
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
@@ -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[];
@@ -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",
@@ -9,4 +9,6 @@ export declare function runLocal(presetId: string, opts: {
9
9
  output?: string;
10
10
  program?: string;
11
11
  debug?: boolean;
12
+ env?: string;
13
+ allowUploadFailure?: boolean;
12
14
  }): Promise<void>;
@@ -24,7 +24,9 @@ export async function runLocal(presetId, opts) {
24
24
  }
25
25
  const run = await runCapture({
26
26
  presetId,
27
+ env: opts.env,
27
28
  program,
29
+ allowUploadFailure: opts.allowUploadFailure,
28
30
  headed: opts.headed,
29
31
  onProgress: (event) => {
30
32
  const prefix = `[capture][${event.variantId}]`;
@@ -73,27 +75,16 @@ async function persistArtifactsLocally(presetId, outputDirOption, variants) {
73
75
  for (const variant of variants) {
74
76
  for (let index = 0; index < variant.artifacts.length; index += 1) {
75
77
  const artifact = variant.artifacts[index];
76
- const ext = artifact.mediaMode === 'dom'
77
- ? 'html'
78
- : artifact.mimeType === 'image/png'
79
- ? 'png'
80
- : artifact.mimeType === 'image/jpeg'
81
- ? 'jpg'
82
- : artifact.mimeType.includes('gif')
83
- ? 'gif'
84
- : artifact.mimeType.includes('mp4')
85
- ? 'mp4'
86
- : 'webm';
87
- let suffix = '';
88
- if (artifact.mediaMode === 'dom') {
89
- if (artifact.fragmentName && artifact.parentStateName) {
90
- suffix = `-${sanitizePathToken(artifact.parentStateName)}-fragment-${sanitizePathToken(artifact.fragmentName)}`;
91
- }
92
- else if (artifact.stateName) {
93
- suffix = `-${sanitizePathToken(artifact.stateName)}`;
94
- }
95
- }
96
- const fileName = `capture-${sanitizePathToken(presetId.slice(0, 8))}-${sanitizePathToken(variant.variantId)}${suffix}-${index}.${ext}`;
78
+ const ext = artifact.mimeType === 'image/png'
79
+ ? 'png'
80
+ : artifact.mimeType === 'image/jpeg'
81
+ ? 'jpg'
82
+ : artifact.mimeType.includes('gif')
83
+ ? 'gif'
84
+ : artifact.mimeType.includes('mp4')
85
+ ? 'mp4'
86
+ : 'webm';
87
+ const fileName = `capture-${sanitizePathToken(presetId.slice(0, 8))}-${sanitizePathToken(variant.variantId)}-${index}.${ext}`;
97
88
  const filePath = resolveContainedPath(outputDir, fileName);
98
89
  await fs.writeFile(filePath, artifact.buffer);
99
90
  logger.info(`[capture] Artifact saved locally: ${filePath}`);
@@ -15,8 +15,12 @@ import type { ExecutionProgram, RunResult } from './execution-types.js';
15
15
  export interface CLIRunnerOptions {
16
16
  /** Preset ID to run */
17
17
  presetId: string;
18
+ /** Project environment name used by the server to resolve capture URLs */
19
+ env?: string;
18
20
  /** Override: provide program directly instead of fetching from server */
19
21
  program?: ExecutionProgram;
22
+ /** Keep the legacy success result when upload/telemetry persistence fails */
23
+ allowUploadFailure?: boolean;
20
24
  /** Selector memory map (fetched from server or cached locally) */
21
25
  selectorMemory?: Record<string, string[]>;
22
26
  /** Show browser window. Default: false (headless) */
@@ -46,7 +46,7 @@ export async function runCapture(options) {
46
46
  };
47
47
  }
48
48
  else {
49
- const fetched = await fetchProgram(config, options.presetId);
49
+ const fetched = await fetchProgram(config, options.presetId, options.env);
50
50
  if (!fetched.success) {
51
51
  return { success: false, error: fetched.error };
52
52
  }
@@ -151,15 +151,29 @@ export async function runCapture(options) {
151
151
  logger.info(`[capture] Captures saved successfully — total ${totalDurationSec}s`);
152
152
  }
153
153
  catch (err) {
154
- logger.error(`[capture] Failed to upload results: ${err instanceof Error ? err.message : String(err)}`);
154
+ const message = err instanceof Error ? err.message : String(err);
155
+ logger.error(`[capture] Failed to upload results: ${message}`);
156
+ if (!options.allowUploadFailure) {
157
+ return {
158
+ success: false,
159
+ runResult,
160
+ error: runResult.success
161
+ ? `upload failed: ${message}`
162
+ : `${runResult.error ?? 'capture failed'}; upload failed: ${message}`,
163
+ };
164
+ }
165
+ logger.warn('[capture] Continuing after upload failure because --allow-upload-failure was set');
155
166
  }
156
167
  return { success: runResult.success, runResult };
157
168
  }
158
169
  // ── Server communication ────────────────────────────────────────────
159
- async function fetchProgram(config, presetId) {
170
+ async function fetchProgram(config, presetId, environmentName) {
160
171
  try {
161
- const url = `${config.apiBaseUrl}/api/cli/programs/${presetId}`;
162
- const response = await fetch(url, {
172
+ const url = new URL(`${config.apiBaseUrl}/api/cli/programs/${presetId}`);
173
+ if (environmentName) {
174
+ url.searchParams.set('env', environmentName);
175
+ }
176
+ const response = await fetch(url.toString(), {
163
177
  headers: {
164
178
  'Authorization': `Bearer ${config.apiKey}`,
165
179
  'Content-Type': 'application/json',
@@ -167,7 +181,7 @@ async function fetchProgram(config, presetId) {
167
181
  },
168
182
  });
169
183
  if (!response.ok) {
170
- return { success: false, error: await formatServerError(response, url) };
184
+ return { success: false, error: await formatServerError(response, url.toString()) };
171
185
  }
172
186
  const data = await response.json();
173
187
  const envelope = verifySignedExecutionProgramEnvelope({
@@ -195,7 +209,7 @@ async function uploadResults(config, program, result) {
195
209
  const formData = new FormData();
196
210
  const filename = buildArtifactFilename(program.presetId, variant.variantId, artifact);
197
211
  uploadedCount += 1;
198
- const label = artifact.captureName ?? artifact.clipName ?? artifact.fragmentName ?? artifact.stateName ?? filename;
212
+ const label = artifact.captureName ?? artifact.clipName ?? filename;
199
213
  logger.info(`[capture] Exporting capture ${uploadedCount}/${totalArtifacts}: ${label}`);
200
214
  formData.append('file', new Blob([new Uint8Array(artifact.buffer)], { type: artifact.mimeType }), filename);
201
215
  formData.append('presetId', program.presetId);
@@ -253,35 +267,6 @@ async function uploadResults(config, program, result) {
253
267
  if (typeof artifact.trimStartMs === 'number') {
254
268
  formData.append('trimStartMs', String(artifact.trimStartMs));
255
269
  }
256
- // ── Interactive demo (mediaMode === 'dom') ──
257
- if (artifact.stateName) {
258
- formData.append('stateName', artifact.stateName);
259
- }
260
- if (artifact.domAssetUrls && artifact.domAssetUrls.length > 0) {
261
- formData.append('domAssetUrls', JSON.stringify(artifact.domAssetUrls));
262
- }
263
- if (artifact.domThumbnailBuffer) {
264
- formData.append('domThumbnail', new Blob([new Uint8Array(artifact.domThumbnailBuffer)], { type: 'image/png' }), 'thumbnail.png');
265
- }
266
- // Phase 5: fragment fields
267
- if (artifact.fragmentName) {
268
- formData.append('fragmentName', artifact.fragmentName);
269
- }
270
- // Phase 8: variant of the fragment capture (defaults to 'default'
271
- // server-side when omitted, but we always send it explicitly so the
272
- // upload row's unique key includes it).
273
- if (artifact.fragmentVariantName) {
274
- formData.append('fragmentVariantName', artifact.fragmentVariantName);
275
- }
276
- if (artifact.parentStateName) {
277
- formData.append('parentStateName', artifact.parentStateName);
278
- }
279
- if (artifact.mountStrategy) {
280
- formData.append('mountStrategy', artifact.mountStrategy);
281
- }
282
- if (artifact.mountTargetSelector) {
283
- formData.append('mountTargetSelector', artifact.mountTargetSelector);
284
- }
285
270
  const response = await fetch(`${config.apiBaseUrl}/api/cli/artifacts`, {
286
271
  method: 'POST',
287
272
  headers: { 'Authorization': `Bearer ${config.apiKey}` },
@@ -438,17 +423,15 @@ function createHealerLLMProvider(llmConfig) {
438
423
  };
439
424
  }
440
425
  function buildArtifactFilename(presetId, variantId, artifact) {
441
- const ext = artifact.mediaMode === 'dom'
442
- ? 'html'
443
- : artifact.mimeType === 'image/jpeg'
444
- ? 'jpg'
445
- : artifact.mimeType === 'image/png'
446
- ? 'png'
447
- : artifact.mimeType.includes('gif')
448
- ? 'gif'
449
- : artifact.mimeType.includes('mp4')
450
- ? 'mp4'
451
- : 'webm';
426
+ const ext = artifact.mimeType === 'image/jpeg'
427
+ ? 'jpg'
428
+ : artifact.mimeType === 'image/png'
429
+ ? 'png'
430
+ : artifact.mimeType.includes('gif')
431
+ ? 'gif'
432
+ : artifact.mimeType.includes('mp4')
433
+ ? 'mp4'
434
+ : 'webm';
452
435
  const stepToken = typeof artifact.stepIndex === 'number' ? `-${artifact.stepIndex}` : '';
453
436
  return `${sanitizeArtifactToken(presetId)}-${sanitizeArtifactToken(variantId)}${stepToken}.${ext}`;
454
437
  }
@@ -497,9 +480,6 @@ function sanitizeArtifactForTelemetry(artifact) {
497
480
  altText: redactTelemetryText(artifact.altText),
498
481
  captureUrl: redactUrl(artifact.captureUrl),
499
482
  elementSelector: undefined,
500
- domAssetUrls: artifact.domAssetUrls
501
- ?.map((entry) => redactUrl(entry))
502
- .filter((entry) => Boolean(entry)),
503
483
  };
504
484
  }
505
485
  function sanitizeHealerPatches(patches) {