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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +187 -0
  3. package/dist/adapters/base.d.ts +8 -0
  4. package/dist/adapters/base.d.ts.map +1 -0
  5. package/dist/adapters/base.js +2 -0
  6. package/dist/adapters/base.js.map +1 -0
  7. package/dist/adapters/web.d.ts +14 -0
  8. package/dist/adapters/web.d.ts.map +1 -0
  9. package/dist/adapters/web.js +120 -0
  10. package/dist/adapters/web.js.map +1 -0
  11. package/dist/cli/commands/agents.d.ts +3 -0
  12. package/dist/cli/commands/agents.d.ts.map +1 -0
  13. package/dist/cli/commands/agents.js +9 -0
  14. package/dist/cli/commands/agents.js.map +1 -0
  15. package/dist/cli/commands/run.d.ts +3 -0
  16. package/dist/cli/commands/run.d.ts.map +1 -0
  17. package/dist/cli/commands/run.js +49 -0
  18. package/dist/cli/commands/run.js.map +1 -0
  19. package/dist/cli/commands/setup.d.ts +3 -0
  20. package/dist/cli/commands/setup.d.ts.map +1 -0
  21. package/dist/cli/commands/setup.js +93 -0
  22. package/dist/cli/commands/setup.js.map +1 -0
  23. package/dist/cli/index.d.ts +2 -0
  24. package/dist/cli/index.d.ts.map +1 -0
  25. package/dist/cli/index.js +17 -0
  26. package/dist/cli/index.js.map +1 -0
  27. package/dist/core/director.d.ts +14 -0
  28. package/dist/core/director.d.ts.map +1 -0
  29. package/dist/core/director.js +112 -0
  30. package/dist/core/director.js.map +1 -0
  31. package/dist/core/extractor.d.ts +6 -0
  32. package/dist/core/extractor.d.ts.map +1 -0
  33. package/dist/core/extractor.js +42 -0
  34. package/dist/core/extractor.js.map +1 -0
  35. package/dist/core/orchestrator.d.ts +27 -0
  36. package/dist/core/orchestrator.d.ts.map +1 -0
  37. package/dist/core/orchestrator.js +62 -0
  38. package/dist/core/orchestrator.js.map +1 -0
  39. package/dist/core/reporter.d.ts +8 -0
  40. package/dist/core/reporter.d.ts.map +1 -0
  41. package/dist/core/reporter.js +132 -0
  42. package/dist/core/reporter.js.map +1 -0
  43. package/dist/core/runner.d.ts +13 -0
  44. package/dist/core/runner.d.ts.map +1 -0
  45. package/dist/core/runner.js +61 -0
  46. package/dist/core/runner.js.map +1 -0
  47. package/dist/lib/browser.d.ts +4 -0
  48. package/dist/lib/browser.d.ts.map +1 -0
  49. package/dist/lib/browser.js +24 -0
  50. package/dist/lib/browser.js.map +1 -0
  51. package/dist/lib/display.d.ts +7 -0
  52. package/dist/lib/display.d.ts.map +1 -0
  53. package/dist/lib/display.js +80 -0
  54. package/dist/lib/display.js.map +1 -0
  55. package/dist/lib/getConfigs.d.ts +3 -0
  56. package/dist/lib/getConfigs.d.ts.map +1 -0
  57. package/dist/lib/getConfigs.js +12 -0
  58. package/dist/lib/getConfigs.js.map +1 -0
  59. package/dist/llm/index.d.ts +15 -0
  60. package/dist/llm/index.d.ts.map +1 -0
  61. package/dist/llm/index.js +147 -0
  62. package/dist/llm/index.js.map +1 -0
  63. package/dist/personas/index.d.ts +5 -0
  64. package/dist/personas/index.d.ts.map +1 -0
  65. package/dist/personas/index.js +100 -0
  66. package/dist/personas/index.js.map +1 -0
  67. package/dist/personas/loader.d.ts +4 -0
  68. package/dist/personas/loader.d.ts.map +1 -0
  69. package/dist/personas/loader.js +72 -0
  70. package/dist/personas/loader.js.map +1 -0
  71. package/dist/types/index.d.ts +76 -0
  72. package/dist/types/index.d.ts.map +1 -0
  73. package/dist/types/index.js +3 -0
  74. package/dist/types/index.js.map +1 -0
  75. package/package.json +57 -0
