- Use fish as the source-of-truth shell.
- Run terminal commands as
fish -lc '<command>'. - Use
uvfor Python. - Use
js_replfor persistent browser control when working interactively in FreeTaxUSA.
Use a separate real Chrome instance with remote debugging enabled, then attach to it from js_repl with Playwright over CDP.
This is the workflow that worked reliably for interactive FreeTaxUSA filing.
Run from this repo:
fish -lc 'npm init -y'
fish -lc 'npm install playwright'Use a dedicated profile inside this repo:
fish -lc 'mkdir -p .chrome-debug-profile'
fish -lc "google-chrome-stable \
--remote-debugging-port=9222 \
--user-data-dir=/home/sharno/src/taxbot/.chrome-debug-profile \
--no-first-run \
--no-default-browser-check"Notes:
- Keep this Chrome process running while working.
- The user signs in manually in this Chrome window.
- Reuse the same
.chrome-debug-profileacross turns.
Bootstrap:
var chromium;
({ chromium } = await import("playwright"));Attach handles:
var cdpBrowser;
var cdpContext;
var cdpPage;
cdpBrowser = await chromium.connectOverCDP("http://127.0.0.1:9222");
cdpContext = cdpBrowser.contexts()[0];
cdpPage = cdpContext.pages()[0] ?? await cdpContext.newPage();
await cdpPage.goto("https://www.freetaxusa.com/", { waitUntil: "domcontentloaded" });After attachment, keep using cdpPage.
- Let the user handle authentication manually.
- Read visible page text before acting:
console.log("URL:", cdpPage.url());
console.log("Title:", await cdpPage.title());
console.log(await cdpPage.evaluate(() => document.body.innerText.slice(0, 4000)));- Use direct Playwright actions for buttons/radios when the page is simple:
await cdpPage.getByRole("button", { name: /^Continue$/i }).click();
await cdpPage.getByRole("button", { name: /^Edit$/i }).click();
await cdpPage.getByRole("radio", { name: /^No$/i }).click();- After each action:
await cdpPage.waitForLoadState("domcontentloaded");
await cdpPage.waitForTimeout(2500);For large FreeTaxUSA forms, prefer direct DOM field assignment using name/id inside page.evaluate(...) rather than long chains of Playwright label locators.
This is faster and avoids js_repl timeouts on large forms.
Example pattern:
await cdpPage.evaluate(() => {
const setValue = (name, value) => {
const el = document.querySelector(`[name="${name}"]`);
if (!el) return;
el.value = value;
el.dispatchEvent(new Event("input", { bubbles: true }));
el.dispatchEvent(new Event("change", { bubbles: true }));
};
setValue("fed_tax-CURRENCY", "35860.56");
});Then verify values immediately:
console.log(await cdpPage.evaluate(() => ({
fed: document.querySelector('[name="fed_tax-CURRENCY"]')?.value
})));If FreeTaxUSA reformats a money field unexpectedly, re-check the rendered value before saving.
- Read files from
/home/sharno/Downloads/Tax 2025. - Use shell tools first:
fish -lc 'ls -la "/home/sharno/Downloads/Tax 2025"'
fish -lc 'pdftotext -layout "/path/to/w2.pdf" -'
fish -lc 'pdftoppm -png "/path/to/w2.pdf" /tmp/taxbot-w2/page'- Use rendered PNG pages to visually confirm ambiguous W-2 labels.
- Treat clearly labeled fields as safe to enter.
- If a label-to-tax-field mapping is not explicit, state the uncertainty before deciding.
Keep progress.md current at all times.
Record:
- current page / stop point
- exact question waiting on the user
- values observed on-screen
- files used
- any judgment calls or uncertainty
Update progress.md whenever:
- the stop point changes
- the user answers a question
- a document is read and used
- a meaningful tax form section is completed
- Source of truth for resume state:
progress.md - Browser to use for resume: Chrome launched with
.chrome-debug-profile - Control surface:
js_replattached over CDP tohttp://127.0.0.1:9222