qpilot 0.5.3 → 0.7.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 (91) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-build-manifest.json +14 -7
  3. package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
  4. package/.next/standalone/.next/build-manifest.json +2 -2
  5. package/.next/standalone/.next/prerender-manifest.json +10 -10
  6. package/.next/standalone/.next/routes-manifest.json +9 -0
  7. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  9. package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
  10. package/.next/standalone/.next/server/app/api/run/[id]/answer/route.js +1 -1
  11. package/.next/standalone/.next/server/app/api/run/[id]/answer/route_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/api/run/[id]/pause/route.js +1 -1
  13. package/.next/standalone/.next/server/app/api/run/[id]/pause/route_client-reference-manifest.js +1 -1
  14. package/.next/standalone/.next/server/app/api/run/[id]/shot/[num]/route.js +1 -0
  15. package/.next/standalone/.next/server/app/api/run/[id]/shot/[num]/route.js.nft.json +1 -0
  16. package/.next/standalone/.next/server/app/api/run/[id]/shot/[num]/route_client-reference-manifest.js +1 -0
  17. package/.next/standalone/.next/server/app/api/run/[id]/stream/route.js +2 -2
  18. package/.next/standalone/.next/server/app/api/run/[id]/stream/route_client-reference-manifest.js +1 -1
  19. package/.next/standalone/.next/server/app/api/run/route.js +14 -6
  20. package/.next/standalone/.next/server/app/api/run/route_client-reference-manifest.js +1 -1
  21. package/.next/standalone/.next/server/app/index.html +1 -1
  22. package/.next/standalone/.next/server/app/index.rsc +3 -3
  23. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  24. package/.next/standalone/.next/server/app/run/[id]/page.js +1 -1
  25. package/.next/standalone/.next/server/app/run/[id]/page_client-reference-manifest.js +1 -1
  26. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  27. package/.next/standalone/.next/server/chunks/960.js +1 -1
  28. package/.next/standalone/.next/server/functions-config-manifest.json +3 -2
  29. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  30. package/.next/standalone/.next/server/next-font-manifest.json +1 -1
  31. package/.next/standalone/.next/server/pages/404.html +1 -1
  32. package/.next/standalone/.next/server/pages/500.html +1 -1
  33. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  34. package/.next/standalone/.next/static/chunks/app/api/run/[id]/answer/route-e10dd0bc8788750a.js +1 -0
  35. package/.next/standalone/.next/static/chunks/app/api/run/[id]/pause/route-e10dd0bc8788750a.js +1 -0
  36. package/.next/standalone/.next/static/chunks/app/api/run/[id]/shot/[num]/route-e10dd0bc8788750a.js +1 -0
  37. package/.next/standalone/.next/static/chunks/app/api/run/[id]/stream/route-e10dd0bc8788750a.js +1 -0
  38. package/.next/standalone/.next/static/chunks/app/api/run/route-e10dd0bc8788750a.js +1 -0
  39. package/.next/standalone/.next/static/chunks/app/{layout-85e06e327ed56a3c.js → layout-6818616dc0d82320.js} +1 -1
  40. package/.next/standalone/.next/static/chunks/app/run/[id]/page-3b25ede818499602.js +1 -0
  41. package/.next/{static/css/4c75ab9da933ec7d.css → standalone/.next/static/css/2e8aee54e9780b29.css} +1 -1
  42. package/.next/standalone/.next/static/media/013b72fa676f92e0-s.woff2 +0 -0
  43. package/.next/standalone/.next/static/media/2b5b02fc7e511755-s.woff2 +0 -0
  44. package/.next/standalone/.next/static/media/65f03d54ccadf4a8-s.woff2 +0 -0
  45. package/.next/standalone/.next/static/media/7d4881bb7e1bf84d-s.p.woff2 +0 -0
  46. package/.next/standalone/.next/static/media/b9408752a0c24fb9-s.woff2 +0 -0
  47. package/.next/standalone/.next/static/media/e038a29029a234f2-s.woff2 +0 -0
  48. package/.next/standalone/package.json +7 -3
  49. package/.next/static/chunks/app/api/run/[id]/answer/route-e10dd0bc8788750a.js +1 -0
  50. package/.next/static/chunks/app/api/run/[id]/pause/route-e10dd0bc8788750a.js +1 -0
  51. package/.next/static/chunks/app/api/run/[id]/shot/[num]/route-e10dd0bc8788750a.js +1 -0
  52. package/.next/static/chunks/app/api/run/[id]/stream/route-e10dd0bc8788750a.js +1 -0
  53. package/.next/static/chunks/app/api/run/route-e10dd0bc8788750a.js +1 -0
  54. package/.next/static/chunks/app/{layout-85e06e327ed56a3c.js → layout-6818616dc0d82320.js} +1 -1
  55. package/.next/static/chunks/app/run/[id]/page-3b25ede818499602.js +1 -0
  56. package/.next/{standalone/.next/static/css/4c75ab9da933ec7d.css → static/css/2e8aee54e9780b29.css} +1 -1
  57. package/.next/static/media/013b72fa676f92e0-s.woff2 +0 -0
  58. package/.next/static/media/2b5b02fc7e511755-s.woff2 +0 -0
  59. package/.next/static/media/65f03d54ccadf4a8-s.woff2 +0 -0
  60. package/.next/static/media/7d4881bb7e1bf84d-s.p.woff2 +0 -0
  61. package/.next/static/media/b9408752a0c24fb9-s.woff2 +0 -0
  62. package/.next/static/media/e038a29029a234f2-s.woff2 +0 -0
  63. package/README.md +38 -11
  64. package/bin/qa-agent.js +257 -38
  65. package/package.json +7 -3
  66. package/.next/standalone/.next/static/chunks/app/api/run/[id]/answer/route-512bf413c7eb7802.js +0 -1
  67. package/.next/standalone/.next/static/chunks/app/api/run/[id]/pause/route-512bf413c7eb7802.js +0 -1
  68. package/.next/standalone/.next/static/chunks/app/api/run/[id]/stream/route-512bf413c7eb7802.js +0 -1
  69. package/.next/standalone/.next/static/chunks/app/api/run/route-512bf413c7eb7802.js +0 -1
  70. package/.next/standalone/.next/static/chunks/app/run/[id]/page-a5676bd71f033295.js +0 -1
  71. package/.next/standalone/.next/static/media/0f1bdadaf30e2d5f-s.woff2 +0 -0
  72. package/.next/standalone/.next/static/media/2c34d62a75506231-s.woff2 +0 -0
  73. package/.next/standalone/.next/static/media/601f5c280d60caca-s.woff2 +0 -0
  74. package/.next/standalone/.next/static/media/a115172161b307bb-s.woff2 +0 -0
  75. package/.next/standalone/.next/static/media/d100b2a099e34044-s.woff2 +0 -0
  76. package/.next/standalone/.next/static/media/f5271587012faf78-s.p.woff2 +0 -0
  77. package/.next/static/chunks/app/api/run/[id]/answer/route-512bf413c7eb7802.js +0 -1
  78. package/.next/static/chunks/app/api/run/[id]/pause/route-512bf413c7eb7802.js +0 -1
  79. package/.next/static/chunks/app/api/run/[id]/stream/route-512bf413c7eb7802.js +0 -1
  80. package/.next/static/chunks/app/api/run/route-512bf413c7eb7802.js +0 -1
  81. package/.next/static/chunks/app/run/[id]/page-a5676bd71f033295.js +0 -1
  82. package/.next/static/media/0f1bdadaf30e2d5f-s.woff2 +0 -0
  83. package/.next/static/media/2c34d62a75506231-s.woff2 +0 -0
  84. package/.next/static/media/601f5c280d60caca-s.woff2 +0 -0
  85. package/.next/static/media/a115172161b307bb-s.woff2 +0 -0
  86. package/.next/static/media/d100b2a099e34044-s.woff2 +0 -0
  87. package/.next/static/media/f5271587012faf78-s.p.woff2 +0 -0
  88. /package/.next/standalone/.next/static/{3ynLRpjI9IVS8O8v_Wuds → OTVFCdqWzaakBh2l-H9cW}/_buildManifest.js +0 -0
  89. /package/.next/standalone/.next/static/{3ynLRpjI9IVS8O8v_Wuds → OTVFCdqWzaakBh2l-H9cW}/_ssgManifest.js +0 -0
  90. /package/.next/static/{3ynLRpjI9IVS8O8v_Wuds → OTVFCdqWzaakBh2l-H9cW}/_buildManifest.js +0 -0
  91. /package/.next/static/{3ynLRpjI9IVS8O8v_Wuds → OTVFCdqWzaakBh2l-H9cW}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/qpilot)](https://www.npmjs.com/package/qpilot)
6
6
  [![GitHub stars](https://img.shields.io/github/stars/broxhq/qpilot?style=social)](https://github.com/broxhq/qpilot)
7
- ![node](https://img.shields.io/badge/node-%3E%3D18-brightgreen)
7
+ ![node](https://img.shields.io/badge/node-%3E%3D20.12-brightgreen)
8
8
  ![license](https://img.shields.io/badge/license-MIT-blue)
9
9
 
10
10
  > If qpilot saved you time → **[⭐ Star it on GitHub](https://github.com/broxhq/qpilot)**. It helps more than you'd think.
@@ -24,42 +24,69 @@ No code. No Selenium. No config files.
24
24
 
25
25
  ## Quick start
26
26
 
27
- **Requirements:** Node.js 18+, Google Chrome, an [Anthropic API key](https://console.anthropic.com)
27
+ **Requirements:** Node.js 20.12+, Google Chrome, an [Anthropic API key](https://console.anthropic.com) — or any OpenAI-compatible model endpoint (Qwen, vLLM, Ollama, corporate gateway)
28
28
 
29
29
  ```bash
30
30
  npx qpilot
31
31
  ```
32
32
 
33
- That's it. If no API key is found, qpilot will ask for it on first run.
33
+ That's it. On first run qpilot walks you through a quick provider setup (arrow-key menu), then every launch shows your config and a Start menu.
34
34
 
35
35
  Browser opens automatically at `http://localhost:3847`.
36
36
 
37
37
  ---
38
38
 
39
- ## API key
39
+ ## Models & providers
40
40
 
41
- qpilot looks for the key in this order:
41
+ On first run qpilot asks which model to use. You can re-run setup anytime:
42
+
43
+ ```bash
44
+ npx qpilot config
45
+ ```
46
+
47
+ Two options:
48
+
49
+ - **Anthropic (Claude)** — enter your `sk-ant-…` key. Default model is `claude-haiku-4-5`.
50
+ Base URL is optional — set it if you reach Claude through a corporate proxy/gateway.
51
+ - **Custom** — any **OpenAI-compatible** endpoint: Qwen, vLLM, Ollama, a corporate
52
+ gateway, OpenRouter, or OpenAI itself. You provide a **base URL**, **API token**
53
+ and **model id**, e.g.:
54
+
55
+ ```
56
+ Base URL: https://dashscope-intl.aliyuncs.com/compatible-mode/v1
57
+ Model id: qwen2.5-72b-instruct
58
+ ```
59
+
60
+ Your choice is saved to `~/.qpilot/config.json` (mode `600`) and reused on every run.
61
+
62
+ > The custom path speaks the OpenAI `/chat/completions` protocol with tool calling —
63
+ > so the model must support function/tool calling for the agent to drive the browser.
64
+
65
+ ### API key (Anthropic shortcut)
66
+
67
+ For the Anthropic provider you can skip setup by supplying the key via env:
42
68
 
43
69
  1. `ANTHROPIC_API_KEY` environment variable
44
70
  2. `.env.local` file in the current directory
45
- 3. Prompts you to enter it if neither is found
46
-
47
- To avoid entering it every time:
48
71
 
49
72
  ```bash
50
73
  echo "ANTHROPIC_API_KEY=sk-ant-..." > .env.local
51
74
  ```
52
75
 
76
+ The key is never stored except in `~/.qpilot/config.json` when you run setup.
77
+
53
78
  ---
54
79
 
55
80
  ## Options
56
81
 
57
- | Flag | Description |
82
+ | Command / flag | Description |
58
83
  |------|-------------|
59
- | `--visible` | Show the Chrome window while the agent runs |
84
+ | `qpilot config` | Re-run provider setup (Anthropic or custom model) |
85
+ | `--visible` | Show the Chrome window while the agent runs (also available in the start menu) |
60
86
 
61
87
  ```bash
62
88
  npx qpilot --visible
89
+ npx qpilot config
63
90
  ```
64
91
 
65
92
  ---
@@ -91,6 +118,6 @@ You can paste multiple test cases at once — the agent runs them in order.
91
118
 
92
119
  ## Notes
93
120
 
94
- - API key is never stored — lives only in the running process
121
+ - API key is stored only in `~/.qpilot/config.json` (file mode `600`) — never sent anywhere except your chosen model provider
95
122
  - Runs are in-memory; restarting clears them
96
123
  - Powered by [Claude](https://anthropic.com) + [Playwright](https://playwright.dev)
package/bin/qa-agent.js CHANGED
@@ -3,14 +3,27 @@
3
3
 
4
4
  const { spawn } = require("child_process");
5
5
  const { createServer } = require("net");
6
- const { createInterface } = require("readline");
7
6
  const { join } = require("path");
8
- const { existsSync, readFileSync } = require("fs");
7
+ const { homedir } = require("os");
8
+ const {
9
+ existsSync,
10
+ readFileSync,
11
+ writeFileSync,
12
+ mkdirSync,
13
+ } = require("fs");
14
+ const pc = require("picocolors");
9
15
 
10
16
  const PKG_DIR = join(__dirname, "..");
17
+ const CONFIG_DIR = join(homedir(), ".qpilot");
18
+ const CONFIG_PATH = join(CONFIG_DIR, "config.json");
19
+ const DEFAULT_ANTHROPIC_MODEL = "claude-haiku-4-5-20251001";
11
20
 
12
21
  const args = process.argv.slice(2);
13
- const VISIBLE = args.includes("--visible") || args.includes("-v");
22
+ let VISIBLE = args.includes("--visible") || args.includes("-v");
23
+ const WANTS_SETUP =
24
+ args[0] === "config" || args[0] === "setup" || args.includes("--config");
25
+ // menu/prompts only in a real terminal; in scripts start silently
26
+ const INTERACTIVE = Boolean(process.stdout.isTTY && process.stdin.isTTY);
14
27
 
15
28
  function findFreePort(port) {
16
29
  return new Promise((resolve) => {
@@ -28,8 +41,8 @@ function openBrowser(url) {
28
41
  : process.platform === "win32"
29
42
  ? "cmd"
30
43
  : "xdg-open";
31
- const args = process.platform === "win32" ? ["/c", "start", url] : [url];
32
- spawn(cmd, args, { detached: true, stdio: "ignore" }).unref();
44
+ const a = process.platform === "win32" ? ["/c", "start", url] : [url];
45
+ spawn(cmd, a, { detached: true, stdio: "ignore" }).unref();
33
46
  }
34
47
 
35
48
  function loadEnvFile(filePath) {
@@ -48,33 +61,218 @@ function loadEnvFile(filePath) {
48
61
  return env;
49
62
  }
50
63
 
51
- function promptApiKey() {
52
- return new Promise((resolve) => {
53
- const rl = createInterface({ input: process.stdin, output: process.stdout });
64
+ // ── config (~/.qpilot/config.json) ─────────────────────────────────────────
65
+
66
+ function loadConfig() {
67
+ try {
68
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
54
73
 
55
- const onData = () => {
56
- process.stdout.clearLine(0);
57
- process.stdout.cursorTo(0);
58
- process.stdout.write(" API key: " + "·".repeat(rl.line.length));
74
+ function saveConfig(cfg) {
75
+ mkdirSync(CONFIG_DIR, { recursive: true });
76
+ writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), { mode: 0o600 });
77
+ }
78
+
79
+ /** Fallback: Anthropic key from env / .env.local (backwards compatibility). */
80
+ function envConfig(fileEnv) {
81
+ const apiKey = process.env.ANTHROPIC_API_KEY || fileEnv.ANTHROPIC_API_KEY;
82
+ if (!apiKey) return null;
83
+ return {
84
+ provider: "anthropic",
85
+ apiKey,
86
+ model: process.env.QPILOT_MODEL || fileEnv.QPILOT_MODEL || "",
87
+ };
88
+ }
89
+
90
+ /** config → env vars the server understands (lib/provider.ts). */
91
+ function configToEnv(cfg) {
92
+ if (cfg.provider === "custom") {
93
+ return {
94
+ QPILOT_PROVIDER: "custom",
95
+ QPILOT_BASE_URL: cfg.baseURL,
96
+ QPILOT_API_KEY: cfg.apiKey,
97
+ QPILOT_MODEL: cfg.model,
59
98
  };
60
- process.stdin.on("data", onData);
99
+ }
100
+ return {
101
+ QPILOT_PROVIDER: "anthropic",
102
+ ANTHROPIC_API_KEY: cfg.apiKey,
103
+ ...(cfg.model ? { QPILOT_MODEL: cfg.model } : {}),
104
+ ...(cfg.baseURL ? { QPILOT_BASE_URL: cfg.baseURL } : {}),
105
+ };
106
+ }
61
107
 
62
- rl.question(" API key: ", (answer) => {
63
- process.stdin.removeListener("data", onData);
64
- rl.close();
65
- process.stdout.write("\n");
66
- resolve(answer.trim());
67
- });
68
- });
108
+ function describeProvider(cfg) {
109
+ return cfg.provider === "custom"
110
+ ? `${cfg.model} @ custom`
111
+ : cfg.model || DEFAULT_ANTHROPIC_MODEL;
112
+ }
113
+
114
+ function maskKey(key) {
115
+ if (!key) return pc.red("(not set)");
116
+ return key.length > 12 ? `${key.slice(0, 7)}…${key.slice(-4)}` : "••••";
117
+ }
118
+
119
+ function configSummary(cfg, source) {
120
+ const row = (label, value) => `${pc.dim(label.padEnd(10))}${value}`;
121
+ const lines = [
122
+ row(
123
+ "Provider",
124
+ cfg.provider === "custom"
125
+ ? pc.magenta("Custom (OpenAI-compatible)")
126
+ : pc.magenta("Anthropic (Claude)"),
127
+ ),
128
+ row("Model", pc.cyan(cfg.model || DEFAULT_ANTHROPIC_MODEL)),
129
+ ];
130
+ if (cfg.baseURL) lines.push(row("Base URL", pc.cyan(cfg.baseURL)));
131
+ lines.push(row("API key", maskKey(cfg.apiKey)));
132
+ lines.push(row("Source", pc.dim(source)));
133
+ return lines.join("\n");
134
+ }
135
+
136
+ // ── interactive (@clack/prompts, ESM → dynamic import) ──────────────────────
137
+
138
+ const required = (v) => (v && v.trim() ? undefined : "Required");
139
+
140
+ function bailIfCancel(p, v) {
141
+ if (p.isCancel(v)) {
142
+ p.cancel("Cancelled.");
143
+ process.exit(1);
144
+ }
145
+ return v;
69
146
  }
70
147
 
71
- function startServer(serverPath, port, apiKey, fileEnv) {
148
+ async function runSetup(p) {
149
+ const bail = (v) => bailIfCancel(p, v);
150
+
151
+ const provider = bail(
152
+ await p.select({
153
+ message: "Choose your model provider",
154
+ initialValue: "anthropic",
155
+ options: [
156
+ { value: "anthropic", label: "Anthropic (Claude)", hint: "default" },
157
+ {
158
+ value: "custom",
159
+ label: "Custom — OpenAI-compatible",
160
+ hint: "Qwen, vLLM, Ollama, corp gateway, OpenRouter",
161
+ },
162
+ ],
163
+ }),
164
+ );
165
+
166
+ let cfg;
167
+ if (provider === "custom") {
168
+ const baseURL = bail(
169
+ await p.text({
170
+ message: "Base URL",
171
+ placeholder: "https://.../v1",
172
+ validate: (v) =>
173
+ !v || !v.trim()
174
+ ? "Required"
175
+ : /^https?:\/\//.test(v.trim())
176
+ ? undefined
177
+ : "Must start with http:// or https://",
178
+ }),
179
+ );
180
+ const apiKey = bail(
181
+ await p.password({ message: "API token", validate: required }),
182
+ );
183
+ const model = bail(
184
+ await p.text({
185
+ message: "Model id",
186
+ placeholder: "qwen2.5-72b-instruct",
187
+ validate: required,
188
+ }),
189
+ );
190
+ cfg = {
191
+ provider: "custom",
192
+ baseURL: baseURL.trim(),
193
+ apiKey: apiKey.trim(),
194
+ model: model.trim(),
195
+ };
196
+ } else {
197
+ const apiKey = bail(
198
+ await p.password({
199
+ message: "Anthropic API key (sk-ant-…)",
200
+ validate: required,
201
+ }),
202
+ );
203
+ const model = bail(
204
+ await p.text({
205
+ message: "Model id",
206
+ placeholder: DEFAULT_ANTHROPIC_MODEL,
207
+ defaultValue: DEFAULT_ANTHROPIC_MODEL,
208
+ }),
209
+ );
210
+ const baseURL = bail(
211
+ await p.text({
212
+ message: "Base URL (Enter to use api.anthropic.com)",
213
+ placeholder: "https://api.anthropic.com",
214
+ defaultValue: "",
215
+ validate: (v) =>
216
+ !v || !v.trim() || /^https?:\/\//.test(v.trim())
217
+ ? undefined
218
+ : "Must start with http:// or https://",
219
+ }),
220
+ );
221
+ cfg = {
222
+ provider: "anthropic",
223
+ apiKey: apiKey.trim(),
224
+ model: (model || DEFAULT_ANTHROPIC_MODEL).trim(),
225
+ ...(baseURL && baseURL.trim() ? { baseURL: baseURL.trim() } : {}),
226
+ };
227
+ }
228
+
229
+ saveConfig(cfg);
230
+ p.log.success(`Saved to ${pc.cyan(CONFIG_PATH)}`);
231
+ return cfg;
232
+ }
233
+
234
+ /** Config card + menu: Start / Start visible / Change / Exit. */
235
+ async function startMenu(p, cfg, source) {
236
+ for (;;) {
237
+ p.note(configSummary(cfg, source), "Provider");
238
+ const action = bailIfCancel(
239
+ p,
240
+ await p.select({
241
+ message: "Ready to go?",
242
+ initialValue: VISIBLE ? "start-visible" : "start",
243
+ options: [
244
+ { value: "start", label: "Start", hint: "headless — agent works in background" },
245
+ {
246
+ value: "start-visible",
247
+ label: "Start with visible browser",
248
+ hint: "watch the agent click through pages",
249
+ },
250
+ { value: "config", label: "Change provider / model" },
251
+ { value: "exit", label: "Exit" },
252
+ ],
253
+ }),
254
+ );
255
+ if (action === "start" || action === "start-visible") {
256
+ VISIBLE = action === "start-visible";
257
+ return cfg;
258
+ }
259
+ if (action === "exit") {
260
+ p.cancel("Bye!");
261
+ process.exit(0);
262
+ }
263
+ cfg = await runSetup(p);
264
+ source = CONFIG_PATH;
265
+ }
266
+ }
267
+
268
+ // ── server ───────────────────────────────────────────────────────────────────
269
+
270
+ function startServer(serverPath, port, envExtra) {
72
271
  return new Promise((resolve, reject) => {
73
272
  const child = spawn("node", [serverPath], {
74
273
  env: {
75
274
  ...process.env,
76
- ...fileEnv,
77
- ANTHROPIC_API_KEY: apiKey,
275
+ ...envExtra,
78
276
  PORT: String(port),
79
277
  HOSTNAME: "127.0.0.1",
80
278
  HEADLESS: VISIBLE ? "false" : "true",
@@ -100,31 +298,50 @@ function startServer(serverPath, port, apiKey, fileEnv) {
100
298
  async function main() {
101
299
  const serverPath = join(PKG_DIR, ".next", "standalone", "server.js");
102
300
  if (!existsSync(serverPath)) {
103
- console.error("App is not built. Run: npm run build");
301
+ console.error(pc.red("App is not built. Run: npm run build"));
104
302
  process.exit(1);
105
303
  }
106
304
 
107
305
  const fileEnv = loadEnvFile(join(process.cwd(), ".env.local"));
108
- let apiKey = process.env.ANTHROPIC_API_KEY || fileEnv.ANTHROPIC_API_KEY;
109
-
110
- if (!apiKey) {
111
- console.log("\n No ANTHROPIC_API_KEY found.");
112
- console.log(" Get one at https://console.anthropic.com\n");
113
- apiKey = await promptApiKey();
114
- if (!apiKey) {
115
- console.error("\n API key is required. Exiting.");
306
+
307
+ let cfg;
308
+ if (!INTERACTIVE) {
309
+ // scripts/CI: no menu — use saved config or env, else error
310
+ cfg = loadConfig() || envConfig(fileEnv);
311
+ if (WANTS_SETUP || !cfg) {
312
+ console.error(
313
+ "No interactive terminal. Run `qpilot config` in a terminal first, or set ANTHROPIC_API_KEY.",
314
+ );
116
315
  process.exit(1);
117
316
  }
317
+ } else {
318
+ const p = await import("@clack/prompts");
319
+ p.intro(`${pc.bgCyan(pc.black(" qpilot "))} ${pc.dim("AI agent for manual test cases")}`);
320
+
321
+ if (WANTS_SETUP) {
322
+ cfg = await runSetup(p);
323
+ } else {
324
+ cfg = loadConfig() || envConfig(fileEnv);
325
+ if (!cfg) {
326
+ p.log.warn("No provider configured yet — let's set one up.");
327
+ cfg = await runSetup(p);
328
+ }
329
+ }
330
+ const source = loadConfig() ? CONFIG_PATH : "env / .env.local";
331
+ cfg = await startMenu(p, cfg, source);
332
+
333
+ if (VISIBLE) p.log.info("--visible: browser window will be shown");
334
+ p.outro(pc.dim("Launching…"));
118
335
  }
119
336
 
120
- if (VISIBLE) console.log("\n --visible: browser window will be shown");
337
+ const envExtra = { ...fileEnv, ...configToEnv(cfg) };
121
338
 
122
339
  // try ports until one works
123
340
  let port = await findFreePort(3847);
124
341
  let child;
125
342
  while (true) {
126
343
  try {
127
- child = await startServer(serverPath, port, apiKey, fileEnv);
344
+ child = await startServer(serverPath, port, envExtra);
128
345
  break;
129
346
  } catch {
130
347
  port = await findFreePort(port + 1);
@@ -132,15 +349,17 @@ async function main() {
132
349
  }
133
350
 
134
351
  const url = `http://localhost:${port}`;
135
- console.log(`\n qpilot → ${url}\n`);
352
+ console.log(
353
+ `\n ${pc.bold(pc.cyan("qpilot"))} ${pc.dim("→")} ${pc.underline(pc.cyan(url))} ${pc.dim("·")} ${pc.magenta(describeProvider(cfg))}\n`,
354
+ );
136
355
  openBrowser(url);
137
356
 
138
357
  const shutdown = () => {
139
358
  child.kill("SIGTERM");
140
359
  console.log(
141
- "\n Thanks for using qpilot!" +
142
- "\n If it saved you time, a ⭐ means a lot:" +
143
- "\n https://github.com/broxhq/qpilot\n",
360
+ `\n ${pc.bold("Thanks for using qpilot!")}` +
361
+ `\n If it saved you time, a ${pc.yellow("⭐")} means a lot:` +
362
+ `\n ${pc.underline(pc.cyan("https://github.com/broxhq/qpilot"))}\n`,
144
363
  );
145
364
  process.exit(0);
146
365
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qpilot",
3
- "version": "0.5.3",
3
+ "version": "0.7.0",
4
4
  "description": "Run manual test cases with an AI agent in a real browser",
5
5
  "bin": {
6
6
  "qpilot": "bin/qa-agent.js"
@@ -30,7 +30,7 @@
30
30
  ],
31
31
  "license": "MIT",
32
32
  "engines": {
33
- "node": ">=18"
33
+ "node": ">=20.12"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@anthropic-ai/sdk": "^0.40.0",
@@ -55,5 +55,9 @@
55
55
  "type": "git",
56
56
  "url": "https://github.com/broxhq/qpilot.git"
57
57
  },
58
- "homepage": "https://brox.sh"
58
+ "homepage": "https://brox.sh",
59
+ "dependencies": {
60
+ "@clack/prompts": "^1.5.1",
61
+ "picocolors": "^1.1.1"
62
+ }
59
63
  }
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[54,569,774,964],{4441:()=>{}},_=>{_.O(0,[441,255,358],()=>_(_.s=4441)),_N_E=_.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[54,569,774,964],{4441:()=>{}},_=>{_.O(0,[441,255,358],()=>_(_.s=4441)),_N_E=_.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[54,569,774,964],{4441:()=>{}},_=>{_.O(0,[441,255,358],()=>_(_.s=4441)),_N_E=_.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[54,569,774,964],{4441:()=>{}},_=>{_.O(0,[441,255,358],()=>_(_.s=4441)),_N_E=_.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[170],{191:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>U});var r=n(5155),s=n(2115),a=n(2619),i=n.n(a),o=n(3229),l=n(3228),d=n(2638),c=n(3410),u=n(9746),g=n(7274),x=n(8215),m=n(5352),p=n(4788),f=n(7212),b=n(3916),h=n(1083),v=n(2027),j=n(3101),w=n(5016);let N=(0,j.F)("group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",{variants:{variant:{default:"bg-primary text-primary-foreground [a]:hover:bg-primary/80",secondary:"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",destructive:"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",outline:"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",ghost:"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",link:"text-primary underline-offset-4 hover:underline"}},defaultVariants:{variant:"default"}});function k(e){let{className:t,variant:n="default",render:r,...s}=e;return(0,v.C)({defaultTagName:"span",props:(0,h.v6)({className:(0,w.cn)(N({variant:n}),t)},s),render:r,state:{slot:"badge",variant:n}})}var y=n(7003),z=n(4036),S=n(5949),A=n(4915),_=n(8502),q=n(8905),E=n(989);function C(e){let{...t}=e;return(0,r.jsx)(z.D,{"data-slot":"dialog",...t})}function F(e){let{...t}=e;return(0,r.jsx)(S.Z,{"data-slot":"dialog-portal",...t})}function O(e){let{className:t,...n}=e;return(0,r.jsx)(A.X,{"data-slot":"dialog-overlay",className:(0,w.cn)("fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",t),...n})}function P(e){let{className:t,children:n,showCloseButton:s=!0,...a}=e;return(0,r.jsxs)(F,{children:[(0,r.jsx)(O,{}),(0,r.jsxs)(_.h,{"data-slot":"dialog-content",className:(0,w.cn)("fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",t),...a,children:[n,s&&(0,r.jsxs)(q.H,{"data-slot":"dialog-close",render:(0,r.jsx)(y.$,{variant:"ghost",className:"absolute top-2 right-2",size:"icon-sm"}),children:[(0,r.jsx)(p.A,{}),(0,r.jsx)("span",{className:"sr-only",children:"Close"})]})]})]})}function T(e){let{className:t,...n}=e;return(0,r.jsx)("div",{"data-slot":"dialog-header",className:(0,w.cn)("flex flex-col gap-2",t),...n})}function D(e){let{className:t,...n}=e;return(0,r.jsx)(E.L,{"data-slot":"dialog-title",className:(0,w.cn)("font-heading text-base leading-none font-medium",t),...n})}var I=n(2045);function R(e){let{className:t,type:n,...s}=e;return(0,r.jsx)(I.p,{type:n,"data-slot":"input",className:(0,w.cn)("h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",t),...s})}var W=n(8521),$=n(2577),H=n(1127);function M(e){let{...t}=e;return(0,r.jsx)(W.N,{"data-slot":"collapsible",...t})}function B(e){let{...t}=e;return(0,r.jsx)($.R,{"data-slot":"collapsible-trigger",...t})}function J(e){let{...t}=e;return(0,r.jsx)(H.j,{"data-slot":"collapsible-content",...t})}function U(e){var t,n,a,g,x;let{params:m}=e,{id:p}=(0,s.use)(m),[f,b]=(0,s.useState)(null),[h,v]=(0,s.useState)([]),[j,N]=(0,s.useState)(!1),[k,z]=(0,s.useState)(null),[S,A]=(0,s.useState)(()=>Date.now()),[_,q]=(0,s.useState)(!1),E=(0,s.useRef)(!1);(0,s.useEffect)(()=>{E.current=!1;let e=new EventSource("/api/run/".concat(p,"/stream"));return e.onmessage=e=>{var t,n;let r=JSON.parse(e.data);if("snapshot"===r.kind){if(E.current=!0,b(r.run),v(r.run.events),"running"!==r.run.status&&"waiting"!==r.run.status){let e=r.run.events.at(-1);z(null!=(t=null==e?void 0:e.ts)?t:Date.now())}return}v(e=>[...e,r]),"plan"===r.kind&&r.steps&&b(e=>e?{...e,steps:r.steps}:e),"step"===r.kind&&r.step&&b(e=>{if(!e)return e;let t=e.steps.findIndex(e=>e.num===r.step.num),n=t>=0?e.steps.map((e,n)=>n===t?{...e,...r.step}:e):[...e.steps,r.step];return{...e,steps:n}}),"question"===r.kind&&r.question&&b(e=>e?{...e,status:"waiting",pending:r.question}:e),"answer"===r.kind&&b(e=>e?{...e,status:"running",pending:null}:e),"paused"===r.kind&&b(e=>e?{...e,status:"paused"}:e),"resumed"===r.kind&&b(e=>e?{...e,status:"running"}:e),("done"===r.kind||"error"===r.kind)&&(z(null!=(n=r.ts)?n:Date.now()),b(e=>{var t,n;return e?{...e,status:null!=(t=r.status)?t:"error"===r.kind?"error":"passed",summary:null!=(n=r.summary)?n:r.text,pending:null,steps:e.steps.map(e=>"queued"===e.status?{...e,status:"skipped"}:e)}:e}))},e.onerror=()=>{e.close(),E.current||N(!0)},()=>e.close()},[p]);let F=null!=(n=null==f?void 0:f.status)?n:"running",O=null!=(a=null==f?void 0:f.steps)?a:[],T=O.filter(e=>"pass"===e.status).length,D=O.filter(e=>"fail"===e.status).length,I=O.filter(e=>"warn"===e.status).length,R="running"===F||"waiting"===F,W=R||"paused"===F;async function $(){W&&(q(!0),await fetch("/api/run/".concat(p,"/pause"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:"paused"===F?"resume":"pause"})}).catch(()=>{}),q(!1))}let H=R&&null!=(g=null==(t=O.find(e=>"queued"===e.status))?void 0:t.num)?g:null;(0,s.useEffect)(()=>{if(!R)return;let e=setInterval(()=>A(Date.now()),1e3);return()=>clearInterval(e)},[R]),(0,s.useEffect)(()=>{var e;null!=H&&(null==(e=document.getElementById("step-".concat(H)))||e.scrollIntoView({behavior:"smooth",block:"center"}))},[H]);let M=f?(null!=k?k:S)-f.createdAt:0,B=O.filter(e=>"queued"!==e.status).length,J=O.length?B/O.length*100:0,U=[];for(let e of O){let t=U[U.length-1];t&&t.title===e.group?t.steps.push(e):U.push({title:e.group,steps:[e]})}return j?(0,r.jsxs)("main",{className:"flex flex-col items-center justify-center min-h-screen px-6 text-center",children:[(0,r.jsx)("p",{className:"text-6xl font-semibold text-muted-foreground/20 mb-4",children:"404"}),(0,r.jsx)("p",{className:"text-sm text-muted-foreground mb-6",children:"Run not found — it may have finished and been cleared from memory."}),(0,r.jsxs)(i(),{href:"/",className:"inline-flex items-center gap-1.5 text-sm text-foreground/70 hover:text-foreground transition-colors",children:[(0,r.jsx)(o.A,{className:"size-3.5"}),"New run"]})]}):(0,r.jsxs)("main",{className:"mx-auto max-w-2xl px-6 pt-10 pb-24",children:[(0,r.jsxs)(i(),{href:"/",className:"inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors mb-8",children:[(0,r.jsx)(o.A,{className:"size-3.5"}),"new run"]}),(0,r.jsxs)("section",{className:"mb-8",children:[(0,r.jsx)(G,{status:F}),(0,r.jsx)("h1",{className:"text-lg font-semibold tracking-tight mt-5 break-words text-foreground/90",children:null!=(x=null==f?void 0:f.title)?x:p}),f&&(0,r.jsxs)("div",{className:"flex items-center justify-between gap-3 mt-1.5",children:[(0,r.jsx)("p",{className:"text-xs text-muted-foreground font-mono",children:O.length>0?(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)("span",{className:"text-foreground/60",children:[B,"/",O.length," steps"]}),T>0&&(0,r.jsxs)("span",{className:"text-success ml-2",children:["\xb7 ",T," passed"]}),I>0&&(0,r.jsxs)("span",{className:"text-warning ml-2",children:["\xb7 ",I," warn"]}),D>0&&(0,r.jsxs)("span",{className:"text-destructive ml-2",children:["\xb7 ",D," failed"]})]}):(0,r.jsx)("span",{className:"text-muted-foreground/50",children:"waiting for plan…"})}),(0,r.jsxs)("div",{className:"flex items-center gap-2 shrink-0",children:[W&&(0,r.jsx)(y.$,{variant:"ghost",size:"sm",onClick:$,disabled:_||"waiting"===F,className:"h-7 px-2 text-muted-foreground/60 hover:text-foreground",children:"paused"===F?(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(l.A,{className:"size-3.5 mr-1"}),"Resume"]}):(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(d.A,{className:"size-3.5 mr-1"}),"Pause"]})}),(0,r.jsxs)("span",{className:"inline-flex items-center gap-1 text-xs text-muted-foreground/60 font-mono tabular-nums",children:[(0,r.jsx)(c.A,{className:"size-3"}),function(e){let t=Math.max(0,Math.floor(e/1e3));return"".concat(Math.floor(t/60),":").concat(String(t%60).padStart(2,"0"))}(M)]})]})]}),O.length>0&&(0,r.jsx)("div",{className:"relative mt-3 h-[3px] rounded-full bg-white/5 overflow-hidden",children:(0,r.jsx)("div",{className:(0,w.cn)("absolute inset-y-0 left-0 rounded-full transition-all duration-700 ease-out","failed"===F||"error"===F?"bg-destructive":"bg-success"),style:{width:"".concat(J,"%")}})})]}),(0,r.jsxs)("section",{className:"space-y-2",children:[0===O.length&&(0,r.jsxs)("div",{className:"flex flex-col items-center justify-center py-16 text-center",children:[(0,r.jsx)(u.A,{className:"size-5 text-muted-foreground/30 animate-spin mb-3"}),(0,r.jsx)("p",{className:"text-sm text-muted-foreground/50",children:"waiting for agent to start…"})]}),U.map((e,t)=>(0,r.jsxs)("div",{className:"space-y-2",children:[e.title&&(0,r.jsx)("div",{className:"pt-4 first:pt-0 text-[10.5px] font-semibold uppercase tracking-widest text-muted-foreground/40",children:e.title}),e.steps.map((e,t)=>(0,r.jsx)("div",{id:"step-".concat(e.num),className:"scroll-mt-6",children:(0,r.jsx)(K,{step:e,displayNum:t+1,active:e.num===H,events:h.filter(t=>t.stepNum===e.num)})},e.num))]},t))]}),(null==f?void 0:f.summary)&&(0,r.jsxs)("div",{className:"mt-6 rounded-xl border border-white/7 bg-card/60 px-4 py-3",children:[(0,r.jsx)("div",{className:"text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/40 mb-1.5",children:"Summary"}),(0,r.jsx)("div",{className:"text-sm leading-relaxed text-foreground/80",children:f.summary})]}),("passed"===F||"failed"===F)&&(0,r.jsx)(V,{}),(0,r.jsx)(C,{open:!!(null==f?void 0:f.pending),children:(0,r.jsx)(P,{showCloseButton:!1,children:(null==f?void 0:f.pending)&&(0,r.jsx)(ee,{runId:p,question:f.pending})})})]})}function V(){return(0,r.jsxs)("a",{href:"https://github.com/broxhq/qpilot",target:"_blank",rel:"noreferrer",className:"group mt-6 flex items-center gap-3 rounded-xl border border-white/7 bg-card/60 px-4 py-3 transition-colors hover:border-warning/30 hover:bg-warning/[0.03]",children:[(0,r.jsx)(g.A,{className:"size-4 shrink-0 text-muted-foreground/50 transition-colors group-hover:text-warning group-hover:fill-warning"}),(0,r.jsxs)("div",{className:"min-w-0",children:[(0,r.jsxs)("div",{className:"text-sm text-foreground/80",children:["Did qpilot save you time?"," ",(0,r.jsx)("span",{className:"text-foreground/95 group-hover:text-warning transition-colors",children:"Star it on GitHub"})]}),(0,r.jsx)("div",{className:"text-xs text-muted-foreground/50",children:"A ⭐ helps more people find it — takes two seconds."})]})]})}let L={running:{label:"Running",sub:"agent is executing steps",icon:(0,r.jsx)(u.A,{className:"size-4 animate-spin"}),color:"text-foreground/70",bg:"bg-white/5",border:"border-white/10"},waiting:{label:"Waiting",sub:"agent needs your input",icon:(0,r.jsx)(x.A,{className:"size-4"}),color:"text-warning",bg:"bg-warning/10",border:"border-warning/20"},paused:{label:"Paused",sub:"run is paused",icon:(0,r.jsx)(d.A,{className:"size-4"}),color:"text-foreground/50",bg:"bg-white/5",border:"border-white/10"},passed:{label:"Passed",sub:"all steps passed",icon:(0,r.jsx)(m.A,{className:"size-4",strokeWidth:2.5}),color:"text-success",bg:"bg-success/10",border:"border-success/20"},failed:{label:"Failed",sub:"some steps failed",icon:(0,r.jsx)(p.A,{className:"size-4",strokeWidth:2.5}),color:"text-destructive",bg:"bg-destructive/10",border:"border-destructive/20"},error:{label:"Error",sub:"agent crashed",icon:(0,r.jsx)(x.A,{className:"size-4"}),color:"text-destructive",bg:"bg-destructive/10",border:"border-destructive/20"}};function G(e){var t;let{status:n}=e,s=null!=(t=L[n])?t:L.running;return(0,r.jsxs)("div",{className:"flex items-center gap-3",children:[(0,r.jsx)("div",{className:(0,w.cn)("flex items-center justify-center size-9 rounded-xl border transition-colors",s.bg,s.border,s.color),children:s.icon}),(0,r.jsxs)("div",{children:[(0,r.jsx)("div",{className:(0,w.cn)("text-sm font-semibold tracking-tight",s.color),children:s.label}),(0,r.jsx)("div",{className:"text-xs text-muted-foreground/60",children:s.sub})]})]})}let Q={queued:{border:"border-l-white/8",badge:"bg-white/5 text-muted-foreground/50",bg:"",label:"queued"},pass:{border:"border-l-success/60",badge:"bg-success/10 text-success",bg:"",label:"pass"},warn:{border:"border-l-warning/60",badge:"bg-warning/10 text-warning",bg:"",label:"warn"},fail:{border:"border-l-destructive/60",badge:"bg-destructive/10 text-destructive",bg:"bg-destructive/[0.03]",label:"fail"},skipped:{border:"border-l-white/8",badge:"bg-white/4 text-muted-foreground/30",bg:"",label:"skipped"}},X={border:"border-l-warning/80",badge:"bg-warning/10 text-warning",bg:"bg-warning/[0.03]"};function Z(e){if("action"===e.kind&&("report_step"===e.toolName||"finish"===e.toolName))return!1;if("observation"===e.kind){var t;let n=null!=(t=e.text)?t:"";return n.startsWith("Error:")||n.startsWith("[debug]")}return!0}function K(e){var t;let{step:n,events:a,active:i,displayNum:o}=e,l=null!=(t=Q[n.status])?t:Q.queued,d=i?{...l,...X}:l,c=a.filter(Z),g=i||"fail"===n.status,[x,m]=(0,s.useState)(g);return(0,s.useEffect)(()=>m(g),[g]),(0,r.jsx)(M,{open:x,onOpenChange:m,children:(0,r.jsxs)("div",{className:(0,w.cn)("rounded-xl border border-white/7 border-l-2 overflow-hidden transition-colors",d.border,d.bg),children:[(0,r.jsxs)(B,{className:"group w-full flex items-center gap-3 px-4 py-3 text-left hover:bg-white/[0.02] transition-colors",children:[(0,r.jsx)(k,{className:(0,w.cn)("border-0 text-[9.5px] tracking-widest font-semibold shrink-0 gap-1 uppercase rounded-md px-1.5",d.badge),children:i?(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(u.A,{className:"size-2.5 animate-spin"}),"running"]}):l.label}),(0,r.jsxs)("span",{className:"flex-1 min-w-0 text-[13px] text-foreground/80",children:[(0,r.jsx)("span",{className:"text-muted-foreground/40 mr-2 font-mono text-[11px]",children:String(null!=o?o:n.num).padStart(2,"0")}),n.description]}),(0,r.jsx)(f.A,{className:"size-3.5 text-muted-foreground/30 shrink-0 group-data-open:hidden"}),(0,r.jsx)(b.A,{className:"size-3.5 text-muted-foreground/30 shrink-0 hidden group-data-open:inline"})]}),n.evidence&&(0,r.jsx)("div",{className:"px-4 pb-3 -mt-0.5 text-[11.5px] text-muted-foreground/60 leading-relaxed font-mono",children:n.evidence}),(0,r.jsx)(J,{children:(0,r.jsxs)("div",{className:"px-4 pb-4 pt-3 space-y-3 border-t border-white/5",children:[n.screenshot&&(0,r.jsx)("a",{href:n.screenshot,target:"_blank",rel:"noreferrer",className:"block rounded-lg overflow-hidden border border-white/8 hover:border-white/15 transition-colors",children:(0,r.jsx)("img",{src:n.screenshot,alt:"screenshot step ".concat(n.num),className:"max-h-64 w-full object-contain bg-black/20"})}),c.length>0?(0,r.jsx)("div",{className:"space-y-0.5",children:c.map((e,t)=>(0,r.jsx)(Y,{event:e},t))}):(0,r.jsx)("div",{className:"text-[11px] text-muted-foreground/30",children:"no events for this step"})]})})]})})}function Y(e){let{event:t}=e,{icon:n,text:s,cls:a}=function(e){var t,n,r,s,a,i,o,l,d;switch(e.kind){case"action":return{icon:"→",text:function(e,t){var n,r,s,a,i,o,l,d;let c=null!=t?t:{};switch(e){case"navigate":return"Opening ".concat(null!=(n=c.url)?n:"");case"snapshot":return c.near?'Reading "'.concat(c.near,'" block'):"Reading page";case"click":return c.name?'Clicking "'.concat(c.name,'"'):"Clicking element";case"fill":return c.name?'Filling "'.concat(c.name,'" → "').concat(null!=(r=c.value)?r:"",'"'):'Filling "'.concat(null!=(s=c.value)?s:"",'"');case"select":return c.name?'Selecting "'.concat(null!=(a=c.value)?a:"",'" in "').concat(c.name,'"'):'Selecting "'.concat(null!=(i=c.value)?i:"",'"');case"hover":return c.name?'Hovering "'.concat(c.name,'"'):"Hovering element";case"scroll_to":return"Scrolling to ".concat(c.text?'"'.concat(c.text,'"'):null!=(o=c.ref)?o:"element");case"scroll":{let e=[Number(c.y)?"".concat(Number(c.y)>0?"down":"up"," ").concat(Math.abs(Number(c.y)),"px"):"",Number(c.x)?"".concat(Number(c.x)>0?"right":"left"," ").concat(Math.abs(Number(c.x)),"px"):""].filter(Boolean).join(", "),t=c.ref?" inside ".concat(c.ref):"";return"Scrolling ".concat(e||"0px").concat(t)}case"press":return"Pressing ".concat(null!=(l=c.key)?l:"");case"wait":return"Waiting ".concat(null!=(d=c.ms)?d:""," ms");case"ask_user":return"Asking user";default:return null!=e?e:"action"}}(e.toolName,e.toolInput),cls:"text-foreground/70"};case"step":{let s=null!=(r=null==(t=e.step)?void 0:t.status)?r:"",a="pass"===s?"text-success":"fail"===s?"text-destructive":"text-warning";return{icon:"pass"===s?"✓":"fail"===s?"✗":"!",text:"Step ".concat(null==(n=e.step)?void 0:n.num," — ").concat(s.toUpperCase()),cls:a}}case"observation":{let t=null!=(s=e.text)?s:"";if(t.startsWith("Error:"))return{icon:"✗",text:t,cls:"text-destructive"};return{icon:"\xb7",text:t,cls:"text-muted-foreground/40"}}case"question":return{icon:"?",text:"Asking: ".concat(null!=(i=null==(a=e.question)?void 0:a.prompt)?i:""),cls:"text-warning"};case"answer":return{icon:"✎",text:"User answered",cls:"text-foreground/50"};case"thought":return{icon:"›",text:null!=(o=e.text)?o:"",cls:"text-muted-foreground/40 italic"};case"done":return{icon:"■",text:"Run finished — ".concat(null!=(l=e.status)?l:""),cls:"text-foreground/60"};case"error":return{icon:"✗",text:null!=(d=e.text)?d:"",cls:"text-destructive"};default:return{icon:"\xb7",text:"",cls:"text-muted-foreground/30"}}}(t),i=new Date(t.ts).toLocaleTimeString("en-US",{hour12:!1});return(0,r.jsxs)("div",{className:"flex gap-2.5 text-[11.5px] leading-relaxed items-baseline font-mono",children:[(0,r.jsx)("span",{className:"shrink-0 tabular-nums text-muted-foreground/25 text-[10px]",children:i}),(0,r.jsx)("span",{className:(0,w.cn)("shrink-0 w-3.5 text-center",a),children:n}),(0,r.jsx)("span",{className:(0,w.cn)("min-w-0 break-words",a),children:s})]})}function ee(e){let{runId:t,question:n}=e,[a,i]=(0,s.useState)(""),[o,l]=(0,s.useState)(!1),[d,c]=(0,s.useState)(null);async function u(e){if(e.preventDefault(),a){l(!0),c(null);try{let e=await fetch("/api/run/".concat(t,"/answer"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({questionId:n.id,answer:a})});if(!e.ok){let t=await e.json().catch(()=>({}));throw Error(t.error||"HTTP ".concat(e.status))}i("")}catch(e){c(e instanceof Error?e.message:String(e)),l(!1)}}}return(0,r.jsxs)("form",{onSubmit:u,className:"space-y-4",children:[(0,r.jsx)(T,{children:(0,r.jsxs)(D,{className:"flex items-center gap-2 text-base",children:[(0,r.jsx)(x.A,{className:"size-4 text-warning shrink-0"}),"Agent needs input"]})}),(0,r.jsx)("p",{className:"text-sm text-muted-foreground leading-relaxed",children:n.prompt}),(0,r.jsx)(R,{autoFocus:!0,type:n.secret?"password":"text",value:a,onChange:e=>i(e.target.value),placeholder:n.secret?"OTP / password":"answer",className:"font-mono"}),d&&(0,r.jsx)("p",{className:"text-destructive text-xs",children:d}),(0,r.jsx)(y.$,{type:"submit",disabled:!a||o,className:"w-full",children:o?"Sending…":"Submit"})]})}},5016:(e,t,n)=>{"use strict";n.d(t,{cn:()=>a});var r=n(2821),s=n(5889);function a(){for(var e=arguments.length,t=Array(e),n=0;n<e;n++)t[n]=arguments[n];return(0,s.QP)((0,r.$)(t))}},7003:(e,t,n)=>{"use strict";n.d(t,{$:()=>l});var r=n(5155),s=n(9574),a=n(3101),i=n(5016);let o=(0,a.F)("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",{variants:{variant:{default:"bg-primary text-primary-foreground [a]:hover:bg-primary/80",outline:"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",secondary:"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",ghost:"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",destructive:"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",xs:"h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",sm:"h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",lg:"h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",icon:"size-8","icon-xs":"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3","icon-sm":"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg","icon-lg":"size-9"}},defaultVariants:{variant:"default",size:"default"}});function l(e){let{className:t,variant:n="default",size:a="default",...l}=e;return(0,r.jsx)(s.$,{"data-slot":"button",className:(0,i.cn)(o({variant:n,size:a,className:t})),...l})}},9578:(e,t,n)=>{Promise.resolve().then(n.bind(n,191))}},e=>{e.O(0,[248,532,441,255,358],()=>e(e.s=9578)),_N_E=e.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[54,569,774,964],{4441:()=>{}},_=>{_.O(0,[441,255,358],()=>_(_.s=4441)),_N_E=_.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[54,569,774,964],{4441:()=>{}},_=>{_.O(0,[441,255,358],()=>_(_.s=4441)),_N_E=_.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[54,569,774,964],{4441:()=>{}},_=>{_.O(0,[441,255,358],()=>_(_.s=4441)),_N_E=_.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[54,569,774,964],{4441:()=>{}},_=>{_.O(0,[441,255,358],()=>_(_.s=4441)),_N_E=_.O()}]);