From ea4a2c19025470d9a1595fb7ccfec6f697390ffa Mon Sep 17 00:00:00 2001 From: zbeyens Date: Wed, 6 May 2026 21:02:59 +0800 Subject: [PATCH] fix plugin dependency package manager detection --- .agents/AGENTS.md | 23 +--- .agents/rules/agent-browser-issue.mdc | 2 +- .agents/rules/browser-debug-setup.mdc | 125 ----------------- .agents/rules/dev-browser.mdc | 129 ++++++++++++++++++ .agents/rules/major-task.mdc | 2 +- .agents/rules/task.mdc | 8 +- .agents/skills/agent-browser-issue/SKILL.md | 2 +- .agents/skills/browser-debug-setup/SKILL.md | 129 ------------------ .agents/skills/dev-browser/SKILL.md | 122 ++++++++++++++++- .../references/features/auth-organizations.md | 12 +- .agents/skills/major-task/SKILL.md | 2 +- .agents/skills/task/SKILL.md | 8 +- .changeset/fair-pandas-wave.md | 7 + .claude/prompt.yml | 2 +- AGENTS.md | 23 +--- .../2026-03-28-ship-blockers-followup.md | 2 +- ...4-05-auth-query-cache-after-auth-switch.md | 2 +- fixtures/expo-auth/package.json | 26 ++-- fixtures/expo/package.json | 26 ++-- packages/kitcn/src/cli/backend-core.ts | 68 ++------- packages/kitcn/src/cli/cli.ts | 10 +- packages/kitcn/src/cli/package-manager.ts | 77 +++++++++++ .../src/cli/registry/dependencies.test.ts | 59 ++++++++ .../kitcn/src/cli/registry/dependencies.ts | 41 +++--- .../kitcn/src/cli/registry/planner.test.ts | 95 +++++++++++++ packages/kitcn/src/cli/registry/planner.ts | 25 +++- skills-lock.json | 5 - 27 files changed, 614 insertions(+), 418 deletions(-) delete mode 100644 .agents/rules/browser-debug-setup.mdc create mode 100644 .agents/rules/dev-browser.mdc delete mode 100644 .agents/skills/browser-debug-setup/SKILL.md create mode 100644 .changeset/fair-pandas-wave.md create mode 100644 packages/kitcn/src/cli/package-manager.ts diff --git a/.agents/AGENTS.md b/.agents/AGENTS.md index a652ac3c..cc560d68 100644 --- a/.agents/AGENTS.md +++ b/.agents/AGENTS.md @@ -65,25 +65,16 @@ When using the following skills, override the default behavior. - Do not create `task_plan.md`, `findings.md`, or `progress.md` at repo root. Merge that content into one file under `docs/plans/`. Example: `docs/plans/2026-02-07-fix-schema.md` -`dev-browser`: - -- Use `dev-browser --connect http://127.0.0.1:9222` by default for browser work. Do not preflight `9222` first. -- Only inspect `9222` or use `browser-debug-setup` after a direct `dev-browser --connect http://127.0.0.1:9222` attempt fails. -- Reuse one persistent debug Chrome on `127.0.0.1:9222`. Do not spin up disposable browser instances unless the user asks. -- Use a dedicated Chrome `--user-data-dir` for that debug browser, not the user's normal daily Chrome data dir. -- Clone the signed-in Chrome profile into the dedicated debug dir, then launch the debug browser from that clone. -- On macOS, launch the debug browser with `open -na "Google Chrome" --args ... --remote-debugging-port=9222` so it opens as a separate Chrome instance without hijacking the user's normal window. -- Do not close or stop the user's connected debug browser. Leave that debug window open and reuse it. Close named pages only when needed. -- Keep scripts small and direct. Prefer `browser.getPage("persistent-main")` for the main app. -- Use `dev-browser` instead of `agent-browser` or next-devtools `browser_eval`. -- If `dev-browser` gets blocked by a human prompt or loops on the same step, stop and ask the user to unblock. After the unblock works: - - [Add browser learning] +Browser usage: + +- Always try `[@browser-use](plugin://browser-use@openai-bundled)` first for browser usage. +- Do not substitute Puppeteer, standalone Playwright, or raw Chrome DevTools for browser usage. `ce-*`: -- **plan:** Include `dev-browser` in acceptance criteria for browser features +- **plan:** Include Browser Use in acceptance criteria for browser features - **deepen-plan:** Context7 only when not covered by skills -- **work:** UI tasks require `dev-browser` BEFORE marking complete. Never guess. +- **work:** UI tasks require Browser Use BEFORE marking complete. Never guess. ## Prompt Hook @@ -124,7 +115,7 @@ When using the following skills, override the default behavior. - [ ] Typecheck (IF updated .ts files): Run `typecheck` - [ ] Lint: Run `lint:fix` - [ ] PR gate (IF creating/updating a PR): Run `check` -- [ ] Browser verification (IF a browser surface changed): verify with `dev-browser --connect http://127.0.0.1:9222` before done +- [ ] Browser verification (IF a browser surface changed): verify with Browser Use before done - [ ] ce-compound (SKIP if trivial): CRITICAL: After completing this request, you MUST evaluate whether it produced extractable knowledge. EVALUATION PROTOCOL (NON-NEGOTIABLE): (1) COMPLETE the user's request first (2) EVALUATE - Did this require non-obvious investigation or debugging? Was the solution something that would help in future similar situations? Did I discover something not immediately obvious from documentation? (3) IF YES to any: load `ce-compound` after the fix is verified and follow its workflow to capture the solution in `docs/solutions/` (4) IF NO to all: Skip - no extraction needed This is NOT optional. Failing to evaluate = valuable knowledge lost. ### Post Compact Recovery diff --git a/.agents/rules/agent-browser-issue.mdc b/.agents/rules/agent-browser-issue.mdc index 501597cd..85cd25ee 100644 --- a/.agents/rules/agent-browser-issue.mdc +++ b/.agents/rules/agent-browser-issue.mdc @@ -1,5 +1,5 @@ --- -description: Open a concise GitHub follow-up for reusable dev-browser or agent-browser limitations. Use when browser automation is blocked by a likely tool-side issue that is worth fixing separately, especially for clicks, dropdowns, file inputs, focus traps, or other repeatable agent/browser failures. +description: Open a concise GitHub follow-up for reusable browser-use limitations. Use when browser automation is blocked by a likely tool-side issue that is worth fixing separately, especially for clicks, dropdowns, file inputs, focus traps, or other repeatable agent/browser failures. argument-hint: '[browser block summary]' disable-model-invocation: true --- diff --git a/.agents/rules/browser-debug-setup.mdc b/.agents/rules/browser-debug-setup.mdc deleted file mode 100644 index 7ef78ef7..00000000 --- a/.agents/rules/browser-debug-setup.mdc +++ /dev/null @@ -1,125 +0,0 @@ ---- -description: One-time setup for a persistent debug browser on `127.0.0.1:9222` for `dev-browser --connect`. Use when browser work is needed but no reusable debug browser is running yet. -user-invocable: false ---- - -# Browser Debug Setup - -Use this skill when `dev-browser --connect http://127.0.0.1:9222` fails because -no persistent debug browser is running yet. - -## Goal - -Get the user onto one persistent browser/profile that both the human and the -agent reuse. Minimize the `Allow remote debugging?` popup by keeping one -dedicated debug browser/profile alive. - -## Rules - -- Prefer one permanent debug browser/profile over disposable automation - browsers. -- Treat a custom `--user-data-dir` as mandatory, not optional. Chrome 136+ - basically wants remote debugging to happen from a dedicated profile. -- Keep auth in that profile. Do not fall back to cookie dumps or state files - unless the user asks. -- Use a separate signed-in Chrome profile for browser work, like `dev`. Do not - use the user's normal daily `Default` profile as the source profile. -- Clone that separate signed-in Chrome profile into the dedicated debug - `--user-data-dir`; do not point `9222` straight at the user's daily Chrome - data dir. -- On macOS, use `open -na "Google Chrome" --args ...` for the debug browser. - That starts a separate Chrome instance with the dedicated debug profile - without touching the user's normal Chrome window. - -## Preferred Shape - -Use a dedicated browser/profile with: - -- `--remote-debugging-address=127.0.0.1` -- `--remote-debugging-port=9222` -- a persistent `--user-data-dir=` - -Sign in once in that dedicated browser and keep reusing it for agent work. - -Quick sanity check: - -```bash -curl -sS http://127.0.0.1:9222/json/version -``` - -Healthy output includes a JSON object with `webSocketDebuggerUrl`. Empty output -or `404` means the wrong process owns `9222`. - -Then verify `dev-browser`: - -```bash -dev-browser --connect http://127.0.0.1:9222 <<'EOF' -const page = await browser.getPage("persistent-main"); -console.log(await page.title()); -EOF -``` - -If `dev-browser --connect http://127.0.0.1:9222` still cannot resolve CDP even -though `/json/version` is healthy, connect with the exact websocket URL: - -```bash -WS=$(curl -sS http://127.0.0.1:9222/json/version | jq -r '.webSocketDebuggerUrl') - -dev-browser --connect "$WS" <<'EOF' -const page = await browser.getPage("persistent-main"); -console.log(await page.title()); -EOF -``` - -## Google Chrome Path - -Default setup on macOS: - -1. Pick a separate signed-in Chrome profile for agent work, like `dev`, not - the daily `Default` profile. -2. Map that human-facing Chrome profile name to the real folder in `Local State`. -3. Clone that profile into the dedicated debug dir. -4. Launch a separate Chrome instance on `9222`. -5. Leave that debug window open and reuse it. - -```bash -python3 - <<'PY' -import json, pathlib -p = pathlib.Path('~/Library/Application Support/Google/Chrome/Local State').expanduser() -obj = json.loads(p.read_text()) -for key, val in obj.get('profile', {}).get('info_cache', {}).items(): - print(f"{key}\tname={val.get('name')}\tgaia_name={val.get('gaia_name')}") -PY - -# Example: if `dev` maps to `Profile 1`, clone `Profile 1`. -mkdir -p "$HOME/.config/google-chrome-debug-profile/Default" -rsync -a --delete \ - --exclude='Singleton*' \ - --exclude='DevToolsActivePort' \ - --exclude='lockfile' \ - "$HOME/Library/Application Support/Google/Chrome/Profile 1/" \ - "$HOME/.config/google-chrome-debug-profile/Default/" -cp "$HOME/Library/Application Support/Google/Chrome/Local State" \ - "$HOME/.config/google-chrome-debug-profile/Local State" - -open -na "Google Chrome" --args \ - --user-data-dir="$HOME/.config/google-chrome-debug-profile" \ - --profile-directory="Default" \ - --remote-debugging-address=127.0.0.1 \ - --remote-debugging-port=9222 -``` - -That keeps the signed-in identity while still satisfying Chrome's dedicated -`--user-data-dir` requirement. - -Then keep reusing that exact debug browser. Do not point `9222` at your normal -daily `Default` Chrome profile. - -## After Setup - -- Use `dev-browser --connect http://127.0.0.1:9222` for browser work. -- Reuse named pages like `persistent-main`. -- Do not stop the user's debug browser unless they ask. -- If the wrong Chrome steals `9222`, identify it with `lsof -nP -iTCP:9222 -sTCP:LISTEN`, - kill that listener, and relaunch the dedicated debug browser. Do not keep - debugging against a stale `404` or empty `/json/version` owner. diff --git a/.agents/rules/dev-browser.mdc b/.agents/rules/dev-browser.mdc new file mode 100644 index 00000000..91d28101 --- /dev/null +++ b/.agents/rules/dev-browser.mdc @@ -0,0 +1,129 @@ +--- +description: Fallback browser automation with persistent Chrome state. Use only when Browser Use is unavailable or blocked. +--- + +# Dev Browser + +Use this only as the fallback browser path when `[@browser-use](plugin://browser-use@openai-bundled)` is unavailable or blocked. + +Do not substitute Puppeteer, standalone Playwright, or raw Chrome DevTools for this fallback path. + +## Installation + +```bash +npm install -g dev-browser +dev-browser install +``` + +Run `dev-browser --help` to learn more. + +## Defaults + +- Use `dev-browser --connect http://127.0.0.1:9222` by default. Do not preflight `9222` first. +- Only inspect `9222` after a direct `dev-browser --connect http://127.0.0.1:9222` attempt fails. +- Reuse one persistent debug Chrome on `127.0.0.1:9222`. Do not spin up disposable browser instances unless the user asks. +- Use a dedicated Chrome `--user-data-dir` for that debug browser, not the user's normal daily Chrome data dir. +- Clone the signed-in Chrome profile into the dedicated debug dir, then launch the debug browser from that clone. +- On macOS, launch the debug browser with `open -na "Google Chrome" --args ... --remote-debugging-port=9222` so it opens as a separate Chrome instance without hijacking the user's normal window. +- Do not close or stop the user's connected debug browser. Leave that debug window open and reuse it. Close named pages only when needed. +- Keep scripts small and direct. Prefer `browser.getPage("persistent-main")` for the main app. +- Use `dev-browser` instead of `agent-browser` or next-devtools `browser_eval`. +- If `dev-browser` gets blocked by a human prompt or loops on the same step, stop and ask the user to unblock. + +## Fallback Setup + +Use this only after `dev-browser --connect http://127.0.0.1:9222` fails because no reusable debug Chrome is available or the CDP endpoint is broken. + +## Rules + +- Prefer one permanent debug browser/profile over disposable automation browsers. +- Treat a custom `--user-data-dir` as mandatory, not optional. Chrome 136+ expects remote debugging to happen from a dedicated profile. +- Keep auth in that profile. Do not fall back to cookie dumps or state files unless the user asks. +- Use a separate signed-in Chrome profile for browser work, like `dev`. Do not use the user's normal daily `Default` profile as the source profile. +- Clone that separate signed-in Chrome profile into the dedicated debug `--user-data-dir`; do not point `9222` straight at the user's daily Chrome data dir. +- On macOS, use `open -na "Google Chrome" --args ...` for the debug browser. That starts a separate Chrome instance with the dedicated debug profile without touching the user's normal Chrome window. + +## Preferred Shape + +Use a dedicated browser/profile with: + +- `--remote-debugging-address=127.0.0.1` +- `--remote-debugging-port=9222` +- a persistent `--user-data-dir=` + +Sign in once in that dedicated browser and keep reusing it for agent work. + +Quick sanity check: + +```bash +curl -sS http://127.0.0.1:9222/json/version +``` + +Healthy output includes a JSON object with `webSocketDebuggerUrl`. Empty output or `404` means the wrong process owns `9222`. + +Then verify: + +```bash +dev-browser --connect http://127.0.0.1:9222 <<'EOF' +const page = await browser.getPage("persistent-main"); +console.log(await page.title()); +EOF +``` + +If direct connect still cannot resolve CDP even though `/json/version` is healthy, connect with the exact websocket URL: + +```bash +WS=$(curl -sS http://127.0.0.1:9222/json/version | jq -r '.webSocketDebuggerUrl') + +dev-browser --connect "$WS" <<'EOF' +const page = await browser.getPage("persistent-main"); +console.log(await page.title()); +EOF +``` + +## Google Chrome Path + +Default setup on macOS: + +1. Pick a separate signed-in Chrome profile for agent work, like `dev`, not the daily `Default` profile. +2. Map that human-facing Chrome profile name to the real folder in `Local State`. +3. Clone that profile into the dedicated debug dir. +4. Launch a separate Chrome instance on `9222`. +5. Leave that debug window open and reuse it. + +```bash +python3 - <<'PY' +import json, pathlib +p = pathlib.Path('~/Library/Application Support/Google/Chrome/Local State').expanduser() +obj = json.loads(p.read_text()) +for key, val in obj.get('profile', {}).get('info_cache', {}).items(): + print(f"{key}\tname={val.get('name')}\tgaia_name={val.get('gaia_name')}") +PY + +# Example: if `dev` maps to `Profile 1`, clone `Profile 1`. +mkdir -p "$HOME/.config/google-chrome-debug-profile/Default" +rsync -a --delete \ + --exclude='Singleton*' \ + --exclude='DevToolsActivePort' \ + --exclude='lockfile' \ + "$HOME/Library/Application Support/Google/Chrome/Profile 1/" \ + "$HOME/.config/google-chrome-debug-profile/Default/" +cp "$HOME/Library/Application Support/Google/Chrome/Local State" \ + "$HOME/.config/google-chrome-debug-profile/Local State" + +open -na "Google Chrome" --args \ + --user-data-dir="$HOME/.config/google-chrome-debug-profile" \ + --profile-directory="Default" \ + --remote-debugging-address=127.0.0.1 \ + --remote-debugging-port=9222 +``` + +Do not point `9222` at the normal daily `Default` Chrome profile. + +If the wrong Chrome steals `9222`, identify it with: + +```bash +lsof -nP -iTCP:9222 -sTCP:LISTEN +``` + +Kill that listener and relaunch the dedicated debug browser. Do not keep debugging against a stale `404` or empty `/json/version` owner. diff --git a/.agents/rules/major-task.mdc b/.agents/rules/major-task.mdc index 160b94f2..b3f36c7d 100644 --- a/.agents/rules/major-task.mdc +++ b/.agents/rules/major-task.mdc @@ -155,7 +155,7 @@ Apply this section only when the task source is a tracker item. Use only when major work actually turns into risky code-changing execution or architecture-sensitive diffs. - `agent-native-reviewer` Use only when the change touches `.agents/**`, `.claude/**`, AI/tooling surfaces, commands, or user actions that an agent should also be able to perform. -- `dev-browser` +- `browser-use` Use only when there is a real browser surface to verify. - `agent-browser-issue` Use when browser automation is blocked by a likely reusable tool-side issue that deserves a separate GitHub follow-up. diff --git a/.agents/rules/task.mdc b/.agents/rules/task.mdc index 0616e363..a86ab050 100644 --- a/.agents/rules/task.mdc +++ b/.agents/rules/task.mdc @@ -275,7 +275,7 @@ Apply this section only when the task source is a tracker item. - `framework-docs-researcher` Use when touching unfamiliar, version-sensitive, or unstable third-party APIs after checking local clones and docs per AGENTS. -- `dev-browser` +- `browser-use` Use only when there is a real browser surface to verify. Require real browser proof only for browser or UI tasks. - `agent-browser-issue` @@ -469,7 +469,7 @@ Every final response must include: ### UI Or Browser Tasks - Include at least one real browser proof screenshot in the final response. -- The screenshot must come from `dev-browser` or the real browser workflow used +- The screenshot must come from `browser-use` or the real browser workflow used for verification. - When `**🌐 Browser Check**` is present, put the screenshot immediately after that section. @@ -477,7 +477,7 @@ Every final response must include: table, before the completion summary. - If no real browser proof exists, the task is not done unless the user explicitly waived it. -- If `dev-browser` is blocked on a likely reusable tool-side issue and the +- If `browser-use` is blocked on a likely reusable tool-side issue and the product task is still otherwise fixable, load `agent-browser-issue`. - If that follow-up issue is opened, mention it in the caveat or handoff. - `**🌐 Browser Check**` must be a flat bullet list: @@ -534,7 +534,7 @@ meaningful outcome. - replace the placeholder with the real hosted proof before handoff - If the PR description includes a local image path for proof, do not leave it that way on GitHub. -- Use `dev-browser --connect http://127.0.0.1:9222` to upload the image through +- Use `browser-use` to upload the image through the PR comment file input as a staging area, then replace the local proof path in the PR body with the hosted GitHub attachment URL. - Use the PR comment textarea only as staging: diff --git a/.agents/skills/agent-browser-issue/SKILL.md b/.agents/skills/agent-browser-issue/SKILL.md index 58b6aab6..b8eba242 100644 --- a/.agents/skills/agent-browser-issue/SKILL.md +++ b/.agents/skills/agent-browser-issue/SKILL.md @@ -1,5 +1,5 @@ --- -description: Open a concise GitHub follow-up for reusable dev-browser or agent-browser limitations. Use when browser automation is blocked by a likely tool-side issue that is worth fixing separately, especially for clicks, dropdowns, file inputs, focus traps, or other repeatable agent/browser failures. +description: Open a concise GitHub follow-up for reusable browser-use limitations. Use when browser automation is blocked by a likely tool-side issue that is worth fixing separately, especially for clicks, dropdowns, file inputs, focus traps, or other repeatable agent/browser failures. argument-hint: '[browser block summary]' disable-model-invocation: true name: agent-browser-issue diff --git a/.agents/skills/browser-debug-setup/SKILL.md b/.agents/skills/browser-debug-setup/SKILL.md deleted file mode 100644 index 45e9a8a3..00000000 --- a/.agents/skills/browser-debug-setup/SKILL.md +++ /dev/null @@ -1,129 +0,0 @@ ---- -description: One-time setup for a persistent debug browser on `127.0.0.1:9222` for `dev-browser --connect`. Use when browser work is needed but no reusable debug browser is running yet. -user-invocable: false -name: browser-debug-setup -metadata: - skiller: - source: .agents/rules/browser-debug-setup.mdc ---- - -# Browser Debug Setup - -Use this skill when `dev-browser --connect http://127.0.0.1:9222` fails because -no persistent debug browser is running yet. - -## Goal - -Get the user onto one persistent browser/profile that both the human and the -agent reuse. Minimize the `Allow remote debugging?` popup by keeping one -dedicated debug browser/profile alive. - -## Rules - -- Prefer one permanent debug browser/profile over disposable automation - browsers. -- Treat a custom `--user-data-dir` as mandatory, not optional. Chrome 136+ - basically wants remote debugging to happen from a dedicated profile. -- Keep auth in that profile. Do not fall back to cookie dumps or state files - unless the user asks. -- Use a separate signed-in Chrome profile for browser work, like `dev`. Do not - use the user's normal daily `Default` profile as the source profile. -- Clone that separate signed-in Chrome profile into the dedicated debug - `--user-data-dir`; do not point `9222` straight at the user's daily Chrome - data dir. -- On macOS, use `open -na "Google Chrome" --args ...` for the debug browser. - That starts a separate Chrome instance with the dedicated debug profile - without touching the user's normal Chrome window. - -## Preferred Shape - -Use a dedicated browser/profile with: - -- `--remote-debugging-address=127.0.0.1` -- `--remote-debugging-port=9222` -- a persistent `--user-data-dir=` - -Sign in once in that dedicated browser and keep reusing it for agent work. - -Quick sanity check: - -```bash -curl -sS http://127.0.0.1:9222/json/version -``` - -Healthy output includes a JSON object with `webSocketDebuggerUrl`. Empty output -or `404` means the wrong process owns `9222`. - -Then verify `dev-browser`: - -```bash -dev-browser --connect http://127.0.0.1:9222 <<'EOF' -const page = await browser.getPage("persistent-main"); -console.log(await page.title()); -EOF -``` - -If `dev-browser --connect http://127.0.0.1:9222` still cannot resolve CDP even -though `/json/version` is healthy, connect with the exact websocket URL: - -```bash -WS=$(curl -sS http://127.0.0.1:9222/json/version | jq -r '.webSocketDebuggerUrl') - -dev-browser --connect "$WS" <<'EOF' -const page = await browser.getPage("persistent-main"); -console.log(await page.title()); -EOF -``` - -## Google Chrome Path - -Default setup on macOS: - -1. Pick a separate signed-in Chrome profile for agent work, like `dev`, not - the daily `Default` profile. -2. Map that human-facing Chrome profile name to the real folder in `Local State`. -3. Clone that profile into the dedicated debug dir. -4. Launch a separate Chrome instance on `9222`. -5. Leave that debug window open and reuse it. - -```bash -python3 - <<'PY' -import json, pathlib -p = pathlib.Path('~/Library/Application Support/Google/Chrome/Local State').expanduser() -obj = json.loads(p.read_text()) -for key, val in obj.get('profile', {}).get('info_cache', {}).items(): - print(f"{key}\tname={val.get('name')}\tgaia_name={val.get('gaia_name')}") -PY - -# Example: if `dev` maps to `Profile 1`, clone `Profile 1`. -mkdir -p "$HOME/.config/google-chrome-debug-profile/Default" -rsync -a --delete \ - --exclude='Singleton*' \ - --exclude='DevToolsActivePort' \ - --exclude='lockfile' \ - "$HOME/Library/Application Support/Google/Chrome/Profile 1/" \ - "$HOME/.config/google-chrome-debug-profile/Default/" -cp "$HOME/Library/Application Support/Google/Chrome/Local State" \ - "$HOME/.config/google-chrome-debug-profile/Local State" - -open -na "Google Chrome" --args \ - --user-data-dir="$HOME/.config/google-chrome-debug-profile" \ - --profile-directory="Default" \ - --remote-debugging-address=127.0.0.1 \ - --remote-debugging-port=9222 -``` - -That keeps the signed-in identity while still satisfying Chrome's dedicated -`--user-data-dir` requirement. - -Then keep reusing that exact debug browser. Do not point `9222` at your normal -daily `Default` Chrome profile. - -## After Setup - -- Use `dev-browser --connect http://127.0.0.1:9222` for browser work. -- Reuse named pages like `persistent-main`. -- Do not stop the user's debug browser unless they ask. -- If the wrong Chrome steals `9222`, identify it with `lsof -nP -iTCP:9222 -sTCP:LISTEN`, - kill that listener, and relaunch the dedicated debug browser. Do not keep - debugging against a stale `404` or empty `/json/version` owner. diff --git a/.agents/skills/dev-browser/SKILL.md b/.agents/skills/dev-browser/SKILL.md index 0ce87d9a..361749d9 100644 --- a/.agents/skills/dev-browser/SKILL.md +++ b/.agents/skills/dev-browser/SKILL.md @@ -1,11 +1,16 @@ --- +description: Fallback browser automation with persistent Chrome state. Use only when Browser Use is unavailable or blocked. name: dev-browser -description: Browser automation with persistent page state. Use when users ask to navigate websites, fill forms, take screenshots, extract web data, test web apps, or automate browser workflows. Trigger phrases include "go to [url]", "click on", "fill out the form", "take a screenshot", "scrape", "automate", "test the website", "log into", or any browser interaction request. +metadata: + skiller: + source: .agents/rules/dev-browser.mdc --- # Dev Browser -A CLI for controlling browsers with sandboxed JavaScript scripts. +Use this only as the fallback browser path when `[@browser-use](plugin://browser-use@openai-bundled)` is unavailable or blocked. + +Do not substitute Puppeteer, standalone Playwright, or raw Chrome DevTools for this fallback path. ## Installation @@ -14,6 +19,115 @@ npm install -g dev-browser dev-browser install ``` -## Usage - Run `dev-browser --help` to learn more. + +## Defaults + +- Use `dev-browser --connect http://127.0.0.1:9222` by default. Do not preflight `9222` first. +- Only inspect `9222` after a direct `dev-browser --connect http://127.0.0.1:9222` attempt fails. +- Reuse one persistent debug Chrome on `127.0.0.1:9222`. Do not spin up disposable browser instances unless the user asks. +- Use a dedicated Chrome `--user-data-dir` for that debug browser, not the user's normal daily Chrome data dir. +- Clone the signed-in Chrome profile into the dedicated debug dir, then launch the debug browser from that clone. +- On macOS, launch the debug browser with `open -na "Google Chrome" --args ... --remote-debugging-port=9222` so it opens as a separate Chrome instance without hijacking the user's normal window. +- Do not close or stop the user's connected debug browser. Leave that debug window open and reuse it. Close named pages only when needed. +- Keep scripts small and direct. Prefer `browser.getPage("persistent-main")` for the main app. +- Use `dev-browser` instead of `agent-browser` or next-devtools `browser_eval`. +- If `dev-browser` gets blocked by a human prompt or loops on the same step, stop and ask the user to unblock. + +## Fallback Setup + +Use this only after `dev-browser --connect http://127.0.0.1:9222` fails because no reusable debug Chrome is available or the CDP endpoint is broken. + +## Rules + +- Prefer one permanent debug browser/profile over disposable automation browsers. +- Treat a custom `--user-data-dir` as mandatory, not optional. Chrome 136+ expects remote debugging to happen from a dedicated profile. +- Keep auth in that profile. Do not fall back to cookie dumps or state files unless the user asks. +- Use a separate signed-in Chrome profile for browser work, like `dev`. Do not use the user's normal daily `Default` profile as the source profile. +- Clone that separate signed-in Chrome profile into the dedicated debug `--user-data-dir`; do not point `9222` straight at the user's daily Chrome data dir. +- On macOS, use `open -na "Google Chrome" --args ...` for the debug browser. That starts a separate Chrome instance with the dedicated debug profile without touching the user's normal Chrome window. + +## Preferred Shape + +Use a dedicated browser/profile with: + +- `--remote-debugging-address=127.0.0.1` +- `--remote-debugging-port=9222` +- a persistent `--user-data-dir=` + +Sign in once in that dedicated browser and keep reusing it for agent work. + +Quick sanity check: + +```bash +curl -sS http://127.0.0.1:9222/json/version +``` + +Healthy output includes a JSON object with `webSocketDebuggerUrl`. Empty output or `404` means the wrong process owns `9222`. + +Then verify: + +```bash +dev-browser --connect http://127.0.0.1:9222 <<'EOF' +const page = await browser.getPage("persistent-main"); +console.log(await page.title()); +EOF +``` + +If direct connect still cannot resolve CDP even though `/json/version` is healthy, connect with the exact websocket URL: + +```bash +WS=$(curl -sS http://127.0.0.1:9222/json/version | jq -r '.webSocketDebuggerUrl') + +dev-browser --connect "$WS" <<'EOF' +const page = await browser.getPage("persistent-main"); +console.log(await page.title()); +EOF +``` + +## Google Chrome Path + +Default setup on macOS: + +1. Pick a separate signed-in Chrome profile for agent work, like `dev`, not the daily `Default` profile. +2. Map that human-facing Chrome profile name to the real folder in `Local State`. +3. Clone that profile into the dedicated debug dir. +4. Launch a separate Chrome instance on `9222`. +5. Leave that debug window open and reuse it. + +```bash +python3 - <<'PY' +import json, pathlib +p = pathlib.Path('~/Library/Application Support/Google/Chrome/Local State').expanduser() +obj = json.loads(p.read_text()) +for key, val in obj.get('profile', {}).get('info_cache', {}).items(): + print(f"{key}\tname={val.get('name')}\tgaia_name={val.get('gaia_name')}") +PY + +# Example: if `dev` maps to `Profile 1`, clone `Profile 1`. +mkdir -p "$HOME/.config/google-chrome-debug-profile/Default" +rsync -a --delete \ + --exclude='Singleton*' \ + --exclude='DevToolsActivePort' \ + --exclude='lockfile' \ + "$HOME/Library/Application Support/Google/Chrome/Profile 1/" \ + "$HOME/.config/google-chrome-debug-profile/Default/" +cp "$HOME/Library/Application Support/Google/Chrome/Local State" \ + "$HOME/.config/google-chrome-debug-profile/Local State" + +open -na "Google Chrome" --args \ + --user-data-dir="$HOME/.config/google-chrome-debug-profile" \ + --profile-directory="Default" \ + --remote-debugging-address=127.0.0.1 \ + --remote-debugging-port=9222 +``` + +Do not point `9222` at the normal daily `Default` Chrome profile. + +If the wrong Chrome steals `9222`, identify it with: + +```bash +lsof -nP -iTCP:9222 -sTCP:LISTEN +``` + +Kill that listener and relaunch the dedicated debug browser. Do not keep debugging against a stale `404` or empty `/json/version` owner. diff --git a/.agents/skills/kitcn/references/features/auth-organizations.md b/.agents/skills/kitcn/references/features/auth-organizations.md index 14295ad8..4a3779ab 100644 --- a/.agents/skills/kitcn/references/features/auth-organizations.md +++ b/.agents/skills/kitcn/references/features/auth-organizations.md @@ -430,14 +430,18 @@ export const updateOrganization = authMutation if (input.name !== undefined) data.name = input.name; if (slug !== undefined) data.slug = slug; - await ctx.auth.api.updateOrganization({ - body: { data, organizationId: input.organizationId }, - headers: ctx.auth.headers, - }); + await ctx.orm + .update(organization) + .set(data) + .where(eq(organization.id, input.organizationId)); return null; }); ``` +Use an `authAction` instead of an `authMutation` for any Better Auth endpoint +that can run external plugin work such as Stripe, Polar, or email delivery. +Convex mutations cannot call those SDKs. + ### Delete Organization ```ts diff --git a/.agents/skills/major-task/SKILL.md b/.agents/skills/major-task/SKILL.md index 2815b7ec..4d822e10 100644 --- a/.agents/skills/major-task/SKILL.md +++ b/.agents/skills/major-task/SKILL.md @@ -159,7 +159,7 @@ Apply this section only when the task source is a tracker item. Use only when major work actually turns into risky code-changing execution or architecture-sensitive diffs. - `agent-native-reviewer` Use only when the change touches `.agents/**`, `.claude/**`, AI/tooling surfaces, commands, or user actions that an agent should also be able to perform. -- `dev-browser` +- `browser-use` Use only when there is a real browser surface to verify. - `agent-browser-issue` Use when browser automation is blocked by a likely reusable tool-side issue that deserves a separate GitHub follow-up. diff --git a/.agents/skills/task/SKILL.md b/.agents/skills/task/SKILL.md index e171f5f2..82492937 100644 --- a/.agents/skills/task/SKILL.md +++ b/.agents/skills/task/SKILL.md @@ -279,7 +279,7 @@ Apply this section only when the task source is a tracker item. - `framework-docs-researcher` Use when touching unfamiliar, version-sensitive, or unstable third-party APIs after checking local clones and docs per AGENTS. -- `dev-browser` +- `browser-use` Use only when there is a real browser surface to verify. Require real browser proof only for browser or UI tasks. - `agent-browser-issue` @@ -473,7 +473,7 @@ Every final response must include: ### UI Or Browser Tasks - Include at least one real browser proof screenshot in the final response. -- The screenshot must come from `dev-browser` or the real browser workflow used +- The screenshot must come from `browser-use` or the real browser workflow used for verification. - When `**🌐 Browser Check**` is present, put the screenshot immediately after that section. @@ -481,7 +481,7 @@ Every final response must include: table, before the completion summary. - If no real browser proof exists, the task is not done unless the user explicitly waived it. -- If `dev-browser` is blocked on a likely reusable tool-side issue and the +- If `browser-use` is blocked on a likely reusable tool-side issue and the product task is still otherwise fixable, load `agent-browser-issue`. - If that follow-up issue is opened, mention it in the caveat or handoff. - `**🌐 Browser Check**` must be a flat bullet list: @@ -538,7 +538,7 @@ meaningful outcome. - replace the placeholder with the real hosted proof before handoff - If the PR description includes a local image path for proof, do not leave it that way on GitHub. -- Use `dev-browser --connect http://127.0.0.1:9222` to upload the image through +- Use `browser-use` to upload the image through the PR comment file input as a staging area, then replace the local proof path in the PR body with the hosted GitHub attachment URL. - Use the PR comment textarea only as staging: diff --git a/.changeset/fair-pandas-wave.md b/.changeset/fair-pandas-wave.md new file mode 100644 index 00000000..f4b11ce7 --- /dev/null +++ b/.changeset/fair-pandas-wave.md @@ -0,0 +1,7 @@ +--- +"kitcn": patch +--- + +## Patches + +- Fix plugin dependency installs to use the project's package manager. diff --git a/.claude/prompt.yml b/.claude/prompt.yml index c9ef99f7..3f4d49ce 100644 --- a/.claude/prompt.yml +++ b/.claude/prompt.yml @@ -30,7 +30,7 @@ beforeComplete: - NEVER git commit unless explicitly asked - 'NEVER `bun dev` or `bun run build` unless explicitly asked' todos: - - 'Test Browser (IF new features, styling, visual bugs, state changes. SKIP trivial markup, non-UI): Skill(dev-browser)' + - 'Test Browser (IF new features, styling, visual bugs, state changes. SKIP trivial markup, non-UI): Browser Use' - 'Typecheck (IF updated .ts files): Bash `bun typecheck`' - 'Lint: Bash `bun lint:fix`' - | diff --git a/AGENTS.md b/AGENTS.md index 4942df46..df8d3c74 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,25 +70,16 @@ When using the following skills, override the default behavior. - Do not create `task_plan.md`, `findings.md`, or `progress.md` at repo root. Merge that content into one file under `docs/plans/`. Example: `docs/plans/2026-02-07-fix-schema.md` -`dev-browser`: - -- Use `dev-browser --connect http://127.0.0.1:9222` by default for browser work. Do not preflight `9222` first. -- Only inspect `9222` or use `browser-debug-setup` after a direct `dev-browser --connect http://127.0.0.1:9222` attempt fails. -- Reuse one persistent debug Chrome on `127.0.0.1:9222`. Do not spin up disposable browser instances unless the user asks. -- Use a dedicated Chrome `--user-data-dir` for that debug browser, not the user's normal daily Chrome data dir. -- Clone the signed-in Chrome profile into the dedicated debug dir, then launch the debug browser from that clone. -- On macOS, launch the debug browser with `open -na "Google Chrome" --args ... --remote-debugging-port=9222` so it opens as a separate Chrome instance without hijacking the user's normal window. -- Do not close or stop the user's connected debug browser. Leave that debug window open and reuse it. Close named pages only when needed. -- Keep scripts small and direct. Prefer `browser.getPage("persistent-main")` for the main app. -- Use `dev-browser` instead of `agent-browser` or next-devtools `browser_eval`. -- If `dev-browser` gets blocked by a human prompt or loops on the same step, stop and ask the user to unblock. After the unblock works: - - [Add browser learning] +Browser usage: + +- Always try `[@browser-use](plugin://browser-use@openai-bundled)` first for browser usage. +- Do not substitute Puppeteer, standalone Playwright, or raw Chrome DevTools for browser usage. `ce-*`: -- **plan:** Include `dev-browser` in acceptance criteria for browser features +- **plan:** Include Browser Use in acceptance criteria for browser features - **deepen-plan:** Context7 only when not covered by skills -- **work:** UI tasks require `dev-browser` BEFORE marking complete. Never guess. +- **work:** UI tasks require Browser Use BEFORE marking complete. Never guess. ## Prompt Hook @@ -129,7 +120,7 @@ When using the following skills, override the default behavior. - [ ] Typecheck (IF updated .ts files): Run `typecheck` - [ ] Lint: Run `lint:fix` - [ ] PR gate (IF creating/updating a PR): Run `check` -- [ ] Browser verification (IF a browser surface changed): verify with `dev-browser --connect http://127.0.0.1:9222` before done +- [ ] Browser verification (IF a browser surface changed): verify with Browser Use before done - [ ] ce-compound (SKIP if trivial): CRITICAL: After completing this request, you MUST evaluate whether it produced extractable knowledge. EVALUATION PROTOCOL (NON-NEGOTIABLE): (1) COMPLETE the user's request first (2) EVALUATE - Did this require non-obvious investigation or debugging? Was the solution something that would help in future similar situations? Did I discover something not immediately obvious from documentation? (3) IF YES to any: load `ce-compound` after the fix is verified and follow its workflow to capture the solution in `docs/solutions/` (4) IF NO to all: Skip - no extraction needed This is NOT optional. Failing to evaluate = valuable knowledge lost. ### Post Compact Recovery diff --git a/docs/plans/2026-03-28-ship-blockers-followup.md b/docs/plans/2026-03-28-ship-blockers-followup.md index 4209ad54..1b7f70e1 100644 --- a/docs/plans/2026-03-28-ship-blockers-followup.md +++ b/docs/plans/2026-03-28-ship-blockers-followup.md @@ -8,7 +8,7 @@ which the user explicitly deferred. - [ ] Fix stale generated-api integration tests to use the chained schema contract and current generated server expectations. - [ ] Replace the remaining `agent-browser` auth E2E tooling seam with the - repo's `dev-browser --connect` direction, including tests. + repo's `browser-use --connect` direction, including tests. - [ ] Update the unreleased changeset so it reflects the real branch delta against `main`, including migrations. - [ ] Re-run focused verification plus the full ship gate that should now pass diff --git a/docs/plans/2026-04-05-auth-query-cache-after-auth-switch.md b/docs/plans/2026-04-05-auth-query-cache-after-auth-switch.md index 3555eb9b..aae3c34f 100644 --- a/docs/plans/2026-04-05-auth-query-cache-after-auth-switch.md +++ b/docs/plans/2026-04-05-auth-query-cache-after-auth-switch.md @@ -15,7 +15,7 @@ using Next as the proving lane. in a temp Next auth app, signed-in UI showed account 1 while the auth-bound query still rendered `guest` - Screenshot proof: - [next-auth-viewer-stuck-guest.png](/Users/zbeyens/.dev-browser/tmp/next-auth-viewer-stuck-guest.png) + [next-auth-viewer-stuck-guest.png](/Users/zbeyens/.browser-use/tmp/next-auth-viewer-stuck-guest.png) - Likely code seam: [auth-mutations.ts](/Users/zbeyens/git/better-convex/packages/kitcn/src/react/auth-mutations.ts) and diff --git a/fixtures/expo-auth/package.json b/fixtures/expo-auth/package.json index 43e8a0c1..18757345 100644 --- a/fixtures/expo-auth/package.json +++ b/fixtures/expo-auth/package.json @@ -8,21 +8,21 @@ "@tanstack/react-query": "5.95.2", "better-auth": "1.6.9", "convex": "1.36.1", - "expo": "~55.0.18", - "expo-constants": "~55.0.15", - "expo-device": "~55.0.15", - "expo-font": "~55.0.6", - "expo-glass-effect": "~55.0.10", - "expo-image": "~55.0.9", - "expo-linking": "~55.0.14", + "expo": "~55.0.23", + "expo-constants": "~55.0.16", + "expo-device": "~55.0.16", + "expo-font": "~55.0.7", + "expo-glass-effect": "~55.0.11", + "expo-image": "~55.0.10", + "expo-linking": "~55.0.15", "expo-network": "~55.0.8", - "expo-router": "~55.0.13", + "expo-router": "~55.0.14", "expo-secure-store": "~55.0.8", - "expo-splash-screen": "~55.0.19", - "expo-status-bar": "~55.0.5", - "expo-symbols": "~55.0.7", - "expo-system-ui": "~55.0.16", - "expo-web-browser": "~55.0.14", + "expo-splash-screen": "~55.0.20", + "expo-status-bar": "~55.0.6", + "expo-symbols": "~55.0.8", + "expo-system-ui": "~55.0.17", + "expo-web-browser": "~55.0.15", "hono": "4.12.9", "kitcn": "workspace:*", "react": "19.2.0", diff --git a/fixtures/expo/package.json b/fixtures/expo/package.json index 9552b307..4ea4890d 100644 --- a/fixtures/expo/package.json +++ b/fixtures/expo/package.json @@ -6,19 +6,19 @@ "@react-navigation/native": "^7.1.33", "@tanstack/react-query": "5.95.2", "convex": "1.36.1", - "expo": "~55.0.18", - "expo-constants": "~55.0.15", - "expo-device": "~55.0.15", - "expo-font": "~55.0.6", - "expo-glass-effect": "~55.0.10", - "expo-image": "~55.0.9", - "expo-linking": "~55.0.14", - "expo-router": "~55.0.13", - "expo-splash-screen": "~55.0.19", - "expo-status-bar": "~55.0.5", - "expo-symbols": "~55.0.7", - "expo-system-ui": "~55.0.16", - "expo-web-browser": "~55.0.14", + "expo": "~55.0.23", + "expo-constants": "~55.0.16", + "expo-device": "~55.0.16", + "expo-font": "~55.0.7", + "expo-glass-effect": "~55.0.11", + "expo-image": "~55.0.10", + "expo-linking": "~55.0.15", + "expo-router": "~55.0.14", + "expo-splash-screen": "~55.0.20", + "expo-status-bar": "~55.0.6", + "expo-symbols": "~55.0.8", + "expo-system-ui": "~55.0.17", + "expo-web-browser": "~55.0.15", "hono": "4.12.9", "kitcn": "workspace:*", "react": "19.2.0", diff --git a/packages/kitcn/src/cli/backend-core.ts b/packages/kitcn/src/cli/backend-core.ts index 198ceae1..1306bbd0 100644 --- a/packages/kitcn/src/cli/backend-core.ts +++ b/packages/kitcn/src/cli/backend-core.ts @@ -36,6 +36,11 @@ import { writeConvexCommandOutput, } from './convex-command.js'; import { pullEnv, resolveAuthEnvState, syncEnv } from './env.js'; +import { + detectPackageManager, + type PackageManager, + resolveDependencyInstallCommand, +} from './package-manager.js'; import { type ExpoScaffoldContext, type NextAppScaffoldContext, @@ -1486,8 +1491,6 @@ function overrideConfigBackend( }; } -type PackageManager = 'bun' | 'pnpm' | 'yarn' | 'npm'; - type DependencyInstallItem = { installSpec: string; packageName: string; @@ -2888,55 +2891,6 @@ function buildTemplateInitializationPlanFiles(params: { ]; } -function detectPackageManager(projectDir: string): PackageManager { - let current = resolve(projectDir); - while (true) { - const packageJsonPath = join(current, 'package.json'); - if (fs.existsSync(packageJsonPath)) { - try { - const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as { - packageManager?: unknown; - }; - if (typeof pkg.packageManager === 'string') { - if (pkg.packageManager.startsWith('bun@')) return 'bun'; - if (pkg.packageManager.startsWith('pnpm@')) return 'pnpm'; - if (pkg.packageManager.startsWith('yarn@')) return 'yarn'; - if (pkg.packageManager.startsWith('npm@')) return 'npm'; - } - } catch { - // ignore invalid package.json here; later reads will fail loudly if needed - } - } - - if ( - fs.existsSync(join(current, 'bun.lock')) || - fs.existsSync(join(current, 'bun.lockb')) - ) { - return 'bun'; - } - if ( - fs.existsSync(join(current, 'pnpm-lock.yaml')) || - fs.existsSync(join(current, 'pnpm-workspace.yaml')) - ) { - return 'pnpm'; - } - if (fs.existsSync(join(current, 'yarn.lock'))) { - return 'yarn'; - } - if (fs.existsSync(join(current, 'package-lock.json'))) { - return 'npm'; - } - - const parent = dirname(current); - if (parent === current) { - break; - } - current = parent; - } - - return 'bun'; -} - function resolveShadcnScaffoldProjectDir( projectDir: string, template?: string @@ -2995,15 +2949,15 @@ function buildDependencyInstallPlan( const packageManager = detectPackageManager(projectDir); const missingSpecs = missing.map((dependency) => dependency.installSpec); - const args = - packageManager === 'npm' - ? ['install', ...missingSpecs] - : ['add', ...missingSpecs]; + const installCommand = resolveDependencyInstallCommand( + packageManager, + missingSpecs + ); return { packageManager, - command: packageManager, - args, + command: installCommand.command, + args: installCommand.args, packages: missingSpecs, cwd: projectDir, }; diff --git a/packages/kitcn/src/cli/cli.ts b/packages/kitcn/src/cli/cli.ts index 0352f91b..a8da99eb 100644 --- a/packages/kitcn/src/cli/cli.ts +++ b/packages/kitcn/src/cli/cli.ts @@ -31,6 +31,10 @@ import { handleResetCommand } from './commands/reset.js'; import { handleVerifyCommand, VERIFY_HELP_TEXT } from './commands/verify.js'; import { handleViewCommand, VIEW_HELP_TEXT } from './commands/view.js'; import type { CliBackend } from './config.js'; +import { + detectPackageManager, + formatDependencyInstallCommand, +} from './package-manager.js'; import { resolveSupportedDependencyWarnings } from './supported-dependencies.js'; import { handleCliError } from './utils/handle-error.js'; import { logger } from './utils/logger.js'; @@ -205,9 +209,13 @@ function warnSupportedDependencyIssues(command: string) { return; } + const packageManager = detectPackageManager(process.cwd()); for (const warning of resolveSupportedDependencyWarnings()) { + const installCommand = formatDependencyInstallCommand(packageManager, [ + warning.installSpec, + ]); logger.warn( - `⚠️ kitcn expects ${warning.packageName} ${warning.minimum}; found ${warning.current}. Run \`bun add ${warning.installSpec}\` when you can.` + `⚠️ kitcn expects ${warning.packageName} ${warning.minimum}; found ${warning.current}. Run \`${installCommand}\` when you can.` ); } } diff --git a/packages/kitcn/src/cli/package-manager.ts b/packages/kitcn/src/cli/package-manager.ts new file mode 100644 index 00000000..62697fac --- /dev/null +++ b/packages/kitcn/src/cli/package-manager.ts @@ -0,0 +1,77 @@ +import fs from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; + +export type PackageManager = 'bun' | 'pnpm' | 'yarn' | 'npm'; + +export function detectPackageManager(projectDir: string): PackageManager { + let current = resolve(projectDir); + while (true) { + const packageJsonPath = join(current, 'package.json'); + if (fs.existsSync(packageJsonPath)) { + try { + const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as { + packageManager?: unknown; + }; + if (typeof pkg.packageManager === 'string') { + if (pkg.packageManager.startsWith('bun@')) return 'bun'; + if (pkg.packageManager.startsWith('pnpm@')) return 'pnpm'; + if (pkg.packageManager.startsWith('yarn@')) return 'yarn'; + if (pkg.packageManager.startsWith('npm@')) return 'npm'; + } + } catch { + // Later package.json reads fail loudly if the file is invalid. + } + } + + if ( + fs.existsSync(join(current, 'bun.lock')) || + fs.existsSync(join(current, 'bun.lockb')) + ) { + return 'bun'; + } + if ( + fs.existsSync(join(current, 'pnpm-lock.yaml')) || + fs.existsSync(join(current, 'pnpm-workspace.yaml')) + ) { + return 'pnpm'; + } + if (fs.existsSync(join(current, 'yarn.lock'))) { + return 'yarn'; + } + if (fs.existsSync(join(current, 'package-lock.json'))) { + return 'npm'; + } + + const parent = dirname(current); + if (parent === current) { + break; + } + current = parent; + } + + return 'bun'; +} + +export function resolveDependencyInstallCommand( + packageManager: PackageManager, + packageSpecs: readonly string[] +): { args: string[]; command: PackageManager } { + return { + command: packageManager, + args: + packageManager === 'npm' + ? ['install', ...packageSpecs] + : ['add', ...packageSpecs], + }; +} + +export function formatDependencyInstallCommand( + packageManager: PackageManager, + packageSpecs: readonly string[] +): string { + const { args, command } = resolveDependencyInstallCommand( + packageManager, + packageSpecs + ); + return `${command} ${args.join(' ')}`; +} diff --git a/packages/kitcn/src/cli/registry/dependencies.test.ts b/packages/kitcn/src/cli/registry/dependencies.test.ts index 1294b285..bdc81b2a 100644 --- a/packages/kitcn/src/cli/registry/dependencies.test.ts +++ b/packages/kitcn/src/cli/registry/dependencies.test.ts @@ -4,6 +4,7 @@ import os from 'node:os'; import path from 'node:path'; import { OPENTELEMETRY_API_INSTALL_SPEC } from '../supported-dependencies'; import { + applyPlanningDependencyInstall, applyPluginDependencyInstall, resolveBunPeerWarningPreinstallSpecs, resolveMissingDependencyHints, @@ -142,4 +143,62 @@ describe('cli/registry/dependencies', () => { ['bun', ['add', '@kitcn/resend@0.12.5']], ]); }); + + test('uses npm for planning dependency installs in npm projects', async () => { + fs.writeFileSync( + path.join(process.cwd(), 'package.json'), + JSON.stringify( + { + name: 'test-app', + packageManager: 'npm@10.9.0', + private: true, + }, + null, + 2 + ) + ); + + const execaStub = mock(async () => ({ exitCode: 0 }) as any); + + await applyPlanningDependencyInstall( + [OPENTELEMETRY_API_INSTALL_SPEC], + execaStub as any + ); + + expect(execaStub.mock.calls.map((call) => call.slice(0, 2))).toStrictEqual([ + ['npm', ['install', OPENTELEMETRY_API_INSTALL_SPEC]], + ]); + }); + + test('uses pnpm for plugin dependency installs in pnpm projects', async () => { + fs.writeFileSync( + path.join(process.cwd(), 'package.json'), + JSON.stringify( + { + name: 'test-app', + packageManager: 'pnpm@9.15.9', + private: true, + }, + null, + 2 + ) + ); + + const execaStub = mock(async () => ({ exitCode: 0 }) as any); + + await applyPluginDependencyInstall( + { + installed: false, + packageJsonPath: path.join(process.cwd(), 'package.json'), + packageName: '@kitcn/resend', + packageSpec: '@kitcn/resend@0.12.5', + skipped: false, + }, + execaStub as any + ); + + expect(execaStub.mock.calls.map((call) => call.slice(0, 2))).toStrictEqual([ + ['pnpm', ['add', '@kitcn/resend@0.12.5']], + ]); + }); }); diff --git a/packages/kitcn/src/cli/registry/dependencies.ts b/packages/kitcn/src/cli/registry/dependencies.ts index 09433f94..86e3de4a 100644 --- a/packages/kitcn/src/cli/registry/dependencies.ts +++ b/packages/kitcn/src/cli/registry/dependencies.ts @@ -1,6 +1,10 @@ import fs from 'node:fs'; import { dirname, join, resolve } from 'node:path'; import type { execa } from 'execa'; +import { + detectPackageManager, + resolveDependencyInstallCommand, +} from '../package-manager.js'; import { getPackageNameFromInstallSpec, OPENTELEMETRY_API_INSTALL_SPEC, @@ -62,6 +66,23 @@ const resolvePackageJsonInstallTarget = () => { }; }; +const runDependencyInstall = async ( + packageJsonPath: string, + packageSpecs: readonly string[], + execaFn: typeof execa +) => { + const cwd = dirname(packageJsonPath); + const packageManager = detectPackageManager(cwd); + const { args, command } = resolveDependencyInstallCommand( + packageManager, + packageSpecs + ); + await execaFn(command, args, { + cwd, + stdio: 'inherit', + }); +}; + export const resolveBunPeerWarningPreinstallSpecs = () => { const { packageJsonPath, packageJson } = resolvePackageJsonInstallTarget(); if (!packageJsonPath || !packageJson) { @@ -104,10 +125,7 @@ const applyBunPeerWarningPreinstall = async (execaFn: typeof execa) => { } const { packageJsonPath } = resolvePackageJsonInstallTarget(); - await execaFn('bun', ['add', ...dependencySpecs], { - cwd: dirname(packageJsonPath!), - stdio: 'inherit', - }); + await runDependencyInstall(packageJsonPath, dependencySpecs, execaFn); return dependencySpecs; }; @@ -179,10 +197,7 @@ export const applyDependencyHintsInstall = async ( } const { packageJsonPath } = resolvePackageJsonInstallTarget(); - await execaFn('bun', ['add', ...installSpecs], { - cwd: dirname(packageJsonPath), - stdio: 'inherit', - }); + await runDependencyInstall(packageJsonPath, installSpecs, execaFn); return [...preinstalledSpecs, ...installSpecs]; }; @@ -203,10 +218,7 @@ export const applyPlanningDependencyInstall = async ( } const { packageJsonPath } = resolvePackageJsonInstallTarget(); - await execaFn('bun', ['add', ...installSpecs], { - cwd: dirname(packageJsonPath), - stdio: 'inherit', - }); + await runDependencyInstall(packageJsonPath, installSpecs, execaFn); return [...preinstalledSpecs, ...installSpecs]; }; @@ -221,10 +233,7 @@ export const applyPluginDependencyInstall = async ( await applyBunPeerWarningPreinstall(execaFn); const packageSpec = install.packageSpec ?? install.packageName; - await execaFn('bun', ['add', packageSpec], { - cwd: dirname(install.packageJsonPath), - stdio: 'inherit', - }); + await runDependencyInstall(install.packageJsonPath, [packageSpec], execaFn); return { packageName: install.packageName, packageSpec, diff --git a/packages/kitcn/src/cli/registry/planner.test.ts b/packages/kitcn/src/cli/registry/planner.test.ts index b81f7916..fa9a67b4 100644 --- a/packages/kitcn/src/cli/registry/planner.test.ts +++ b/packages/kitcn/src/cli/registry/planner.test.ts @@ -111,6 +111,101 @@ describe('cli registry planner', () => { } }); + test('uses detected package manager in install plan commands', async () => { + const dir = fs.mkdtempSync( + path.join(os.tmpdir(), 'kitcn-registry-package-manager-') + ); + const originalCwd = process.cwd(); + process.chdir(dir); + + try { + writePackageJson(dir, { + name: 'test-app', + packageManager: 'npm@10.9.0', + private: true, + }); + writeMinimalSchema(dir); + + const plan = await buildPluginInstallPlan({ + config: createDefaultConfig(), + descriptor: { + defaultPreset: 'default', + description: 'fake', + docs: { + localPath: 'www/content/docs/fake.mdx', + publicUrl: 'https://example.com/fake', + }, + key: 'resend', + keywords: [], + label: 'Fake', + packageInstallSpec: '@kitcn/resend@0.12.5', + packageName: '@kitcn/resend', + presets: [ + { + description: 'default', + key: 'default', + templateIds: ['fake-template'], + }, + ], + schemaRegistration: { + importName: 'fakeExtension', + path: 'schema.ts', + target: 'lib', + }, + templates: [ + { + content: 'original content', + dependencyHints: ['@react-email/render@1.0.0'], + id: 'fake-template', + path: 'plugins/fake.ts', + requires: [], + target: 'functions', + }, + ], + }, + existingTemplatePathMap: {}, + functionsDir: path.join(dir, 'convex'), + lockfile: { plugins: {} }, + noCodegen: true, + overwrite: false, + preset: 'default', + preview: true, + promptAdapter: { + confirm: async () => true, + isInteractive: () => false, + multiselect: async () => [], + select: async () => 'ignored', + }, + presetTemplateIds: ['fake-template'], + selectedPlugin: 'resend', + selectedTemplateIds: ['fake-template'], + selectedTemplates: [ + { + content: 'original content', + dependencyHints: ['@react-email/render@1.0.0'], + id: 'fake-template', + path: 'plugins/fake.ts', + requires: [], + target: 'functions', + }, + ], + selectionSource: 'preset', + yes: false, + }); + + expect(plan.nextSteps).toEqual([ + 'Install scaffold dependencies: npm install @react-email/render@1.0.0', + ]); + expect( + plan.operations.find( + (operation) => operation.kind === 'dependency_install' + )?.command + ).toBe('npm install @kitcn/resend@0.12.5'); + } finally { + process.chdir(originalCwd); + } + }); + test('adds a live bootstrap operation when the plugin requires local post-codegen bootstrap', async () => { const dir = fs.mkdtempSync( path.join(os.tmpdir(), 'kitcn-registry-live-bootstrap-') diff --git a/packages/kitcn/src/cli/registry/planner.ts b/packages/kitcn/src/cli/registry/planner.ts index b3e6735a..2300a0e4 100644 --- a/packages/kitcn/src/cli/registry/planner.ts +++ b/packages/kitcn/src/cli/registry/planner.ts @@ -7,6 +7,10 @@ import { generateAuthSecret, serializeEnvValue, } from '../env.js'; +import { + detectPackageManager, + formatDependencyInstallCommand, +} from '../package-manager.js'; import { resolveProjectScaffoldContext } from '../project-context.js'; import { FUNCTIONS_DIR_IMPORT_PLACEHOLDER, @@ -777,14 +781,25 @@ export const buildPluginInstallPlan = async (params: { const dependency = await inspectPluginDependencyInstall({ descriptor: params.descriptor, }); + const dependencyPackageSpec = + dependency.packageSpec ?? dependency.packageName; const dependencyHints = [ ...new Set( effectiveTemplates.flatMap((template) => template.dependencyHints) ), ]; + const dependencyInstallTargetDir = dependency.packageJsonPath + ? dirname(dependency.packageJsonPath) + : process.cwd(); + const dependencyPackageManager = detectPackageManager( + dependencyInstallTargetDir + ); const dependencyHintCommand = dependencyHints.length > 0 - ? `bun add ${dependencyHints.join(' ')}` + ? formatDependencyInstallCommand( + dependencyPackageManager, + dependencyHints + ) : undefined; const envReminders = rawConvexAuthPreset ? [] @@ -925,12 +940,14 @@ export const buildPluginInstallPlan = async (params: { path: dependency.packageJsonPath ? normalizePath(relative(process.cwd(), dependency.packageJsonPath)) : undefined, - packageName: dependency.packageSpec ?? dependency.packageName, + packageName: dependencyPackageSpec, command: - (dependency.packageSpec ?? dependency.packageName) && + dependencyPackageSpec && dependency.packageJsonPath && !dependency.skipped - ? `bun add ${dependency.packageSpec ?? dependency.packageName}` + ? formatDependencyInstallCommand(dependencyPackageManager, [ + dependencyPackageSpec, + ]) : undefined, }, { diff --git a/skills-lock.json b/skills-lock.json index 26cd4fa7..d44918d7 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -46,11 +46,6 @@ "sourceType": "github", "computedHash": "09c9096e132640e201c4ffd592ffa144d949243ca7a71c5ef020e111927c495a" }, - "dev-browser": { - "source": "sawyerhood/dev-browser", - "sourceType": "github", - "computedHash": "d50d11bbefdc1599d78900828dcb69d48e5091769d3f5ce10e06a60efe2405bf" - }, "frontend-design": { "source": "EveryInc/compound-engineering-plugin", "sourceType": "github",