autokap 1.0.6 → 1.0.8
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/chrome/ios-statusbar-comparison-reference.jpg +0 -0
- package/assets/chrome/ios-statusbar-dark-reference.jpg +0 -0
- package/assets/chrome/ios-statusbar-light-reference.jpg +0 -0
- package/assets/cursors/macos.svg +4 -0
- package/assets/cursors/windows.svg +15 -0
- package/assets/devices/ipad-pro-11-m4.json +52 -0
- package/assets/devices/iphone-16-pro.json +53 -0
- package/assets/devices/macbook-air-13.json +45 -0
- package/assets/frames/MacBook Air 13.svg +242 -0
- package/assets/frames/Status bar - iPhone.png +0 -0
- Menu bar- iPad.png +0 -0
- package/assets/frames/iPad Pro M4 11_.png +0 -0
- package/assets/frames/iPhone 16 Pro.png +0 -0
- package/assets/icons/Cellular Connection.svg +3 -0
- package/assets/icons/Union.svg +6 -0
- package/assets/icons/Wifi.svg +3 -0
- package/assets/icons/battery.svg +5 -0
- package/assets/icons/battery_charging.svg +8 -0
- package/assets/skill/OPCODE-REFERENCE.md +607 -0
- package/assets/skill/README.md +39 -0
- package/assets/skill/SKILL.md +453 -468
- package/assets/skill/STUDIO-SKILL.md +476 -0
- package/assets/skill/references/examples.md +104 -0
- package/assets/skill/references/interactive-demo.md +225 -0
- package/assets/skill/references/mock-data.md +178 -0
- package/dist/abort.d.ts +5 -0
- package/dist/abort.js +44 -0
- package/dist/action-verifier.d.ts +29 -0
- package/dist/action-verifier.js +133 -0
- package/dist/agent-action-recovery.d.ts +45 -0
- package/dist/agent-action-recovery.js +370 -0
- package/dist/agent-message-utils.d.ts +21 -0
- package/dist/agent-message-utils.js +77 -0
- package/dist/agent-url-utils.d.ts +30 -0
- package/dist/agent-url-utils.js +138 -0
- package/dist/agent.d.ts +226 -0
- package/dist/agent.js +6666 -0
- package/dist/ak-tree.d.ts +39 -0
- package/dist/ak-tree.js +368 -0
- package/dist/alt-text.d.ts +26 -0
- package/dist/alt-text.js +55 -0
- package/dist/auth-capture.d.ts +17 -0
- package/dist/auth-capture.js +164 -0
- package/dist/benchmark.d.ts +59 -0
- package/dist/benchmark.js +135 -0
- package/dist/billing-operation-logging.d.ts +38 -0
- package/dist/billing-operation-logging.js +248 -0
- package/dist/browser-bar.d.ts +48 -0
- package/dist/browser-bar.js +284 -0
- package/dist/browser-pool.d.ts +7 -0
- package/dist/browser-pool.js +15 -5
- package/dist/browser-utils.d.ts +31 -0
- package/dist/browser-utils.js +97 -0
- package/dist/browser.d.ts +76 -1
- package/dist/browser.js +1657 -39
- package/dist/capture-alt-text.d.ts +12 -0
- package/dist/capture-alt-text.js +52 -0
- package/dist/capture-encryption.d.ts +10 -0
- package/dist/capture-encryption.js +41 -0
- package/dist/capture-language-preflight.d.ts +41 -0
- package/dist/capture-language-preflight.js +300 -0
- package/dist/capture-llm-page-identity.d.ts +15 -0
- package/dist/capture-llm-page-identity.js +128 -0
- package/dist/capture-model-resolution.d.ts +9 -0
- package/dist/capture-model-resolution.js +21 -0
- package/dist/capture-page-identity.d.ts +7 -0
- package/dist/capture-page-identity.js +352 -0
- package/dist/capture-preset-credentials.d.ts +62 -0
- package/dist/capture-preset-credentials.js +184 -0
- package/dist/capture-request-plan.d.ts +58 -0
- package/dist/capture-request-plan.js +264 -0
- package/dist/capture-run-optimizer.d.ts +139 -0
- package/dist/capture-run-optimizer.js +863 -0
- package/dist/capture-selector-memory.d.ts +31 -0
- package/dist/capture-selector-memory.js +345 -0
- package/dist/capture-session-profile-encryption.d.ts +2 -0
- package/dist/capture-session-profile-encryption.js +22 -0
- package/dist/capture-step-timeout.d.ts +10 -0
- package/dist/capture-step-timeout.js +30 -0
- package/dist/capture-strategy.d.ts +36 -0
- package/dist/capture-strategy.js +95 -0
- package/dist/capture-studio-sync.d.ts +23 -0
- package/dist/capture-studio-sync.js +172 -0
- package/dist/capture-surface-contract.d.ts +36 -0
- package/dist/capture-surface-contract.js +299 -0
- package/dist/capture-transition-engine.d.ts +28 -0
- package/dist/capture-transition-engine.js +292 -0
- package/dist/capture-variant-state.d.ts +56 -0
- package/dist/capture-variant-state.js +182 -0
- package/dist/capture-verification.d.ts +35 -0
- package/dist/capture-verification.js +95 -0
- package/dist/capture-viewport-lock.d.ts +48 -0
- package/dist/capture-viewport-lock.js +74 -0
- package/dist/circuit-breaker.d.ts +42 -0
- package/dist/circuit-breaker.js +119 -0
- package/dist/cli-config.d.ts +8 -1
- package/dist/cli-config.js +62 -6
- package/dist/cli-contract.d.ts +15 -0
- package/dist/cli-contract.js +167 -0
- package/dist/cli-runner-local.d.ts +12 -0
- package/dist/cli-runner-local.js +102 -0
- package/dist/cli-runner.d.ts +34 -0
- package/dist/cli-runner.js +433 -0
- package/dist/cli-utils.d.ts +0 -1
- package/dist/cli-utils.js +2 -5
- package/dist/cli.js +1005 -252
- package/dist/clip-orchestrator.d.ts +148 -0
- package/dist/clip-orchestrator.js +957 -0
- package/dist/clip-postprocess.d.ts +42 -0
- package/dist/clip-postprocess.js +201 -0
- package/dist/cookie-dismiss.d.ts +2 -0
- package/dist/cookie-dismiss.js +48 -13
- package/dist/cost-logging.d.ts +35 -0
- package/dist/cost-logging.js +242 -0
- package/dist/cost-resolution-monitor.d.ts +16 -0
- package/dist/cost-resolution-monitor.js +34 -0
- package/dist/credential-templates.d.ts +5 -0
- package/dist/credential-templates.js +60 -0
- package/dist/cursor-overlay-script.d.ts +6 -0
- package/dist/cursor-overlay-script.js +169 -0
- package/dist/dom-css-purger.d.ts +65 -0
- package/dist/dom-css-purger.js +333 -0
- package/dist/dom-font-inliner.d.ts +45 -0
- package/dist/dom-font-inliner.js +148 -0
- package/dist/dom-patch-resolver.d.ts +52 -0
- package/dist/dom-patch-resolver.js +242 -0
- package/dist/dom-serializer.d.ts +82 -0
- package/dist/dom-serializer.js +378 -0
- package/dist/element-capture.d.ts +13 -0
- package/dist/element-capture.js +522 -0
- package/dist/env-validation.d.ts +5 -0
- package/dist/env-validation.js +29 -0
- package/dist/execution-schema.d.ts +4423 -0
- package/dist/execution-schema.js +507 -0
- package/dist/execution-types.d.ts +886 -0
- package/dist/execution-types.js +65 -0
- package/dist/fonts-loader.d.ts +14 -0
- package/dist/fonts-loader.js +55 -0
- package/dist/hybrid-navigator.d.ts +138 -0
- package/dist/hybrid-navigator.js +468 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +17 -0
- package/dist/legacy/agent-action-recovery.d.ts +45 -0
- package/dist/legacy/agent-action-recovery.js +370 -0
- package/dist/legacy/agent-message-utils.d.ts +21 -0
- package/dist/legacy/agent-message-utils.js +77 -0
- package/dist/legacy/agent-url-utils.d.ts +30 -0
- package/dist/legacy/agent-url-utils.js +138 -0
- package/dist/legacy/agent.d.ts +226 -0
- package/dist/legacy/agent.js +6666 -0
- package/dist/legacy/clip-orchestrator.d.ts +148 -0
- package/dist/legacy/clip-orchestrator.js +957 -0
- package/dist/legacy/credential-templates.d.ts +5 -0
- package/dist/legacy/credential-templates.js +60 -0
- package/dist/legacy/hybrid-navigator.d.ts +138 -0
- package/dist/legacy/hybrid-navigator.js +468 -0
- package/dist/legacy/llm-usage.d.ts +17 -0
- package/dist/legacy/llm-usage.js +45 -0
- package/dist/legacy/prompt-cache.d.ts +10 -0
- package/dist/legacy/prompt-cache.js +24 -0
- package/dist/legacy/prompts.d.ts +175 -0
- package/dist/legacy/prompts.js +1038 -0
- package/dist/legacy/tools.d.ts +4 -0
- package/dist/legacy/tools.js +216 -0
- package/dist/legacy/video-agent.d.ts +143 -0
- package/dist/legacy/video-agent.js +4788 -0
- package/dist/legacy/video-observation.d.ts +36 -0
- package/dist/legacy/video-observation.js +192 -0
- package/dist/legacy/video-planner.d.ts +12 -0
- package/dist/legacy/video-planner.js +501 -0
- package/dist/legacy/video-prompts.d.ts +37 -0
- package/dist/legacy/video-prompts.js +569 -0
- package/dist/legacy/video-tools.d.ts +3 -0
- package/dist/legacy/video-tools.js +59 -0
- package/dist/legacy/video-variant-state.d.ts +29 -0
- package/dist/legacy/video-variant-state.js +80 -0
- package/dist/legacy/vision-model.d.ts +17 -0
- package/dist/legacy/vision-model.js +74 -0
- package/dist/llm-healer.d.ts +63 -0
- package/dist/llm-healer.js +166 -0
- package/dist/llm-provider.d.ts +29 -0
- package/dist/llm-provider.js +80 -0
- package/dist/llm-usage.d.ts +17 -0
- package/dist/llm-usage.js +45 -0
- package/dist/logger.d.ts +6 -2
- package/dist/logger.js +15 -1
- package/dist/mockup-html.d.ts +119 -0
- package/dist/mockup-html.js +263 -0
- package/dist/mockup.d.ts +187 -0
- package/dist/mockup.js +869 -0
- package/dist/mouse-animation.d.ts +46 -0
- package/dist/mouse-animation.js +114 -0
- package/dist/opcode-actions.d.ts +42 -0
- package/dist/opcode-actions.js +511 -0
- package/dist/opcode-runner.d.ts +51 -0
- package/dist/opcode-runner.js +770 -0
- package/dist/openrouter-client.d.ts +40 -0
- package/dist/openrouter-client.js +16 -0
- package/dist/overlay-engine.d.ts +24 -0
- package/dist/overlay-engine.js +176 -0
- package/dist/overlay-utils.d.ts +14 -0
- package/dist/overlay-utils.js +13 -0
- package/dist/postcondition.d.ts +16 -0
- package/dist/postcondition.js +269 -0
- package/dist/posthog.d.ts +4 -0
- package/dist/posthog.js +26 -0
- package/dist/program-patcher.d.ts +25 -0
- package/dist/program-patcher.js +44 -0
- package/dist/prompt-cache.d.ts +10 -0
- package/dist/prompt-cache.js +24 -0
- package/dist/prompts.d.ts +175 -0
- package/dist/prompts.js +1038 -0
- package/dist/provider-config.d.ts +12 -0
- package/dist/provider-config.js +15 -0
- package/dist/recovery-chain.d.ts +37 -0
- package/dist/recovery-chain.js +350 -0
- package/dist/remote-browser.d.ts +215 -0
- package/dist/remote-browser.js +360 -0
- package/dist/safari-browser-bar.d.ts +15 -0
- package/dist/safari-browser-bar.js +95 -0
- package/dist/safari-toolbar-asset.d.ts +15 -0
- package/dist/safari-toolbar-asset.js +12 -0
- package/dist/security.d.ts +21 -0
- package/dist/security.js +608 -0
- package/dist/selector-resolver.d.ts +34 -0
- package/dist/selector-resolver.js +181 -0
- package/dist/semantic-resolver.d.ts +35 -0
- package/dist/semantic-resolver.js +161 -0
- package/dist/server-capture-runtime.d.ts +125 -0
- package/dist/server-capture-runtime.js +585 -0
- package/dist/server-credit-usage.d.ts +12 -0
- package/dist/server-credit-usage.js +41 -0
- package/dist/server-posthog.d.ts +2 -0
- package/dist/server-posthog.js +16 -0
- package/dist/server-project-webhooks.d.ts +59 -0
- package/dist/server-project-webhooks.js +123 -0
- package/dist/server-screenshot-watermark.d.ts +7 -0
- package/dist/server-screenshot-watermark.js +60 -0
- package/dist/session-profile.d.ts +86 -0
- package/dist/session-profile.js +1536 -0
- package/dist/sf-pro-fonts.d.ts +4 -0
- package/dist/sf-pro-fonts.js +7 -0
- package/dist/sf-pro-symbols.d.ts +1 -0
- package/dist/sf-pro-symbols.js +55 -0
- package/dist/skill-packaging.d.ts +28 -0
- package/dist/skill-packaging.js +169 -0
- package/dist/smart-wait.d.ts +27 -0
- package/dist/smart-wait.js +81 -0
- package/dist/status-bar-l10n.d.ts +14 -0
- package/dist/status-bar-l10n.js +177 -0
- package/dist/status-bar-render.d.ts +20 -0
- package/dist/status-bar-render.js +410 -0
- package/dist/status-bar.d.ts +53 -0
- package/dist/status-bar.js +620 -0
- package/dist/svg-browser-bar.d.ts +33 -0
- package/dist/svg-browser-bar.js +206 -0
- package/dist/svg-status-bar.d.ts +36 -0
- package/dist/svg-status-bar.js +597 -0
- package/dist/svg-text.d.ts +61 -0
- package/dist/svg-text.js +118 -0
- package/dist/tools.d.ts +4 -0
- package/dist/tools.js +216 -0
- package/dist/types.d.ts +240 -5
- package/dist/types.js +23 -1
- package/dist/v2/action-verifier.d.ts +29 -0
- package/dist/v2/action-verifier.js +133 -0
- package/dist/v2/alt-text.d.ts +26 -0
- package/dist/v2/alt-text.js +55 -0
- package/dist/v2/benchmark.d.ts +59 -0
- package/dist/v2/benchmark.js +135 -0
- package/dist/v2/capture-strategy.d.ts +30 -0
- package/dist/v2/capture-strategy.js +67 -0
- package/dist/v2/capture-verification.d.ts +35 -0
- package/dist/v2/capture-verification.js +95 -0
- package/dist/v2/circuit-breaker.d.ts +42 -0
- package/dist/v2/circuit-breaker.js +119 -0
- package/dist/v2/cli-runner-local.d.ts +11 -0
- package/dist/v2/cli-runner-local.js +91 -0
- package/dist/v2/cli-runner.d.ts +34 -0
- package/dist/v2/cli-runner.js +300 -0
- package/dist/v2/compiler-prompts.d.ts +27 -0
- package/dist/v2/compiler-prompts.js +123 -0
- package/dist/v2/compiler.d.ts +37 -0
- package/dist/v2/compiler.js +147 -0
- package/dist/v2/explorer.d.ts +41 -0
- package/dist/v2/explorer.js +56 -0
- package/dist/v2/index.d.ts +37 -0
- package/dist/v2/index.js +31 -0
- package/dist/v2/llm-healer.d.ts +62 -0
- package/dist/v2/llm-healer.js +166 -0
- package/dist/v2/llm-provider.d.ts +29 -0
- package/dist/v2/llm-provider.js +80 -0
- package/dist/v2/opcode-runner.d.ts +47 -0
- package/dist/v2/opcode-runner.js +634 -0
- package/dist/v2/overlay-engine.d.ts +24 -0
- package/dist/v2/overlay-engine.js +150 -0
- package/dist/v2/postcondition.d.ts +16 -0
- package/dist/v2/postcondition.js +249 -0
- package/dist/v2/program-patcher.d.ts +25 -0
- package/dist/v2/program-patcher.js +44 -0
- package/dist/v2/recovery-chain.d.ts +30 -0
- package/dist/v2/recovery-chain.js +368 -0
- package/dist/v2/schema.d.ts +2580 -0
- package/dist/v2/schema.js +295 -0
- package/dist/v2/selector-resolver.d.ts +34 -0
- package/dist/v2/selector-resolver.js +181 -0
- package/dist/v2/semantic-resolver.d.ts +35 -0
- package/dist/v2/semantic-resolver.js +161 -0
- package/dist/v2/smart-wait.d.ts +27 -0
- package/dist/v2/smart-wait.js +81 -0
- package/dist/v2/types.d.ts +444 -0
- package/dist/v2/types.js +19 -0
- package/dist/v2/web-playwright-local.d.ts +69 -0
- package/dist/v2/web-playwright-local.js +392 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +5 -0
- package/dist/video-agent.d.ts +143 -0
- package/dist/video-agent.js +4788 -0
- package/dist/video-observation.d.ts +36 -0
- package/dist/video-observation.js +192 -0
- package/dist/video-planner.d.ts +12 -0
- package/dist/video-planner.js +501 -0
- package/dist/video-prompts.d.ts +37 -0
- package/dist/video-prompts.js +554 -0
- package/dist/video-tools.d.ts +3 -0
- package/dist/video-tools.js +59 -0
- package/dist/video-variant-state.d.ts +29 -0
- package/dist/video-variant-state.js +80 -0
- package/dist/vision-model.d.ts +17 -0
- package/dist/vision-model.js +74 -0
- package/dist/web-playwright-local.d.ts +126 -0
- package/dist/web-playwright-local.js +819 -0
- package/dist/ws-auth.d.ts +20 -0
- package/dist/ws-auth.js +70 -0
- package/dist/ws-broadcast.d.ts +34 -0
- package/dist/ws-broadcast.js +85 -0
- package/dist/ws-connection-limits.d.ts +12 -0
- package/dist/ws-connection-limits.js +44 -0
- package/dist/ws-handler-utils.d.ts +32 -0
- package/dist/ws-handler-utils.js +139 -0
- package/dist/ws-handler.d.ts +10 -0
- package/dist/ws-handler.js +1793 -0
- package/dist/ws-metrics-server.d.ts +9 -0
- package/dist/ws-metrics-server.js +31 -0
- package/dist/ws-server.d.ts +9 -0
- package/dist/ws-server.js +92 -0
- package/package.json +142 -71
package/dist/cli.js
CHANGED
|
@@ -3,32 +3,129 @@ import { Command } from 'commander';
|
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import fs from 'node:fs/promises';
|
|
6
|
-
import WebSocket from 'ws';
|
|
7
6
|
const require = createRequire(import.meta.url);
|
|
8
7
|
const { version } = require('../package.json');
|
|
9
|
-
import { Browser } from './browser.js';
|
|
10
8
|
import { logger } from './logger.js';
|
|
11
|
-
import { writeConfig, requireConfig, DEFAULT_API_BASE_URL,
|
|
12
|
-
import {
|
|
9
|
+
import { writeConfig, deleteConfig, requireConfig, getConfigPath, DEFAULT_API_BASE_URL, getDefaultApiBaseUrl, getDefaultWsUrl, LOCAL_API_BASE_URL, LOCAL_WS_URL, API_BASE_URL_ENV_VAR, WS_URL_ENV_VAR, } from './cli-config.js';
|
|
10
|
+
import { renderSkillSingleFile, writeSkillExport } from './skill-packaging.js';
|
|
13
11
|
// ── Program definition ──────────────────────────────────────────────
|
|
14
12
|
export const program = new Command();
|
|
15
13
|
program
|
|
16
14
|
.name('autokap')
|
|
17
15
|
.version(version)
|
|
18
16
|
.description('AI-powered screenshot capture — local Playwright proxy');
|
|
17
|
+
function fatal(message) {
|
|
18
|
+
logger.error(message);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
function authHeaders(config, extra = {}) {
|
|
22
|
+
return {
|
|
23
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
24
|
+
...extra,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function buildApiUrl(config, pathname, searchParams) {
|
|
28
|
+
const suffix = searchParams && Array.from(searchParams.keys()).length > 0
|
|
29
|
+
? `?${searchParams.toString()}`
|
|
30
|
+
: '';
|
|
31
|
+
return `${config.apiBaseUrl}${pathname}${suffix}`;
|
|
32
|
+
}
|
|
33
|
+
async function readApiError(response) {
|
|
34
|
+
const body = await response.json().catch(() => ({ error: response.statusText }));
|
|
35
|
+
return body.error || response.statusText;
|
|
36
|
+
}
|
|
37
|
+
async function requestJson(config, pathname, init, errorPrefix, searchParams) {
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(buildApiUrl(config, pathname, searchParams), init);
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
fatal(`${errorPrefix}: ${await readApiError(response)}`);
|
|
42
|
+
}
|
|
43
|
+
return await response.json();
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
fatal(`${errorPrefix}: ${error.message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function requestText(config, pathname, init, errorPrefix, searchParams) {
|
|
50
|
+
try {
|
|
51
|
+
const response = await fetch(buildApiUrl(config, pathname, searchParams), init);
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
fatal(`${errorPrefix}: ${await readApiError(response)}`);
|
|
54
|
+
}
|
|
55
|
+
return await response.text();
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
fatal(`${errorPrefix}: ${error.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function ensureListFormat(format) {
|
|
62
|
+
if (format !== 'json' && format !== 'table') {
|
|
63
|
+
fatal('Invalid --format. Use "json" or "table".');
|
|
64
|
+
}
|
|
65
|
+
return format;
|
|
66
|
+
}
|
|
67
|
+
function ensureExportFormat(format) {
|
|
68
|
+
if (format !== 'json' && format !== 'csv') {
|
|
69
|
+
fatal('Invalid --format. Use "json" or "csv".');
|
|
70
|
+
}
|
|
71
|
+
return format;
|
|
72
|
+
}
|
|
73
|
+
function printJson(value) {
|
|
74
|
+
console.log(JSON.stringify(value, null, 2));
|
|
75
|
+
}
|
|
76
|
+
function buildEndpointAssetUrl(config, endpointId) {
|
|
77
|
+
return `${config.apiBaseUrl}/api/v1/assets/${endpointId}`;
|
|
78
|
+
}
|
|
79
|
+
function toEndpointOutputRow(config, endpoint) {
|
|
80
|
+
return {
|
|
81
|
+
...endpoint,
|
|
82
|
+
url: buildEndpointAssetUrl(config, endpoint.id),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function toCsv(rows, columns) {
|
|
86
|
+
const escape = (value) => {
|
|
87
|
+
const text = value === null || value === undefined ? '' : String(value);
|
|
88
|
+
return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
|
|
89
|
+
};
|
|
90
|
+
const header = columns.join(',');
|
|
91
|
+
const body = rows.map((row) => columns.map((column) => escape(row[column])).join(','));
|
|
92
|
+
return [header, ...body].join('\n');
|
|
93
|
+
}
|
|
94
|
+
async function loadProject(config, projectId) {
|
|
95
|
+
const data = await requestJson(config, `/api/v1/projects/${projectId}`, { headers: authHeaders(config) }, 'Failed to get project');
|
|
96
|
+
return data.project;
|
|
97
|
+
}
|
|
98
|
+
async function listProjectAccounts(config, projectId) {
|
|
99
|
+
const data = await requestJson(config, `/api/cli/projects/${projectId}/credentials`, { headers: authHeaders(config) }, 'Failed to list accounts');
|
|
100
|
+
return data.accounts;
|
|
101
|
+
}
|
|
102
|
+
async function resolveProjectAccount(config, projectId, accountNameOrId) {
|
|
103
|
+
const accounts = await listProjectAccounts(config, projectId);
|
|
104
|
+
const needle = accountNameOrId.toLowerCase();
|
|
105
|
+
const account = accounts.find((candidate) => candidate.id === accountNameOrId ||
|
|
106
|
+
candidate.name.toLowerCase() === needle);
|
|
107
|
+
if (!account) {
|
|
108
|
+
const available = accounts.map((candidate) => candidate.name).join(', ');
|
|
109
|
+
fatal(available
|
|
110
|
+
? `Account "${accountNameOrId}" not found. Available accounts: ${available}`
|
|
111
|
+
: `Account "${accountNameOrId}" not found. This project has no accounts.`);
|
|
112
|
+
}
|
|
113
|
+
return account;
|
|
114
|
+
}
|
|
19
115
|
// ── login command ───────────────────────────────────────────────────
|
|
20
116
|
program
|
|
21
117
|
.command('login <key>')
|
|
22
|
-
.description('Authenticate
|
|
23
|
-
.option('--api-base-url <url>',
|
|
24
|
-
.option('--ws-url <url>',
|
|
118
|
+
.description('Authenticate the AutoKap CLI with your CLI key')
|
|
119
|
+
.option('--api-base-url <url>', `API base URL (env: ${API_BASE_URL_ENV_VAR})`, getDefaultApiBaseUrl())
|
|
120
|
+
.option('--ws-url <url>', `WebSocket server URL (env: ${WS_URL_ENV_VAR})`)
|
|
25
121
|
.action(async (key, opts) => {
|
|
122
|
+
const wsUrl = opts.wsUrl?.trim() || getDefaultWsUrl(opts.apiBaseUrl);
|
|
26
123
|
try {
|
|
27
124
|
const res = await fetch(`${opts.apiBaseUrl}/api/cli/validate`, {
|
|
28
125
|
headers: { Authorization: `Bearer ${key}` },
|
|
29
126
|
});
|
|
30
127
|
if (!res.ok) {
|
|
31
|
-
logger.error('Invalid
|
|
128
|
+
logger.error('Invalid CLI key. Generate one in the AutoKap dashboard.');
|
|
32
129
|
process.exit(1);
|
|
33
130
|
}
|
|
34
131
|
}
|
|
@@ -39,291 +136,947 @@ program
|
|
|
39
136
|
await writeConfig({
|
|
40
137
|
apiKey: key,
|
|
41
138
|
apiBaseUrl: opts.apiBaseUrl,
|
|
42
|
-
wsUrl
|
|
139
|
+
wsUrl,
|
|
43
140
|
});
|
|
44
|
-
logger.success(
|
|
141
|
+
logger.success(`Authenticated. Key stored in ${getConfigPath()}`);
|
|
45
142
|
process.exit(0);
|
|
46
143
|
});
|
|
47
|
-
// ──
|
|
144
|
+
// ── ping command ───────────────────────────────────────────────────
|
|
145
|
+
program
|
|
146
|
+
.command('ping')
|
|
147
|
+
.description('Verify CLI connection with the AutoKap server')
|
|
148
|
+
.action(async () => {
|
|
149
|
+
const config = await requireConfig();
|
|
150
|
+
try {
|
|
151
|
+
const res = await fetch(`${config.apiBaseUrl}/api/cli/validate`, {
|
|
152
|
+
headers: { Authorization: `Bearer ${config.apiKey}` },
|
|
153
|
+
});
|
|
154
|
+
if (!res.ok) {
|
|
155
|
+
logger.error('CLI key is no longer valid. Run `npx autokap@latest init --cli-key <key>` to re-authenticate.');
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
logger.error(`Cannot reach API: ${err.message}`);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
logger.success('Connection verified. Stored CLI key is active.');
|
|
164
|
+
process.exit(0);
|
|
165
|
+
});
|
|
166
|
+
// ── logout command ─────────────────────────────────────────────────
|
|
167
|
+
program
|
|
168
|
+
.command('logout')
|
|
169
|
+
.description('Remove stored credentials')
|
|
170
|
+
.action(async () => {
|
|
171
|
+
await deleteConfig();
|
|
172
|
+
logger.success(`Logged out. Credentials removed from ${getConfigPath()}`);
|
|
173
|
+
process.exit(0);
|
|
174
|
+
});
|
|
175
|
+
// ── whoami command ─────────────────────────────────────────────────
|
|
176
|
+
program
|
|
177
|
+
.command('whoami')
|
|
178
|
+
.description('Show the account linked to the current CLI key')
|
|
179
|
+
.action(async () => {
|
|
180
|
+
const config = await requireConfig();
|
|
181
|
+
try {
|
|
182
|
+
const res = await fetch(`${config.apiBaseUrl}/api/cli/validate`, {
|
|
183
|
+
headers: { Authorization: `Bearer ${config.apiKey}` },
|
|
184
|
+
});
|
|
185
|
+
if (!res.ok) {
|
|
186
|
+
logger.error('CLI key is no longer valid. Run `npx autokap@latest init --cli-key <key>` to re-authenticate.');
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
const data = await res.json();
|
|
190
|
+
const display = data.name
|
|
191
|
+
? `${data.name} (${data.email})`
|
|
192
|
+
: data.email ?? 'Unknown';
|
|
193
|
+
logger.info(`Logged in as: ${display}`);
|
|
194
|
+
logger.info(`Server: ${config.apiBaseUrl}`);
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
logger.error(`Cannot reach API: ${err.message}`);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
process.exit(0);
|
|
201
|
+
});
|
|
202
|
+
// ── run command (deterministic capture engine) ───────────────────────
|
|
48
203
|
program
|
|
49
204
|
.command('run <preset-id>')
|
|
50
|
-
.description('Run a
|
|
205
|
+
.description('Run a capture using the deterministic opcode engine (local Playwright)')
|
|
51
206
|
.option('--headed', 'Show browser window for debugging', false)
|
|
207
|
+
.option('--local', `Use the local AutoKap dev server (${LOCAL_API_BASE_URL})`, false)
|
|
208
|
+
.option('--output <dir>', 'Optional output directory for local artifact copies')
|
|
209
|
+
.option('--program <file>', 'Path to a program JSON file')
|
|
210
|
+
.option('--debug', 'Verbose logging: per-substep timing, opcode dumps, recovery strategy traces', false)
|
|
52
211
|
.action(async (presetId, opts) => {
|
|
212
|
+
if (opts.debug) {
|
|
213
|
+
const { setDebugEnabled } = await import('./logger.js');
|
|
214
|
+
setDebugEnabled(true);
|
|
215
|
+
logger.info('[capture] Debug mode enabled — verbose logging on');
|
|
216
|
+
}
|
|
217
|
+
if (opts.local) {
|
|
218
|
+
process.env[API_BASE_URL_ENV_VAR] = LOCAL_API_BASE_URL;
|
|
219
|
+
process.env[WS_URL_ENV_VAR] = LOCAL_WS_URL;
|
|
220
|
+
logger.info(`Using local AutoKap dev server: ${LOCAL_API_BASE_URL}`);
|
|
221
|
+
}
|
|
222
|
+
const { runLocal } = await import('./cli-runner-local.js');
|
|
223
|
+
await runLocal(presetId, opts);
|
|
224
|
+
});
|
|
225
|
+
// ── project commands ───────────────────────────────────────────────
|
|
226
|
+
const projectCmd = program
|
|
227
|
+
.command('project')
|
|
228
|
+
.description('Manage projects');
|
|
229
|
+
projectCmd
|
|
230
|
+
.command('list')
|
|
231
|
+
.description('List accessible projects')
|
|
232
|
+
.action(async () => {
|
|
53
233
|
const config = await requireConfig();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
234
|
+
const data = await requestJson(config, '/api/v1/projects', { headers: authHeaders(config) }, 'Failed to list projects');
|
|
235
|
+
printJson(data.projects);
|
|
236
|
+
process.exit(0);
|
|
237
|
+
});
|
|
238
|
+
projectCmd
|
|
239
|
+
.command('get <project-id>')
|
|
240
|
+
.description('Get project details')
|
|
241
|
+
.action(async (projectId) => {
|
|
242
|
+
const config = await requireConfig();
|
|
243
|
+
const project = await loadProject(config, projectId);
|
|
244
|
+
printJson(project);
|
|
245
|
+
process.exit(0);
|
|
246
|
+
});
|
|
247
|
+
projectCmd
|
|
248
|
+
.command('create')
|
|
249
|
+
.description('Create a project')
|
|
250
|
+
.requiredOption('--name <name>', 'Project name')
|
|
251
|
+
.requiredOption('--url <url>', 'Project URL')
|
|
252
|
+
.option('--description <text>', 'Project description')
|
|
253
|
+
.action(async (opts) => {
|
|
254
|
+
const config = await requireConfig();
|
|
255
|
+
const data = await requestJson(config, '/api/v1/projects', {
|
|
256
|
+
method: 'POST',
|
|
257
|
+
headers: authHeaders(config, { 'Content-Type': 'application/json' }),
|
|
258
|
+
body: JSON.stringify({
|
|
259
|
+
name: opts.name,
|
|
260
|
+
url: opts.url,
|
|
261
|
+
description: opts.description,
|
|
262
|
+
}),
|
|
263
|
+
}, 'Failed to create project');
|
|
264
|
+
console.log(data.project.id);
|
|
265
|
+
process.exit(0);
|
|
266
|
+
});
|
|
267
|
+
// ── capture commands ───────────────────────────────────────────────
|
|
268
|
+
const captureCmd = program
|
|
269
|
+
.command('capture')
|
|
270
|
+
.description('Query captured outputs');
|
|
271
|
+
captureCmd
|
|
272
|
+
.command('list')
|
|
273
|
+
.description('List captures')
|
|
274
|
+
.option('--project <id>', 'Filter by project ID')
|
|
275
|
+
.option('--preset <id>', 'Filter by preset ID')
|
|
276
|
+
.option('--limit <n>', 'Limit results', '50')
|
|
277
|
+
.option('--offset <n>', 'Offset results', '0')
|
|
278
|
+
.action(async (opts) => {
|
|
279
|
+
const config = await requireConfig();
|
|
280
|
+
const searchParams = new URLSearchParams();
|
|
281
|
+
if (opts.project)
|
|
282
|
+
searchParams.set('project_id', opts.project);
|
|
283
|
+
if (opts.preset)
|
|
284
|
+
searchParams.set('preset_id', opts.preset);
|
|
285
|
+
searchParams.set('limit', String(Math.max(1, Number.parseInt(opts.limit, 10) || 50)));
|
|
286
|
+
searchParams.set('offset', String(Math.max(0, Number.parseInt(opts.offset, 10) || 0)));
|
|
287
|
+
const data = await requestJson(config, '/api/v1/captures', { headers: authHeaders(config) }, 'Failed to list captures', searchParams);
|
|
288
|
+
printJson(data);
|
|
289
|
+
process.exit(0);
|
|
290
|
+
});
|
|
291
|
+
program
|
|
292
|
+
.command('usage')
|
|
293
|
+
.description('Show usage for the current billing period')
|
|
294
|
+
.action(async () => {
|
|
295
|
+
const config = await requireConfig();
|
|
296
|
+
const data = await requestJson(config, '/api/v1/usage', { headers: authHeaders(config) }, 'Failed to get usage');
|
|
297
|
+
printJson(data);
|
|
298
|
+
process.exit(0);
|
|
299
|
+
});
|
|
300
|
+
// ── preset commands ────────────────────────────────────────────────
|
|
301
|
+
async function readJsonInput(filePath) {
|
|
302
|
+
if (filePath === '-') {
|
|
303
|
+
const chunks = [];
|
|
304
|
+
for await (const chunk of process.stdin)
|
|
305
|
+
chunks.push(chunk);
|
|
306
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
307
|
+
}
|
|
308
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
309
|
+
return JSON.parse(raw);
|
|
310
|
+
}
|
|
311
|
+
const presetCmd = program
|
|
312
|
+
.command('preset')
|
|
313
|
+
.description('Manage capture presets');
|
|
314
|
+
presetCmd
|
|
315
|
+
.command('create')
|
|
316
|
+
.description('Create a new preset from a JSON config file')
|
|
317
|
+
.requiredOption('--project <id>', 'Project ID')
|
|
318
|
+
.requiredOption('--name <name>', 'Preset name')
|
|
319
|
+
.option('--description <text>', 'Preset description', '')
|
|
320
|
+
.requiredOption('--config <file>', 'Path to config JSON file (use "-" for stdin)')
|
|
321
|
+
.action(async (opts) => {
|
|
322
|
+
const cfg = await requireConfig();
|
|
323
|
+
let configJson;
|
|
324
|
+
try {
|
|
325
|
+
configJson = await readJsonInput(opts.config);
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
logger.error(`Failed to read config: ${err.message}`);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
const res = await fetch(`${cfg.apiBaseUrl}/api/v1/presets`, {
|
|
332
|
+
method: 'POST',
|
|
333
|
+
headers: {
|
|
334
|
+
Authorization: `Bearer ${cfg.apiKey}`,
|
|
335
|
+
'Content-Type': 'application/json',
|
|
336
|
+
},
|
|
337
|
+
body: JSON.stringify({
|
|
338
|
+
project_id: opts.project,
|
|
339
|
+
name: opts.name,
|
|
340
|
+
description: opts.description,
|
|
341
|
+
config: configJson,
|
|
342
|
+
}),
|
|
58
343
|
});
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const ws = new WebSocket(wsUrl);
|
|
63
|
-
const cleanup = async () => {
|
|
64
|
-
try {
|
|
65
|
-
await browser.close();
|
|
66
|
-
}
|
|
67
|
-
catch { /* ignore */ }
|
|
68
|
-
try {
|
|
69
|
-
ws.close();
|
|
70
|
-
}
|
|
71
|
-
catch { /* ignore */ }
|
|
72
|
-
};
|
|
73
|
-
ws.on('error', async (err) => {
|
|
74
|
-
logger.error(`WebSocket error: ${err.message}`);
|
|
75
|
-
await cleanup();
|
|
344
|
+
if (!res.ok) {
|
|
345
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
346
|
+
logger.error(`Failed to create preset: ${body.error || res.statusText}`);
|
|
76
347
|
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
const data = await res.json();
|
|
350
|
+
// Output only the preset ID so callers can chain: autokap run $(autokap preset create ...)
|
|
351
|
+
console.log(data.preset.id);
|
|
352
|
+
process.exit(0);
|
|
353
|
+
});
|
|
354
|
+
presetCmd
|
|
355
|
+
.command('update <preset-id>')
|
|
356
|
+
.description('Update an existing preset config')
|
|
357
|
+
.option('--name <name>', 'New preset name')
|
|
358
|
+
.option('--description <text>', 'New description')
|
|
359
|
+
.requiredOption('--config <file>', 'Path to config JSON file (use "-" for stdin)')
|
|
360
|
+
.action(async (presetId, opts) => {
|
|
361
|
+
const cfg = await requireConfig();
|
|
362
|
+
let configJson;
|
|
363
|
+
try {
|
|
364
|
+
configJson = await readJsonInput(opts.config);
|
|
365
|
+
}
|
|
366
|
+
catch (err) {
|
|
367
|
+
logger.error(`Failed to read config: ${err.message}`);
|
|
368
|
+
process.exit(1);
|
|
369
|
+
}
|
|
370
|
+
const payload = { config: configJson };
|
|
371
|
+
if (opts.name !== undefined)
|
|
372
|
+
payload.name = opts.name;
|
|
373
|
+
if (opts.description !== undefined)
|
|
374
|
+
payload.description = opts.description;
|
|
375
|
+
const res = await fetch(`${cfg.apiBaseUrl}/api/v1/presets/${presetId}`, {
|
|
376
|
+
method: 'PATCH',
|
|
377
|
+
headers: {
|
|
378
|
+
Authorization: `Bearer ${cfg.apiKey}`,
|
|
379
|
+
'Content-Type': 'application/json',
|
|
380
|
+
},
|
|
381
|
+
body: JSON.stringify(payload),
|
|
77
382
|
});
|
|
78
|
-
|
|
79
|
-
await
|
|
383
|
+
if (!res.ok) {
|
|
384
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
385
|
+
logger.error(`Failed to update preset: ${body.error || res.statusText}`);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
const data = await res.json();
|
|
389
|
+
console.log(data.preset.id);
|
|
390
|
+
process.exit(0);
|
|
391
|
+
});
|
|
392
|
+
presetCmd
|
|
393
|
+
.command('delete <preset-id>')
|
|
394
|
+
.description('Delete a preset (soft-delete)')
|
|
395
|
+
.action(async (presetId) => {
|
|
396
|
+
const cfg = await requireConfig();
|
|
397
|
+
const res = await fetch(`${cfg.apiBaseUrl}/api/v1/presets/${presetId}`, {
|
|
398
|
+
method: 'DELETE',
|
|
399
|
+
headers: { Authorization: `Bearer ${cfg.apiKey}` },
|
|
80
400
|
});
|
|
81
|
-
|
|
82
|
-
|
|
401
|
+
if (!res.ok) {
|
|
402
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
403
|
+
logger.error(`Failed to delete preset: ${body.error || res.statusText}`);
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
406
|
+
logger.success(`Preset ${presetId} deleted`);
|
|
407
|
+
process.exit(0);
|
|
408
|
+
});
|
|
409
|
+
presetCmd
|
|
410
|
+
.command('get <preset-id>')
|
|
411
|
+
.description('Get preset details')
|
|
412
|
+
.action(async (presetId) => {
|
|
413
|
+
const cfg = await requireConfig();
|
|
414
|
+
const res = await fetch(`${cfg.apiBaseUrl}/api/v1/presets/${presetId}`, {
|
|
415
|
+
headers: { Authorization: `Bearer ${cfg.apiKey}` },
|
|
83
416
|
});
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
417
|
+
if (!res.ok) {
|
|
418
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
419
|
+
logger.error(`Failed to get preset: ${body.error || res.statusText}`);
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
const data = await res.json();
|
|
423
|
+
console.log(JSON.stringify(data.preset, null, 2));
|
|
424
|
+
process.exit(0);
|
|
425
|
+
});
|
|
426
|
+
presetCmd
|
|
427
|
+
.command('list')
|
|
428
|
+
.description('List presets for a project')
|
|
429
|
+
.requiredOption('--project <id>', 'Project ID')
|
|
430
|
+
.action(async (opts) => {
|
|
431
|
+
const cfg = await requireConfig();
|
|
432
|
+
const res = await fetch(`${cfg.apiBaseUrl}/api/v1/presets?project_id=${encodeURIComponent(opts.project)}`, { headers: { Authorization: `Bearer ${cfg.apiKey}` } });
|
|
433
|
+
if (!res.ok) {
|
|
434
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
435
|
+
logger.error(`Failed to list presets: ${body.error || res.statusText}`);
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
const data = await res.json();
|
|
439
|
+
const rows = data.presets.map((p) => ({
|
|
440
|
+
id: p.id,
|
|
441
|
+
name: p.name,
|
|
442
|
+
description: p.description ?? '',
|
|
443
|
+
}));
|
|
444
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
445
|
+
process.exit(0);
|
|
446
|
+
});
|
|
447
|
+
presetCmd
|
|
448
|
+
.command('info <preset-id>')
|
|
449
|
+
.description('Get structured integration info for a preset (endpoints, variants, URL params)')
|
|
450
|
+
.action(async (presetId) => {
|
|
451
|
+
const cfg = await requireConfig();
|
|
452
|
+
// Fetch preset + endpoints in parallel
|
|
453
|
+
const [presetRes, endpointsRes] = await Promise.all([
|
|
454
|
+
fetch(`${cfg.apiBaseUrl}/api/v1/presets/${presetId}`, {
|
|
455
|
+
headers: { Authorization: `Bearer ${cfg.apiKey}` },
|
|
456
|
+
}),
|
|
457
|
+
fetch(`${cfg.apiBaseUrl}/api/v1/endpoints?preset_id=${encodeURIComponent(presetId)}`, {
|
|
458
|
+
headers: { Authorization: `Bearer ${cfg.apiKey}` },
|
|
459
|
+
}),
|
|
460
|
+
]);
|
|
461
|
+
if (!presetRes.ok) {
|
|
462
|
+
const body = await presetRes.json().catch(() => ({ error: presetRes.statusText }));
|
|
463
|
+
logger.error(`Failed to get preset: ${body.error || presetRes.statusText}`);
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
if (!endpointsRes.ok) {
|
|
467
|
+
const body = await endpointsRes.json().catch(() => ({ error: endpointsRes.statusText }));
|
|
468
|
+
logger.error(`Failed to list endpoints: ${body.error || endpointsRes.statusText}`);
|
|
469
|
+
process.exit(1);
|
|
470
|
+
}
|
|
471
|
+
const { preset } = await presetRes.json();
|
|
472
|
+
const { endpoints } = await endpointsRes.json();
|
|
473
|
+
// Extract variants from config
|
|
474
|
+
const config = preset.config ?? {};
|
|
475
|
+
const langs = Array.isArray(config.langs) ? config.langs : [];
|
|
476
|
+
const themes = Array.isArray(config.themes) ? config.themes : [];
|
|
477
|
+
const targets = Array.isArray(config.targets)
|
|
478
|
+
? config.targets
|
|
479
|
+
.map((t) => ({ id: t.id, label: t.label, viewport: t.viewport, deviceFrame: t.deviceFrame ?? null }))
|
|
480
|
+
: [];
|
|
481
|
+
const captureMode = config.captureMode ?? 'screenshot';
|
|
482
|
+
// Build per-endpoint info with type-specific URL params
|
|
483
|
+
const endpointEntries = endpoints.map((ep) => {
|
|
484
|
+
const assetType = ep.asset_type ?? 'screenshot';
|
|
485
|
+
const url = assetType === 'interactive_demo'
|
|
486
|
+
? `${cfg.apiBaseUrl}/demo/${presetId}`
|
|
487
|
+
: `${cfg.apiBaseUrl}/api/v1/assets/${ep.id}`;
|
|
488
|
+
let urlParams;
|
|
489
|
+
if (assetType === 'interactive_demo') {
|
|
490
|
+
urlParams = {
|
|
491
|
+
embed: 'Set to "1" for iframe embedding',
|
|
492
|
+
lang: 'Language variant',
|
|
493
|
+
theme: 'Color theme ("light" or "dark")',
|
|
494
|
+
target: 'Device target ID',
|
|
495
|
+
};
|
|
89
496
|
}
|
|
90
|
-
|
|
91
|
-
|
|
497
|
+
else if (assetType === 'screenshot') {
|
|
498
|
+
urlParams = {
|
|
499
|
+
lang: 'Language variant',
|
|
500
|
+
theme: 'Color theme ("light" or "dark")',
|
|
501
|
+
target: 'Device target ID',
|
|
502
|
+
w: 'Max width in px (1-2560)',
|
|
503
|
+
quality: 'Image quality 1-100',
|
|
504
|
+
format: 'Output format: "webp", "png", "jpg"',
|
|
505
|
+
scale: 'Resolution multiplier (0.5-4)',
|
|
506
|
+
render: 'Set to "studio" for Studio render',
|
|
507
|
+
slot: 'Studio composition slot',
|
|
508
|
+
};
|
|
92
509
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
logger.success(`Done: ${event.summary.successes}/${event.summary.total} captures succeeded`);
|
|
103
|
-
}
|
|
104
|
-
await cleanup();
|
|
105
|
-
process.exit(0);
|
|
106
|
-
break;
|
|
107
|
-
case 'error':
|
|
108
|
-
logger.error(event.message ?? 'Unknown error');
|
|
109
|
-
await cleanup();
|
|
110
|
-
process.exit(1);
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
return;
|
|
510
|
+
else if (assetType === 'clip') {
|
|
511
|
+
urlParams = {
|
|
512
|
+
lang: 'Language variant',
|
|
513
|
+
theme: 'Color theme ("light" or "dark")',
|
|
514
|
+
target: 'Device target ID',
|
|
515
|
+
format: 'Output format: "gif" (default), "mp4"',
|
|
516
|
+
render: 'Set to "studio" for Studio render',
|
|
517
|
+
slot: 'Studio composition slot',
|
|
518
|
+
};
|
|
114
519
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
520
|
+
else if (assetType === 'composition') {
|
|
521
|
+
urlParams = {
|
|
522
|
+
scale: 'Resolution multiplier (0.5-4)',
|
|
523
|
+
format: 'Output format: "png", "webp", "jpg"',
|
|
524
|
+
};
|
|
120
525
|
}
|
|
121
|
-
|
|
122
|
-
|
|
526
|
+
else {
|
|
527
|
+
urlParams = {};
|
|
123
528
|
}
|
|
529
|
+
return {
|
|
530
|
+
id: ep.id,
|
|
531
|
+
label: ep.label ?? null,
|
|
532
|
+
capture_name: ep.capture_name ?? null,
|
|
533
|
+
asset_type: assetType,
|
|
534
|
+
url,
|
|
535
|
+
url_params: urlParams,
|
|
536
|
+
};
|
|
124
537
|
});
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
538
|
+
const info = {
|
|
539
|
+
preset: {
|
|
540
|
+
id: preset.id,
|
|
541
|
+
name: preset.name,
|
|
542
|
+
description: preset.description ?? null,
|
|
543
|
+
capture_mode: captureMode,
|
|
544
|
+
},
|
|
545
|
+
variants: { langs, themes, targets },
|
|
546
|
+
endpoints: endpointEntries,
|
|
547
|
+
};
|
|
548
|
+
if (captureMode === 'interactive_demo') {
|
|
549
|
+
info.interactive_demo = {
|
|
550
|
+
url: `${cfg.apiBaseUrl}/demo/${presetId}`,
|
|
551
|
+
embed_url: `${cfg.apiBaseUrl}/demo/${presetId}?embed=1`,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
console.log(JSON.stringify(info, null, 2));
|
|
555
|
+
process.exit(0);
|
|
556
|
+
});
|
|
557
|
+
// ── auth commands ──────────────────────────────────────────────────
|
|
558
|
+
const authCmd = program
|
|
559
|
+
.command('auth')
|
|
560
|
+
.description('Manage authentication sessions for project credentials accounts');
|
|
561
|
+
authCmd
|
|
562
|
+
.command('capture <project-id>')
|
|
563
|
+
.description('Open a browser, let you log in, and save the resulting session to a project credentials account')
|
|
564
|
+
.option('--account <name-or-id>', 'Account name or ID to attach the session to. If omitted, lists accounts to choose from.')
|
|
565
|
+
.option('--url <url>', 'URL to open in the browser. Defaults to the project URL.')
|
|
566
|
+
.action(async (projectId, opts) => {
|
|
567
|
+
const cfg = await requireConfig();
|
|
568
|
+
const allAccounts = await listProjectAccounts(cfg, projectId);
|
|
569
|
+
const accounts = allAccounts.filter((a) => a.type === 'session');
|
|
570
|
+
if (!accounts.length) {
|
|
571
|
+
if (allAccounts.length > 0) {
|
|
572
|
+
logger.error(`No session-type accounts in project ${projectId}. This project has ${allAccounts.length} credentials account(s), but none are session accounts. Create a new account with type "session" in Settings → Login accounts.`);
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
logger.error(`No accounts found for project ${projectId}. Create a session account in Settings → Login accounts first.`);
|
|
576
|
+
}
|
|
577
|
+
process.exit(1);
|
|
578
|
+
}
|
|
579
|
+
let account;
|
|
580
|
+
if (opts.account) {
|
|
581
|
+
const needle = opts.account.toLowerCase();
|
|
582
|
+
account = accounts.find((a) => a.id === opts.account || a.name.toLowerCase() === needle);
|
|
583
|
+
if (!account) {
|
|
584
|
+
// If the account exists but is a credentials account, give a precise error.
|
|
585
|
+
const otherTypeMatch = allAccounts.find((a) => a.id === opts.account || a.name.toLowerCase() === needle);
|
|
586
|
+
if (otherTypeMatch) {
|
|
587
|
+
logger.error(`Account "${opts.account}" exists but is a credentials account (email/password). auth capture only works with session accounts.`);
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
logger.error(`Account "${opts.account}" not found in project ${projectId}.`);
|
|
591
|
+
logger.info(`Available session accounts: ${accounts.map((a) => a.name).join(', ')}`);
|
|
592
|
+
}
|
|
593
|
+
process.exit(1);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
else if (accounts.length === 1) {
|
|
597
|
+
account = accounts[0];
|
|
598
|
+
logger.info(`Using the only session account in this project: "${account.name}"`);
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
logger.info('Multiple session accounts found. Re-run with --account <name>:');
|
|
602
|
+
for (const a of accounts) {
|
|
603
|
+
const status = a.storage_state_updated_at
|
|
604
|
+
? `session updated ${a.storage_state_updated_at}`
|
|
605
|
+
: 'no session yet';
|
|
606
|
+
logger.info(` • ${a.name} (${status})`);
|
|
607
|
+
}
|
|
608
|
+
process.exit(0);
|
|
609
|
+
}
|
|
610
|
+
const startUrl = opts.url || (await loadProject(cfg, projectId)).url;
|
|
611
|
+
logger.info(`Opening ${startUrl}`);
|
|
612
|
+
const { captureAuthSession } = await import('./auth-capture.js');
|
|
613
|
+
await captureAuthSession({
|
|
614
|
+
apiBaseUrl: cfg.apiBaseUrl,
|
|
615
|
+
apiKey: cfg.apiKey,
|
|
616
|
+
projectId,
|
|
617
|
+
accountId: account.id,
|
|
618
|
+
startUrl,
|
|
130
619
|
});
|
|
131
620
|
});
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
.
|
|
137
|
-
.
|
|
138
|
-
.
|
|
139
|
-
|
|
140
|
-
|
|
621
|
+
const authAccountCmd = authCmd
|
|
622
|
+
.command('account')
|
|
623
|
+
.description('Manage project credential accounts');
|
|
624
|
+
authAccountCmd
|
|
625
|
+
.command('list <project-id>')
|
|
626
|
+
.description('List accounts for a project')
|
|
627
|
+
.action(async (projectId) => {
|
|
628
|
+
const config = await requireConfig();
|
|
629
|
+
const accounts = await listProjectAccounts(config, projectId);
|
|
630
|
+
printJson(accounts);
|
|
631
|
+
process.exit(0);
|
|
632
|
+
});
|
|
633
|
+
authAccountCmd
|
|
634
|
+
.command('create <project-id>')
|
|
635
|
+
.description('Create a project credential account')
|
|
636
|
+
.requiredOption('--name <name>', 'Account name')
|
|
637
|
+
.option('--type <type>', 'Account type: credentials or session', 'credentials')
|
|
638
|
+
.option('--email <email>', 'Email for credentials accounts')
|
|
639
|
+
.option('--password <password>', 'Password for credentials accounts')
|
|
640
|
+
.option('--login-url <url>', 'Login URL for credentials accounts')
|
|
641
|
+
.action(async (projectId, opts) => {
|
|
642
|
+
const type = opts.type === 'session' ? 'session' : opts.type === 'credentials' ? 'credentials' : null;
|
|
643
|
+
if (!type) {
|
|
644
|
+
fatal('Invalid --type. Use "credentials" or "session".');
|
|
645
|
+
}
|
|
646
|
+
if (type === 'session' && (opts.email || opts.password || opts.loginUrl)) {
|
|
647
|
+
fatal('Session accounts do not accept --email, --password, or --login-url.');
|
|
648
|
+
}
|
|
649
|
+
const config = await requireConfig();
|
|
650
|
+
const data = await requestJson(config, `/api/cli/projects/${projectId}/credentials`, {
|
|
651
|
+
method: 'POST',
|
|
652
|
+
headers: authHeaders(config, { 'Content-Type': 'application/json' }),
|
|
653
|
+
body: JSON.stringify({
|
|
654
|
+
name: opts.name,
|
|
655
|
+
type,
|
|
656
|
+
email: opts.email,
|
|
657
|
+
password: opts.password,
|
|
658
|
+
loginUrl: opts.loginUrl,
|
|
659
|
+
}),
|
|
660
|
+
}, 'Failed to create account');
|
|
661
|
+
console.log(data.account.id);
|
|
662
|
+
process.exit(0);
|
|
663
|
+
});
|
|
664
|
+
authAccountCmd
|
|
665
|
+
.command('update <project-id>')
|
|
666
|
+
.description('Update a project credential account')
|
|
667
|
+
.requiredOption('--account <name-or-id>', 'Account name or ID')
|
|
668
|
+
.option('--name <name>', 'New account name')
|
|
669
|
+
.option('--email <email>', 'Replace the email on a credentials account')
|
|
670
|
+
.option('--password <password>', 'Replace the password on a credentials account')
|
|
671
|
+
.option('--login-url <url>', 'Replace the login URL on a credentials account')
|
|
672
|
+
.option('--clear-email', 'Remove the stored email from a credentials account', false)
|
|
673
|
+
.option('--clear-password', 'Remove the stored password from a credentials account', false)
|
|
674
|
+
.option('--clear-login-url', 'Remove the stored login URL from a credentials account', false)
|
|
675
|
+
.action(async (projectId, opts) => {
|
|
676
|
+
if (opts.email && opts.clearEmail)
|
|
677
|
+
fatal('Cannot use --email and --clear-email together.');
|
|
678
|
+
if (opts.password && opts.clearPassword)
|
|
679
|
+
fatal('Cannot use --password and --clear-password together.');
|
|
680
|
+
if (opts.loginUrl && opts.clearLoginUrl)
|
|
681
|
+
fatal('Cannot use --login-url and --clear-login-url together.');
|
|
682
|
+
const config = await requireConfig();
|
|
683
|
+
const account = await resolveProjectAccount(config, projectId, opts.account);
|
|
684
|
+
const payload = {};
|
|
685
|
+
if (opts.name !== undefined)
|
|
686
|
+
payload.name = opts.name;
|
|
687
|
+
if (opts.email !== undefined)
|
|
688
|
+
payload.email = opts.email;
|
|
689
|
+
if (opts.password !== undefined)
|
|
690
|
+
payload.password = opts.password;
|
|
691
|
+
if (opts.loginUrl !== undefined)
|
|
692
|
+
payload.loginUrl = opts.loginUrl;
|
|
693
|
+
if (opts.clearEmail)
|
|
694
|
+
payload.email = null;
|
|
695
|
+
if (opts.clearPassword)
|
|
696
|
+
payload.password = null;
|
|
697
|
+
if (opts.clearLoginUrl)
|
|
698
|
+
payload.loginUrl = null;
|
|
699
|
+
if (Object.keys(payload).length === 0) {
|
|
700
|
+
fatal('No changes provided. Pass at least one update option.');
|
|
701
|
+
}
|
|
702
|
+
const data = await requestJson(config, `/api/cli/projects/${projectId}/credentials/${account.id}`, {
|
|
703
|
+
method: 'PATCH',
|
|
704
|
+
headers: authHeaders(config, { 'Content-Type': 'application/json' }),
|
|
705
|
+
body: JSON.stringify(payload),
|
|
706
|
+
}, 'Failed to update account');
|
|
707
|
+
console.log(data.account.id);
|
|
708
|
+
process.exit(0);
|
|
709
|
+
});
|
|
710
|
+
authAccountCmd
|
|
711
|
+
.command('delete <project-id>')
|
|
712
|
+
.description('Delete a project credential account')
|
|
713
|
+
.requiredOption('--account <name-or-id>', 'Account name or ID')
|
|
714
|
+
.action(async (projectId, opts) => {
|
|
715
|
+
const config = await requireConfig();
|
|
716
|
+
const account = await resolveProjectAccount(config, projectId, opts.account);
|
|
717
|
+
await requestJson(config, `/api/cli/projects/${projectId}/credentials/${account.id}`, {
|
|
718
|
+
method: 'DELETE',
|
|
719
|
+
headers: authHeaders(config),
|
|
720
|
+
}, 'Failed to delete account');
|
|
721
|
+
logger.success(`Account ${account.name} deleted`);
|
|
722
|
+
process.exit(0);
|
|
723
|
+
});
|
|
724
|
+
const authSessionCmd = authCmd
|
|
725
|
+
.command('session')
|
|
726
|
+
.description('Manage captured browser sessions');
|
|
727
|
+
authSessionCmd
|
|
728
|
+
.command('clear <project-id>')
|
|
729
|
+
.description('Clear the stored browser session for a session account')
|
|
730
|
+
.requiredOption('--account <name-or-id>', 'Account name or ID')
|
|
731
|
+
.action(async (projectId, opts) => {
|
|
732
|
+
const config = await requireConfig();
|
|
733
|
+
const account = await resolveProjectAccount(config, projectId, opts.account);
|
|
734
|
+
if (account.type !== 'session') {
|
|
735
|
+
fatal(`Account "${account.name}" is a credentials account. Only session accounts can clear a stored browser session.`);
|
|
736
|
+
}
|
|
737
|
+
await requestJson(config, `/api/cli/projects/${projectId}/credentials/${account.id}/session`, {
|
|
738
|
+
method: 'DELETE',
|
|
739
|
+
headers: authHeaders(config),
|
|
740
|
+
}, 'Failed to clear session');
|
|
741
|
+
logger.success(`Cleared session for ${account.name}`);
|
|
742
|
+
process.exit(0);
|
|
743
|
+
});
|
|
744
|
+
// ── endpoints commands ─────────────────────────────────────────────
|
|
745
|
+
const endpointsCmd = program
|
|
746
|
+
.command('endpoints')
|
|
747
|
+
.description('Manage screenshot endpoints (dev links)');
|
|
748
|
+
endpointsCmd
|
|
749
|
+
.command('list')
|
|
750
|
+
.description('List endpoints for a preset or project')
|
|
751
|
+
.option('--preset <id>', 'Preset ID')
|
|
752
|
+
.option('--project <id>', 'Project ID')
|
|
753
|
+
.option('--format <fmt>', 'Output format: json or table', 'json')
|
|
141
754
|
.action(async (opts) => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
try {
|
|
145
|
-
rawContent = await fs.readFile(skillPath, 'utf-8');
|
|
755
|
+
if ((opts.preset ? 1 : 0) + (opts.project ? 1 : 0) !== 1) {
|
|
756
|
+
fatal('Pass exactly one of --preset or --project.');
|
|
146
757
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
758
|
+
const format = ensureListFormat(opts.format);
|
|
759
|
+
const cfg = await requireConfig();
|
|
760
|
+
const searchParams = new URLSearchParams();
|
|
761
|
+
if (opts.preset)
|
|
762
|
+
searchParams.set('preset_id', opts.preset);
|
|
763
|
+
if (opts.project)
|
|
764
|
+
searchParams.set('project_id', opts.project);
|
|
765
|
+
const data = await requestJson(cfg, '/api/v1/endpoints', { headers: authHeaders(cfg) }, 'Failed to list endpoints', searchParams);
|
|
766
|
+
const rows = data.endpoints.map((endpoint) => toEndpointOutputRow(cfg, endpoint));
|
|
767
|
+
if (format === 'table') {
|
|
768
|
+
console.table(rows.map((row) => ({
|
|
769
|
+
id: row.id,
|
|
770
|
+
capture_name: row.capture_name ?? '(default)',
|
|
771
|
+
label: row.label,
|
|
772
|
+
asset_type: row.asset_type ?? 'screenshot',
|
|
773
|
+
url: row.url,
|
|
774
|
+
})));
|
|
150
775
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
776
|
+
else {
|
|
777
|
+
printJson(rows);
|
|
778
|
+
}
|
|
779
|
+
process.exit(0);
|
|
780
|
+
});
|
|
781
|
+
endpointsCmd
|
|
782
|
+
.command('get <endpoint-id>')
|
|
783
|
+
.description('Get endpoint details')
|
|
784
|
+
.action(async (endpointId) => {
|
|
785
|
+
const cfg = await requireConfig();
|
|
786
|
+
const data = await requestJson(cfg, `/api/v1/endpoints/${endpointId}`, { headers: authHeaders(cfg) }, 'Failed to get endpoint');
|
|
787
|
+
printJson(toEndpointOutputRow(cfg, data.endpoint));
|
|
788
|
+
process.exit(0);
|
|
789
|
+
});
|
|
790
|
+
endpointsCmd
|
|
791
|
+
.command('export')
|
|
792
|
+
.description('Export endpoints as JSON or CSV')
|
|
793
|
+
.option('--preset <id>', 'Preset ID')
|
|
794
|
+
.option('--project <id>', 'Project ID')
|
|
795
|
+
.option('--format <fmt>', 'Output format: json or csv', 'json')
|
|
796
|
+
.action(async (opts) => {
|
|
797
|
+
if ((opts.preset ? 1 : 0) + (opts.project ? 1 : 0) !== 1) {
|
|
798
|
+
fatal('Pass exactly one of --preset or --project.');
|
|
799
|
+
}
|
|
800
|
+
const format = ensureExportFormat(opts.format);
|
|
801
|
+
const cfg = await requireConfig();
|
|
802
|
+
if (opts.preset) {
|
|
803
|
+
const searchParams = new URLSearchParams({
|
|
804
|
+
preset_id: opts.preset,
|
|
805
|
+
format,
|
|
806
|
+
});
|
|
807
|
+
if (format === 'csv') {
|
|
808
|
+
const csv = await requestText(cfg, '/api/v1/endpoints/export', { headers: authHeaders(cfg) }, 'Failed to export endpoints', searchParams);
|
|
809
|
+
process.stdout.write(csv);
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
const data = await requestJson(cfg, '/api/v1/endpoints/export', { headers: authHeaders(cfg) }, 'Failed to export endpoints', searchParams);
|
|
813
|
+
printJson(data);
|
|
814
|
+
}
|
|
815
|
+
process.exit(0);
|
|
816
|
+
}
|
|
817
|
+
const searchParams = new URLSearchParams({ project_id: opts.project });
|
|
818
|
+
const data = await requestJson(cfg, '/api/v1/endpoints', { headers: authHeaders(cfg) }, 'Failed to list endpoints', searchParams);
|
|
819
|
+
const rows = data.endpoints.map((endpoint) => toEndpointOutputRow(cfg, endpoint));
|
|
820
|
+
if (format === 'csv') {
|
|
821
|
+
process.stdout.write(`${toCsv(rows, ['id', 'label', 'url', 'capture_type', 'asset_type', 'capture_name'])}\n`);
|
|
162
822
|
}
|
|
163
823
|
else {
|
|
164
|
-
|
|
824
|
+
printJson({ endpoints: rows });
|
|
165
825
|
}
|
|
166
826
|
process.exit(0);
|
|
167
827
|
});
|
|
168
|
-
// ──
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
828
|
+
// ── skill command ───────────────────────────────────────────────────
|
|
829
|
+
// Agent output path mappings
|
|
830
|
+
const AGENT_PATHS = {
|
|
831
|
+
claude: '.claude/commands/autokap-preset.md',
|
|
832
|
+
codex: '.agents/skills/autokap-preset/SKILL.md',
|
|
833
|
+
cursor: '.cursor/rules/autokap-preset.md',
|
|
834
|
+
windsurf: '.windsurf/rules/autokap-preset.md',
|
|
835
|
+
copilot: '.github/instructions/autokap-preset.instructions.md',
|
|
836
|
+
};
|
|
837
|
+
const STUDIO_AGENT_PATHS = {
|
|
838
|
+
claude: '.claude/commands/autokap-studio.md',
|
|
839
|
+
codex: '.agents/skills/autokap-studio/SKILL.md',
|
|
840
|
+
cursor: '.cursor/rules/autokap-studio.md',
|
|
841
|
+
windsurf: '.windsurf/rules/autokap-studio.md',
|
|
842
|
+
copilot: '.github/instructions/autokap-studio.instructions.md',
|
|
843
|
+
};
|
|
844
|
+
program
|
|
845
|
+
.command('skill')
|
|
846
|
+
.description('Output or install an AutoKap skill for AI coding agents')
|
|
847
|
+
.option('--output <path>', 'Write the generated skill output to this path instead of stdout')
|
|
848
|
+
.option('--agent <name>', 'Target AI coding agent: claude, codex, cursor, windsurf, copilot (auto-resolves output path and packaging mode)')
|
|
849
|
+
.option('--type <type>', 'Skill type: preset (default) or studio (composition designer)')
|
|
850
|
+
.option('--project-url <url>', 'Replace the project URL placeholder in the skill')
|
|
851
|
+
.option('--project-id <id>', 'Replace the project ID placeholder in the skill')
|
|
852
|
+
.option('--api-base-url <url>', 'Replace the API base URL placeholder (default: https://autokap.app)')
|
|
853
|
+
.action(async (opts) => {
|
|
854
|
+
const skillType = opts.type === 'studio' ? 'studio' : undefined;
|
|
855
|
+
const pathMap = skillType === 'studio' ? STUDIO_AGENT_PATHS : AGENT_PATHS;
|
|
856
|
+
// Resolve --agent to an output path
|
|
857
|
+
if (opts.agent) {
|
|
858
|
+
const agentKey = opts.agent.toLowerCase();
|
|
859
|
+
if (!pathMap[agentKey]) {
|
|
860
|
+
logger.error(`Unknown agent "${opts.agent}". Supported: ${Object.keys(pathMap).join(', ')}`);
|
|
861
|
+
process.exit(1);
|
|
198
862
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
863
|
+
if (opts.output) {
|
|
864
|
+
logger.error('Cannot use both --agent and --output. Pick one.');
|
|
865
|
+
process.exit(1);
|
|
202
866
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
867
|
+
opts.output = pathMap[agentKey];
|
|
868
|
+
}
|
|
869
|
+
try {
|
|
870
|
+
if (opts.output) {
|
|
871
|
+
const result = await writeSkillExport({
|
|
872
|
+
type: skillType === 'studio' ? 'studio' : 'preset',
|
|
873
|
+
agent: opts.agent?.toLowerCase(),
|
|
874
|
+
outputPath: opts.output,
|
|
875
|
+
placeholders: opts,
|
|
876
|
+
});
|
|
877
|
+
if (result.mode === 'bundle') {
|
|
878
|
+
logger.success(`Skill bundle written to: ${path.dirname(opts.output)}`);
|
|
879
|
+
}
|
|
880
|
+
else {
|
|
881
|
+
logger.success(`Skill file written to: ${opts.output}`);
|
|
882
|
+
}
|
|
206
883
|
}
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
884
|
+
else {
|
|
885
|
+
const content = await renderSkillSingleFile({
|
|
886
|
+
type: skillType === 'studio' ? 'studio' : 'preset',
|
|
887
|
+
agent: opts.agent?.toLowerCase(),
|
|
888
|
+
placeholders: opts,
|
|
889
|
+
});
|
|
890
|
+
process.stdout.write(content);
|
|
210
891
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
892
|
+
}
|
|
893
|
+
catch (error) {
|
|
894
|
+
logger.error(error.message);
|
|
895
|
+
process.exit(1);
|
|
896
|
+
}
|
|
897
|
+
process.exit(0);
|
|
898
|
+
});
|
|
899
|
+
// ── init command ───────────────────────────────────────────────────
|
|
900
|
+
program
|
|
901
|
+
.command('init')
|
|
902
|
+
.description('Set up AutoKap: authenticate and install the AI skill in one step')
|
|
903
|
+
.option('--cli-key <key>', 'Your AutoKap CLI key')
|
|
904
|
+
.option('--project-id <id>', 'Your project ID (optional, can be set later)')
|
|
905
|
+
.option('--agent <name>', 'Target AI coding agent: claude, codex, cursor, windsurf, copilot (default: auto-detect)')
|
|
906
|
+
.option('--api-base-url <url>', `API base URL (env: ${API_BASE_URL_ENV_VAR})`, getDefaultApiBaseUrl())
|
|
907
|
+
.option('--ws-url <url>', `WebSocket server URL (env: ${WS_URL_ENV_VAR})`)
|
|
908
|
+
.action(async (opts) => {
|
|
909
|
+
const wsUrl = opts.wsUrl?.trim() || getDefaultWsUrl(opts.apiBaseUrl);
|
|
910
|
+
// If --cli-key not provided, prompt interactively
|
|
911
|
+
let cliKey = opts.cliKey;
|
|
912
|
+
if (!cliKey) {
|
|
913
|
+
const readline = await import('node:readline');
|
|
914
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
915
|
+
cliKey = await new Promise((resolve) => {
|
|
916
|
+
rl.question('Enter your AutoKap CLI key:', (answer) => {
|
|
917
|
+
rl.close();
|
|
918
|
+
resolve(answer.trim());
|
|
919
|
+
});
|
|
920
|
+
});
|
|
921
|
+
if (!cliKey) {
|
|
922
|
+
logger.error('CLI key is required. Get one at https://autokap.app/settings');
|
|
923
|
+
process.exit(1);
|
|
214
924
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
925
|
+
}
|
|
926
|
+
// Validate the key
|
|
927
|
+
logger.info('Validating CLI key...');
|
|
928
|
+
try {
|
|
929
|
+
const res = await fetch(`${opts.apiBaseUrl}/api/cli/validate`, {
|
|
930
|
+
headers: { Authorization: `Bearer ${cliKey}` },
|
|
931
|
+
});
|
|
932
|
+
if (!res.ok) {
|
|
933
|
+
logger.error('Invalid CLI key. Generate one at https://autokap.app/settings');
|
|
934
|
+
process.exit(1);
|
|
218
935
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
936
|
+
}
|
|
937
|
+
catch (err) {
|
|
938
|
+
logger.error(`Cannot reach API: ${err.message}`);
|
|
939
|
+
process.exit(1);
|
|
940
|
+
}
|
|
941
|
+
// Store credentials
|
|
942
|
+
await writeConfig({
|
|
943
|
+
apiKey: cliKey,
|
|
944
|
+
apiBaseUrl: opts.apiBaseUrl,
|
|
945
|
+
wsUrl,
|
|
946
|
+
});
|
|
947
|
+
logger.success(`Authenticated. Key stored in ${getConfigPath()}`);
|
|
948
|
+
// Determine agent target
|
|
949
|
+
let agentKey = opts.agent?.toLowerCase();
|
|
950
|
+
if (!agentKey) {
|
|
951
|
+
// Auto-detect from directory structure
|
|
952
|
+
const detectors = [
|
|
953
|
+
['.claude', 'claude'],
|
|
954
|
+
['.agents', 'codex'],
|
|
955
|
+
['.cursor', 'cursor'],
|
|
956
|
+
['.windsurf', 'windsurf'],
|
|
957
|
+
['.github/instructions', 'copilot'],
|
|
958
|
+
];
|
|
959
|
+
for (const [dir, key] of detectors) {
|
|
960
|
+
try {
|
|
961
|
+
await fs.access(dir);
|
|
962
|
+
agentKey = key;
|
|
963
|
+
break;
|
|
964
|
+
}
|
|
965
|
+
catch { /* try next */ }
|
|
227
966
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
967
|
+
if (!agentKey)
|
|
968
|
+
agentKey = 'claude'; // Default
|
|
969
|
+
logger.info(`Detected agent: ${agentKey}`);
|
|
970
|
+
}
|
|
971
|
+
if (!AGENT_PATHS[agentKey]) {
|
|
972
|
+
logger.error(`Unknown agent "${agentKey}". Supported: ${Object.keys(AGENT_PATHS).join(', ')}`);
|
|
973
|
+
process.exit(1);
|
|
974
|
+
}
|
|
975
|
+
// Install skill file
|
|
976
|
+
const skillOutput = AGENT_PATHS[agentKey];
|
|
977
|
+
try {
|
|
978
|
+
const result = await writeSkillExport({
|
|
979
|
+
type: 'preset',
|
|
980
|
+
agent: agentKey,
|
|
981
|
+
outputPath: skillOutput,
|
|
982
|
+
placeholders: {
|
|
983
|
+
projectId: opts.projectId,
|
|
984
|
+
apiBaseUrl: opts.apiBaseUrl !== DEFAULT_API_BASE_URL ? opts.apiBaseUrl : undefined,
|
|
985
|
+
},
|
|
986
|
+
});
|
|
987
|
+
if (result.mode === 'bundle') {
|
|
988
|
+
logger.success(`Skill bundle installed to: ${path.dirname(skillOutput)}`);
|
|
243
989
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
return { ...bundle, screenshot: bundle.screenshot.toString('base64') };
|
|
990
|
+
else {
|
|
991
|
+
logger.success(`Skill installed to: ${skillOutput}`);
|
|
247
992
|
}
|
|
248
|
-
// Interactions
|
|
249
|
-
case 'clickByIndex':
|
|
250
|
-
await browser.clickByIndex(params.index);
|
|
251
|
-
return null;
|
|
252
|
-
case 'clickBySelector':
|
|
253
|
-
await browser.clickBySelector(params.selector, params);
|
|
254
|
-
return null;
|
|
255
|
-
case 'clickByCoordinates':
|
|
256
|
-
await browser.clickByCoordinates(params.x, params.y);
|
|
257
|
-
return null;
|
|
258
|
-
case 'hoverByIndex':
|
|
259
|
-
await browser.hoverByIndex(params.index);
|
|
260
|
-
return null;
|
|
261
|
-
case 'hoverBySelector':
|
|
262
|
-
await browser.hoverBySelector(params.selector);
|
|
263
|
-
return null;
|
|
264
|
-
case 'hoverByCoordinates':
|
|
265
|
-
await browser.hoverByCoordinates(params.x, params.y);
|
|
266
|
-
return null;
|
|
267
|
-
case 'typeText':
|
|
268
|
-
await browser.typeText(params.text, params);
|
|
269
|
-
return null;
|
|
270
|
-
case 'selectOption':
|
|
271
|
-
await browser.selectOption(params);
|
|
272
|
-
return null;
|
|
273
|
-
case 'scroll':
|
|
274
|
-
await browser.scroll(params.direction, params.amount, params.selector);
|
|
275
|
-
return null;
|
|
276
|
-
case 'scrollElementIntoView':
|
|
277
|
-
await browser.scrollElementIntoView(params.index, params);
|
|
278
|
-
return null;
|
|
279
|
-
case 'safeExpand':
|
|
280
|
-
await browser.safeExpand(params);
|
|
281
|
-
return null;
|
|
282
|
-
case 'pressKey':
|
|
283
|
-
await browser.pressKey(params.key);
|
|
284
|
-
return null;
|
|
285
|
-
case 'searchText': return await browser.searchText(params.query);
|
|
286
|
-
// Reactions
|
|
287
|
-
case 'waitForPageReaction':
|
|
288
|
-
return await browser.waitForPageReaction(params.before, params);
|
|
289
|
-
// UI manipulation
|
|
290
|
-
case 'wait':
|
|
291
|
-
await browser.wait(params.ms);
|
|
292
|
-
return null;
|
|
293
|
-
case 'dismissOverlays': return await browser.dismissOverlays();
|
|
294
|
-
case 'setColorScheme':
|
|
295
|
-
await browser.setColorScheme(params.scheme);
|
|
296
|
-
return null;
|
|
297
|
-
case 'setLanguage':
|
|
298
|
-
await browser.setLanguage(params.lang);
|
|
299
|
-
return null;
|
|
300
|
-
case 'resizeViewport':
|
|
301
|
-
await browser.resizeViewport(params.width, params.height);
|
|
302
|
-
return null;
|
|
303
|
-
case 'forceLoadLazyImages':
|
|
304
|
-
await browser.forceLoadLazyImages(params);
|
|
305
|
-
return null;
|
|
306
|
-
// Storage
|
|
307
|
-
case 'exportStorageState': return await browser.exportStorageState();
|
|
308
|
-
case 'exportSessionStorage': return await browser.exportSessionStorage();
|
|
309
|
-
case 'prepareSessionStorage':
|
|
310
|
-
await browser.prepareSessionStorage(params.bundle, params);
|
|
311
|
-
return null;
|
|
312
|
-
// Network
|
|
313
|
-
case 'observeNetworkRequests': return await browser.observeNetworkRequests(params.url, params.waitMs);
|
|
314
|
-
case 'setupRouteInterception':
|
|
315
|
-
await browser.setupRouteInterception(params.mocks);
|
|
316
|
-
return null;
|
|
317
|
-
case 'clearRouteInterception':
|
|
318
|
-
await browser.clearRouteInterception();
|
|
319
|
-
return null;
|
|
320
|
-
default:
|
|
321
|
-
throw new Error(`Unknown browser command: ${method}`);
|
|
322
993
|
}
|
|
323
|
-
|
|
994
|
+
catch (error) {
|
|
995
|
+
logger.error(error.message);
|
|
996
|
+
process.exit(1);
|
|
997
|
+
}
|
|
998
|
+
console.log('');
|
|
999
|
+
console.log('Setup complete! You can now:');
|
|
1000
|
+
console.log(` - Ask your AI agent to create presets using the autokap-preset skill`);
|
|
1001
|
+
console.log(` - Run captures locally: autokap run <preset-id>`);
|
|
1002
|
+
process.exit(0);
|
|
1003
|
+
});
|
|
1004
|
+
// ── proxy command ──────────────────────────────────────────────────
|
|
1005
|
+
const proxyCmd = program
|
|
1006
|
+
.command('proxy')
|
|
1007
|
+
.description('Manage capture proxy for edge caching');
|
|
1008
|
+
proxyCmd
|
|
1009
|
+
.command('register')
|
|
1010
|
+
.description('Register a user-side proxy for faster capture loading')
|
|
1011
|
+
.requiredOption('--project <id>', 'Project ID')
|
|
1012
|
+
.requiredOption('--proxy-url <url>', 'Proxy base URL (e.g. https://myapp.com/api/autokap/assets)')
|
|
1013
|
+
.option('--webhook-url <url>', 'Webhook URL for cache invalidation (e.g. https://myapp.com/api/autokap/webhook)')
|
|
1014
|
+
.option('--signing-secret <secret>', 'Webhook signing secret (auto-generated if omitted)')
|
|
1015
|
+
.action(async (opts) => {
|
|
1016
|
+
const cfg = await requireConfig();
|
|
1017
|
+
const body = {
|
|
1018
|
+
proxy_base_url: opts.proxyUrl,
|
|
1019
|
+
};
|
|
1020
|
+
if (opts.webhookUrl)
|
|
1021
|
+
body.webhook_url = opts.webhookUrl;
|
|
1022
|
+
if (opts.signingSecret)
|
|
1023
|
+
body.signing_secret = opts.signingSecret;
|
|
1024
|
+
const res = await fetch(`${cfg.apiBaseUrl}/api/v1/projects/${opts.project}/proxy`, {
|
|
1025
|
+
method: 'PATCH',
|
|
1026
|
+
headers: {
|
|
1027
|
+
Authorization: `Bearer ${cfg.apiKey}`,
|
|
1028
|
+
'Content-Type': 'application/json',
|
|
1029
|
+
},
|
|
1030
|
+
body: JSON.stringify(body),
|
|
1031
|
+
});
|
|
1032
|
+
if (!res.ok) {
|
|
1033
|
+
const data = await res.json().catch(() => ({ error: res.statusText }));
|
|
1034
|
+
logger.error(`Failed to register proxy: ${data.error || res.statusText}`);
|
|
1035
|
+
process.exit(1);
|
|
1036
|
+
}
|
|
1037
|
+
const result = await res.json();
|
|
1038
|
+
logger.info(`Proxy registered: ${result.proxy_base_url}`);
|
|
1039
|
+
if (result.webhook_url) {
|
|
1040
|
+
logger.info(`Webhook configured: ${result.webhook_url}`);
|
|
1041
|
+
}
|
|
1042
|
+
if (result.signing_secret) {
|
|
1043
|
+
console.log('');
|
|
1044
|
+
console.log('Add this to your environment variables:');
|
|
1045
|
+
console.log(` AUTOKAP_WEBHOOK_SECRET=${result.signing_secret}`);
|
|
1046
|
+
console.log('');
|
|
1047
|
+
}
|
|
1048
|
+
process.exit(0);
|
|
1049
|
+
});
|
|
1050
|
+
proxyCmd
|
|
1051
|
+
.command('verify')
|
|
1052
|
+
.description('Verify the configured proxy for a project')
|
|
1053
|
+
.requiredOption('--project <id>', 'Project ID')
|
|
1054
|
+
.action(async (opts) => {
|
|
1055
|
+
const config = await requireConfig();
|
|
1056
|
+
const project = await loadProject(config, opts.project);
|
|
1057
|
+
if (!project.proxy_base_url) {
|
|
1058
|
+
fatal(`Project ${opts.project} has no proxy configured. Run "autokap proxy register" first.`);
|
|
1059
|
+
}
|
|
1060
|
+
const result = await requestJson(config, '/api/v1/proxy/verify', {
|
|
1061
|
+
method: 'POST',
|
|
1062
|
+
headers: authHeaders(config, { 'Content-Type': 'application/json' }),
|
|
1063
|
+
body: JSON.stringify({
|
|
1064
|
+
project_id: opts.project,
|
|
1065
|
+
proxy_base_url: project.proxy_base_url,
|
|
1066
|
+
}),
|
|
1067
|
+
}, 'Failed to verify proxy');
|
|
1068
|
+
printJson(result);
|
|
1069
|
+
if (!result.success) {
|
|
1070
|
+
process.exit(1);
|
|
1071
|
+
}
|
|
1072
|
+
process.exit(0);
|
|
1073
|
+
});
|
|
324
1074
|
// ── Entry point ─────────────────────────────────────────────────────
|
|
325
1075
|
const resolvedArgv = process.argv[1] && fs.realpath(process.argv[1]).catch(() => process.argv[1]);
|
|
326
|
-
const isDirectExecution = resolvedArgv && await resolvedArgv.then(p =>
|
|
1076
|
+
const isDirectExecution = resolvedArgv && await resolvedArgv.then(p => {
|
|
1077
|
+
const base = path.basename(p);
|
|
1078
|
+
return base === 'cli.js' || base === 'cli.ts';
|
|
1079
|
+
});
|
|
327
1080
|
if (isDirectExecution) {
|
|
328
1081
|
program.parseAsync().catch(async (err) => {
|
|
329
1082
|
logger.error(err.message);
|