@@ -0,0 +1,80 @@
1
+ import chalk from 'chalk';
2
+ import ora, {} from 'ora';
3
+ import boxen from 'boxen';
4
+ const spinners = new Map();
5
+ export function agentStarted(personaName) {
6
+ const spinner = ora({
7
+ text: chalk.gray(`${personaName} — running...`),
8
+ spinner: 'dots',
9
+ }).start();
10
+ spinners.set(personaName, spinner);
11
+ }
12
+ export function agentDone(result) {
13
+ const spinner = spinners.get(result.persona);
14
+ if (!spinner)
15
+ return;
16
+ const critical = result.findings.filter(f => f.severity === 'critical').length;
17
+ const warnings = result.findings.filter(f => f.severity === 'warning').length;
18
+ const info = result.findings.filter(f => f.severity === 'info').length;
19
+ const duration = (result.duration / 1000).toFixed(1);
20
+ const findings = result.findings.length === 0
21
+ ? chalk.green('no findings')
22
+ : [
23
+ critical > 0 ? chalk.red(`${critical} critical`) : null,
24
+ warnings > 0 ? chalk.yellow(`${warnings} warnings`) : null,
25
+ info > 0 ? chalk.gray(`${info} info`) : null,
26
+ ].filter(Boolean).join(chalk.gray(' · '));
27
+ const status = result.goalReached
28
+ ? chalk.green('✓')
29
+ : result.stuck
30
+ ? chalk.red('✗')
31
+ : chalk.yellow('~');
32
+ spinner.stopAndPersist({
33
+ symbol: status,
34
+ text: `${chalk.white(result.persona.padEnd(16))} ${findings} ${chalk.gray(`${result.steps} steps · ${duration}s`)}`,
35
+ });
36
+ spinners.delete(result.persona);
37
+ }
38
+ export function printFinding(finding, persona) {
39
+ const icon = finding.severity === 'critical'
40
+ ? chalk.red('⚠ critical')
41
+ : finding.severity === 'warning'
42
+ ? chalk.yellow('⚠ warning')
43
+ : chalk.gray('ℹ info');
44
+ console.log(` ${icon} ${chalk.gray(`[${persona}]`)} ${finding.description}`);
45
+ if (finding.element) {
46
+ console.log(` ${chalk.gray('element:')} ${finding.element}`);
47
+ }
48
+ }
49
+ export function printSummary(results) {
50
+ const allFindings = results.flatMap(r => r.findings);
51
+ const critical = allFindings.filter(f => f.severity === 'critical').length;
52
+ const warnings = allFindings.filter(f => f.severity === 'warning').length;
53
+ const info = allFindings.filter(f => f.severity === 'info').length;
54
+ const passed = results.filter(r => r.goalReached).length;
55
+ const stuck = results.filter(r => r.stuck).length;
56
+ const totalTime = results.reduce((acc, r) => acc + r.duration, 0);
57
+ const lines = [
58
+ chalk.bold.white('👾 Crawlix — run complete'),
59
+ '',
60
+ ` ${chalk.red(`${critical} critical`)} ${chalk.yellow(`${warnings} warnings`)} ${chalk.gray(`${info} info`)}`,
61
+ '',
62
+ ` ${chalk.green(`${passed} passed`)} ${chalk.red(`${stuck} stuck`)} ${chalk.gray(`${results.length - passed - stuck} incomplete`)}`,
63
+ '',
64
+ chalk.gray(` total time → ${(totalTime / 1000).toFixed(1)}s`),
65
+ ].join('\n');
66
+ console.log('\n' + boxen(lines, {
67
+ padding: 1,
68
+ borderColor: 'magenta',
69
+ borderStyle: 'round',
70
+ dimBorder: false,
71
+ }));
72
+ }
73
+ export function printHeader(url, goal, personas) {
74
+ console.log('\n' + chalk.bold.white(' 👾 Crawlix - Claw through bugs before your users do.'));
75
+ console.log(chalk.gray(` target → `) + chalk.white(url));
76
+ console.log(chalk.gray(` goal → `) + chalk.white(goal));
77
+ console.log(chalk.gray(` agents → `) + chalk.white(personas.join(', ')));
78
+ console.log();
79
+ }
80
+ //# sourceMappingURL=display.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"display.js","sourceRoot":"","sources":["../../src/lib/display.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,GAAG,EAAE,EAAY,MAAM,KAAK,CAAA;AACnC,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAe,CAAA;AAEvC,MAAM,UAAU,YAAY,CAAC,WAAmB;IAC9C,MAAM,OAAO,GAAG,GAAG,CAAC;QAClB,IAAI,EAAK,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,eAAe,CAAC;QAClD,OAAO,EAAE,MAAM;KAChB,CAAC,CAAC,KAAK,EAAE,CAAA;IACV,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;AACpC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAAiB;IACzC,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAC5C,IAAI,CAAC,OAAO;QAAE,OAAM;IAEpB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IAC9E,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,CAAA;IAC7E,MAAM,IAAI,GAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,MAAM,CAAA;IAC1E,MAAM,QAAQ,GAAG,CAAC,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IAEpD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;QAC3C,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC;QAC5B,CAAC,CAAC;YACE,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,QAAQ,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI;YACvD,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,QAAQ,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI;YAC1D,IAAI,GAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,CAAU,CAAC,CAAC,IAAI;SAC1D,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IAE7C,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW;QAC/B,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;QAClB,CAAC,CAAC,MAAM,CAAC,KAAK;YACZ,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;YAChB,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAEvB,OAAO,CAAC,cAAc,CAAC;QACrB,MAAM,EAAE,MAAM;QACd,IAAI,EAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,QAAQ,KAAK,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,YAAY,QAAQ,GAAG,CAAC,EAAE;KACvH,CAAC,CAAA;IAEF,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;AACjC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAgB,EAAE,OAAe;IAC5D,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,KAAK,UAAU;QAC1C,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC;QACzB,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS;YAC9B,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC;YAC3B,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAE1B,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;IAC9E,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IAC/D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAoB;IAC/C,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;IACpD,MAAM,QAAQ,GAAM,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IAC7E,MAAM,QAAQ,GAAM,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,CAAA;IAC5E,MAAM,IAAI,GAAU,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,MAAM,CAAA;IACzE,MAAM,MAAM,GAAQ,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,MAAM,CAAA;IAC7D,MAAM,KAAK,GAAS,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAA;IACvD,MAAM,SAAS,GAAK,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;IAEnE,MAAM,KAAK,GAAG;QACZ,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,2BAA2B,CAAC;QAC7C,EAAE;QACF,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,QAAQ,WAAW,CAAC,KAAK,KAAK,CAAC,MAAM,CAAC,GAAG,QAAQ,WAAW,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,EAAE;QAChH,EAAE;QACF,KAAK,KAAK,CAAC,KAAK,CAAC,GAAG,MAAM,SAAS,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,QAAQ,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,GAAG,KAAK,aAAa,CAAC,EAAE;QACtI,EAAE;QACF,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;KAC/D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEZ,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,EAAE;QAC9B,OAAO,EAAM,CAAC;QACd,WAAW,EAAE,SAAS;QACtB,WAAW,EAAE,OAAO;QACpB,SAAS,EAAI,KAAK;KACnB,CAAC,CAAC,CAAA;AACL,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,IAAY,EAAE,QAAkB;IACvE,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC,CAAA;IAC9F,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;IAC3D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;IAC5D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC3E,OAAO,CAAC,GAAG,EAAE,CAAA;AACf,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { CrawlixConfig } from '../types/index.js';
2
+ export declare function getConfig(): CrawlixConfig;
3
+ //# sourceMappingURL=getConfigs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getConfigs.d.ts","sourceRoot":"","sources":["../../src/lib/getConfigs.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,wBAAgB,SAAS,IAAI,aAAa,CAOzC"}
@@ -0,0 +1,12 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ export function getConfig() {
5
+ const configPath = path.join(os.homedir(), '.crawlix', 'crawlix.config.json');
6
+ if (!fs.existsSync(configPath)) {
7
+ throw new Error("Config file not found. Run 'crawlix setup' to create one.");
8
+ }
9
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
10
+ return config;
11
+ }
12
+ //# sourceMappingURL=getConfigs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getConfigs.js","sourceRoot":"","sources":["../../src/lib/getConfigs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,IAAI,MAAM,MAAM,CAAA;AAGvB,MAAM,UAAU,SAAS;IACrB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,qBAAqB,CAAC,CAAC;IAC9E,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;IACjF,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;IAChE,OAAO,MAAM,CAAC;AAClB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { ProviderConfig, LLMInput, LLMOutput } from "../types/index.js";
2
+ export declare class LLM {
3
+ private primaryConfig;
4
+ private fallbackConfig?;
5
+ private client;
6
+ private fallbackClient?;
7
+ private roundRobinProviders;
8
+ private roundRobinClients;
9
+ private roundRobinIndex;
10
+ private useRoundRobin;
11
+ constructor(primaryConfig: ProviderConfig, fallbackConfig?: ProviderConfig, roundRobin?: ProviderConfig[]);
12
+ complete(input: LLMInput): Promise<LLMOutput>;
13
+ private callProvider;
14
+ }
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/llm/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAgB,MAAM,mBAAmB,CAAC;AAoC3F,qBAAa,GAAG;IACZ,OAAO,CAAC,aAAa,CAAiB;IACtC,OAAO,CAAC,cAAc,CAAC,CAA6B;IAEpD,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,cAAc,CAAC,CAAiC;IACxD,OAAO,CAAC,mBAAmB,CAAwB;IACnD,OAAO,CAAC,iBAAiB,CAA8B;IACvD,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,aAAa,CAAkB;gBAE3B,aAAa,EAAE,cAAc,EAAE,cAAc,CAAC,EAAE,cAAc,EAAE,UAAU,CAAC,EAAE,cAAc,EAAE;IAcnG,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;YAsDrC,YAAY;CA2D7B"}
@@ -0,0 +1,147 @@
1
+ import Anthropic from "@anthropic-ai/sdk";
2
+ import OpenAI from "openai";
3
+ // Default baseURLs per provider
4
+ const BASE_URLS = {
5
+ groq: 'https://api.groq.com/openai/v1',
6
+ gemini: 'https://generativelanguage.googleapis.com/v1beta/openai',
7
+ openrouter: 'https://openrouter.ai/api/v1',
8
+ ollama: 'http://localhost:11434/v1',
9
+ cerebras: 'https://api.cerebras.ai/v1',
10
+ mistral: 'https://api.mistral.ai/v1',
11
+ };
12
+ const DEFAULT_MODELS = {
13
+ groq: 'llama-3.3-70b-versatile',
14
+ openai: 'gpt-4o-mini',
15
+ gemini: 'gemini-2.5-flash',
16
+ anthropic: 'claude-haiku-4-5-20251001',
17
+ openrouter: 'meta-llama/llama-3.3-70b-instruct:free',
18
+ ollama: 'llama3.2',
19
+ cerebras: 'gpt-oss-120b',
20
+ mistral: 'mistral-medium-3-5',
21
+ };
22
+ // Client factory
23
+ function createClient(config) {
24
+ if (config.provider === 'anthropic') {
25
+ return new Anthropic({ apiKey: config.apiKey });
26
+ }
27
+ return new OpenAI({
28
+ apiKey: config.apiKey ?? 'no-key-needed', // ollama doesn't need a key
29
+ baseURL: config.baseURL ?? BASE_URLS[config.provider],
30
+ });
31
+ }
32
+ // LLM
33
+ export class LLM {
34
+ primaryConfig;
35
+ fallbackConfig;
36
+ client;
37
+ fallbackClient;
38
+ roundRobinProviders = [];
39
+ roundRobinClients = [];
40
+ roundRobinIndex = 0;
41
+ useRoundRobin = false;
42
+ constructor(primaryConfig, fallbackConfig, roundRobin) {
43
+ this.primaryConfig = primaryConfig;
44
+ this.fallbackConfig = fallbackConfig;
45
+ if (roundRobin && roundRobin.length > 0) {
46
+ this.useRoundRobin = true;
47
+ this.roundRobinProviders = roundRobin;
48
+ this.roundRobinClients = roundRobin.map(c => createClient(c));
49
+ }
50
+ this.client = createClient(primaryConfig);
51
+ if (fallbackConfig) {
52
+ this.fallbackClient = createClient(fallbackConfig);
53
+ }
54
+ }
55
+ async complete(input) {
56
+ if (this.useRoundRobin && this.roundRobinProviders.length > 0) {
57
+ // try current provider
58
+ const index = this.roundRobinIndex % this.roundRobinProviders.length;
59
+ const config = this.roundRobinProviders[index];
60
+ const client = this.roundRobinClients[index];
61
+ this.roundRobinIndex++;
62
+ try {
63
+ return await this.callProvider(client, config, input);
64
+ }
65
+ catch (err) {
66
+ const isRateLimit = err?.status === 429 ||
67
+ String(err).toLowerCase().includes('rate');
68
+ if (isRateLimit) {
69
+ // remove current provider from available pool temporarily
70
+ const available = this.roundRobinProviders
71
+ .map((p, i) => ({ p, i }))
72
+ .filter(({ i }) => i !== index);
73
+ if (available.length === 0)
74
+ throw err;
75
+ // pick randomly from remaining
76
+ const random = available[Math.floor(Math.random() * available.length)];
77
+ console.warn(`👾 ${config.provider} rate limited — randomly switching to ${this.roundRobinProviders[random.i].provider}`);
78
+ return await this.callProvider(this.roundRobinClients[random.i], this.roundRobinProviders[random.i], input);
79
+ }
80
+ throw err;
81
+ }
82
+ }
83
+ try {
84
+ return await this.callProvider(this.client, this.primaryConfig, input);
85
+ }
86
+ catch (err) {
87
+ const isRateLimit = err?.status === 429 ||
88
+ String(err).toLowerCase().includes('rate');
89
+ if (isRateLimit && this.fallbackClient && this.fallbackConfig) {
90
+ console.warn(`👾 ${this.primaryConfig.provider} rate limited — falling back to ${this.fallbackConfig.provider}`);
91
+ return await this.callProvider(this.fallbackClient, this.fallbackConfig, input);
92
+ }
93
+ throw err;
94
+ }
95
+ }
96
+ async callProvider(client, config, input) {
97
+ const model = config.model ?? DEFAULT_MODELS[config.provider];
98
+ // Anthropic
99
+ if (client instanceof Anthropic) {
100
+ const res = await client.messages.create({
101
+ model,
102
+ max_tokens: 2048,
103
+ system: [{
104
+ type: 'text',
105
+ text: input.system,
106
+ cache_control: { type: 'ephemeral' }
107
+ }],
108
+ messages: input.messages.map(m => ({
109
+ role: m.role,
110
+ content: m.content,
111
+ })),
112
+ });
113
+ const block = res.content[0];
114
+ if (!block || block.type !== 'text') {
115
+ throw new Error('Anthropic returned empty response');
116
+ }
117
+ return {
118
+ content: block.text,
119
+ provider: config.provider,
120
+ model,
121
+ };
122
+ }
123
+ // OpenAI-compatible (Groq, Gemini, etc.)
124
+ const res = await client.chat.completions.create({
125
+ model,
126
+ temperature: 0.2,
127
+ max_tokens: 2048,
128
+ response_format: { type: 'json_object' },
129
+ messages: [
130
+ { role: 'system', content: input.system },
131
+ ...input.messages.map(m => ({
132
+ role: m.role,
133
+ content: m.content,
134
+ })),
135
+ ],
136
+ });
137
+ const content = res.choices[0]?.message?.content;
138
+ if (!content)
139
+ throw new Error(`${config.provider} returned empty response`);
140
+ return {
141
+ content,
142
+ provider: config.provider,
143
+ model,
144
+ };
145
+ }
146
+ }
147
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/llm/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { PersonaConfig } from '../types/index.js';
2
+ export declare const PERSONAS: Record<string, PersonaConfig>;
3
+ export declare function resolvePersona(input: PersonaConfig | string): PersonaConfig;
4
+ export declare const PERSONA_NAMES: Array<keyof typeof PERSONAS>;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/personas/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAGtD,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAwFlD,CAAA;AAID,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,GAAG,aAAa,CAa3E;AAID,eAAO,MAAM,aAAa,EAA4B,KAAK,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAA"}
@@ -0,0 +1,100 @@
1
+ // Persona Definitions
2
+ export const PERSONAS = {
3
+ 'first-timer': {
4
+ name: 'First-Timer',
5
+ description: 'A new user encountering this product for the first time',
6
+ patience: 7,
7
+ aggression: 1,
8
+ readingBehavior: 'thorough',
9
+ systemPrompt: `You are a first-time user of this application. You have no prior knowledge
10
+ of how it works. You read everything carefully before clicking. You get lost easily
11
+ and click whatever looks most obvious. You notice when labels are confusing or when
12
+ the UI doesn't explain what it does. You try the happy path but may misread instructions.
13
+ You stop and re-read if something doesn't work. You sometimes click the wrong button
14
+ by accident. You never use keyboard shortcuts.`,
15
+ },
16
+ 'impatient': {
17
+ name: 'Impatient',
18
+ description: 'A busy user who skims and clicks fast without reading',
19
+ patience: 3,
20
+ aggression: 4,
21
+ readingBehavior: 'skip',
22
+ systemPrompt: `You are a busy, impatient user. You skip reading instructions entirely.
23
+ You click the first thing that looks right. You submit forms immediately without
24
+ filling all fields. You abandon flows quickly if they take more than 2 steps.
25
+ You double-click buttons. You spam the submit button if nothing happens immediately.
26
+ You get frustrated by loading states and try to click through them. You ignore
27
+ confirmation dialogs and click the default option.`,
28
+ },
29
+ 'power-user': {
30
+ name: 'Power User',
31
+ description: 'An experienced user who uses advanced features and shortcuts',
32
+ patience: 9,
33
+ aggression: 3,
34
+ readingBehavior: 'skim',
35
+ systemPrompt: `You are an experienced power user. You use keyboard shortcuts whenever
36
+ possible. You try edge cases deliberately: very long inputs, special characters,
37
+ emoji in text fields, extreme values in number fields. You try to navigate directly
38
+ to deep URLs. You open multiple flows. You look for admin or settings panels.
39
+ You test the absolute limits of the application. You notice missing features and
40
+ document them as findings. You move fast but methodically.`,
41
+ },
42
+ 'adversarial': {
43
+ name: 'Adversarial',
44
+ description: 'A malicious user probing for security issues and broken flows',
45
+ patience: 8,
46
+ aggression: 10,
47
+ readingBehavior: 'skim',
48
+ systemPrompt: `You are a malicious user deliberately trying to break the application
49
+ and find security vulnerabilities. You inject script tags into input fields: <script>alert(1)</script>.
50
+ You try SQL injection: ' OR 1=1 --. You enter extremely long strings (500+ characters).
51
+ You try to access other users' data by modifying IDs in URLs. You submit forms
52
+ with missing required fields. You try to upload malicious file types. You look for
53
+ exposed API keys or tokens in the page source. You test every boundary condition.
54
+ You document every vulnerability as a critical finding.`,
55
+ },
56
+ 'non-native': {
57
+ name: 'Non-Native Speaker',
58
+ description: 'A user who reads English as a second language and gets confused by jargon',
59
+ patience: 6,
60
+ aggression: 1,
61
+ readingBehavior: 'thorough',
62
+ systemPrompt: `You are a user for whom English is not your first language. You read
63
+ carefully but misunderstand idioms and technical jargon. You get confused by
64
+ abbreviations and acronyms that aren't explained. You misread instructions when
65
+ they use complex sentence structures. You sometimes click the wrong option because
66
+ labels use synonyms you don't recognize. You note when UI copy is unclear or uses
67
+ unexplained terminology. You are patient and try multiple times before giving up.`,
68
+ },
69
+ 'slow-network': {
70
+ name: 'Slow Network',
71
+ description: 'A user on a throttled connection who encounters loading and timeout issues',
72
+ patience: 5,
73
+ aggression: 2,
74
+ readingBehavior: 'skim',
75
+ systemPrompt: `You are using this application on a very slow mobile network connection.
76
+ Pages take a long time to load. You notice when content loads without a visible
77
+ loading indicator. You record it as a warning whenever you're left staring at
78
+ a blank screen with no feedback. You sometimes click buttons multiple times because
79
+ you think the first click didn't register. You notice layout shifts when content
80
+ loads in. You look for missing skeleton screens and loading states. You document
81
+ every slow response as at least a warning-level finding.`,
82
+ },
83
+ };
84
+ // Resolver
85
+ // Accepts either a persona name string or a full PersonaConfig
86
+ export function resolvePersona(input) {
87
+ if (typeof input === 'string') {
88
+ const persona = PERSONAS[input.toLowerCase()];
89
+ if (!persona) {
90
+ const available = Object.keys(PERSONAS).join(', ');
91
+ throw new Error(`Unknown persona "${input}". Available: ${available}\n` +
92
+ `Or pass a full PersonaConfig object.`);
93
+ }
94
+ return persona;
95
+ }
96
+ return input;
97
+ }
98
+ // All persona names
99
+ export const PERSONA_NAMES = Object.keys(PERSONAS);
100
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/personas/index.ts"],"names":[],"mappings":"AAEA,uBAAuB;AACvB,MAAM,CAAC,MAAM,QAAQ,GAAkC;IAErD,aAAa,EAAE;QACb,IAAI,EAAa,aAAa;QAC9B,WAAW,EAAM,yDAAyD;QAC1E,QAAQ,EAAS,CAAC;QAClB,UAAU,EAAO,CAAC;QAClB,eAAe,EAAE,UAAU;QAC3B,YAAY,EAAE;;;;;+CAK6B;KAC5C;IAED,WAAW,EAAE;QACX,IAAI,EAAa,WAAW;QAC5B,WAAW,EAAM,uDAAuD;QACxE,QAAQ,EAAS,CAAC;QAClB,UAAU,EAAO,CAAC;QAClB,eAAe,EAAE,MAAM;QACvB,YAAY,EAAE;;;;;mDAKiC;KAChD;IAED,YAAY,EAAE;QACZ,IAAI,EAAa,YAAY;QAC7B,WAAW,EAAM,8DAA8D;QAC/E,QAAQ,EAAS,CAAC;QAClB,UAAU,EAAO,CAAC;QAClB,eAAe,EAAE,MAAM;QACvB,YAAY,EAAE;;;;;2DAKyC;KACxD;IAED,aAAa,EAAE;QACb,IAAI,EAAa,aAAa;QAC9B,WAAW,EAAM,+DAA+D;QAChF,QAAQ,EAAS,CAAC;QAClB,UAAU,EAAO,EAAE;QACnB,eAAe,EAAE,MAAM;QACvB,YAAY,EAAE;;;;;;wDAMsC;KACrD;IAED,YAAY,EAAE;QACZ,IAAI,EAAa,oBAAoB;QACrC,WAAW,EAAM,2EAA2E;QAC5F,QAAQ,EAAS,CAAC;QAClB,UAAU,EAAO,CAAC;QAClB,eAAe,EAAE,UAAU;QAC3B,YAAY,EAAE;;;;;kFAKgE;KAC/E;IAED,cAAc,EAAE;QACd,IAAI,EAAa,cAAc;QAC/B,WAAW,EAAM,4EAA4E;QAC7F,QAAQ,EAAS,CAAC;QAClB,UAAU,EAAO,CAAC;QAClB,eAAe,EAAE,MAAM;QACvB,YAAY,EAAE;;;;;;yDAMuC;KACtD;CAEF,CAAA;AAED,YAAY;AACZ,+DAA+D;AAC/D,MAAM,UAAU,cAAc,CAAC,KAA6B;IAC1D,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAA;QAC7C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAClD,MAAM,IAAI,KAAK,CACb,oBAAoB,KAAK,iBAAiB,SAAS,IAAI;gBACvD,sCAAsC,CACvC,CAAA;QACH,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,qBAAqB;AAErB,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAiC,CAAA"}
@@ -0,0 +1,4 @@
1
+ import type { PersonaConfig } from '../types/index.js';
2
+ export declare function loadPersonas(agentFlag?: string): Promise<PersonaConfig[]>;
3
+ export declare function listPersonas(): Promise<void>;
4
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/personas/loader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAyCtD,wBAAsB,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CA0B/E;AAED,wBAAsB,YAAY,kBAmBjC"}
@@ -0,0 +1,72 @@
1
+ import { PERSONAS } from './index.js';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ function isValidPersona(config) {
5
+ const c = config;
6
+ return (typeof c.name === 'string' &&
7
+ typeof c.description === 'string' &&
8
+ typeof c.systemPrompt === 'string' &&
9
+ typeof c.patience === 'number' &&
10
+ typeof c.aggression === 'number' &&
11
+ ['thorough', 'skim', 'skip'].includes(c.readingBehavior));
12
+ }
13
+ async function loadCustomPersonas() {
14
+ const dir = path.resolve(process.cwd(), '.crawlix/agents/');
15
+ if (!fs.existsSync(dir))
16
+ return [];
17
+ const result = [];
18
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
19
+ for (const file of files) {
20
+ try {
21
+ const content = fs.readFileSync(path.join(dir, file), 'utf-8');
22
+ const config = JSON.parse(content);
23
+ if (isValidPersona(config)) {
24
+ result.push(config);
25
+ }
26
+ else {
27
+ console.warn(`👾 skipping ${file} — missing or invalid fields`);
28
+ }
29
+ }
30
+ catch {
31
+ console.warn(`👾 skipping ${file} — invalid JSON`);
32
+ }
33
+ }
34
+ return result;
35
+ }
36
+ export async function loadPersonas(agentFlag) {
37
+ const builtIns = Object.values(PERSONAS);
38
+ // if --agent is specified, only load those
39
+ if (agentFlag) {
40
+ const requested = agentFlag.split(',').map(a => a.trim().toLowerCase());
41
+ const customs = await loadCustomPersonas();
42
+ const allPersonas = [
43
+ ...Object.values(PERSONAS),
44
+ ...customs
45
+ ];
46
+ return requested.map(name => {
47
+ const found = allPersonas.find(p => p.name.toLowerCase() === name);
48
+ if (!found)
49
+ throw new Error(`Unknown agent "${name}". Run 'crawlix agents --list' to see available agents.`);
50
+ return found;
51
+ });
52
+ }
53
+ const customs = await loadCustomPersonas();
54
+ return [...builtIns, ...customs];
55
+ }
56
+ export async function listPersonas() {
57
+ const builtIns = Object.values(PERSONAS);
58
+ const customs = await loadCustomPersonas();
59
+ console.log('\n 👾 available agents\n');
60
+ console.log(' built-in:');
61
+ builtIns.forEach(p => {
62
+ console.log(` ${p.name.padEnd(16)} ${p.description}`);
63
+ });
64
+ if (customs.length > 0) {
65
+ console.log('\n custom:');
66
+ customs.forEach(p => {
67
+ console.log(` ${p.name.padEnd(16)} ${p.description}`);
68
+ });
69
+ }
70
+ console.log();
71
+ }
72
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/personas/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ export type ActionType = 'open' | 'click' | 'type' | 'scroll' | 'select' | 'hover' | 'press' | 'wait' | 'done' | 'stuck';
2
+ export interface Action {
3
+ type: ActionType;
4
+ target?: string;
5
+ value?: string;
6
+ reasoning: string;
7
+ finding?: Finding;
8
+ }
9
+ export interface ActionResult {
10
+ success: boolean;
11
+ error?: string;
12
+ }
13
+ export interface Finding {
14
+ severity: 'critical' | 'warning' | 'info';
15
+ description: string;
16
+ element?: string | undefined;
17
+ screenshot?: string;
18
+ step?: number;
19
+ }
20
+ export interface PageState {
21
+ url?: string;
22
+ title?: string | undefined;
23
+ tree: string;
24
+ timestamp: number;
25
+ }
26
+ export interface HistoryEntry {
27
+ step: number;
28
+ pageState: PageState;
29
+ action: Action;
30
+ }
31
+ export interface PersonaConfig {
32
+ name: string;
33
+ description: string;
34
+ systemPrompt: string;
35
+ patience: number;
36
+ aggression: number;
37
+ readingBehavior: 'thorough' | 'skim' | 'skip';
38
+ maxSteps?: number;
39
+ }
40
+ export type ProviderName = 'groq' | 'openai' | 'anthropic' | 'gemini' | 'openrouter' | 'ollama' | 'cerebras' | 'mistral';
41
+ export interface ProviderConfig {
42
+ provider: ProviderName;
43
+ apiKey?: string;
44
+ model?: string;
45
+ baseURL?: string;
46
+ }
47
+ export interface LLMInput {
48
+ system: string;
49
+ messages: LLMMessage[];
50
+ }
51
+ export interface LLMMessage {
52
+ role: 'user' | 'assistant';
53
+ content: string;
54
+ }
55
+ export interface LLMOutput {
56
+ content: string;
57
+ provider: ProviderName;
58
+ model: string;
59
+ }
60
+ export interface CrawlixConfig {
61
+ primary: ProviderConfig;
62
+ fallback?: ProviderConfig;
63
+ roundRobin?: ProviderConfig[];
64
+ }
65
+ export interface RunResult {
66
+ persona: string;
67
+ url: string;
68
+ goal: string;
69
+ steps: number;
70
+ findings: Finding[];
71
+ goalReached: boolean;
72
+ stuck: boolean;
73
+ duration: number;
74
+ history: HistoryEntry[];
75
+ }
76
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAK,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAE1H,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,UAAU,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAGD,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,CAAA;IACzC,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAGD,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;CAClB;AAGD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,SAAS,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;CACf;AAGD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAAA;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAGD,MAAM,MAAM,YAAY,GAAK,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AAE3H,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,YAAY,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,UAAU,EAAE,CAAA;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IAC1B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,YAAY,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;CACd;AAGD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,cAAc,CAAA;IACvB,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,UAAU,CAAC,EAAE,cAAc,EAAE,CAAA;CAC9B;AAGD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,WAAW,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,OAAO,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB"}
@@ -0,0 +1,3 @@
1
+ // Crawlix — Types
2
+ export {};
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,mBAAmB"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "crawlix",
3
+ "version": "0.1.0",
4
+ "description": "AI testers that think like users. Your users will break it anyway. Crawlix finds it first.",
5
+ "main": "./dist/cli/index.js",
6
+ "exports": {
7
+ ".": "./dist/cli/index.js"
8
+ },
9
+ "keywords": [
10
+ "ai",
11
+ "testing",
12
+ "qa",
13
+ "automation",
14
+ "playwright",
15
+ "agents",
16
+ "llm"
17
+ ],
18
+ "author": "Muhammad Taqi",
19
+ "license": "MIT",
20
+ "devEngines": {
21
+ "packageManager": {
22
+ "name": "pnpm",
23
+ "version": "^11.2.2",
24
+ "onFail": "download"
25
+ }
26
+ },
27
+ "type": "module",
28
+ "bin": {
29
+ "crawlix": "./dist/cli/index.js"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "dependencies": {
37
+ "@anthropic-ai/sdk": "^0.98.0",
38
+ "@inquirer/prompts": "^8.5.0",
39
+ "boxen": "^8.0.1",
40
+ "chalk": "^5.6.2",
41
+ "commander": "^14.0.3",
42
+ "openai": "^6.39.0",
43
+ "ora": "^9.4.0",
44
+ "playwright": "^1.60.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^25.9.1",
48
+ "tsx": "^4.22.3",
49
+ "typescript": "^6.0.3"
50
+ },
51
+ "scripts": {
52
+ "test": "echo \"Error: no test specified\" && exit 1",
53
+ "dev": "tsx src/cli/index.ts",
54
+ "build": "tsc",
55
+ "typecheck": "tsc --noEmit"
56
+ }
57
+ }