crawlix 0.1.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/LICENSE +21 -0
- package/README.md +187 -0
- package/dist/adapters/base.d.ts +8 -0
- package/dist/adapters/base.d.ts.map +1 -0
- package/dist/adapters/base.js +2 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/web.d.ts +14 -0
- package/dist/adapters/web.d.ts.map +1 -0
- package/dist/adapters/web.js +120 -0
- package/dist/adapters/web.js.map +1 -0
- package/dist/cli/commands/agents.d.ts +3 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/agents.js +9 -0
- package/dist/cli/commands/agents.js.map +1 -0
- package/dist/cli/commands/run.d.ts +3 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +49 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +3 -0
- package/dist/cli/commands/setup.d.ts.map +1 -0
- package/dist/cli/commands/setup.js +93 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +17 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/director.d.ts +14 -0
- package/dist/core/director.d.ts.map +1 -0
- package/dist/core/director.js +112 -0
- package/dist/core/director.js.map +1 -0
- package/dist/core/extractor.d.ts +6 -0
- package/dist/core/extractor.d.ts.map +1 -0
- package/dist/core/extractor.js +42 -0
- package/dist/core/extractor.js.map +1 -0
- package/dist/core/orchestrator.d.ts +27 -0
- package/dist/core/orchestrator.d.ts.map +1 -0
- package/dist/core/orchestrator.js +62 -0
- package/dist/core/orchestrator.js.map +1 -0
- package/dist/core/reporter.d.ts +8 -0
- package/dist/core/reporter.d.ts.map +1 -0
- package/dist/core/reporter.js +132 -0
- package/dist/core/reporter.js.map +1 -0
- package/dist/core/runner.d.ts +13 -0
- package/dist/core/runner.d.ts.map +1 -0
- package/dist/core/runner.js +61 -0
- package/dist/core/runner.js.map +1 -0
- package/dist/lib/browser.d.ts +4 -0
- package/dist/lib/browser.d.ts.map +1 -0
- package/dist/lib/browser.js +24 -0
- package/dist/lib/browser.js.map +1 -0
- package/dist/lib/display.d.ts +7 -0
- package/dist/lib/display.d.ts.map +1 -0
- package/dist/lib/display.js +80 -0
- package/dist/lib/display.js.map +1 -0
- package/dist/lib/getConfigs.d.ts +3 -0
- package/dist/lib/getConfigs.d.ts.map +1 -0
- package/dist/lib/getConfigs.js +12 -0
- package/dist/lib/getConfigs.js.map +1 -0
- package/dist/llm/index.d.ts +15 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +147 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/personas/index.d.ts +5 -0
- package/dist/personas/index.d.ts.map +1 -0
- package/dist/personas/index.js +100 -0
- package/dist/personas/index.js.map +1 -0
- package/dist/personas/loader.d.ts +4 -0
- package/dist/personas/loader.d.ts.map +1 -0
- package/dist/personas/loader.js +72 -0
- package/dist/personas/loader.js.map +1 -0
- package/dist/types/index.d.ts +76 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 M.Taqi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# 👾 Crawlix
|
|
2
|
+
|
|
3
|
+
> Claw through bugs before your users do.
|
|
4
|
+
|
|
5
|
+
Crawlix is an open-source autonomous QA agent that spawns AI-powered user personas and unleashes them on your product. Each persona navigates independently, makes real decisions, hits dead ends, and finds bugs - without you writing a single test script.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## How it works
|
|
10
|
+
|
|
11
|
+
Crawlix spawns multiple AI agents simultaneously. Each one opens your app in a real browser, reads the UI, and navigates toward the goal exactly as that type of user would behave - including their mistakes, impatience, and confusion. When they find something broken, confusing, or unexpected - they report it.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
👾 Crawlix - Claw through bugs before your users do.
|
|
15
|
+
|
|
16
|
+
target → http://localhost:3000/
|
|
17
|
+
goal → Check the landing page is everything working fine
|
|
18
|
+
agents → First-Timer, Impatient, Power User, Adversarial, Non-Native Speaker, Slow Network
|
|
19
|
+
|
|
20
|
+
✓ First-Timer 2 critical · 3 warnings 18 steps · 12.3s
|
|
21
|
+
~ Impatient 1 warning 6 steps · 4.1s
|
|
22
|
+
✓ Power User no findings 22 steps · 15.7s
|
|
23
|
+
✗ Adversarial 3 critical 14 steps · 9.2s
|
|
24
|
+
~ Non-Native 106 warnings · 1 info 10 steps · 27.5s
|
|
25
|
+
~ Slow Network no findings 4 steps · 27.7s
|
|
26
|
+
|
|
27
|
+
╭──────────────────────────────────────╮
|
|
28
|
+
│ 👾 Crawlix - run complete │
|
|
29
|
+
│ │
|
|
30
|
+
│ 1 critical 106 warnings 1 info │
|
|
31
|
+
│ │
|
|
32
|
+
│ 0 passed 0 stuck 6 incomplete │
|
|
33
|
+
│ │
|
|
34
|
+
│ total time → 539.8s │
|
|
35
|
+
╰──────────────────────────────────────╯
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
No test scripts. No selectors. No maintenance.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install -g crawlix
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Setup
|
|
51
|
+
|
|
52
|
+
Run once. Crawlix asks for your LLM provider and API key - remembers it forever.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
crawlix setup
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Supported providers:
|
|
59
|
+
|
|
60
|
+
- Groq
|
|
61
|
+
- Gemini
|
|
62
|
+
- Cerebras
|
|
63
|
+
- Mistral
|
|
64
|
+
- OpenRouter
|
|
65
|
+
- Ollama
|
|
66
|
+
- OpenAI
|
|
67
|
+
- Anthropic
|
|
68
|
+
|
|
69
|
+
Config is saved to `~/.crawlix/crawlix.config.json`.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Usage
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# run all agents against your app
|
|
77
|
+
crawlix run --url https://myapp.com --goal "complete the signup flow"
|
|
78
|
+
|
|
79
|
+
# run specific agents only
|
|
80
|
+
crawlix run --url https://myapp.com --goal "login" --agent first-timer,adversarial
|
|
81
|
+
|
|
82
|
+
# run headed - watch agents navigate in real browser
|
|
83
|
+
crawlix run --url https://myapp.com --goal "checkout" --headed
|
|
84
|
+
|
|
85
|
+
# control steps and concurrency
|
|
86
|
+
crawlix run --url https://myapp.com --goal "find pricing" --steps 15 --concurrency 1
|
|
87
|
+
|
|
88
|
+
# list all available agents
|
|
89
|
+
crawlix agents
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Built-in agents
|
|
95
|
+
|
|
96
|
+
| Agent | Behavior |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `first-timer` | Never seen this app. Reads nothing. Clicks whatever looks obvious. |
|
|
99
|
+
| `impatient` | Skips everything. Rage-clicks. Abandons if stuck for more than 2 steps. |
|
|
100
|
+
| `power-user` | Tries every edge case, advanced flow, and keyboard shortcut. |
|
|
101
|
+
| `adversarial` | SQL injection, XSS attempts, wrong inputs, broken sequences. |
|
|
102
|
+
| `non-native` | Misreads labels, confused by idioms. Tests copy clarity ruthlessly. |
|
|
103
|
+
| `slow-network` | Throttled connection. Finds missing loading states and timeouts. |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Custom agents
|
|
108
|
+
|
|
109
|
+
Drop a JSON file into `.crawlix/agents/` in your project root:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"name": "doctor",
|
|
114
|
+
"description": "Medical professional, time-pressured, technically literate",
|
|
115
|
+
"systemPrompt": "You are a busy doctor with 2 minutes between patients. You know what you want, you don't read instructions, and you get frustrated fast if the UI isn't obvious.",
|
|
116
|
+
"patience": 4,
|
|
117
|
+
"aggression": 3,
|
|
118
|
+
"readingBehavior": "skim"
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Crawlix picks it up automatically on the next run. No code, no imports, no build step.
|
|
123
|
+
|
|
124
|
+
Run a specific custom agent:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
crawlix run --url https://myapp.com --goal "book an appointment" --agent doctor
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Findings
|
|
133
|
+
|
|
134
|
+
Crawlix reports three severity levels:
|
|
135
|
+
|
|
136
|
+
| Severity | Meaning |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `critical` | Broken element, crash, security issue, complete blocker |
|
|
139
|
+
| `warning` | Confusing flow, missing feedback, slow response, unclear copy |
|
|
140
|
+
| `info` | Minor friction, accessibility gap, copy improvement |
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Why no test scripts?
|
|
145
|
+
|
|
146
|
+
Traditional QA tools require you to write and maintain selectors, flows, and assertions. They break when your UI changes. They only test paths you already thought of.
|
|
147
|
+
|
|
148
|
+
Crawlix doesn't know your app. That's the point. It finds the paths you didn't think of - the ones your real users will find on their own.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Contributing
|
|
153
|
+
|
|
154
|
+
Contributions are welcome - bug fixes, new agents, adapter improvements, or anything that makes it better.
|
|
155
|
+
|
|
156
|
+
### Getting started
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
git clone https://github.com/m-taqii/crawlix
|
|
160
|
+
cd crawlix
|
|
161
|
+
pnpm install
|
|
162
|
+
pnpm tsx src/cli/index.ts run --url https://example.com --goal "find the more information link"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Ways to contribute
|
|
166
|
+
|
|
167
|
+
- **Add a built-in agent** - add a persona to `src/personas/index.ts` and open a PR
|
|
168
|
+
- **Fix a bug** - open an issue first, then a PR with the fix
|
|
169
|
+
- **Improve element resolution** - `src/adapters/web.ts` `resolve()` method always needs work
|
|
170
|
+
- **Add an adapter** - API testing, mobile, desktop - see `src/adapters/base.ts` for the interface
|
|
171
|
+
- **Improve the report** - `src/core/reporter.ts` - better prompts, better structure
|
|
172
|
+
|
|
173
|
+
### Before opening a PR
|
|
174
|
+
|
|
175
|
+
- Run `pnpm exec tsc --noEmit` - must be clean
|
|
176
|
+
- Test against a real URL
|
|
177
|
+
- Keep it focused - one thing per PR
|
|
178
|
+
|
|
179
|
+
### Found a bug?
|
|
180
|
+
|
|
181
|
+
Open an issue with the URL you were testing, the goal you gave, and the error output.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
MIT - see [LICENSE](LICENSE)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Action, ActionResult, PageState } from "../types/index.js";
|
|
2
|
+
export interface Adapter {
|
|
3
|
+
open(url: string): Promise<void>;
|
|
4
|
+
getState(): Promise<PageState>;
|
|
5
|
+
execute(action: Action): Promise<ActionResult>;
|
|
6
|
+
close(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/adapters/base.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAExE,MAAM,WAAW,OAAO;IACtB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChC,QAAQ,IAAI,OAAO,CAAC,SAAS,CAAC,CAAA;IAC9B,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAC9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/adapters/base.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Adapter } from "./base.js";
|
|
2
|
+
import type { ActionResult, Action, PageState } from "../types/index.js";
|
|
3
|
+
import type { Page } from "playwright";
|
|
4
|
+
export declare class WebAdapter implements Adapter {
|
|
5
|
+
private page;
|
|
6
|
+
private extractor;
|
|
7
|
+
constructor(page: Page);
|
|
8
|
+
open(url: string): Promise<void>;
|
|
9
|
+
getState(): Promise<PageState>;
|
|
10
|
+
private resolve;
|
|
11
|
+
execute(action: Action): Promise<ActionResult>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/adapters/web.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AACxE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC,qBAAa,UAAW,YAAW,OAAO;IACtC,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,SAAS,CAAW;gBAEhB,IAAI,EAAE,IAAI;IAKhB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhC,QAAQ,IAAI,OAAO,CAAC,SAAS,CAAC;YAItB,OAAO;IA8Bf,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAiF9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Extractor } from "../core/extractor.js";
|
|
2
|
+
export class WebAdapter {
|
|
3
|
+
page;
|
|
4
|
+
extractor;
|
|
5
|
+
constructor(page) {
|
|
6
|
+
this.page = page;
|
|
7
|
+
this.extractor = new Extractor();
|
|
8
|
+
}
|
|
9
|
+
async open(url) {
|
|
10
|
+
await this.page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
11
|
+
}
|
|
12
|
+
async getState() {
|
|
13
|
+
return this.extractor.extract(this.page);
|
|
14
|
+
}
|
|
15
|
+
async resolve(target) {
|
|
16
|
+
const cleaned = target
|
|
17
|
+
.replace(/^\w+:\s*/, '') // remove 'link: ' or 'button: ' prefix
|
|
18
|
+
.replace(/^"|"$/g, '') // remove surrounding quotes
|
|
19
|
+
.trim();
|
|
20
|
+
const strategies = [
|
|
21
|
+
() => this.page.getByRole('button', { name: cleaned, exact: false }).first(),
|
|
22
|
+
() => this.page.getByRole('link', { name: cleaned, exact: false }).first(),
|
|
23
|
+
() => this.page.getByRole('textbox', { name: cleaned, exact: false }).first(),
|
|
24
|
+
() => this.page.getByRole('combobox', { name: cleaned, exact: false }).first(),
|
|
25
|
+
() => this.page.getByRole('checkbox', { name: cleaned, exact: false }).first(),
|
|
26
|
+
() => this.page.getByLabel(cleaned, { exact: false }).first(),
|
|
27
|
+
() => this.page.getByPlaceholder(cleaned, { exact: false }).first(),
|
|
28
|
+
() => this.page.getByText(cleaned, { exact: false }).first(),
|
|
29
|
+
];
|
|
30
|
+
for (const strategy of strategies) {
|
|
31
|
+
try {
|
|
32
|
+
const el = strategy();
|
|
33
|
+
if (await el.isVisible().catch(() => false)) {
|
|
34
|
+
return el;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
async execute(action) {
|
|
43
|
+
try {
|
|
44
|
+
switch (action.type) {
|
|
45
|
+
case 'click': {
|
|
46
|
+
// find element by description, click it
|
|
47
|
+
const el = await this.resolve(action.target);
|
|
48
|
+
if (!el)
|
|
49
|
+
return { success: false, error: `Element not found: "${action.target}"` };
|
|
50
|
+
await el.click({ timeout: 5000 });
|
|
51
|
+
// wait for page to settle after click
|
|
52
|
+
await this.page.waitForLoadState('domcontentloaded', { timeout: 5000 }).catch(() => { });
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case 'type': {
|
|
56
|
+
// find input, fill it with value
|
|
57
|
+
const el = await this.resolve(action.target);
|
|
58
|
+
if (!el)
|
|
59
|
+
return { success: false, error: `Element not found: "${action.target}"` };
|
|
60
|
+
await el.fill(action.value ?? '');
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case 'scroll': {
|
|
64
|
+
// scroll direction comes in action.value: up/down/left/right
|
|
65
|
+
const directions = {
|
|
66
|
+
up: [0, -500],
|
|
67
|
+
down: [0, 500],
|
|
68
|
+
left: [-500, 0],
|
|
69
|
+
right: [500, 0],
|
|
70
|
+
};
|
|
71
|
+
const [x, y] = directions[action.value ?? 'down'] ?? [0, 500];
|
|
72
|
+
await this.page.mouse.wheel(x, y);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case 'select': {
|
|
76
|
+
// dropdown selection
|
|
77
|
+
const el = await this.resolve(action.target);
|
|
78
|
+
if (!el)
|
|
79
|
+
return { success: false, error: `Element not found: "${action.target}"` };
|
|
80
|
+
await el.selectOption(action.value ?? '');
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case 'hover': {
|
|
84
|
+
const el = await this.resolve(action.target);
|
|
85
|
+
if (!el)
|
|
86
|
+
return { success: false, error: `Element not found: "${action.target}"` };
|
|
87
|
+
await el.hover();
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case 'press': {
|
|
91
|
+
// keyboard key press - Enter, Tab, Escape, ArrowDown etc
|
|
92
|
+
await this.page.keyboard.press(action.value ?? 'Enter');
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case 'open': {
|
|
96
|
+
await this.page.goto(action.value ?? '', { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case 'wait': {
|
|
100
|
+
await this.page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => { });
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
case 'done':
|
|
104
|
+
case 'stuck':
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
return { success: true };
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
error: err instanceof Error ? err.message : String(err)
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async close() {
|
|
117
|
+
await this.page.close();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/adapters/web.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAGnC,eAAO,MAAM,aAAa,SAKpB,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { listPersonas } from '../../personas/loader.js';
|
|
3
|
+
export const agentsCommand = new Command('agents')
|
|
4
|
+
.description('List available agents')
|
|
5
|
+
.option('--list', 'Show all available agents')
|
|
6
|
+
.action(async () => {
|
|
7
|
+
await listPersonas();
|
|
8
|
+
});
|
|
9
|
+
//# sourceMappingURL=agents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../../../src/cli/commands/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEvD,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC;KAC7C,WAAW,CAAC,uBAAuB,CAAC;KACpC,MAAM,CAAC,QAAQ,EAAE,2BAA2B,CAAC;KAC7C,MAAM,CAAC,KAAK,IAAI,EAAE;IACf,MAAM,YAAY,EAAE,CAAA;AACxB,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,eAAO,MAAM,UAAU,SA2CjB,CAAA"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { Orchestrator } from '../../core/orchestrator.js';
|
|
5
|
+
import { LLM } from '../../llm/index.js';
|
|
6
|
+
import { loadPersonas } from '../../personas/loader.js';
|
|
7
|
+
import { getConfig } from '../../lib/getConfigs.js';
|
|
8
|
+
import { agentDone, agentStarted, printFinding, printHeader, printSummary } from '../../lib/display.js';
|
|
9
|
+
import { generateReport, saveReport } from "../../core/reporter.js";
|
|
10
|
+
export const runCommand = new Command('run')
|
|
11
|
+
.description('Run AI agents against your app')
|
|
12
|
+
.requiredOption('--url <url>', 'URL to test')
|
|
13
|
+
.requiredOption('--goal <goal>', 'What to achieve')
|
|
14
|
+
.option('--agent <agents>', 'Specific agent(s) to run, comma separated')
|
|
15
|
+
.option('--steps <number>', 'Max steps per agent', '25')
|
|
16
|
+
.option('--concurrency <number>', 'Agents running in parallel', '2')
|
|
17
|
+
.option('--headed', 'Run browser in headed mode')
|
|
18
|
+
.option('--round-robin', 'Spread load across multiple providers to avoid rate limiting')
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
const config = getConfig();
|
|
21
|
+
if (options.roundRobin && !config.roundRobin?.length) {
|
|
22
|
+
console.warn(chalk.yellow(' ⚠ --round-robin flag used but no round robin providers configured. Run crawlix setup to add them.'));
|
|
23
|
+
}
|
|
24
|
+
const llm = new LLM(config.primary, config.fallback, options.roundRobin ? config.roundRobin : undefined);
|
|
25
|
+
const { url, goal, agent, steps, concurrency, headed } = options;
|
|
26
|
+
const personas = await loadPersonas(agent);
|
|
27
|
+
printHeader(url, goal, personas.map(p => p.name));
|
|
28
|
+
const orchestrator = new Orchestrator({
|
|
29
|
+
url,
|
|
30
|
+
goal,
|
|
31
|
+
llm,
|
|
32
|
+
personas,
|
|
33
|
+
maxSteps: parseInt(steps),
|
|
34
|
+
concurrency: parseInt(concurrency),
|
|
35
|
+
headless: !headed,
|
|
36
|
+
onAgentStart: (name) => agentStarted(name),
|
|
37
|
+
onAgentDone: (result) => {
|
|
38
|
+
agentDone(result);
|
|
39
|
+
result.findings.forEach(f => printFinding(f, result.persona));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const results = await orchestrator.run();
|
|
43
|
+
printSummary(results);
|
|
44
|
+
const reportSpinner = ora({ text: chalk.gray('generating report...'), spinner: 'dots' }).start();
|
|
45
|
+
const { title, report } = await generateReport(results, url, goal, llm);
|
|
46
|
+
const filepath = saveReport(title, report);
|
|
47
|
+
reportSpinner.stopAndPersist({ symbol: '📋', text: chalk.gray('report saved → ') + chalk.white(filepath) });
|
|
48
|
+
});
|
|
49
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/cli/commands/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AA0DnC,eAAO,MAAM,YAAY,SAoDnB,CAAA"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { select, input, password, confirm } from '@inquirer/prompts';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
const DEFAULT_MODELS = {
|
|
7
|
+
groq: 'llama-3.3-70b-versatile',
|
|
8
|
+
openai: 'gpt-4o-mini',
|
|
9
|
+
gemini: 'gemini-2.5-flash',
|
|
10
|
+
anthropic: 'claude-haiku-4-5-20251001',
|
|
11
|
+
openrouter: 'meta-llama/llama-3.3-70b-instruct:free',
|
|
12
|
+
ollama: 'llama3.2',
|
|
13
|
+
cerebras: 'gpt-oss-120b',
|
|
14
|
+
mistral: 'mistral-medium-3-5',
|
|
15
|
+
};
|
|
16
|
+
async function setupProvider(label) {
|
|
17
|
+
const provider = await select({
|
|
18
|
+
message: `Select ${label} provider`,
|
|
19
|
+
choices: [
|
|
20
|
+
{ name: 'Groq', value: 'groq' },
|
|
21
|
+
{ name: 'Gemini', value: 'gemini' },
|
|
22
|
+
{ name: 'Cerebras', value: 'cerebras' },
|
|
23
|
+
{ name: 'Mistral', value: 'mistral' },
|
|
24
|
+
{ name: 'OpenRouter', value: 'openrouter' },
|
|
25
|
+
{ name: 'Ollama', value: 'ollama' },
|
|
26
|
+
{ name: 'OpenAI', value: 'openai' },
|
|
27
|
+
{ name: 'Anthropic', value: 'anthropic' },
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
let apiKey;
|
|
31
|
+
if (provider === 'ollama') {
|
|
32
|
+
console.log(' ✓ Ollama runs locally — no API key needed');
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
apiKey = await password({
|
|
36
|
+
message: `Enter your ${provider} API key:`,
|
|
37
|
+
validate: v => v.trim().length > 0 ? true : 'API key cannot be empty'
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const defaultModel = DEFAULT_MODELS[provider];
|
|
41
|
+
const customModel = await input({
|
|
42
|
+
message: `Model to use (press enter for default: ${defaultModel}):`,
|
|
43
|
+
});
|
|
44
|
+
const config = {
|
|
45
|
+
provider,
|
|
46
|
+
...(apiKey && { apiKey }),
|
|
47
|
+
...(customModel.trim().length > 0 && { model: customModel.trim() }),
|
|
48
|
+
};
|
|
49
|
+
return config;
|
|
50
|
+
}
|
|
51
|
+
export const setupCommand = new Command('setup')
|
|
52
|
+
.description('Configure your LLM provider')
|
|
53
|
+
.action(async () => {
|
|
54
|
+
console.log('\n 👾 crawlix setup\n');
|
|
55
|
+
const primary = await setupProvider('primary');
|
|
56
|
+
const wantsFallback = await confirm({
|
|
57
|
+
message: 'Add a fallback provider?',
|
|
58
|
+
default: false,
|
|
59
|
+
});
|
|
60
|
+
let fallback;
|
|
61
|
+
if (wantsFallback) {
|
|
62
|
+
fallback = await setupProvider('fallback');
|
|
63
|
+
}
|
|
64
|
+
// after fallback setup
|
|
65
|
+
const wantsRoundRobin = await confirm({
|
|
66
|
+
message: 'Add round robin providers to spread load across multiple providers?',
|
|
67
|
+
default: false,
|
|
68
|
+
});
|
|
69
|
+
const roundRobin = [];
|
|
70
|
+
if (wantsRoundRobin) {
|
|
71
|
+
console.log('\n Add providers one by one. Press N when done.\n');
|
|
72
|
+
let addMore = true;
|
|
73
|
+
while (addMore) {
|
|
74
|
+
const provider = await setupProvider(`round robin #${roundRobin.length + 1}`);
|
|
75
|
+
roundRobin.push(provider);
|
|
76
|
+
addMore = await confirm({ message: 'Add another provider?', default: false });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// build config
|
|
80
|
+
const crawlixConfig = {
|
|
81
|
+
primary,
|
|
82
|
+
...(fallback && { fallback }),
|
|
83
|
+
...(roundRobin.length > 0 && { roundRobin }),
|
|
84
|
+
};
|
|
85
|
+
// save to ~/.crawlix/crawlix.config.json
|
|
86
|
+
const configDir = path.join(os.homedir(), '.crawlix');
|
|
87
|
+
const configPath = path.join(configDir, 'crawlix.config.json');
|
|
88
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
89
|
+
fs.writeFileSync(configPath, JSON.stringify(crawlixConfig, null, 2));
|
|
90
|
+
console.log('\n ✓ Config saved to', configPath);
|
|
91
|
+
console.log(' Run crawlix run --url <url> --goal "<goal>" to start testing\n');
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../src/cli/commands/setup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { runCommand } from './commands/run.js';
|
|
3
|
+
import { setupCommand } from './commands/setup.js';
|
|
4
|
+
import { agentsCommand } from './commands/agents.js';
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const pkg = require('../../package.json');
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program
|
|
10
|
+
.name('crawlix')
|
|
11
|
+
.description('👾 Crawlix: Claw through bugs before your users do.')
|
|
12
|
+
.version(pkg.version);
|
|
13
|
+
program.addCommand(runCommand);
|
|
14
|
+
program.addCommand(setupCommand);
|
|
15
|
+
program.addCommand(agentsCommand);
|
|
16
|
+
program.parse();
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA;AACtC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC9C,MAAM,GAAG,GAAG,OAAO,CAAC,oBAAoB,CAAwB,CAAA;AAChE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO;KACF,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,qDAAqD,CAAC;KAClE,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;AAEzB,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAA;AAC9B,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAA;AAChC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAA;AAEjC,OAAO,CAAC,KAAK,EAAE,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Action, HistoryEntry, PageState, PersonaConfig } from "../types/index.js";
|
|
2
|
+
import { LLM } from "../llm/index.js";
|
|
3
|
+
export declare class Director {
|
|
4
|
+
private persona;
|
|
5
|
+
private llm;
|
|
6
|
+
private goal;
|
|
7
|
+
constructor(persona: PersonaConfig, llm: LLM, goal: string);
|
|
8
|
+
get personaName(): string;
|
|
9
|
+
private buildSystemPrompt;
|
|
10
|
+
private buildUserMessage;
|
|
11
|
+
private parseAction;
|
|
12
|
+
decide(pageState: PageState, history: HistoryEntry[]): Promise<Action>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=director.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"director.d.ts","sourceRoot":"","sources":["../../src/core/director.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAY,SAAS,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClG,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAEtC,qBAAa,QAAQ;IACjB,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,IAAI,CAAS;gBAGT,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM;IAM1D,IAAI,WAAW,IAAI,MAAM,CAExB;IAGD,OAAO,CAAC,iBAAiB;IAuCzB,OAAO,CAAC,gBAAgB;IA+BxB,OAAO,CAAC,WAAW;IA0BN,MAAM,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAStF"}
|