agex 0.2.7 → 0.2.10
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/README.md +206 -0
- package/assets/assets/cursor.js +115 -37
- package/assets/assets/effects.js +150 -8
- package/assets/scripts/ab-click.sh +85 -0
- package/assets/scripts/ab-close.sh +3 -3
- package/assets/scripts/ab-record-start.sh +2 -2
- package/assets/scripts/ab-record.sh +2 -2
- package/assets/scripts/ab-type.sh +47 -0
- package/assets/scripts/agent-browser-wrapper.sh +2 -1
- package/assets/scripts/fx-highlight.sh +11 -3
- package/assets/scripts/fx-subtitle.sh +14 -0
- package/dist/{chunk-FCCNIKUD.js → chunk-HPJHT4WX.js} +93 -30
- package/dist/cli.js +387 -25
- package/dist/index.js +1 -1
- package/package.json +5 -4
package/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# agex
|
|
2
|
+
|
|
3
|
+
**Let AI agents prove their work — with video.**
|
|
4
|
+
|
|
5
|
+
AI agents work in the background. They write code, fix bugs, ship features. But how do they prove their work?
|
|
6
|
+
|
|
7
|
+
No screenshots. No recordings. No proof. You just have to trust the logs — or worse, manually test it yourself. A bottleneck.
|
|
8
|
+
|
|
9
|
+
agex lets any AI agent (Cursor, Claude, Codex) open a browser, take screenshots, record video, and deliver visual evidence that the work is done. Run it locally. Run it in CI. Ship with proof.
|
|
10
|
+
|
|
11
|
+
Built on [agent-browser](https://github.com/vercel-labs/agent-browser) by Vercel.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx agex prove "the homepage has a search bar and a sign up button" --url https://github.com
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
On your PR CI
|
|
20
|
+
```
|
|
21
|
+
npx agex prove-pr --url http://localhost:3000 # your local dev URL, start your app in the CI
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g agex
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Commands
|
|
32
|
+
|
|
33
|
+
### `agex prove` - Visual assertion testing
|
|
34
|
+
|
|
35
|
+
Prove visual assertions on any URL with screenshots and video evidence.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Is there a dog in Google's logo? (spoiler: no — and the agent catches it)
|
|
39
|
+
agex prove "google home page has a dog in the logo" \
|
|
40
|
+
--url "https://www.google.com/?hl=en&gl=us" --agent codex
|
|
41
|
+
|
|
42
|
+
# Verify GitHub's Code dropdown has HTTPS/SSH/GitHub CLI tabs
|
|
43
|
+
agex prove "clicking the Code dropdown opens a panel with HTTPS/SSH/GitHub CLI tabs" \
|
|
44
|
+
--url https://github.com/facebook/react
|
|
45
|
+
|
|
46
|
+
# Multi-element verification
|
|
47
|
+
agex prove "github.com homepage has a search bar, a sign up button, and at least 3 navigation links" \
|
|
48
|
+
--url https://github.com --agent claude
|
|
49
|
+
|
|
50
|
+
# Complex page structure
|
|
51
|
+
agex prove "the wikipedia page for JavaScript has a History section, a Syntax section, and an infobox sidebar" \
|
|
52
|
+
--url https://en.wikipedia.org/wiki/JavaScript --agent claude
|
|
53
|
+
|
|
54
|
+
# E-commerce flow
|
|
55
|
+
agex prove "search for 'mechanical keyboard', filter by 4+ stars, verify the first result shows product image, price, and rating" \
|
|
56
|
+
--url https://amazon.com --agent claude
|
|
57
|
+
|
|
58
|
+
# Video content
|
|
59
|
+
agex prove "youtube homepage shows at least 6 video thumbnails with titles and view counts" \
|
|
60
|
+
--url https://www.youtube.com --agent codex
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Flag | Description | Default |
|
|
64
|
+
|------|-------------|---------|
|
|
65
|
+
| `--agent, -a` | Agent to use: `cursor`, `claude`, `codex` | `cursor` |
|
|
66
|
+
| `--url, -u` | URL to navigate to | - |
|
|
67
|
+
| `--output, -o` | Output directory | `./prove-results` |
|
|
68
|
+
| `--video` | Enable video recording | `true` |
|
|
69
|
+
| `--screenshots` | Enable screenshots | `true` |
|
|
70
|
+
| `--model, -m` | Model to use | - |
|
|
71
|
+
| `--timeout, -t` | Timeout in ms | `300000` |
|
|
72
|
+
| `--viewport` | Viewport size (WxH) | `1920x1080` |
|
|
73
|
+
| `--headless` | Run headless | `true` |
|
|
74
|
+
| `--browser, -b` | Browser mode: `mcp` or `cli` | `mcp` |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### `agex demo` - Record demo videos
|
|
79
|
+
|
|
80
|
+
Record a narrated browser walkthrough as a video. The agent opens a URL, performs the task, and captures the session.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Record a demo of selecting a brand design from a menu
|
|
84
|
+
agex demo "how to select a brand design from the menu" --url https://example.com
|
|
85
|
+
|
|
86
|
+
# With subtitles and custom colors (via task text)
|
|
87
|
+
agex demo "how to create a new project. use subtitles with white text on red background" \
|
|
88
|
+
--url https://app.example.com --agent cursor
|
|
89
|
+
|
|
90
|
+
# Non-headless for debugging
|
|
91
|
+
agex demo "sign up for a free trial" --url https://example.com --no-headless
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| Flag | Description | Default |
|
|
95
|
+
|------|-------------|---------|
|
|
96
|
+
| `--agent, -a` | Agent to use: `cursor`, `claude`, `codex` | auto-detected |
|
|
97
|
+
| `--url, -u` | URL to navigate to | - |
|
|
98
|
+
| `--output, -o` | Output directory | `./demo-results` |
|
|
99
|
+
| `--video` | Enable video recording | `true` |
|
|
100
|
+
| `--screenshots` | Enable screenshots | `true` |
|
|
101
|
+
| `--model, -m` | Model to use | - |
|
|
102
|
+
| `--timeout, -t` | Timeout in ms | `300000` |
|
|
103
|
+
| `--viewport` | Viewport size (WxH) | `1920x1080` |
|
|
104
|
+
| `--headless` | Run headless | `true` |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### `agex prove-pr` - PR verification in CI
|
|
109
|
+
|
|
110
|
+
Run it in your CI. It reads the diff, thinks, acts, and reports autonomously.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
agex prove-pr --base main --url http://localhost:3000
|
|
114
|
+
agex prove-pr --base HEAD~3 --agent claude --hypotheses 10 --hint "focus on mobile layout"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
| Flag | Description | Default |
|
|
118
|
+
|------|-------------|---------|
|
|
119
|
+
| `--base` | Base branch/commit to compare against | auto-detected |
|
|
120
|
+
| `--agent, -a` | Agent to use: `cursor`, `claude`, `codex` | `cursor` |
|
|
121
|
+
| `--url, -u` | Dev server URL for visual testing | - |
|
|
122
|
+
| `--output, -o` | Output directory | `./prove-pr-results` |
|
|
123
|
+
| `--hypotheses` | Number of hypotheses to generate | `5` |
|
|
124
|
+
| `--model, -m` | Model to use | - |
|
|
125
|
+
| `--viewport` | Viewport size (WxH) | `1920x1080` |
|
|
126
|
+
| `--hint` | Additional prompt hint for hypothesis generation | - |
|
|
127
|
+
| `--timeout, -t` | Timeout in ms | `300000` |
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### `agex review` - AI code review
|
|
132
|
+
|
|
133
|
+
Review code changes using an AI agent.
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
agex review --base main --agent cursor
|
|
137
|
+
agex review --base HEAD~3 --agent claude --hint "focus on security"
|
|
138
|
+
agex review --agent codex --hypotheses 3 --no-worktree
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
| Flag | Description | Default |
|
|
142
|
+
|------|-------------|---------|
|
|
143
|
+
| `--base` | Base branch/commit to compare against | auto-detected |
|
|
144
|
+
| `--agent, -a` | Agent to use: `cursor`, `claude`, `codex` | required |
|
|
145
|
+
| `--model, -m` | Model to use | - |
|
|
146
|
+
| `--worktree` | Include worktree changes | `true` |
|
|
147
|
+
| `--hypotheses` | Number of hypotheses to generate | - |
|
|
148
|
+
| `--hint` | Additional prompt hint | - |
|
|
149
|
+
| `--timeout, -t` | Timeout in ms | `300000` |
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
### `agex run` - Execute AI agent tasks
|
|
154
|
+
|
|
155
|
+
Run a prompt through any supported AI agent.
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
agex run "build a landing page" --agent cursor
|
|
159
|
+
agex run "refactor this function" --agent claude --mode json
|
|
160
|
+
agex run "fix the bug" --approval on-request --timeout 600000
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
| Flag | Description | Default |
|
|
164
|
+
|------|-------------|---------|
|
|
165
|
+
| `--agent, -a` | Agent to use: `cursor`, `claude`, `codex` | auto-detected |
|
|
166
|
+
| `--model, -m` | Model to use | - |
|
|
167
|
+
| `--mode` | Output mode: `text`, `json`, `debug` | `text` |
|
|
168
|
+
| `--approval` | Approval policy: `never`, `on-request`, `on-failure`, `untrusted` | - |
|
|
169
|
+
| `--timeout` | Timeout in ms | `300000` |
|
|
170
|
+
| `--install` | Run install command before agent | `false` |
|
|
171
|
+
| `--install-command` | Custom install command | - |
|
|
172
|
+
| `--stream` | Enable streaming output | `true` |
|
|
173
|
+
| `--browser` | Enable browser | `true` |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### `agex browse` - Browser automation
|
|
178
|
+
|
|
179
|
+
Low-level browser automation via agent-browser.
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
agex browse open https://example.com
|
|
183
|
+
agex browse snapshot -i
|
|
184
|
+
agex browse click @e1
|
|
185
|
+
agex browse fill @e2 "hello@example.com"
|
|
186
|
+
agex browse screenshot page.png
|
|
187
|
+
agex browse close
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
| Flag | Description | Default |
|
|
191
|
+
|------|-------------|---------|
|
|
192
|
+
| `--install` | Force reinstall browser | `false` |
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Supported Agents
|
|
197
|
+
|
|
198
|
+
| Agent | Description |
|
|
199
|
+
|-------|-------------|
|
|
200
|
+
| `cursor` | Cursor IDE agent |
|
|
201
|
+
| `claude` | Anthropic Claude CLI |
|
|
202
|
+
| `codex` | OpenAI Codex CLI |
|
|
203
|
+
|
|
204
|
+
## Ship with proof.
|
|
205
|
+
|
|
206
|
+
MIT License
|
package/assets/assets/cursor.js
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
let clickRippleEnabled = true;
|
|
4
4
|
let trailEnabled = false;
|
|
5
5
|
let trailPoints = [];
|
|
6
|
+
let currentMode = 'default'; // 'default' or 'pointer'
|
|
7
|
+
let animationLock = false; // when true, mousemove won't update demo cursor position
|
|
8
|
+
|
|
9
|
+
const ARROW_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
|
|
10
|
+
<path d="M8.5 3L8.5 24.2l5-5.6 3.6 7.7 3.5-1.5-3.7-7.6 7.1-.5L8.5 3z" fill="#000" stroke="#fff" stroke-width="1.8" stroke-linejoin="round"></path>
|
|
11
|
+
</svg>`;
|
|
12
|
+
|
|
13
|
+
const POINTER_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
|
|
14
|
+
<g transform="rotate(-20, 16, 16)">
|
|
15
|
+
<path d="M14 2 C14 2 11.5 2 11.5 4.5 L11.5 15 L10 13.5 C10 13.5 8.5 12 7 12.5 C5.5 13 5.8 14.5 6.5 15.8 L10.5 22.5 C12 25.5 14.5 27.5 18 27.5 L19.5 27.5 C23.5 27.5 26.5 24 26.5 20 L26.5 15.5 C26.5 14 25 13 23.5 13.5 C23 13.7 22.5 14.2 22.5 15 L22.5 14 C22.5 12.5 21 11.5 19.5 12 C19 12.2 18.5 12.7 18.5 13.5 L18.5 12.5 C18.5 11 17 10 15.5 10.5 C15 10.7 14.5 11.3 14.5 12 L14.5 4.5 C14.5 4.5 14.5 2 14 2 Z" fill="#000" stroke="#fff" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/>
|
|
16
|
+
<line x1="18.5" y1="13" x2="18.5" y2="17" stroke="#fff" stroke-width="0.6" opacity="0.3"/><line x1="22.5" y1="14.5" x2="22.5" y2="17" stroke="#fff" stroke-width="0.6" opacity="0.3"/>
|
|
17
|
+
</g>
|
|
18
|
+
</svg>`;
|
|
6
19
|
|
|
7
20
|
function createCursor() {
|
|
8
21
|
if (document.getElementById('agex-cursor')) return;
|
|
@@ -10,21 +23,36 @@
|
|
|
10
23
|
const cursor = document.createElement('div');
|
|
11
24
|
cursor.id = 'agex-cursor';
|
|
12
25
|
cursor.style.cssText = `
|
|
13
|
-
width: 24px;
|
|
14
|
-
height: 24px;
|
|
15
|
-
background: rgba(255, 50, 50, 0.85);
|
|
16
|
-
border: 2px solid white;
|
|
17
|
-
border-radius: 50%;
|
|
18
26
|
position: fixed;
|
|
19
27
|
pointer-events: none;
|
|
20
28
|
z-index: 2147483647;
|
|
21
|
-
transform: translate(-50%, -50%);
|
|
22
|
-
box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
|
|
23
29
|
left: -100px;
|
|
24
30
|
top: -100px;
|
|
25
31
|
opacity: 0;
|
|
26
|
-
transition:
|
|
32
|
+
transition: opacity 0.15s, transform 0.1s ease-out;
|
|
33
|
+
transform-origin: 2px 2px;
|
|
34
|
+
will-change: left, top;
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
const dot = document.createElement('div');
|
|
38
|
+
dot.className = 'agex-cursor-dot';
|
|
39
|
+
dot.style.cssText = `
|
|
40
|
+
position: absolute;
|
|
41
|
+
width: 38px;
|
|
42
|
+
height: 38px;
|
|
43
|
+
border-radius: 50%;
|
|
44
|
+
background: rgb(251, 255, 0, 0.45);
|
|
45
|
+
top: -16px;
|
|
46
|
+
left: -10px;
|
|
47
|
+
pointer-events: none;
|
|
27
48
|
`;
|
|
49
|
+
|
|
50
|
+
const svg = document.createElement('div');
|
|
51
|
+
svg.className = 'agex-cursor-svg';
|
|
52
|
+
svg.innerHTML = ARROW_SVG;
|
|
53
|
+
|
|
54
|
+
cursor.appendChild(dot);
|
|
55
|
+
cursor.appendChild(svg);
|
|
28
56
|
document.body.appendChild(cursor);
|
|
29
57
|
|
|
30
58
|
const style = document.createElement('style');
|
|
@@ -33,20 +61,28 @@
|
|
|
33
61
|
.agex-cursor-ripple {
|
|
34
62
|
position: fixed;
|
|
35
63
|
border-radius: 50%;
|
|
36
|
-
background: rgba(255, 50, 50, 0.4);
|
|
37
64
|
pointer-events: none;
|
|
38
65
|
z-index: 2147483646;
|
|
39
66
|
transform: translate(-50%, -50%) scale(0);
|
|
40
|
-
animation: agex-ripple 0.
|
|
67
|
+
animation: agex-ripple 0.5s ease-out forwards;
|
|
68
|
+
}
|
|
69
|
+
.agex-cursor-ripple-ring {
|
|
70
|
+
position: fixed;
|
|
71
|
+
border-radius: 50%;
|
|
72
|
+
border: 3px solid rgba(0, 0, 0, 0.4);
|
|
73
|
+
pointer-events: none;
|
|
74
|
+
z-index: 2147483646;
|
|
75
|
+
transform: translate(-50%, -50%) scale(0);
|
|
76
|
+
animation: agex-ripple 0.6s ease-out forwards;
|
|
41
77
|
}
|
|
42
78
|
@keyframes agex-ripple {
|
|
43
79
|
to { transform: translate(-50%, -50%) scale(1); opacity: 0; }
|
|
44
80
|
}
|
|
45
81
|
.agex-cursor-trail {
|
|
46
82
|
position: fixed;
|
|
47
|
-
width:
|
|
48
|
-
height:
|
|
49
|
-
background: rgba(
|
|
83
|
+
width: 6px;
|
|
84
|
+
height: 6px;
|
|
85
|
+
background: rgba(0, 0, 0, 0.12);
|
|
50
86
|
border-radius: 50%;
|
|
51
87
|
pointer-events: none;
|
|
52
88
|
z-index: 2147483645;
|
|
@@ -55,16 +91,29 @@
|
|
|
55
91
|
`;
|
|
56
92
|
document.head.appendChild(style);
|
|
57
93
|
|
|
94
|
+
const CLICKABLE = 'a, button, [role="button"], input[type="submit"], input[type="button"], select, label[for], [onclick], [data-action], summary, [tabindex]';
|
|
95
|
+
|
|
58
96
|
document.addEventListener('mousemove', (e) => {
|
|
59
97
|
const c = document.getElementById('agex-cursor');
|
|
60
98
|
if (c) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
hasMoved
|
|
65
|
-
|
|
99
|
+
if (!animationLock) {
|
|
100
|
+
c.style.left = `${e.clientX}px`;
|
|
101
|
+
c.style.top = `${e.clientY}px`;
|
|
102
|
+
if (!hasMoved) {
|
|
103
|
+
hasMoved = true;
|
|
104
|
+
c.style.opacity = '1';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const hovered = document.elementFromPoint(e.clientX, e.clientY);
|
|
109
|
+
const isClickable = hovered && hovered.closest(CLICKABLE);
|
|
110
|
+
const newMode = isClickable ? 'pointer' : 'default';
|
|
111
|
+
if (newMode !== currentMode) {
|
|
112
|
+
currentMode = newMode;
|
|
113
|
+
const svgEl = c.querySelector('.agex-cursor-svg');
|
|
114
|
+
if (svgEl) svgEl.innerHTML = currentMode === 'pointer' ? POINTER_SVG : ARROW_SVG;
|
|
66
115
|
}
|
|
67
|
-
|
|
116
|
+
|
|
68
117
|
if (trailEnabled) {
|
|
69
118
|
createTrailPoint(e.clientX, e.clientY);
|
|
70
119
|
}
|
|
@@ -74,9 +123,8 @@
|
|
|
74
123
|
document.addEventListener('mousedown', (e) => {
|
|
75
124
|
const c = document.getElementById('agex-cursor');
|
|
76
125
|
if (c) {
|
|
77
|
-
c.style.transform = '
|
|
78
|
-
|
|
79
|
-
|
|
126
|
+
c.style.transform = 'scale(0.85)';
|
|
127
|
+
|
|
80
128
|
if (clickRippleEnabled) {
|
|
81
129
|
createClickRipple(e.clientX, e.clientY);
|
|
82
130
|
}
|
|
@@ -86,21 +134,30 @@
|
|
|
86
134
|
document.addEventListener('mouseup', () => {
|
|
87
135
|
const c = document.getElementById('agex-cursor');
|
|
88
136
|
if (c) {
|
|
89
|
-
c.style.transform = '
|
|
90
|
-
c.style.background = 'rgba(255, 50, 50, 0.85)';
|
|
137
|
+
c.style.transform = 'scale(1)';
|
|
91
138
|
}
|
|
92
139
|
}, true);
|
|
93
140
|
}
|
|
94
141
|
|
|
95
142
|
function createClickRipple(x, y) {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
143
|
+
const fill = document.createElement('div');
|
|
144
|
+
fill.className = 'agex-cursor-ripple';
|
|
145
|
+
fill.style.left = `${x}px`;
|
|
146
|
+
fill.style.top = `${y}px`;
|
|
147
|
+
fill.style.width = '50px';
|
|
148
|
+
fill.style.height = '50px';
|
|
149
|
+
fill.style.background = 'rgba(251, 255, 0, 0.35)';
|
|
150
|
+
document.body.appendChild(fill);
|
|
151
|
+
setTimeout(() => fill.remove(), 500);
|
|
152
|
+
|
|
153
|
+
const ring = document.createElement('div');
|
|
154
|
+
ring.className = 'agex-cursor-ripple-ring';
|
|
155
|
+
ring.style.left = `${x}px`;
|
|
156
|
+
ring.style.top = `${y}px`;
|
|
157
|
+
ring.style.width = '70px';
|
|
158
|
+
ring.style.height = '70px';
|
|
159
|
+
document.body.appendChild(ring);
|
|
160
|
+
setTimeout(() => ring.remove(), 600);
|
|
104
161
|
}
|
|
105
162
|
|
|
106
163
|
function createTrailPoint(x, y) {
|
|
@@ -122,15 +179,16 @@
|
|
|
122
179
|
}, 400);
|
|
123
180
|
}
|
|
124
181
|
|
|
125
|
-
// Expose cursor configuration API
|
|
126
182
|
window.__agexCursor = {
|
|
183
|
+
setAnimationLock(locked) {
|
|
184
|
+
animationLock = locked;
|
|
185
|
+
},
|
|
127
186
|
setSize(size) {
|
|
128
187
|
const cursor = document.getElementById('agex-cursor');
|
|
129
188
|
if (!cursor) return;
|
|
130
|
-
const
|
|
131
|
-
const s =
|
|
132
|
-
cursor.style.
|
|
133
|
-
cursor.style.height = `${s}px`;
|
|
189
|
+
const scales = { normal: 0.75, large: 1, 'extra-large': 1.3 };
|
|
190
|
+
const s = scales[size] || scales.large;
|
|
191
|
+
cursor.style.transform = `scale(${s})`;
|
|
134
192
|
},
|
|
135
193
|
setClickRipple(enabled) {
|
|
136
194
|
clickRippleEnabled = enabled;
|
|
@@ -143,6 +201,26 @@
|
|
|
143
201
|
if (options.clickRipple !== undefined) this.setClickRipple(options.clickRipple);
|
|
144
202
|
if (options.trail !== undefined) this.setTrail(options.trail);
|
|
145
203
|
},
|
|
204
|
+
setMode(mode) {
|
|
205
|
+
const cursor = document.getElementById('agex-cursor');
|
|
206
|
+
if (!cursor) return;
|
|
207
|
+
const svgEl = cursor.querySelector('.agex-cursor-svg');
|
|
208
|
+
if (!svgEl) return;
|
|
209
|
+
if (mode === 'pointer') {
|
|
210
|
+
currentMode = 'pointer';
|
|
211
|
+
svgEl.innerHTML = POINTER_SVG;
|
|
212
|
+
} else {
|
|
213
|
+
currentMode = 'default';
|
|
214
|
+
svgEl.innerHTML = ARROW_SVG;
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
showClickRipple() {
|
|
218
|
+
const cursor = document.getElementById('agex-cursor');
|
|
219
|
+
if (!cursor) return;
|
|
220
|
+
const x = parseFloat(cursor.style.left) || 0;
|
|
221
|
+
const y = parseFloat(cursor.style.top) || 0;
|
|
222
|
+
createClickRipple(x, y);
|
|
223
|
+
},
|
|
146
224
|
sync() {
|
|
147
225
|
const cursor = document.getElementById('agex-cursor');
|
|
148
226
|
if (!cursor) return Promise.resolve('no cursor');
|
package/assets/assets/effects.js
CHANGED
|
@@ -95,6 +95,18 @@
|
|
|
95
95
|
0%, 100% { box-shadow: 0 0 20px var(--color, #ff4444), inset 0 0 20px var(--color, #ff4444); }
|
|
96
96
|
50% { box-shadow: 0 0 35px var(--color, #ff4444), inset 0 0 35px var(--color, #ff4444); }
|
|
97
97
|
}
|
|
98
|
+
.${EFFECTS_ID}-input-focus {
|
|
99
|
+
position: absolute;
|
|
100
|
+
pointer-events: none;
|
|
101
|
+
z-index: 2147483639;
|
|
102
|
+
border-radius: 6px;
|
|
103
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5), 0 0 16px rgba(59, 130, 246, 0.3);
|
|
104
|
+
animation: ${EFFECTS_ID}-input-focus-pulse 1.5s ease-in-out infinite;
|
|
105
|
+
}
|
|
106
|
+
@keyframes ${EFFECTS_ID}-input-focus-pulse {
|
|
107
|
+
0%, 100% { box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5), 0 0 16px rgba(59, 130, 246, 0.3); }
|
|
108
|
+
50% { box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.7), 0 0 24px rgba(59, 130, 246, 0.5); }
|
|
109
|
+
}
|
|
98
110
|
.${EFFECTS_ID}-highlight.pulse {
|
|
99
111
|
animation: ${EFFECTS_ID}-pulse 1s ease-in-out infinite;
|
|
100
112
|
border: 3px solid var(--color, #ff4444);
|
|
@@ -150,17 +162,22 @@
|
|
|
150
162
|
font-family: system-ui, sans-serif;
|
|
151
163
|
transition: all 0.3s ease;
|
|
152
164
|
}
|
|
165
|
+
@keyframes ${EFFECTS_ID}-subtitle-slide-down {
|
|
166
|
+
from { opacity: 0; transform: translateX(-50%) translateY(-20px); }
|
|
167
|
+
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
168
|
+
}
|
|
153
169
|
.${EFFECTS_ID}-subtitle {
|
|
154
170
|
position: fixed;
|
|
155
171
|
left: 50%;
|
|
156
172
|
transform: translateX(-50%);
|
|
157
|
-
background: rgba(0, 0, 0, 0.8);
|
|
158
|
-
color: white;
|
|
173
|
+
background: var(--subtitle-bg, rgba(0, 0, 0, 0.8));
|
|
174
|
+
color: var(--subtitle-fg, white);
|
|
159
175
|
padding: 12px 24px;
|
|
160
176
|
border-radius: 8px;
|
|
161
177
|
font-size: 18px;
|
|
162
178
|
max-width: 80%;
|
|
163
179
|
text-align: center;
|
|
180
|
+
animation: ${EFFECTS_ID}-subtitle-slide-down 0.35s ease-out;
|
|
164
181
|
}
|
|
165
182
|
.${EFFECTS_ID}-drawing-canvas {
|
|
166
183
|
position: fixed;
|
|
@@ -182,6 +199,7 @@
|
|
|
182
199
|
// HIGHLIGHT
|
|
183
200
|
highlight: {
|
|
184
201
|
add(selector, { color = '#ff4444', style = 'border', duration } = {}) {
|
|
202
|
+
injectStyles();
|
|
185
203
|
const el = utils.getElement(selector);
|
|
186
204
|
if (!el) return false;
|
|
187
205
|
|
|
@@ -213,6 +231,7 @@
|
|
|
213
231
|
// ANNOTATION
|
|
214
232
|
annotation: {
|
|
215
233
|
add(selector, { text, position = 'top', color = '#333', arrow = true } = {}) {
|
|
234
|
+
injectStyles();
|
|
216
235
|
const el = utils.getElement(selector);
|
|
217
236
|
if (!el) return false;
|
|
218
237
|
|
|
@@ -247,6 +266,7 @@
|
|
|
247
266
|
// SPOTLIGHT
|
|
248
267
|
spotlight: {
|
|
249
268
|
show(selector, { opacity = 0.7, label = '' } = {}) {
|
|
269
|
+
injectStyles();
|
|
250
270
|
const el = utils.getElement(selector);
|
|
251
271
|
if (!el) return false;
|
|
252
272
|
|
|
@@ -355,10 +375,9 @@
|
|
|
355
375
|
const cursor = document.getElementById('agex-cursor');
|
|
356
376
|
if (!cursor) return;
|
|
357
377
|
|
|
358
|
-
const
|
|
359
|
-
const s =
|
|
360
|
-
cursor.style.
|
|
361
|
-
cursor.style.height = `${s}px`;
|
|
378
|
+
const scales = { normal: 0.75, large: 1, 'extra-large': 1.3 };
|
|
379
|
+
const s = scales[size] || 1;
|
|
380
|
+
cursor.style.transform = `scale(${s})`;
|
|
362
381
|
|
|
363
382
|
cursor.dataset.clickRipple = clickRipple;
|
|
364
383
|
cursor.dataset.trail = trail;
|
|
@@ -390,7 +409,7 @@
|
|
|
390
409
|
return true;
|
|
391
410
|
},
|
|
392
411
|
|
|
393
|
-
showClick(x, y, color = '
|
|
412
|
+
showClick(x, y, color = 'rgba(0, 0, 0, 0.15)') {
|
|
394
413
|
const ripple = utils.createElement('div', {
|
|
395
414
|
position: 'fixed',
|
|
396
415
|
left: `${x}px`,
|
|
@@ -457,6 +476,117 @@
|
|
|
457
476
|
await this.moveTo(points[i + 1].x, points[i + 1].y, segmentDuration);
|
|
458
477
|
}
|
|
459
478
|
return true;
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
_lastPos: null,
|
|
482
|
+
|
|
483
|
+
animateToPosition(targetX, targetY) {
|
|
484
|
+
const c = document.getElementById('agex-cursor');
|
|
485
|
+
if (!c) return 0;
|
|
486
|
+
|
|
487
|
+
// Lock demo cursor so native mousemove won't fight the animation
|
|
488
|
+
if (window.__agexCursor) window.__agexCursor.setAnimationLock(true);
|
|
489
|
+
|
|
490
|
+
const vh = window.innerHeight;
|
|
491
|
+
const wasHidden = c.style.opacity === '0' || c.style.opacity === '' || c.style.left === '-100px';
|
|
492
|
+
const startX = wasHidden ? (this._lastPos ? this._lastPos.x : 0) : parseFloat(c.style.left) || 0;
|
|
493
|
+
const startY = wasHidden ? (this._lastPos ? this._lastPos.y : vh / 2) : parseFloat(c.style.top) || vh / 2;
|
|
494
|
+
|
|
495
|
+
c.style.transition = 'none';
|
|
496
|
+
c.style.left = startX + 'px';
|
|
497
|
+
c.style.top = startY + 'px';
|
|
498
|
+
c.style.opacity = '1';
|
|
499
|
+
c.offsetHeight;
|
|
500
|
+
|
|
501
|
+
if (isNaN(targetX) || isNaN(targetY)) {
|
|
502
|
+
if (window.__agexCursor) window.__agexCursor.setAnimationLock(false);
|
|
503
|
+
return 0;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
this._lastPos = { x: targetX, y: targetY };
|
|
507
|
+
|
|
508
|
+
const dist = Math.sqrt((targetX - startX) ** 2 + (targetY - startY) ** 2);
|
|
509
|
+
const duration = Math.max(1200, Math.round(dist / 0.3));
|
|
510
|
+
const start = performance.now();
|
|
511
|
+
const self = this;
|
|
512
|
+
|
|
513
|
+
const step = (now) => {
|
|
514
|
+
const elapsed = now - start;
|
|
515
|
+
const t = Math.min(elapsed / duration, 1);
|
|
516
|
+
const ease = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
|
517
|
+
c.style.left = (startX + (targetX - startX) * ease) + 'px';
|
|
518
|
+
c.style.top = (startY + (targetY - startY) * ease) + 'px';
|
|
519
|
+
if (t < 1) {
|
|
520
|
+
requestAnimationFrame(step);
|
|
521
|
+
} else {
|
|
522
|
+
// Animation done — release lock
|
|
523
|
+
if (window.__agexCursor) window.__agexCursor.setAnimationLock(false);
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
requestAnimationFrame(step);
|
|
527
|
+
return duration;
|
|
528
|
+
},
|
|
529
|
+
|
|
530
|
+
showInputFocus(selector) {
|
|
531
|
+
this.clearInputFocus();
|
|
532
|
+
var el = utils.getElement(selector);
|
|
533
|
+
if (!el) return;
|
|
534
|
+
var rect = el.getBoundingClientRect();
|
|
535
|
+
var overlay = document.createElement('div');
|
|
536
|
+
overlay.id = EFFECTS_ID + '-input-focus';
|
|
537
|
+
overlay.className = EFFECTS_ID + '-input-focus';
|
|
538
|
+
overlay.style.left = (rect.left + window.scrollX - 2) + 'px';
|
|
539
|
+
overlay.style.top = (rect.top + window.scrollY - 2) + 'px';
|
|
540
|
+
overlay.style.width = (rect.width + 4) + 'px';
|
|
541
|
+
overlay.style.height = (rect.height + 4) + 'px';
|
|
542
|
+
document.body.appendChild(overlay);
|
|
543
|
+
},
|
|
544
|
+
|
|
545
|
+
clearInputFocus() {
|
|
546
|
+
var el = document.getElementById(EFFECTS_ID + '-input-focus');
|
|
547
|
+
if (el) el.remove();
|
|
548
|
+
},
|
|
549
|
+
|
|
550
|
+
typeText(selector, text, charDelay) {
|
|
551
|
+
charDelay = charDelay || 80;
|
|
552
|
+
var el = utils.getElement(selector);
|
|
553
|
+
if (!el) return 0;
|
|
554
|
+
el.focus();
|
|
555
|
+
var i = 0;
|
|
556
|
+
var len = text.length;
|
|
557
|
+
function typeNext() {
|
|
558
|
+
if (i >= len) return;
|
|
559
|
+
var ch = text[i];
|
|
560
|
+
el.dispatchEvent(new KeyboardEvent('keydown', { key: ch, bubbles: true }));
|
|
561
|
+
el.dispatchEvent(new KeyboardEvent('keypress', { key: ch, bubbles: true }));
|
|
562
|
+
// For contenteditable elements, insert text differently
|
|
563
|
+
if (el.isContentEditable) {
|
|
564
|
+
document.execCommand('insertText', false, ch);
|
|
565
|
+
} else {
|
|
566
|
+
// Use InputEvent for proper React/framework compatibility
|
|
567
|
+
var nativeInputValueSetter = Object.getOwnPropertyDescriptor(
|
|
568
|
+
Object.getPrototypeOf(el), 'value'
|
|
569
|
+
);
|
|
570
|
+
if (nativeInputValueSetter && nativeInputValueSetter.set) {
|
|
571
|
+
nativeInputValueSetter.set.call(el, el.value + ch);
|
|
572
|
+
} else {
|
|
573
|
+
el.value = el.value + ch;
|
|
574
|
+
}
|
|
575
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
576
|
+
}
|
|
577
|
+
el.dispatchEvent(new KeyboardEvent('keyup', { key: ch, bubbles: true }));
|
|
578
|
+
i++;
|
|
579
|
+
if (i < len) setTimeout(typeNext, charDelay);
|
|
580
|
+
}
|
|
581
|
+
typeNext();
|
|
582
|
+
return len * charDelay;
|
|
583
|
+
},
|
|
584
|
+
|
|
585
|
+
fadeOut() {
|
|
586
|
+
const c = document.getElementById('agex-cursor');
|
|
587
|
+
if (!c) return;
|
|
588
|
+
c.style.transition = 'opacity 0.4s ease-out';
|
|
589
|
+
c.style.opacity = '0';
|
|
460
590
|
}
|
|
461
591
|
},
|
|
462
592
|
|
|
@@ -1289,12 +1419,15 @@
|
|
|
1289
1419
|
});
|
|
1290
1420
|
},
|
|
1291
1421
|
|
|
1292
|
-
showSubtitle(text, duration = 3000, position = 'bottom') {
|
|
1422
|
+
showSubtitle(text, duration = 3000, position = 'bottom', fg = '', bg = '') {
|
|
1423
|
+
injectStyles();
|
|
1293
1424
|
this.clear();
|
|
1294
1425
|
const subtitle = utils.createElement('div', {
|
|
1295
1426
|
[position]: '40px'
|
|
1296
1427
|
}, `${EFFECTS_ID}-subtitle`);
|
|
1297
1428
|
subtitle.classList.add(`${EFFECTS_ID}-subtitle`);
|
|
1429
|
+
if (fg) subtitle.style.setProperty('--subtitle-fg', fg);
|
|
1430
|
+
if (bg) subtitle.style.setProperty('--subtitle-bg', bg);
|
|
1298
1431
|
subtitle.textContent = text;
|
|
1299
1432
|
document.body.appendChild(subtitle);
|
|
1300
1433
|
|
|
@@ -1305,6 +1438,7 @@
|
|
|
1305
1438
|
|
|
1306
1439
|
clear() {
|
|
1307
1440
|
utils.removeElements(`#${EFFECTS_ID}-subtitle`);
|
|
1441
|
+
utils.removeElements(`.${EFFECTS_ID}-subtitle`);
|
|
1308
1442
|
if ('speechSynthesis' in window) {
|
|
1309
1443
|
speechSynthesis.cancel();
|
|
1310
1444
|
}
|
|
@@ -1499,6 +1633,14 @@
|
|
|
1499
1633
|
if (el) el.scrollIntoView({ block: 'center', behavior: 'instant' });
|
|
1500
1634
|
},
|
|
1501
1635
|
|
|
1636
|
+
scrollElementToCenter(selector) {
|
|
1637
|
+
var el = typeof selector === 'string' ? document.querySelector(selector) : selector;
|
|
1638
|
+
if (!el) return false;
|
|
1639
|
+
// scrollIntoView with block:'center' handles nested scrollable containers
|
|
1640
|
+
el.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'instant' });
|
|
1641
|
+
return true;
|
|
1642
|
+
},
|
|
1643
|
+
|
|
1502
1644
|
scrollToCoords(y, viewportHeight, elHeight) {
|
|
1503
1645
|
window.scrollTo({
|
|
1504
1646
|
top: window.scrollY + y - (viewportHeight / 2) + (elHeight / 2),
|