@runchr/gstack-antigravity 0.1.0 → 0.1.2
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.
Potentially problematic release.
This version of @runchr/gstack-antigravity might be problematic. Click here for more details.
- package/.agents/skills/gstack/.agents/skills/gstack/SKILL.md +651 -0
- package/.agents/skills/gstack/.agents/skills/gstack-autoplan/SKILL.md +678 -0
- package/.agents/skills/gstack/.agents/skills/gstack-benchmark/SKILL.md +482 -0
- package/.agents/skills/gstack/.agents/skills/gstack-browse/SKILL.md +511 -0
- package/.agents/skills/gstack/.agents/skills/gstack-canary/SKILL.md +486 -0
- package/.agents/skills/gstack/.agents/skills/gstack-careful/SKILL.md +50 -0
- package/.agents/skills/gstack/.agents/skills/gstack-cso/SKILL.md +607 -0
- package/.agents/skills/gstack/.agents/skills/gstack-design-consultation/SKILL.md +615 -0
- package/.agents/skills/gstack/.agents/skills/gstack-design-review/SKILL.md +988 -0
- package/.agents/skills/gstack/.agents/skills/gstack-document-release/SKILL.md +604 -0
- package/.agents/skills/gstack/.agents/skills/gstack-freeze/SKILL.md +67 -0
- package/.agents/skills/gstack/.agents/skills/gstack-guard/SKILL.md +62 -0
- package/.agents/skills/gstack/.agents/skills/gstack-investigate/SKILL.md +415 -0
- package/.agents/skills/gstack/.agents/skills/gstack-land-and-deploy/SKILL.md +873 -0
- package/.agents/skills/gstack/.agents/skills/gstack-office-hours/SKILL.md +986 -0
- package/.agents/skills/gstack/.agents/skills/gstack-plan-ceo-review/SKILL.md +1268 -0
- package/.agents/skills/gstack/.agents/skills/gstack-plan-design-review/SKILL.md +668 -0
- package/.agents/skills/gstack/.agents/skills/gstack-plan-eng-review/SKILL.md +826 -0
- package/.agents/skills/gstack/.agents/skills/gstack-qa/SKILL.md +1006 -0
- package/.agents/skills/gstack/.agents/skills/gstack-qa-only/SKILL.md +626 -0
- package/.agents/skills/gstack/.agents/skills/gstack-retro/SKILL.md +1065 -0
- package/.agents/skills/gstack/.agents/skills/gstack-review/SKILL.md +704 -0
- package/.agents/skills/gstack/.agents/skills/gstack-setup-browser-cookies/SKILL.md +325 -0
- package/.agents/skills/gstack/.agents/skills/gstack-setup-deploy/SKILL.md +450 -0
- package/.agents/skills/gstack/.agents/skills/gstack-ship/SKILL.md +1312 -0
- package/.agents/skills/gstack/.agents/skills/gstack-unfreeze/SKILL.md +36 -0
- package/.agents/skills/gstack/.agents/skills/gstack-upgrade/SKILL.md +220 -0
- package/.agents/skills/gstack/.env.example +5 -0
- package/.agents/skills/gstack/.github/workflows/skill-docs.yml +17 -0
- package/.agents/skills/gstack/AGENTS.md +49 -0
- package/.agents/skills/gstack/ARCHITECTURE.md +359 -0
- package/.agents/skills/gstack/BROWSER.md +271 -0
- package/.agents/skills/gstack/CHANGELOG.md +800 -0
- package/.agents/skills/gstack/CLAUDE.md +284 -0
- package/.agents/skills/gstack/CONTRIBUTING.md +370 -0
- package/.agents/skills/gstack/ETHOS.md +129 -0
- package/.agents/skills/gstack/LICENSE +21 -0
- package/.agents/skills/gstack/README.md +228 -0
- package/.agents/skills/gstack/SKILL.md +657 -0
- package/.agents/skills/gstack/SKILL.md.tmpl +281 -0
- package/.agents/skills/gstack/TODOS.md +564 -0
- package/.agents/skills/gstack/VERSION +1 -0
- package/.agents/skills/gstack/autoplan/SKILL.md +689 -0
- package/.agents/skills/gstack/autoplan/SKILL.md.tmpl +416 -0
- package/.agents/skills/gstack/benchmark/SKILL.md +489 -0
- package/.agents/skills/gstack/benchmark/SKILL.md.tmpl +233 -0
- package/.agents/skills/gstack/bin/dev-setup +68 -0
- package/.agents/skills/gstack/bin/dev-teardown +56 -0
- package/.agents/skills/gstack/bin/gstack-analytics +191 -0
- package/.agents/skills/gstack/bin/gstack-community-dashboard +113 -0
- package/.agents/skills/gstack/bin/gstack-config +38 -0
- package/.agents/skills/gstack/bin/gstack-diff-scope +71 -0
- package/.agents/skills/gstack/bin/gstack-global-discover.ts +591 -0
- package/.agents/skills/gstack/bin/gstack-repo-mode +93 -0
- package/.agents/skills/gstack/bin/gstack-review-log +9 -0
- package/.agents/skills/gstack/bin/gstack-review-read +12 -0
- package/.agents/skills/gstack/bin/gstack-slug +15 -0
- package/.agents/skills/gstack/bin/gstack-telemetry-log +158 -0
- package/.agents/skills/gstack/bin/gstack-telemetry-sync +127 -0
- package/.agents/skills/gstack/bin/gstack-update-check +196 -0
- package/.agents/skills/gstack/browse/SKILL.md +517 -0
- package/.agents/skills/gstack/browse/SKILL.md.tmpl +141 -0
- package/.agents/skills/gstack/browse/bin/find-browse +21 -0
- package/.agents/skills/gstack/browse/bin/remote-slug +14 -0
- package/.agents/skills/gstack/browse/scripts/build-node-server.sh +48 -0
- package/.agents/skills/gstack/browse/src/browser-manager.ts +634 -0
- package/.agents/skills/gstack/browse/src/buffers.ts +137 -0
- package/.agents/skills/gstack/browse/src/bun-polyfill.cjs +109 -0
- package/.agents/skills/gstack/browse/src/cli.ts +420 -0
- package/.agents/skills/gstack/browse/src/commands.ts +111 -0
- package/.agents/skills/gstack/browse/src/config.ts +150 -0
- package/.agents/skills/gstack/browse/src/cookie-import-browser.ts +417 -0
- package/.agents/skills/gstack/browse/src/cookie-picker-routes.ts +207 -0
- package/.agents/skills/gstack/browse/src/cookie-picker-ui.ts +541 -0
- package/.agents/skills/gstack/browse/src/find-browse.ts +61 -0
- package/.agents/skills/gstack/browse/src/meta-commands.ts +269 -0
- package/.agents/skills/gstack/browse/src/platform.ts +17 -0
- package/.agents/skills/gstack/browse/src/read-commands.ts +335 -0
- package/.agents/skills/gstack/browse/src/server.ts +369 -0
- package/.agents/skills/gstack/browse/src/snapshot.ts +398 -0
- package/.agents/skills/gstack/browse/src/url-validation.ts +91 -0
- package/.agents/skills/gstack/browse/src/write-commands.ts +352 -0
- package/.agents/skills/gstack/browse/test/bun-polyfill.test.ts +72 -0
- package/.agents/skills/gstack/browse/test/commands.test.ts +1836 -0
- package/.agents/skills/gstack/browse/test/config.test.ts +250 -0
- package/.agents/skills/gstack/browse/test/cookie-import-browser.test.ts +397 -0
- package/.agents/skills/gstack/browse/test/cookie-picker-routes.test.ts +205 -0
- package/.agents/skills/gstack/browse/test/find-browse.test.ts +50 -0
- package/.agents/skills/gstack/browse/test/fixtures/basic.html +33 -0
- package/.agents/skills/gstack/browse/test/fixtures/cursor-interactive.html +22 -0
- package/.agents/skills/gstack/browse/test/fixtures/dialog.html +15 -0
- package/.agents/skills/gstack/browse/test/fixtures/empty.html +2 -0
- package/.agents/skills/gstack/browse/test/fixtures/forms.html +55 -0
- package/.agents/skills/gstack/browse/test/fixtures/qa-eval-checkout.html +108 -0
- package/.agents/skills/gstack/browse/test/fixtures/qa-eval-spa.html +98 -0
- package/.agents/skills/gstack/browse/test/fixtures/qa-eval.html +51 -0
- package/.agents/skills/gstack/browse/test/fixtures/responsive.html +49 -0
- package/.agents/skills/gstack/browse/test/fixtures/snapshot.html +55 -0
- package/.agents/skills/gstack/browse/test/fixtures/spa.html +24 -0
- package/.agents/skills/gstack/browse/test/fixtures/states.html +17 -0
- package/.agents/skills/gstack/browse/test/fixtures/upload.html +25 -0
- package/.agents/skills/gstack/browse/test/gstack-config.test.ts +125 -0
- package/.agents/skills/gstack/browse/test/gstack-update-check.test.ts +467 -0
- package/.agents/skills/gstack/browse/test/handoff.test.ts +235 -0
- package/.agents/skills/gstack/browse/test/path-validation.test.ts +63 -0
- package/.agents/skills/gstack/browse/test/platform.test.ts +37 -0
- package/.agents/skills/gstack/browse/test/snapshot.test.ts +467 -0
- package/.agents/skills/gstack/browse/test/test-server.ts +57 -0
- package/.agents/skills/gstack/browse/test/url-validation.test.ts +72 -0
- package/.agents/skills/gstack/canary/SKILL.md +493 -0
- package/.agents/skills/gstack/canary/SKILL.md.tmpl +220 -0
- package/.agents/skills/gstack/careful/SKILL.md +59 -0
- package/.agents/skills/gstack/careful/SKILL.md.tmpl +57 -0
- package/.agents/skills/gstack/careful/bin/check-careful.sh +112 -0
- package/.agents/skills/gstack/codex/SKILL.md +677 -0
- package/.agents/skills/gstack/codex/SKILL.md.tmpl +356 -0
- package/.agents/skills/gstack/conductor.json +6 -0
- package/.agents/skills/gstack/cso/SKILL.md +615 -0
- package/.agents/skills/gstack/cso/SKILL.md.tmpl +376 -0
- package/.agents/skills/gstack/design-consultation/SKILL.md +625 -0
- package/.agents/skills/gstack/design-consultation/SKILL.md.tmpl +369 -0
- package/.agents/skills/gstack/design-review/SKILL.md +998 -0
- package/.agents/skills/gstack/design-review/SKILL.md.tmpl +262 -0
- package/.agents/skills/gstack/docs/images/github-2013.png +0 -0
- package/.agents/skills/gstack/docs/images/github-2026.png +0 -0
- package/.agents/skills/gstack/docs/skills.md +877 -0
- package/.agents/skills/gstack/document-release/SKILL.md +613 -0
- package/.agents/skills/gstack/document-release/SKILL.md.tmpl +357 -0
- package/.agents/skills/gstack/freeze/SKILL.md +82 -0
- package/.agents/skills/gstack/freeze/SKILL.md.tmpl +80 -0
- package/.agents/skills/gstack/freeze/bin/check-freeze.sh +68 -0
- package/.agents/skills/gstack/gstack-upgrade/SKILL.md +226 -0
- package/.agents/skills/gstack/gstack-upgrade/SKILL.md.tmpl +224 -0
- package/.agents/skills/gstack/guard/SKILL.md +82 -0
- package/.agents/skills/gstack/guard/SKILL.md.tmpl +80 -0
- package/.agents/skills/gstack/investigate/SKILL.md +435 -0
- package/.agents/skills/gstack/investigate/SKILL.md.tmpl +196 -0
- package/.agents/skills/gstack/land-and-deploy/SKILL.md +880 -0
- package/.agents/skills/gstack/land-and-deploy/SKILL.md.tmpl +575 -0
- package/.agents/skills/gstack/office-hours/SKILL.md +996 -0
- package/.agents/skills/gstack/office-hours/SKILL.md.tmpl +624 -0
- package/.agents/skills/gstack/package.json +55 -0
- package/.agents/skills/gstack/plan-ceo-review/SKILL.md +1277 -0
- package/.agents/skills/gstack/plan-ceo-review/SKILL.md.tmpl +838 -0
- package/.agents/skills/gstack/plan-design-review/SKILL.md +676 -0
- package/.agents/skills/gstack/plan-design-review/SKILL.md.tmpl +314 -0
- package/.agents/skills/gstack/plan-eng-review/SKILL.md +836 -0
- package/.agents/skills/gstack/plan-eng-review/SKILL.md.tmpl +279 -0
- package/.agents/skills/gstack/qa/SKILL.md +1016 -0
- package/.agents/skills/gstack/qa/SKILL.md.tmpl +316 -0
- package/.agents/skills/gstack/qa/references/issue-taxonomy.md +85 -0
- package/.agents/skills/gstack/qa/templates/qa-report-template.md +126 -0
- package/.agents/skills/gstack/qa-only/SKILL.md +633 -0
- package/.agents/skills/gstack/qa-only/SKILL.md.tmpl +101 -0
- package/.agents/skills/gstack/retro/SKILL.md +1072 -0
- package/.agents/skills/gstack/retro/SKILL.md.tmpl +833 -0
- package/.agents/skills/gstack/review/SKILL.md +849 -0
- package/.agents/skills/gstack/review/SKILL.md.tmpl +259 -0
- package/.agents/skills/gstack/review/TODOS-format.md +62 -0
- package/.agents/skills/gstack/review/checklist.md +190 -0
- package/.agents/skills/gstack/review/design-checklist.md +132 -0
- package/.agents/skills/gstack/review/greptile-triage.md +220 -0
- package/.agents/skills/gstack/scripts/analytics.ts +190 -0
- package/.agents/skills/gstack/scripts/dev-skill.ts +82 -0
- package/.agents/skills/gstack/scripts/eval-compare.ts +96 -0
- package/.agents/skills/gstack/scripts/eval-list.ts +116 -0
- package/.agents/skills/gstack/scripts/eval-select.ts +86 -0
- package/.agents/skills/gstack/scripts/eval-summary.ts +187 -0
- package/.agents/skills/gstack/scripts/eval-watch.ts +172 -0
- package/.agents/skills/gstack/scripts/gen-skill-docs.ts +2414 -0
- package/.agents/skills/gstack/scripts/skill-check.ts +167 -0
- package/.agents/skills/gstack/setup +269 -0
- package/.agents/skills/gstack/setup-browser-cookies/SKILL.md +330 -0
- package/.agents/skills/gstack/setup-browser-cookies/SKILL.md.tmpl +74 -0
- package/.agents/skills/gstack/setup-deploy/SKILL.md +459 -0
- package/.agents/skills/gstack/setup-deploy/SKILL.md.tmpl +220 -0
- package/.agents/skills/gstack/ship/SKILL.md +1457 -0
- package/.agents/skills/gstack/ship/SKILL.md.tmpl +528 -0
- package/.agents/skills/gstack/supabase/config.sh +10 -0
- package/.agents/skills/gstack/supabase/functions/community-pulse/index.ts +59 -0
- package/.agents/skills/gstack/supabase/functions/telemetry-ingest/index.ts +135 -0
- package/.agents/skills/gstack/supabase/functions/update-check/index.ts +37 -0
- package/.agents/skills/gstack/supabase/migrations/001_telemetry.sql +89 -0
- package/.agents/skills/gstack/test/analytics.test.ts +277 -0
- package/.agents/skills/gstack/test/codex-e2e.test.ts +197 -0
- package/.agents/skills/gstack/test/fixtures/coverage-audit-fixture.ts +76 -0
- package/.agents/skills/gstack/test/fixtures/eval-baselines.json +7 -0
- package/.agents/skills/gstack/test/fixtures/qa-eval-checkout-ground-truth.json +43 -0
- package/.agents/skills/gstack/test/fixtures/qa-eval-ground-truth.json +43 -0
- package/.agents/skills/gstack/test/fixtures/qa-eval-spa-ground-truth.json +43 -0
- package/.agents/skills/gstack/test/fixtures/review-eval-design-slop.css +86 -0
- package/.agents/skills/gstack/test/fixtures/review-eval-design-slop.html +41 -0
- package/.agents/skills/gstack/test/fixtures/review-eval-enum-diff.rb +30 -0
- package/.agents/skills/gstack/test/fixtures/review-eval-enum.rb +27 -0
- package/.agents/skills/gstack/test/fixtures/review-eval-vuln.rb +14 -0
- package/.agents/skills/gstack/test/gemini-e2e.test.ts +173 -0
- package/.agents/skills/gstack/test/gen-skill-docs.test.ts +1049 -0
- package/.agents/skills/gstack/test/global-discover.test.ts +187 -0
- package/.agents/skills/gstack/test/helpers/codex-session-runner.ts +282 -0
- package/.agents/skills/gstack/test/helpers/e2e-helpers.ts +239 -0
- package/.agents/skills/gstack/test/helpers/eval-store.test.ts +548 -0
- package/.agents/skills/gstack/test/helpers/eval-store.ts +689 -0
- package/.agents/skills/gstack/test/helpers/gemini-session-runner.test.ts +104 -0
- package/.agents/skills/gstack/test/helpers/gemini-session-runner.ts +201 -0
- package/.agents/skills/gstack/test/helpers/llm-judge.ts +130 -0
- package/.agents/skills/gstack/test/helpers/observability.test.ts +283 -0
- package/.agents/skills/gstack/test/helpers/session-runner.test.ts +96 -0
- package/.agents/skills/gstack/test/helpers/session-runner.ts +357 -0
- package/.agents/skills/gstack/test/helpers/skill-parser.ts +206 -0
- package/.agents/skills/gstack/test/helpers/touchfiles.ts +260 -0
- package/.agents/skills/gstack/test/hook-scripts.test.ts +373 -0
- package/.agents/skills/gstack/test/skill-e2e-browse.test.ts +293 -0
- package/.agents/skills/gstack/test/skill-e2e-deploy.test.ts +279 -0
- package/.agents/skills/gstack/test/skill-e2e-design.test.ts +614 -0
- package/.agents/skills/gstack/test/skill-e2e-plan.test.ts +538 -0
- package/.agents/skills/gstack/test/skill-e2e-qa-bugs.test.ts +194 -0
- package/.agents/skills/gstack/test/skill-e2e-qa-workflow.test.ts +412 -0
- package/.agents/skills/gstack/test/skill-e2e-review.test.ts +535 -0
- package/.agents/skills/gstack/test/skill-e2e-workflow.test.ts +586 -0
- package/.agents/skills/gstack/test/skill-e2e.test.ts +3325 -0
- package/.agents/skills/gstack/test/skill-llm-eval.test.ts +787 -0
- package/.agents/skills/gstack/test/skill-parser.test.ts +179 -0
- package/.agents/skills/gstack/test/skill-routing-e2e.test.ts +605 -0
- package/.agents/skills/gstack/test/skill-validation.test.ts +1520 -0
- package/.agents/skills/gstack/test/telemetry.test.ts +278 -0
- package/.agents/skills/gstack/test/touchfiles.test.ts +262 -0
- package/.agents/skills/gstack/unfreeze/SKILL.md +40 -0
- package/.agents/skills/gstack/unfreeze/SKILL.md.tmpl +38 -0
- package/README.md +12 -7
- package/README_KO.md +12 -6
- package/package.json +3 -2
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write commands — navigate and interact with pages (side effects)
|
|
3
|
+
*
|
|
4
|
+
* goto, back, forward, reload, click, fill, select, hover, type,
|
|
5
|
+
* press, scroll, wait, viewport, cookie, header, useragent
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { BrowserManager } from './browser-manager';
|
|
9
|
+
import { findInstalledBrowsers, importCookies } from './cookie-import-browser';
|
|
10
|
+
import { validateNavigationUrl } from './url-validation';
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import { TEMP_DIR, isPathWithin } from './platform';
|
|
14
|
+
|
|
15
|
+
export async function handleWriteCommand(
|
|
16
|
+
command: string,
|
|
17
|
+
args: string[],
|
|
18
|
+
bm: BrowserManager
|
|
19
|
+
): Promise<string> {
|
|
20
|
+
const page = bm.getPage();
|
|
21
|
+
|
|
22
|
+
switch (command) {
|
|
23
|
+
case 'goto': {
|
|
24
|
+
const url = args[0];
|
|
25
|
+
if (!url) throw new Error('Usage: browse goto <url>');
|
|
26
|
+
await validateNavigationUrl(url);
|
|
27
|
+
const response = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
28
|
+
const status = response?.status() || 'unknown';
|
|
29
|
+
return `Navigated to ${url} (${status})`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
case 'back': {
|
|
33
|
+
await page.goBack({ waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
34
|
+
return `Back → ${page.url()}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
case 'forward': {
|
|
38
|
+
await page.goForward({ waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
39
|
+
return `Forward → ${page.url()}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
case 'reload': {
|
|
43
|
+
await page.reload({ waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
44
|
+
return `Reloaded ${page.url()}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
case 'click': {
|
|
48
|
+
const selector = args[0];
|
|
49
|
+
if (!selector) throw new Error('Usage: browse click <selector>');
|
|
50
|
+
|
|
51
|
+
// Auto-route: if ref points to a real <option> inside a <select>, use selectOption
|
|
52
|
+
const role = bm.getRefRole(selector);
|
|
53
|
+
if (role === 'option') {
|
|
54
|
+
const resolved = await bm.resolveRef(selector);
|
|
55
|
+
if ('locator' in resolved) {
|
|
56
|
+
const optionInfo = await resolved.locator.evaluate(el => {
|
|
57
|
+
if (el.tagName !== 'OPTION') return null; // custom [role=option], not real <option>
|
|
58
|
+
const option = el as HTMLOptionElement;
|
|
59
|
+
const select = option.closest('select');
|
|
60
|
+
if (!select) return null;
|
|
61
|
+
return { value: option.value, text: option.text };
|
|
62
|
+
});
|
|
63
|
+
if (optionInfo) {
|
|
64
|
+
await resolved.locator.locator('xpath=ancestor::select').selectOption(optionInfo.value, { timeout: 5000 });
|
|
65
|
+
return `Selected "${optionInfo.text}" (auto-routed from click on <option>) → now at ${page.url()}`;
|
|
66
|
+
}
|
|
67
|
+
// Real <option> with no parent <select> or custom [role=option] — fall through to normal click
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const resolved = await bm.resolveRef(selector);
|
|
72
|
+
try {
|
|
73
|
+
if ('locator' in resolved) {
|
|
74
|
+
await resolved.locator.click({ timeout: 5000 });
|
|
75
|
+
} else {
|
|
76
|
+
await page.click(resolved.selector, { timeout: 5000 });
|
|
77
|
+
}
|
|
78
|
+
} catch (err: any) {
|
|
79
|
+
// Enhanced error guidance: clicking <option> elements always fails (not visible / timeout)
|
|
80
|
+
const isOption = 'locator' in resolved
|
|
81
|
+
? await resolved.locator.evaluate(el => el.tagName === 'OPTION').catch(() => false)
|
|
82
|
+
: await page.evaluate(
|
|
83
|
+
(sel: string) => document.querySelector(sel)?.tagName === 'OPTION',
|
|
84
|
+
(resolved as { selector: string }).selector
|
|
85
|
+
).catch(() => false);
|
|
86
|
+
if (isOption) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Cannot click <option> elements. Use 'browse select <parent-select> <value>' instead of 'click' for dropdown options.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
// Wait briefly for any navigation/DOM update
|
|
94
|
+
await page.waitForLoadState('domcontentloaded').catch(() => {});
|
|
95
|
+
return `Clicked ${selector} → now at ${page.url()}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
case 'fill': {
|
|
99
|
+
const [selector, ...valueParts] = args;
|
|
100
|
+
const value = valueParts.join(' ');
|
|
101
|
+
if (!selector || !value) throw new Error('Usage: browse fill <selector> <value>');
|
|
102
|
+
const resolved = await bm.resolveRef(selector);
|
|
103
|
+
if ('locator' in resolved) {
|
|
104
|
+
await resolved.locator.fill(value, { timeout: 5000 });
|
|
105
|
+
} else {
|
|
106
|
+
await page.fill(resolved.selector, value, { timeout: 5000 });
|
|
107
|
+
}
|
|
108
|
+
return `Filled ${selector}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
case 'select': {
|
|
112
|
+
const [selector, ...valueParts] = args;
|
|
113
|
+
const value = valueParts.join(' ');
|
|
114
|
+
if (!selector || !value) throw new Error('Usage: browse select <selector> <value>');
|
|
115
|
+
const resolved = await bm.resolveRef(selector);
|
|
116
|
+
if ('locator' in resolved) {
|
|
117
|
+
await resolved.locator.selectOption(value, { timeout: 5000 });
|
|
118
|
+
} else {
|
|
119
|
+
await page.selectOption(resolved.selector, value, { timeout: 5000 });
|
|
120
|
+
}
|
|
121
|
+
return `Selected "${value}" in ${selector}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
case 'hover': {
|
|
125
|
+
const selector = args[0];
|
|
126
|
+
if (!selector) throw new Error('Usage: browse hover <selector>');
|
|
127
|
+
const resolved = await bm.resolveRef(selector);
|
|
128
|
+
if ('locator' in resolved) {
|
|
129
|
+
await resolved.locator.hover({ timeout: 5000 });
|
|
130
|
+
} else {
|
|
131
|
+
await page.hover(resolved.selector, { timeout: 5000 });
|
|
132
|
+
}
|
|
133
|
+
return `Hovered ${selector}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
case 'type': {
|
|
137
|
+
const text = args.join(' ');
|
|
138
|
+
if (!text) throw new Error('Usage: browse type <text>');
|
|
139
|
+
await page.keyboard.type(text);
|
|
140
|
+
return `Typed ${text.length} characters`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
case 'press': {
|
|
144
|
+
const key = args[0];
|
|
145
|
+
if (!key) throw new Error('Usage: browse press <key> (e.g., Enter, Tab, Escape)');
|
|
146
|
+
await page.keyboard.press(key);
|
|
147
|
+
return `Pressed ${key}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
case 'scroll': {
|
|
151
|
+
const selector = args[0];
|
|
152
|
+
if (selector) {
|
|
153
|
+
const resolved = await bm.resolveRef(selector);
|
|
154
|
+
if ('locator' in resolved) {
|
|
155
|
+
await resolved.locator.scrollIntoViewIfNeeded({ timeout: 5000 });
|
|
156
|
+
} else {
|
|
157
|
+
await page.locator(resolved.selector).scrollIntoViewIfNeeded({ timeout: 5000 });
|
|
158
|
+
}
|
|
159
|
+
return `Scrolled ${selector} into view`;
|
|
160
|
+
}
|
|
161
|
+
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
|
162
|
+
return 'Scrolled to bottom';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
case 'wait': {
|
|
166
|
+
const selector = args[0];
|
|
167
|
+
if (!selector) throw new Error('Usage: browse wait <selector|--networkidle|--load|--domcontentloaded>');
|
|
168
|
+
if (selector === '--networkidle') {
|
|
169
|
+
const timeout = args[1] ? parseInt(args[1], 10) : 15000;
|
|
170
|
+
await page.waitForLoadState('networkidle', { timeout });
|
|
171
|
+
return 'Network idle';
|
|
172
|
+
}
|
|
173
|
+
if (selector === '--load') {
|
|
174
|
+
await page.waitForLoadState('load');
|
|
175
|
+
return 'Page loaded';
|
|
176
|
+
}
|
|
177
|
+
if (selector === '--domcontentloaded') {
|
|
178
|
+
await page.waitForLoadState('domcontentloaded');
|
|
179
|
+
return 'DOM content loaded';
|
|
180
|
+
}
|
|
181
|
+
const timeout = args[1] ? parseInt(args[1], 10) : 15000;
|
|
182
|
+
const resolved = await bm.resolveRef(selector);
|
|
183
|
+
if ('locator' in resolved) {
|
|
184
|
+
await resolved.locator.waitFor({ state: 'visible', timeout });
|
|
185
|
+
} else {
|
|
186
|
+
await page.waitForSelector(resolved.selector, { timeout });
|
|
187
|
+
}
|
|
188
|
+
return `Element ${selector} appeared`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
case 'viewport': {
|
|
192
|
+
const size = args[0];
|
|
193
|
+
if (!size || !size.includes('x')) throw new Error('Usage: browse viewport <WxH> (e.g., 375x812)');
|
|
194
|
+
const [w, h] = size.split('x').map(Number);
|
|
195
|
+
await bm.setViewport(w, h);
|
|
196
|
+
return `Viewport set to ${w}x${h}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
case 'cookie': {
|
|
200
|
+
const cookieStr = args[0];
|
|
201
|
+
if (!cookieStr || !cookieStr.includes('=')) throw new Error('Usage: browse cookie <name>=<value>');
|
|
202
|
+
const eq = cookieStr.indexOf('=');
|
|
203
|
+
const name = cookieStr.slice(0, eq);
|
|
204
|
+
const value = cookieStr.slice(eq + 1);
|
|
205
|
+
const url = new URL(page.url());
|
|
206
|
+
await page.context().addCookies([{
|
|
207
|
+
name,
|
|
208
|
+
value,
|
|
209
|
+
domain: url.hostname,
|
|
210
|
+
path: '/',
|
|
211
|
+
}]);
|
|
212
|
+
return `Cookie set: ${name}=****`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
case 'header': {
|
|
216
|
+
const headerStr = args[0];
|
|
217
|
+
if (!headerStr || !headerStr.includes(':')) throw new Error('Usage: browse header <name>:<value>');
|
|
218
|
+
const sep = headerStr.indexOf(':');
|
|
219
|
+
const name = headerStr.slice(0, sep).trim();
|
|
220
|
+
const value = headerStr.slice(sep + 1).trim();
|
|
221
|
+
await bm.setExtraHeader(name, value);
|
|
222
|
+
const sensitiveHeaders = ['authorization', 'cookie', 'set-cookie', 'x-api-key', 'x-auth-token'];
|
|
223
|
+
const redactedValue = sensitiveHeaders.includes(name.toLowerCase()) ? '****' : value;
|
|
224
|
+
return `Header set: ${name}: ${redactedValue}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
case 'useragent': {
|
|
228
|
+
const ua = args.join(' ');
|
|
229
|
+
if (!ua) throw new Error('Usage: browse useragent <string>');
|
|
230
|
+
bm.setUserAgent(ua);
|
|
231
|
+
const error = await bm.recreateContext();
|
|
232
|
+
if (error) {
|
|
233
|
+
return `User agent set to "${ua}" but: ${error}`;
|
|
234
|
+
}
|
|
235
|
+
return `User agent set: ${ua}`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
case 'upload': {
|
|
239
|
+
const [selector, ...filePaths] = args;
|
|
240
|
+
if (!selector || filePaths.length === 0) throw new Error('Usage: browse upload <selector> <file1> [file2...]');
|
|
241
|
+
|
|
242
|
+
// Validate all files exist before upload
|
|
243
|
+
for (const fp of filePaths) {
|
|
244
|
+
if (!fs.existsSync(fp)) throw new Error(`File not found: ${fp}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const resolved = await bm.resolveRef(selector);
|
|
248
|
+
if ('locator' in resolved) {
|
|
249
|
+
await resolved.locator.setInputFiles(filePaths);
|
|
250
|
+
} else {
|
|
251
|
+
await page.locator(resolved.selector).setInputFiles(filePaths);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const fileInfo = filePaths.map(fp => {
|
|
255
|
+
const stat = fs.statSync(fp);
|
|
256
|
+
return `${path.basename(fp)} (${stat.size}B)`;
|
|
257
|
+
}).join(', ');
|
|
258
|
+
return `Uploaded: ${fileInfo}`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
case 'dialog-accept': {
|
|
262
|
+
const text = args.length > 0 ? args.join(' ') : null;
|
|
263
|
+
bm.setDialogAutoAccept(true);
|
|
264
|
+
bm.setDialogPromptText(text);
|
|
265
|
+
return text
|
|
266
|
+
? `Dialogs will be accepted with text: "${text}"`
|
|
267
|
+
: 'Dialogs will be accepted';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
case 'dialog-dismiss': {
|
|
271
|
+
bm.setDialogAutoAccept(false);
|
|
272
|
+
bm.setDialogPromptText(null);
|
|
273
|
+
return 'Dialogs will be dismissed';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
case 'cookie-import': {
|
|
277
|
+
const filePath = args[0];
|
|
278
|
+
if (!filePath) throw new Error('Usage: browse cookie-import <json-file>');
|
|
279
|
+
// Path validation — prevent reading arbitrary files
|
|
280
|
+
if (path.isAbsolute(filePath)) {
|
|
281
|
+
const safeDirs = [TEMP_DIR, process.cwd()];
|
|
282
|
+
const resolved = path.resolve(filePath);
|
|
283
|
+
if (!safeDirs.some(dir => isPathWithin(resolved, dir))) {
|
|
284
|
+
throw new Error(`Path must be within: ${safeDirs.join(', ')}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (path.normalize(filePath).includes('..')) {
|
|
288
|
+
throw new Error('Path traversal sequences (..) are not allowed');
|
|
289
|
+
}
|
|
290
|
+
if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
|
|
291
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
292
|
+
let cookies: any[];
|
|
293
|
+
try { cookies = JSON.parse(raw); } catch { throw new Error(`Invalid JSON in ${filePath}`); }
|
|
294
|
+
if (!Array.isArray(cookies)) throw new Error('Cookie file must contain a JSON array');
|
|
295
|
+
|
|
296
|
+
// Auto-fill domain from current page URL when missing (consistent with cookie command)
|
|
297
|
+
const pageUrl = new URL(page.url());
|
|
298
|
+
const defaultDomain = pageUrl.hostname;
|
|
299
|
+
|
|
300
|
+
for (const c of cookies) {
|
|
301
|
+
if (!c.name || c.value === undefined) throw new Error('Each cookie must have "name" and "value" fields');
|
|
302
|
+
if (!c.domain) c.domain = defaultDomain;
|
|
303
|
+
if (!c.path) c.path = '/';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
await page.context().addCookies(cookies);
|
|
307
|
+
return `Loaded ${cookies.length} cookies from ${filePath}`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
case 'cookie-import-browser': {
|
|
311
|
+
// Two modes:
|
|
312
|
+
// 1. Direct CLI import: cookie-import-browser <browser> --domain <domain>
|
|
313
|
+
// 2. Open picker UI: cookie-import-browser [browser]
|
|
314
|
+
const browserArg = args[0];
|
|
315
|
+
const domainIdx = args.indexOf('--domain');
|
|
316
|
+
|
|
317
|
+
if (domainIdx !== -1 && domainIdx + 1 < args.length) {
|
|
318
|
+
// Direct import mode — no UI
|
|
319
|
+
const domain = args[domainIdx + 1];
|
|
320
|
+
const browser = browserArg || 'comet';
|
|
321
|
+
const result = await importCookies(browser, [domain]);
|
|
322
|
+
if (result.cookies.length > 0) {
|
|
323
|
+
await page.context().addCookies(result.cookies);
|
|
324
|
+
}
|
|
325
|
+
const msg = [`Imported ${result.count} cookies for ${domain} from ${browser}`];
|
|
326
|
+
if (result.failed > 0) msg.push(`(${result.failed} failed to decrypt)`);
|
|
327
|
+
return msg.join(' ');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Picker UI mode — open in user's browser
|
|
331
|
+
const port = bm.serverPort;
|
|
332
|
+
if (!port) throw new Error('Server port not available');
|
|
333
|
+
|
|
334
|
+
const browsers = findInstalledBrowsers();
|
|
335
|
+
if (browsers.length === 0) {
|
|
336
|
+
throw new Error('No Chromium browsers found. Supported: Comet, Chrome, Arc, Brave, Edge');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const pickerUrl = `http://127.0.0.1:${port}/cookie-picker`;
|
|
340
|
+
try {
|
|
341
|
+
Bun.spawn(['open', pickerUrl], { stdout: 'ignore', stderr: 'ignore' });
|
|
342
|
+
} catch {
|
|
343
|
+
// open may fail silently — URL is in the message below
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return `Cookie picker opened at ${pickerUrl}\nDetected browsers: ${browsers.map(b => b.name).join(', ')}\nSelect domains to import, then close the picker when done.`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
default:
|
|
350
|
+
throw new Error(`Unknown write command: ${command}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, test, expect, afterAll } from 'bun:test';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
// Load the polyfill into a fresh object (don't clobber globalThis.Bun)
|
|
5
|
+
const polyfillPath = path.resolve(import.meta.dir, '../src/bun-polyfill.cjs');
|
|
6
|
+
|
|
7
|
+
describe('bun-polyfill', () => {
|
|
8
|
+
// We test the polyfill by requiring it in a subprocess under Node.js
|
|
9
|
+
// since it's designed for Node, not Bun.
|
|
10
|
+
|
|
11
|
+
test('Bun.sleep resolves after delay', async () => {
|
|
12
|
+
const result = Bun.spawnSync(['node', '-e', `
|
|
13
|
+
require('${polyfillPath}');
|
|
14
|
+
(async () => {
|
|
15
|
+
const start = Date.now();
|
|
16
|
+
await Bun.sleep(50);
|
|
17
|
+
const elapsed = Date.now() - start;
|
|
18
|
+
console.log(elapsed >= 40 ? 'OK' : 'TOO_FAST');
|
|
19
|
+
})();
|
|
20
|
+
`], { stdout: 'pipe', stderr: 'pipe' });
|
|
21
|
+
expect(result.stdout.toString().trim()).toBe('OK');
|
|
22
|
+
expect(result.exitCode).toBe(0);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('Bun.spawnSync runs a command and returns stdout', () => {
|
|
26
|
+
const result = Bun.spawnSync(['node', '-e', `
|
|
27
|
+
require('${polyfillPath}');
|
|
28
|
+
const r = Bun.spawnSync(['echo', 'hello'], { stdout: 'pipe' });
|
|
29
|
+
console.log(r.stdout.toString().trim());
|
|
30
|
+
console.log('exit:' + r.exitCode);
|
|
31
|
+
`], { stdout: 'pipe', stderr: 'pipe' });
|
|
32
|
+
const lines = result.stdout.toString().trim().split('\n');
|
|
33
|
+
expect(lines[0]).toBe('hello');
|
|
34
|
+
expect(lines[1]).toBe('exit:0');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('Bun.spawn launches a process with pid', async () => {
|
|
38
|
+
const result = Bun.spawnSync(['node', '-e', `
|
|
39
|
+
require('${polyfillPath}');
|
|
40
|
+
const p = Bun.spawn(['echo', 'test'], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
41
|
+
console.log(typeof p.pid === 'number' ? 'HAS_PID' : 'NO_PID');
|
|
42
|
+
console.log(typeof p.kill === 'function' ? 'HAS_KILL' : 'NO_KILL');
|
|
43
|
+
console.log(typeof p.unref === 'function' ? 'HAS_UNREF' : 'NO_UNREF');
|
|
44
|
+
`], { stdout: 'pipe', stderr: 'pipe' });
|
|
45
|
+
const lines = result.stdout.toString().trim().split('\n');
|
|
46
|
+
expect(lines[0]).toBe('HAS_PID');
|
|
47
|
+
expect(lines[1]).toBe('HAS_KILL');
|
|
48
|
+
expect(lines[2]).toBe('HAS_UNREF');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('Bun.serve creates an HTTP server that responds', async () => {
|
|
52
|
+
const result = Bun.spawnSync(['node', '-e', `
|
|
53
|
+
require('${polyfillPath}');
|
|
54
|
+
const server = Bun.serve({
|
|
55
|
+
port: 0, // Note: polyfill uses port directly, so we pick one
|
|
56
|
+
hostname: '127.0.0.1',
|
|
57
|
+
fetch(req) {
|
|
58
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
59
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
// The polyfill doesn't support port 0, so we test the object shape
|
|
64
|
+
console.log(typeof server.stop === 'function' ? 'HAS_STOP' : 'NO_STOP');
|
|
65
|
+
console.log(typeof server.port === 'number' ? 'HAS_PORT' : 'NO_PORT');
|
|
66
|
+
server.stop();
|
|
67
|
+
`], { stdout: 'pipe', stderr: 'pipe' });
|
|
68
|
+
const lines = result.stdout.toString().trim().split('\n');
|
|
69
|
+
expect(lines[0]).toBe('HAS_STOP');
|
|
70
|
+
expect(lines[1]).toBe('HAS_PORT');
|
|
71
|
+
});
|
|
72
|
+
});
|