autokap 1.0.0
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/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/SKILL.md +575 -0
- package/dist/abort.d.ts +5 -0
- package/dist/abort.js +44 -0
- package/dist/agent.d.ts +142 -0
- package/dist/agent.js +4504 -0
- package/dist/browser-bar.d.ts +40 -0
- package/dist/browser-bar.js +147 -0
- package/dist/browser-pool.d.ts +34 -0
- package/dist/browser-pool.js +122 -0
- package/dist/browser.d.ts +279 -0
- package/dist/browser.js +2902 -0
- package/dist/cli-utils.d.ts +25 -0
- package/dist/cli-utils.js +80 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +365 -0
- package/dist/clip-orchestrator.d.ts +148 -0
- package/dist/clip-orchestrator.js +950 -0
- package/dist/clip-postprocess.d.ts +42 -0
- package/dist/clip-postprocess.js +192 -0
- package/dist/cookie-dismiss.d.ts +5 -0
- package/dist/cookie-dismiss.js +172 -0
- package/dist/credential-templates.d.ts +5 -0
- package/dist/credential-templates.js +60 -0
- package/dist/element-capture.d.ts +53 -0
- package/dist/element-capture.js +766 -0
- package/dist/hybrid-navigator.d.ts +138 -0
- package/dist/hybrid-navigator.js +468 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +11 -0
- package/dist/llm-usage.d.ts +17 -0
- package/dist/llm-usage.js +45 -0
- package/dist/logger.d.ts +46 -0
- package/dist/logger.js +79 -0
- package/dist/mockup-html.d.ts +119 -0
- package/dist/mockup-html.js +253 -0
- package/dist/mockup.d.ts +94 -0
- package/dist/mockup.js +604 -0
- package/dist/mouse-animation.d.ts +46 -0
- package/dist/mouse-animation.js +100 -0
- package/dist/overlay-utils.d.ts +14 -0
- package/dist/overlay-utils.js +13 -0
- package/dist/posthog.d.ts +4 -0
- package/dist/posthog.js +26 -0
- package/dist/prompt-cache.d.ts +10 -0
- package/dist/prompt-cache.js +24 -0
- package/dist/prompts.d.ts +167 -0
- package/dist/prompts.js +1165 -0
- package/dist/security.d.ts +20 -0
- package/dist/security.js +569 -0
- package/dist/session-profile.d.ts +86 -0
- package/dist/session-profile.js +1471 -0
- package/dist/sf-pro-fonts.d.ts +4 -0
- package/dist/sf-pro-fonts.js +7 -0
- package/dist/status-bar-l10n.d.ts +14 -0
- package/dist/status-bar-l10n.js +177 -0
- package/dist/status-bar.d.ts +44 -0
- package/dist/status-bar.js +336 -0
- package/dist/tools.d.ts +4 -0
- package/dist/tools.js +578 -0
- package/dist/types.d.ts +796 -0
- package/dist/types.js +2 -0
- package/dist/video-agent.d.ts +143 -0
- package/dist/video-agent.js +4783 -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 +500 -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/package.json +165 -0
- package/readme.md +61 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { evaluateRequestedLanguageState, evaluateRequestedThemeState as evaluateRequestedThemeStateRich, } from './session-profile.js';
|
|
2
|
+
function normalizeConfidenceScore(confidence) {
|
|
3
|
+
switch (confidence) {
|
|
4
|
+
case 'high':
|
|
5
|
+
return 1;
|
|
6
|
+
case 'medium':
|
|
7
|
+
return 0.78;
|
|
8
|
+
case 'low':
|
|
9
|
+
default:
|
|
10
|
+
return 0.58;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function scoreLocaleSignals(signals, requestedLang) {
|
|
14
|
+
if (!requestedLang)
|
|
15
|
+
return { score: 1, reasons: [], ambiguous: false };
|
|
16
|
+
const languageState = evaluateRequestedLanguageState({
|
|
17
|
+
currentUrl: signals.url,
|
|
18
|
+
requestedLang,
|
|
19
|
+
signals,
|
|
20
|
+
});
|
|
21
|
+
if (languageState.active) {
|
|
22
|
+
return {
|
|
23
|
+
score: normalizeConfidenceScore(languageState.confidence),
|
|
24
|
+
reasons: languageState.reasons,
|
|
25
|
+
ambiguous: false,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (languageState.ambiguous) {
|
|
29
|
+
return {
|
|
30
|
+
score: languageState.confidence === 'high' ? 0.45 : languageState.confidence === 'medium' ? 0.32 : 0.18,
|
|
31
|
+
reasons: languageState.reasons,
|
|
32
|
+
ambiguous: true,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
score: 0,
|
|
37
|
+
reasons: languageState.reasons,
|
|
38
|
+
ambiguous: false,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function evaluateRequestedThemeState(signals, requestedTheme) {
|
|
42
|
+
const themeState = evaluateRequestedThemeStateRich({
|
|
43
|
+
requestedTheme,
|
|
44
|
+
signals,
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
detected: themeState.detected,
|
|
48
|
+
active: themeState.active,
|
|
49
|
+
ambiguous: themeState.ambiguous,
|
|
50
|
+
reason: themeState.reasons.join('; ') || 'no_theme_signal',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export async function detectVariantStateDeterministic(browser, requestedLang, requestedTheme) {
|
|
54
|
+
const signals = await browser.capturePageSignals();
|
|
55
|
+
const languageState = evaluateRequestedLanguageState({
|
|
56
|
+
currentUrl: signals.url,
|
|
57
|
+
requestedLang,
|
|
58
|
+
signals,
|
|
59
|
+
});
|
|
60
|
+
const themeState = evaluateRequestedThemeStateRich({
|
|
61
|
+
requestedTheme,
|
|
62
|
+
signals,
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
lang: {
|
|
66
|
+
requested: requestedLang,
|
|
67
|
+
detected: languageState.detected,
|
|
68
|
+
active: requestedLang ? languageState.active : true,
|
|
69
|
+
ambiguous: requestedLang ? languageState.ambiguous : false,
|
|
70
|
+
},
|
|
71
|
+
theme: {
|
|
72
|
+
requested: requestedTheme,
|
|
73
|
+
detected: themeState.detected,
|
|
74
|
+
active: requestedTheme ? themeState.active : true,
|
|
75
|
+
ambiguous: requestedTheme ? themeState.ambiguous : false,
|
|
76
|
+
},
|
|
77
|
+
pageSignals: signals,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=video-variant-state.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare class VisionModelUnsupportedError extends Error {
|
|
2
|
+
requestedModel: string;
|
|
3
|
+
fallbackModel?: string;
|
|
4
|
+
constructor(requestedModel: string, fallbackModel?: string);
|
|
5
|
+
}
|
|
6
|
+
export declare function isImageInputUnsupportedError(err: unknown): boolean;
|
|
7
|
+
export declare function buildVisionModelUnavailableMessage(requestedModel: string, fallbackModel?: string): string;
|
|
8
|
+
export declare function callVisionCapableModel<T>(params: {
|
|
9
|
+
primaryModel: string;
|
|
10
|
+
fallbackModel?: string;
|
|
11
|
+
callModel: (model: string) => Promise<T>;
|
|
12
|
+
onFallbackActivated?: (model: string, reason: string) => void;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
result: T;
|
|
15
|
+
model: string;
|
|
16
|
+
fellBack: boolean;
|
|
17
|
+
}>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export class VisionModelUnsupportedError extends Error {
|
|
2
|
+
requestedModel;
|
|
3
|
+
fallbackModel;
|
|
4
|
+
constructor(requestedModel, fallbackModel) {
|
|
5
|
+
super(buildVisionModelUnavailableMessage(requestedModel, fallbackModel));
|
|
6
|
+
this.name = 'VisionModelUnsupportedError';
|
|
7
|
+
this.requestedModel = requestedModel;
|
|
8
|
+
this.fallbackModel = fallbackModel;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function extractNestedErrorMessage(err) {
|
|
12
|
+
if (!err || typeof err !== 'object')
|
|
13
|
+
return '';
|
|
14
|
+
const errorRecord = err;
|
|
15
|
+
const directMessage = typeof errorRecord.message === 'string' ? errorRecord.message : '';
|
|
16
|
+
const nestedError = errorRecord.error;
|
|
17
|
+
const nestedMessage = nestedError && typeof nestedError === 'object' && typeof nestedError.message === 'string'
|
|
18
|
+
? nestedError.message
|
|
19
|
+
: '';
|
|
20
|
+
if (nestedMessage && nestedMessage === directMessage)
|
|
21
|
+
return directMessage;
|
|
22
|
+
return [directMessage, nestedMessage].filter(Boolean).join(' ');
|
|
23
|
+
}
|
|
24
|
+
export function isImageInputUnsupportedError(err) {
|
|
25
|
+
if (!err || typeof err !== 'object')
|
|
26
|
+
return false;
|
|
27
|
+
const errorRecord = err;
|
|
28
|
+
const status = typeof errorRecord.status === 'number' ? errorRecord.status : undefined;
|
|
29
|
+
const message = extractNestedErrorMessage(err).toLowerCase();
|
|
30
|
+
if (!message)
|
|
31
|
+
return false;
|
|
32
|
+
const mentionsImageEndpoint = message.includes('no endpoints found that support image input')
|
|
33
|
+
|| message.includes('no providers that support image input')
|
|
34
|
+
|| (message.includes('image input') && message.includes('no') && message.includes('support'))
|
|
35
|
+
|| (message.includes('image input') && message.includes('endpoint') && message.includes('no'));
|
|
36
|
+
if (!mentionsImageEndpoint)
|
|
37
|
+
return false;
|
|
38
|
+
return status === undefined || status === 400 || status === 404 || status === 422;
|
|
39
|
+
}
|
|
40
|
+
export function buildVisionModelUnavailableMessage(requestedModel, fallbackModel) {
|
|
41
|
+
const fallbackHint = fallbackModel && fallbackModel !== requestedModel
|
|
42
|
+
? ` Automatic fallback to "${fallbackModel}" also failed or was unavailable.`
|
|
43
|
+
: '';
|
|
44
|
+
return `The model "${requestedModel}" has no available endpoint that supports image input.${fallbackHint} Configure a vision-capable default/fallback model or retry later if the provider is degraded.`;
|
|
45
|
+
}
|
|
46
|
+
export async function callVisionCapableModel(params) {
|
|
47
|
+
const { primaryModel, fallbackModel, callModel, onFallbackActivated } = params;
|
|
48
|
+
let fallbackReason = '';
|
|
49
|
+
try {
|
|
50
|
+
const result = await callModel(primaryModel);
|
|
51
|
+
return { result, model: primaryModel, fellBack: false };
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (!isImageInputUnsupportedError(err)) {
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
fallbackReason = extractNestedErrorMessage(err) || 'image input unsupported';
|
|
58
|
+
if (!fallbackModel || fallbackModel === primaryModel) {
|
|
59
|
+
throw new VisionModelUnsupportedError(primaryModel, fallbackModel);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const result = await callModel(fallbackModel);
|
|
64
|
+
onFallbackActivated?.(fallbackModel, fallbackReason);
|
|
65
|
+
return { result, model: fallbackModel, fellBack: true };
|
|
66
|
+
}
|
|
67
|
+
catch (fallbackErr) {
|
|
68
|
+
if (isImageInputUnsupportedError(fallbackErr)) {
|
|
69
|
+
throw new VisionModelUnsupportedError(primaryModel, fallbackModel);
|
|
70
|
+
}
|
|
71
|
+
throw fallbackErr;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=vision-model.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "autokap",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-powered CLI tool for capturing clean screenshots of websites",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./browser": {
|
|
14
|
+
"types": "./dist/browser.d.ts",
|
|
15
|
+
"default": "./dist/browser.js"
|
|
16
|
+
},
|
|
17
|
+
"./agent": {
|
|
18
|
+
"types": "./dist/agent.d.ts",
|
|
19
|
+
"default": "./dist/agent.js"
|
|
20
|
+
},
|
|
21
|
+
"./cookie-dismiss": {
|
|
22
|
+
"types": "./dist/cookie-dismiss.d.ts",
|
|
23
|
+
"default": "./dist/cookie-dismiss.js"
|
|
24
|
+
},
|
|
25
|
+
"./logger": {
|
|
26
|
+
"types": "./dist/logger.d.ts",
|
|
27
|
+
"default": "./dist/logger.js"
|
|
28
|
+
},
|
|
29
|
+
"./types": {
|
|
30
|
+
"types": "./dist/types.d.ts",
|
|
31
|
+
"default": "./dist/types.js"
|
|
32
|
+
},
|
|
33
|
+
"./mockup": {
|
|
34
|
+
"types": "./dist/mockup.d.ts",
|
|
35
|
+
"default": "./dist/mockup.js"
|
|
36
|
+
},
|
|
37
|
+
"./mockup-html": {
|
|
38
|
+
"types": "./dist/mockup-html.d.ts",
|
|
39
|
+
"default": "./dist/mockup-html.js"
|
|
40
|
+
},
|
|
41
|
+
"./status-bar": {
|
|
42
|
+
"types": "./dist/status-bar.d.ts",
|
|
43
|
+
"default": "./dist/status-bar.js"
|
|
44
|
+
},
|
|
45
|
+
"./browser-bar": {
|
|
46
|
+
"types": "./dist/browser-bar.d.ts",
|
|
47
|
+
"default": "./dist/browser-bar.js"
|
|
48
|
+
},
|
|
49
|
+
"./status-bar-l10n": {
|
|
50
|
+
"types": "./dist/status-bar-l10n.d.ts",
|
|
51
|
+
"default": "./dist/status-bar-l10n.js"
|
|
52
|
+
},
|
|
53
|
+
"./element-capture": {
|
|
54
|
+
"types": "./dist/element-capture.d.ts",
|
|
55
|
+
"default": "./dist/element-capture.js"
|
|
56
|
+
},
|
|
57
|
+
"./prompts": {
|
|
58
|
+
"types": "./dist/prompts.d.ts",
|
|
59
|
+
"default": "./dist/prompts.js"
|
|
60
|
+
},
|
|
61
|
+
"./session-profile": {
|
|
62
|
+
"types": "./dist/session-profile.d.ts",
|
|
63
|
+
"default": "./dist/session-profile.js"
|
|
64
|
+
},
|
|
65
|
+
"./tools": {
|
|
66
|
+
"types": "./dist/tools.d.ts",
|
|
67
|
+
"default": "./dist/tools.js"
|
|
68
|
+
},
|
|
69
|
+
"./security": {
|
|
70
|
+
"types": "./dist/security.d.ts",
|
|
71
|
+
"default": "./dist/security.js"
|
|
72
|
+
},
|
|
73
|
+
"./video-agent": {
|
|
74
|
+
"types": "./dist/video-agent.d.ts",
|
|
75
|
+
"default": "./dist/video-agent.js"
|
|
76
|
+
},
|
|
77
|
+
"./video-planner": {
|
|
78
|
+
"types": "./dist/video-planner.d.ts",
|
|
79
|
+
"default": "./dist/video-planner.js"
|
|
80
|
+
},
|
|
81
|
+
"./mouse-animation": {
|
|
82
|
+
"types": "./dist/mouse-animation.d.ts",
|
|
83
|
+
"default": "./dist/mouse-animation.js"
|
|
84
|
+
},
|
|
85
|
+
"./clip-orchestrator": {
|
|
86
|
+
"types": "./dist/clip-orchestrator.d.ts",
|
|
87
|
+
"default": "./dist/clip-orchestrator.js"
|
|
88
|
+
},
|
|
89
|
+
"./clip-postprocess": {
|
|
90
|
+
"types": "./dist/clip-postprocess.d.ts",
|
|
91
|
+
"default": "./dist/clip-postprocess.js"
|
|
92
|
+
},
|
|
93
|
+
"./hybrid-navigator": {
|
|
94
|
+
"types": "./dist/hybrid-navigator.d.ts",
|
|
95
|
+
"default": "./dist/hybrid-navigator.js"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"bin": {
|
|
99
|
+
"autokap": "dist/cli.js"
|
|
100
|
+
},
|
|
101
|
+
"scripts": {
|
|
102
|
+
"postinstall": "node scripts/patch-playwright-video-recorder.mjs",
|
|
103
|
+
"build": "tsc && cd web && npm run build",
|
|
104
|
+
"dev": "cd web && npm run dev",
|
|
105
|
+
"dev:lib:watch": "tsc -w --preserveWatchOutput",
|
|
106
|
+
"start": "cd web && npm run start",
|
|
107
|
+
"rebuild": "tsc && cd web && npx tsc --noEmit",
|
|
108
|
+
"typecheck": "tsc --noEmit",
|
|
109
|
+
"test": "vitest run",
|
|
110
|
+
"test:watch": "vitest",
|
|
111
|
+
"test:billing-limits": "node --test --import tsx scripts/billing-plan-limits.test.ts",
|
|
112
|
+
"deploy": "bash deploy.sh",
|
|
113
|
+
"sync-env": "bash scripts/sync-env.sh",
|
|
114
|
+
"screenshot:benchmark": "tsx scripts/screenshot-benchmark.ts",
|
|
115
|
+
"video:smoke": "tsx scripts/video-smoke-test.ts",
|
|
116
|
+
"video:regression": "tsx scripts/video-regression.ts"
|
|
117
|
+
},
|
|
118
|
+
"engines": {
|
|
119
|
+
"node": ">=20"
|
|
120
|
+
},
|
|
121
|
+
"files": [
|
|
122
|
+
"dist/**/*.js",
|
|
123
|
+
"dist/**/*.d.ts",
|
|
124
|
+
"!dist/**/*.js.map",
|
|
125
|
+
"assets/chrome",
|
|
126
|
+
"assets/devices",
|
|
127
|
+
"assets/frames/iPad*",
|
|
128
|
+
"assets/frames/iPhone*",
|
|
129
|
+
"assets/frames/MacBook*",
|
|
130
|
+
"assets/frames/Status*",
|
|
131
|
+
"assets/icons",
|
|
132
|
+
"assets/skill"
|
|
133
|
+
],
|
|
134
|
+
"keywords": [
|
|
135
|
+
"screenshot",
|
|
136
|
+
"capture",
|
|
137
|
+
"automation",
|
|
138
|
+
"ai",
|
|
139
|
+
"cli",
|
|
140
|
+
"playwright",
|
|
141
|
+
"mockup",
|
|
142
|
+
"website"
|
|
143
|
+
],
|
|
144
|
+
"author": "AutoKap",
|
|
145
|
+
"license": "ISC",
|
|
146
|
+
"homepage": "https://app.autokap.com",
|
|
147
|
+
"dependencies": {
|
|
148
|
+
"chalk": "^5.6.2",
|
|
149
|
+
"commander": "^14.0.3",
|
|
150
|
+
"dotenv": "^17.3.1",
|
|
151
|
+
"openai": "^6.25.0",
|
|
152
|
+
"playwright": "^1.58.2",
|
|
153
|
+
"posthog-node": "^5.26.2",
|
|
154
|
+
"sharp": "^0.34.5"
|
|
155
|
+
},
|
|
156
|
+
"devDependencies": {
|
|
157
|
+
"@types/node": "^25.3.3",
|
|
158
|
+
"tsx": "^4.21.0",
|
|
159
|
+
"typescript": "^5.9.3",
|
|
160
|
+
"vitest": "^4.0.18"
|
|
161
|
+
},
|
|
162
|
+
"overrides": {
|
|
163
|
+
"picomatch": ">=4.0.4"
|
|
164
|
+
}
|
|
165
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# AutoKap
|
|
2
|
+
|
|
3
|
+
AI-powered website screenshot capture across every device, language, and theme.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install dependencies
|
|
9
|
+
cd web && npm install
|
|
10
|
+
|
|
11
|
+
# Set up environment
|
|
12
|
+
cp .env.example .env.local
|
|
13
|
+
# Fill in NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY
|
|
14
|
+
|
|
15
|
+
# Run dev server
|
|
16
|
+
npm run dev
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Auth Setup (Supabase)
|
|
20
|
+
|
|
21
|
+
AutoKap uses Supabase Auth for user authentication. You need to configure the following providers in your Supabase dashboard:
|
|
22
|
+
|
|
23
|
+
### 1. Email/Password
|
|
24
|
+
|
|
25
|
+
- Go to **Authentication > Providers > Email** in the Supabase dashboard
|
|
26
|
+
- Enable **Email provider**
|
|
27
|
+
- Configure email templates if needed
|
|
28
|
+
- Enable/disable **Confirm email** depending on your needs
|
|
29
|
+
|
|
30
|
+
### 2. Google OAuth
|
|
31
|
+
|
|
32
|
+
- Go to **Authentication > Providers > Google**
|
|
33
|
+
- Enable the Google provider
|
|
34
|
+
- Create OAuth credentials in the [Google Cloud Console](https://console.cloud.google.com/apis/credentials):
|
|
35
|
+
- Create an OAuth 2.0 Client ID (Web application)
|
|
36
|
+
- Add `https://<your-supabase-project>.supabase.co/auth/v1/callback` as an authorized redirect URI
|
|
37
|
+
- Copy the **Client ID** and **Client Secret** into the Supabase Google provider settings
|
|
38
|
+
|
|
39
|
+
### 3. GitHub OAuth
|
|
40
|
+
|
|
41
|
+
- Go to **Authentication > Providers > GitHub**
|
|
42
|
+
- Enable the GitHub provider
|
|
43
|
+
- Create an OAuth App in [GitHub Developer Settings](https://github.com/settings/developers):
|
|
44
|
+
- Set the **Authorization callback URL** to `https://<your-supabase-project>.supabase.co/auth/v1/callback`
|
|
45
|
+
- Copy the **Client ID** and **Client Secret** into the Supabase GitHub provider settings
|
|
46
|
+
|
|
47
|
+
### 4. Database Schema
|
|
48
|
+
|
|
49
|
+
Make sure the `captures` table has a `run_id` UUID column:
|
|
50
|
+
|
|
51
|
+
```sql
|
|
52
|
+
ALTER TABLE captures ADD COLUMN IF NOT EXISTS run_id UUID;
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_captures_run_id ON captures(run_id);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Architecture
|
|
57
|
+
|
|
58
|
+
- **Web App**: Next.js 16 (App Router) + Tailwind CSS 4 + shadcn/ui + framer-motion
|
|
59
|
+
- **Backend**: Node.js + Playwright for browser automation + AI agent (OpenRouter)
|
|
60
|
+
- **Database**: Supabase (PostgreSQL + Storage + Auth)
|
|
61
|
+
- **Routes**: `/presets`, `/capture/new`, `/capture/live`, `/gallery`, `/login`
|