opencode-anthropic-multi-account 0.2.24 → 0.2.26

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.
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  scrubTemplate
3
- } from "./chunk-RAX4SFCO.js";
3
+ } from "./chunk-3CTML5AX.js";
4
4
 
5
- // src/fingerprint-capture.ts
5
+ // src/claude-code/fingerprint/capture.ts
6
6
  import { spawn } from "child_process";
7
7
  import { createServer } from "http";
8
8
  import { basename, dirname as dirname2, join as join2 } from "path";
@@ -17,11 +17,11 @@ import {
17
17
  writeFile as writeFile2
18
18
  } from "fs/promises";
19
19
 
20
- // src/fingerprint-data.json
21
- var fingerprint_data_default = {
20
+ // src/claude-code/fingerprint/data.json
21
+ var data_default = {
22
22
  _version: 1,
23
23
  _schemaVersion: 1,
24
- _captured: "2026-04-19T12:56:25.330Z",
24
+ _captured: "2026-04-28T13:59:47.421Z",
25
25
  _source: "bundled",
26
26
  agent_identity: "You are a Claude agent, built on Anthropic's Claude Agent SDK.",
27
27
  system_prompt: `You are an interactive agent that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
@@ -93,15 +93,16 @@ In code: default to writing no comments. Never write multi-paragraph docstrings
93
93
  - Use the Agent tool with specialized agents when the task at hand matches the agent's description. Subagents are valuable for parallelizing independent queries or for protecting the main context window from excessive results, but they should not be used excessively when not needed. Importantly, avoid duplicating work that subagents are already doing - if you delegate research to a subagent, do not also perform the same searches yourself.
94
94
  - For broad codebase exploration or research that'll take more than 3 queries, spawn Agent with subagent_type=Explore. Otherwise use the Glob or Grep directly.
95
95
  - When the user types \`/<skill-name>\`, invoke it via Skill. Only use skills listed in the user-invocable skills section \u2014 don't guess.
96
+ - When work you just finished has a natural future follow-up, end your reply with a one-line offer to \`/schedule\` a background agent to do it \u2014 name the concrete action and cadence ("Want me to /schedule an agent in 2 weeks to open a cleanup PR for the flag?"). One-time signals: a feature flag/gate/experiment/staged rollout (clean it up or ramp it), a soak window or metric to verify (query it and post results), a long-running job with an ETA (check status and report), a temp workaround/instrumentation/.skip left in (open a removal PR), a "remove once X" TODO. Recurring signals: a sweep/triage/report/queue-drain the user just did by hand, or anything "weekly"/"again"/"piling up" \u2014 offer to run it as a routine. The bar is 70%+ odds the user says yes \u2014 skip it for refactors, bug fixes with tests, docs, renames, routine dep bumps, plain feature merges, or when the user signals closure ("nothing else to do", "should be fine now"). Don't stack offers on back-to-back turns; let most tasks just be tasks.
97
+ - If the user asks about "ultrareview" or how to run it, explain that /ultrareview launches a multi-agent cloud review of the current branch (or /ultrareview <PR#> for a GitHub PR). It is user-triggered and billed; you cannot launch it yourself, so do not attempt to via Bash or otherwise. It needs a git repository (offer to "git init" if not in one); the no-arg form bundles the local branch and does not need a GitHub remote.
96
98
 
97
99
  # Language
98
100
  Always respond in Korean. Use Korean for all explanations, comments, and communications with the user. Technical terms and code identifiers should remain in their original form.
99
101
  Maintain full orthographic correctness for Korean, including all required diacritical marks, accents, and special characters. Never substitute accented characters with their ASCII equivalents (e.g., never write "nao" for "n\xE3o", "fur" for "f\xFCr", or "loeschen" for "l\xF6schen").
100
102
 
103
+ # Context management
101
104
  When working with tool results, write down any important information you might need later in your response, as the original tool result may be cleared later.
102
105
 
103
- Length limits: keep text between tool calls to \u226425 words. Keep final responses to \u2264100 words unless the task requires more detail.
104
-
105
106
  gitStatus: This is the git status at the start of the conversation. Note that this status is a snapshot in time, and will not update during the conversation.
106
107
 
107
108
  Current branch: main
@@ -121,7 +122,7 @@ Recent commits:
121
122
  description: `Launch a new agent to handle complex, multi-step tasks. Each agent type has specific capabilities and tools available to it.
122
123
 
123
124
  Available agent types and the tools they have access to:
124
- - Explore: Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions. (Tools: All tools except Agent, ExitPlanMode, Edit, Write, NotebookEdit)
125
+ - Explore: Fast read-only search agent for locating code. Use it to find files by pattern (eg. "src/components/**/*.tsx"), grep for symbols or keywords (eg. "API endpoints"), or answer "where is X defined / which files reference Y." Do NOT use it for code review, design-doc auditing, cross-file consistency checks, or open-ended analysis \u2014 it reads excerpts rather than whole files and will miss content past its read window. When calling, specify search breadth: "quick" for a single targeted lookup, "medium" for moderate exploration, or "very thorough" to search across multiple locations and naming conventions. (Tools: All tools except Agent, ExitPlanMode, Edit, Write, NotebookEdit)
125
126
  - general-purpose: General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you. (Tools: *)
126
127
  - Plan: Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a task. Returns step-by-step plans, identifies critical files, and considers architectural trade-offs. (Tools: All tools except Agent, ExitPlanMode, Edit, Write, NotebookEdit)
127
128
  - statusline-setup: Use this agent to configure the user's Claude Code status line setting. (Tools: Read, Edit)
@@ -347,7 +348,7 @@ The agent starts with no context from this conversation, so the prompt briefs it
347
348
  },
348
349
  {
349
350
  name: "Bash",
350
- description: 'Executes a given bash command and returns its output.\n\nThe working directory persists between commands, but shell state does not. The shell environment is initialized from the user\'s profile (bash or zsh).\n\nIMPORTANT: Avoid using this tool to run `find`, `grep`, `cat`, `head`, `tail`, `sed`, `awk`, or `echo` commands, unless explicitly instructed or after you have verified that a dedicated tool cannot accomplish your task. Instead, use the appropriate dedicated tool as this will provide a much better experience for the user:\n\n - File search: Use Glob (NOT find or ls)\n - Content search: Use Grep (NOT grep or rg)\n - Read files: Use Read (NOT cat/head/tail)\n - Edit files: Use Edit (NOT sed/awk)\n - Write files: Use Write (NOT echo >/cat <<EOF)\n - Communication: Output text directly (NOT echo/printf)\nWhile the Bash tool can do similar things, it\u2019s better to use the built-in tools as they provide a better user experience and make it easier to review tool calls and give permission.\n\n# Instructions\n - If your command will create new directories or files, first use this tool to run `ls` to verify the parent directory exists and is the correct location.\n - Always quote file paths that contain spaces with double quotes in your command (e.g., cd "path with spaces/file.txt")\n - Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of `cd`. You may use `cd` if the User explicitly requests it. In particular, never prepend `cd <current-directory>` to a `git` command \u2014 `git` already operates on the current working tree, and the compound triggers a permission prompt.\n - You may specify an optional timeout in milliseconds (up to 600000ms / 10 minutes). By default, your command will timeout after 120000ms (2 minutes).\n - You can use the `run_in_background` parameter to run the command in the background. Only use this if you don\'t need the result immediately and are OK being notified when the command completes later. You do not need to check the output right away - you\'ll be notified when it finishes. You do not need to use \'&\' at the end of the command when using this parameter.\n - When issuing multiple commands:\n - If the commands are independent and can run in parallel, make multiple Bash tool calls in a single message. Example: if you need to run "git status" and "git diff", send a single message with two Bash tool calls in parallel.\n - If the commands depend on each other and must run sequentially, use a single Bash call with \'&&\' to chain them together.\n - Use \';\' only when you need to run commands sequentially but don\'t care if earlier commands fail.\n - DO NOT use newlines to separate commands (newlines are ok in quoted strings).\n - For git commands:\n - Prefer to create a new commit rather than amending an existing commit.\n - Before running destructive operations (e.g., git reset --hard, git push --force, git checkout --), consider whether there is a safer alternative that achieves the same goal. Only use destructive operations when they are truly the best approach.\n - Never skip hooks (--no-verify) or bypass signing (--no-gpg-sign, -c commit.gpgsign=false) unless the user has explicitly asked for it. If a hook fails, investigate and fix the underlying issue.\n - Avoid unnecessary `sleep` commands:\n - Do not sleep between commands that can run immediately \u2014 just run them.\n - Use the Monitor tool to stream events from a background process (each stdout line is a notification). For one-shot "wait until done," use Bash with run_in_background instead.\n - If your command is long running and you would like to be notified when it finishes \u2014 use `run_in_background`. No sleep needed.\n - Do not retry failing commands in a sleep loop \u2014 diagnose the root cause.\n - If waiting for a background task you started with `run_in_background`, you will be notified when it completes \u2014 do not poll.\n - Long leading `sleep` commands are blocked. To poll until a condition is met, use Monitor with an until-loop (e.g. `until <check>; do sleep 2; done`) \u2014 you get a notification when the loop exits. Do not chain shorter sleeps to work around the block.\n\n\n# Committing changes with git\n\nOnly create commits when requested by the user. If unclear, ask first. When the user asks you to create a new git commit, follow these steps carefully:\n\nYou can call multiple tools in a single response. When multiple independent pieces of information are requested and all commands are likely to succeed, run multiple tool calls in parallel for optimal performance. The numbered steps below indicate which commands should be batched in parallel.\n\nGit Safety Protocol:\n- NEVER update the git config\n- NEVER run destructive git commands (push --force, reset --hard, checkout ., restore ., clean -f, branch -D) unless the user explicitly requests these actions. Taking unauthorized destructive actions is unhelpful and can result in lost work, so it\'s best to ONLY run these commands when given direct instructions \n- NEVER skip hooks (--no-verify, --no-gpg-sign, etc) unless the user explicitly requests it\n- NEVER run force push to main/master, warn the user if they request it\n- CRITICAL: Always create NEW commits rather than amending, unless the user explicitly requests a git amend. When a pre-commit hook fails, the commit did NOT happen \u2014 so --amend would modify the PREVIOUS commit, which may result in destroying work or losing previous changes. Instead, after hook failure, fix the issue, re-stage, and create a NEW commit\n- When staging files, prefer adding specific files by name rather than using "git add -A" or "git add .", which can accidentally include sensitive files (.env, credentials) or large binaries\n- NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise the user will feel that you are being too proactive\n\n1. Run the following bash commands in parallel, each using the Bash tool:\n - Run a git status command to see all untracked files. IMPORTANT: Never use the -uall flag as it can cause memory issues on large repos.\n - Run a git diff command to see both staged and unstaged changes that will be committed.\n - Run a git log command to see recent commit messages, so that you can follow this repository\'s commit message style.\n2. Analyze all staged changes (both previously staged and newly added) and draft a commit message:\n - Summarize the nature of the changes (eg. new feature, enhancement to an existing feature, bug fix, refactoring, test, docs, etc.). Ensure the message accurately reflects the changes and their purpose (i.e. "add" means a wholly new feature, "update" means an enhancement to an existing feature, "fix" means a bug fix, etc.).\n - Do not commit files that likely contain secrets (.env, credentials.json, etc). Warn the user if they specifically request to commit those files\n - Draft a concise (1-2 sentences) commit message that focuses on the "why" rather than the "what"\n - Ensure it accurately reflects the changes and their purpose\n3. Run the following commands in parallel:\n - Add relevant untracked files to the staging area.\n - Create the commit with a message ending with:\n Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>\n - Run git status after the commit completes to verify success.\n Note: git status depends on the commit completing, so run it sequentially after the commit.\n4. If the commit fails due to pre-commit hook: fix the issue and create a NEW commit\n\nImportant notes:\n- NEVER run additional commands to read or explore code, besides git bash commands\n- NEVER use the TodoWrite or Agent tools\n- DO NOT push to the remote repository unless the user explicitly asks you to do so\n- IMPORTANT: Never use git commands with the -i flag (like git rebase -i or git add -i) since they require interactive input which is not supported.\n- IMPORTANT: Do not use --no-edit with git rebase commands, as the --no-edit flag is not a valid option for git rebase.\n- If there are no changes to commit (i.e., no untracked files and no modifications), do not create an empty commit\n- In order to ensure good formatting, ALWAYS pass the commit message via a HEREDOC, a la this example:\n<example>\ngit commit -m "$(cat <<\'EOF\'\n Commit message here.\n\n Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>\n EOF\n )"\n</example>\n\n# Creating pull requests\nUse the gh command via the Bash tool for ALL GitHub-related tasks including working with issues, pull requests, checks, and releases. If given a Github URL use the gh command to get the information needed.\n\nIMPORTANT: When the user asks you to create a pull request, follow these steps carefully:\n\n1. Run the following bash commands in parallel using the Bash tool, in order to understand the current state of the branch since it diverged from the main branch:\n - Run a git status command to see all untracked files (never use -uall flag)\n - Run a git diff command to see both staged and unstaged changes that will be committed\n - Check if the current branch tracks a remote branch and is up to date with the remote, so you know if you need to push to the remote\n - Run a git log command and `git diff [base-branch]...HEAD` to understand the full commit history for the current branch (from the time it diverged from the base branch)\n2. Analyze all changes that will be included in the pull request, making sure to look at all relevant commits (NOT just the latest commit, but ALL commits that will be included in the pull request!!!), and draft a pull request title and summary:\n - Keep the PR title short (under 70 characters)\n - Use the description/body for details, not the title\n3. Run the following commands in parallel:\n - Create new branch if needed\n - Push to remote with -u flag if needed\n - Create PR using gh pr create with the format below. Use a HEREDOC to pass the body to ensure correct formatting.\n<example>\ngh pr create --title "the pr title" --body "$(cat <<\'EOF\'\n## Summary\n<1-3 bullet points>\n\n## Test plan\n[Bulleted markdown checklist of TODOs for testing the pull request...]\n\n\u{1F916} Generated with [Claude Code](https://claude.com/claude-code)\nEOF\n)"\n</example>\n\nImportant:\n- DO NOT use the TodoWrite or Agent tools\n- Return the PR URL when you\'re done, so the user can see it\n\n# Other common operations\n- View comments on a Github PR: gh api repos/foo/bar/pulls/123/comments',
351
+ description: 'Executes a given bash command and returns its output.\n\nThe working directory persists between commands, but shell state does not. The shell environment is initialized from the user\'s profile (bash or zsh).\n\nIMPORTANT: Avoid using this tool to run `find`, `grep`, `cat`, `head`, `tail`, `sed`, `awk`, or `echo` commands, unless explicitly instructed or after you have verified that a dedicated tool cannot accomplish your task. Instead, use the appropriate dedicated tool as this will provide a much better experience for the user:\n\n - File search: Use Glob (NOT find or ls)\n - Content search: Use Grep (NOT grep or rg)\n - Read files: Use Read (NOT cat/head/tail)\n - Edit files: Use Edit (NOT sed/awk)\n - Write files: Use Write (NOT echo >/cat <<EOF)\n - Communication: Output text directly (NOT echo/printf)\nWhile the Bash tool can do similar things, it\u2019s better to use the built-in tools as they provide a better user experience and make it easier to review tool calls and give permission.\n\n# Instructions\n - If your command will create new directories or files, first use this tool to run `ls` to verify the parent directory exists and is the correct location.\n - Always quote file paths that contain spaces with double quotes in your command (e.g., cd "path with spaces/file.txt")\n - Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of `cd`. You may use `cd` if the User explicitly requests it. In particular, never prepend `cd <current-directory>` to a `git` command \u2014 `git` already operates on the current working tree, and the compound triggers a permission prompt.\n - You may specify an optional timeout in milliseconds (up to 600000ms / 10 minutes). By default, your command will timeout after 120000ms (2 minutes).\n - You can use the `run_in_background` parameter to run the command in the background. Only use this if you don\'t need the result immediately and are OK being notified when the command completes later. You do not need to check the output right away - you\'ll be notified when it finishes. You do not need to use \'&\' at the end of the command when using this parameter.\n - For git commands:\n - Prefer to create a new commit rather than amending an existing commit.\n - Before running destructive operations (e.g., git reset --hard, git push --force, git checkout --), consider whether there is a safer alternative that achieves the same goal. Only use destructive operations when they are truly the best approach.\n - Never skip hooks (--no-verify) or bypass signing (--no-gpg-sign, -c commit.gpgsign=false) unless the user has explicitly asked for it. If a hook fails, investigate and fix the underlying issue.\n - Avoid unnecessary `sleep` commands:\n - Do not sleep between commands that can run immediately \u2014 just run them.\n - Use the Monitor tool to stream events from a background process (each stdout line is a notification). For one-shot "wait until done," use Bash with run_in_background instead.\n - If your command is long running and you would like to be notified when it finishes \u2014 use `run_in_background`. No sleep needed.\n - Do not retry failing commands in a sleep loop \u2014 diagnose the root cause.\n - If waiting for a background task you started with `run_in_background`, you will be notified when it completes \u2014 do not poll.\n - Long leading `sleep` commands are blocked. To poll until a condition is met, use Monitor with an until-loop (e.g. `until <check>; do sleep 2; done`) \u2014 you get a notification when the loop exits. Do not chain shorter sleeps to work around the block.\n\n\n# Committing changes with git\n\nOnly create commits when requested by the user. If unclear, ask first. When the user asks you to create a new git commit, follow these steps carefully:\n\nYou can call multiple tools in a single response. When multiple independent pieces of information are requested and all commands are likely to succeed, run multiple tool calls in parallel for optimal performance. The numbered steps below indicate which commands should be batched in parallel.\n\nGit Safety Protocol:\n- NEVER update the git config\n- NEVER run destructive git commands (push --force, reset --hard, checkout ., restore ., clean -f, branch -D) unless the user explicitly requests these actions. Taking unauthorized destructive actions is unhelpful and can result in lost work, so it\'s best to ONLY run these commands when given direct instructions \n- NEVER skip hooks (--no-verify, --no-gpg-sign, etc) unless the user explicitly requests it\n- NEVER run force push to main/master, warn the user if they request it\n- CRITICAL: Always create NEW commits rather than amending, unless the user explicitly requests a git amend. When a pre-commit hook fails, the commit did NOT happen \u2014 so --amend would modify the PREVIOUS commit, which may result in destroying work or losing previous changes. Instead, after hook failure, fix the issue, re-stage, and create a NEW commit\n- When staging files, prefer adding specific files by name rather than using "git add -A" or "git add .", which can accidentally include sensitive files (.env, credentials) or large binaries\n- NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise the user will feel that you are being too proactive\n\n1. Run the following bash commands in parallel, each using the Bash tool:\n - Run a git status command to see all untracked files. IMPORTANT: Never use the -uall flag as it can cause memory issues on large repos.\n - Run a git diff command to see both staged and unstaged changes that will be committed.\n - Run a git log command to see recent commit messages, so that you can follow this repository\'s commit message style.\n2. Analyze all staged changes (both previously staged and newly added) and draft a commit message:\n - Summarize the nature of the changes (eg. new feature, enhancement to an existing feature, bug fix, refactoring, test, docs, etc.). Ensure the message accurately reflects the changes and their purpose (i.e. "add" means a wholly new feature, "update" means an enhancement to an existing feature, "fix" means a bug fix, etc.).\n - Do not commit files that likely contain secrets (.env, credentials.json, etc). Warn the user if they specifically request to commit those files\n - Draft a concise (1-2 sentences) commit message that focuses on the "why" rather than the "what"\n - Ensure it accurately reflects the changes and their purpose\n3. Run the following commands in parallel:\n - Add relevant untracked files to the staging area.\n - Create the commit with a message ending with:\n Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>\n - Run git status after the commit completes to verify success.\n Note: git status depends on the commit completing, so run it sequentially after the commit.\n4. If the commit fails due to pre-commit hook: fix the issue and create a NEW commit\n\nImportant notes:\n- NEVER run additional commands to read or explore code, besides git bash commands\n- NEVER use the TodoWrite or Agent tools\n- DO NOT push to the remote repository unless the user explicitly asks you to do so\n- IMPORTANT: Never use git commands with the -i flag (like git rebase -i or git add -i) since they require interactive input which is not supported.\n- IMPORTANT: Do not use --no-edit with git rebase commands, as the --no-edit flag is not a valid option for git rebase.\n- If there are no changes to commit (i.e., no untracked files and no modifications), do not create an empty commit\n- In order to ensure good formatting, ALWAYS pass the commit message via a HEREDOC, a la this example:\n<example>\ngit commit -m "$(cat <<\'EOF\'\n Commit message here.\n\n Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>\n EOF\n )"\n</example>\n\n# Creating pull requests\nUse the gh command via the Bash tool for ALL GitHub-related tasks including working with issues, pull requests, checks, and releases. If given a Github URL use the gh command to get the information needed.\n\nIMPORTANT: When the user asks you to create a pull request, follow these steps carefully:\n\n1. Run the following bash commands in parallel using the Bash tool, in order to understand the current state of the branch since it diverged from the main branch:\n - Run a git status command to see all untracked files (never use -uall flag)\n - Run a git diff command to see both staged and unstaged changes that will be committed\n - Check if the current branch tracks a remote branch and is up to date with the remote, so you know if you need to push to the remote\n - Run a git log command and `git diff [base-branch]...HEAD` to understand the full commit history for the current branch (from the time it diverged from the base branch)\n2. Analyze all changes that will be included in the pull request, making sure to look at all relevant commits (NOT just the latest commit, but ALL commits that will be included in the pull request!!!), and draft a pull request title and summary:\n - Keep the PR title short (under 70 characters)\n - Use the description/body for details, not the title\n3. Run the following commands in parallel:\n - Create new branch if needed\n - Push to remote with -u flag if needed\n - Create PR using gh pr create with the format below. Use a HEREDOC to pass the body to ensure correct formatting.\n<example>\ngh pr create --title "the pr title" --body "$(cat <<\'EOF\'\n## Summary\n<1-3 bullet points>\n\n## Test plan\n[Bulleted markdown checklist of TODOs for testing the pull request...]\n\n\u{1F916} Generated with [Claude Code](https://claude.com/claude-code)\nEOF\n)"\n</example>\n\nImportant:\n- DO NOT use the TodoWrite or Agent tools\n- Return the PR URL when you\'re done, so the user can see it\n\n# Other common operations\n- View comments on a Github PR: gh api repos/foo/bar/pulls/123/comments',
351
352
  input_schema: {
352
353
  $schema: "https://json-schema.org/draft/2020-12/schema",
353
354
  type: "object",
@@ -779,7 +780,7 @@ Ensure your plan is complete and unambiguous:
779
780
  },
780
781
  {
781
782
  name: "Monitor",
782
- description: 'Start a background monitor that streams events from a long-running script. Each stdout line is an event \u2014 you keep working and notifications arrive in the chat. Events arrive on their own schedule and are not replies from the user, even if one lands while you\'re waiting for the user to answer a question.\n\nMonitor is for the **streaming** case: "tell me every time X happens." For one-shot "wait until X is done," use Bash with run_in_background instead \u2014 you\'ll get a completion notification when it exits.\n\nYour script\'s stdout is the event stream. Each line becomes a notification. Exit ends the watch.\n\n # Each matching log line is an event\n tail -f /var/log/app.log | grep --line-buffered "ERROR"\n\n # Each file change is an event\n inotifywait -m --format \'%e %f\' /watched/dir\n\n # Poll GitHub for new PR comments and emit one line per new comment\n last=$(date -u +%Y-%m-%dT%H:%M:%SZ)\n while true; do\n now=$(date -u +%Y-%m-%dT%H:%M:%SZ)\n gh api "repos/owner/repo/issues/123/comments?since=$last" --jq \'.[] | "\\(.user.login): \\(.body)"\'\n last=$now; sleep 30\n done\n\n # Node script that emits events as they arrive (e.g. WebSocket listener)\n node watch-for-events.js\n\n**Script quality:**\n- Always use `grep --line-buffered` in pipes \u2014 without it, pipe buffering delays events by minutes.\n- In poll loops, handle transient failures (`curl ... || true`) \u2014 one failed request shouldn\'t kill the monitor.\n- Poll intervals: 30s+ for remote APIs (rate limits), 0.5-1s for local checks.\n- Write a specific `description` \u2014 it appears in every notification ("errors in deploy.log" not "watching logs").\n- Only stdout is the event stream. Stderr goes to the output file (readable via Read) but does not trigger notifications \u2014 for a command you run directly (e.g. `python train.py 2>&1 | grep --line-buffered ...`), merge stderr with `2>&1` so its failures reach your filter. (No effect on `tail -f` of an existing log \u2014 that file only contains what its writer redirected.)\n\n**Coverage \u2014 silence is not success.** When watching a job or process for an outcome, your filter must match every terminal state, not just the happy path. A monitor that greps only for the success marker stays silent through a crashloop, a hung process, or an unexpected exit \u2014 and silence looks identical to "still running." Before arming, ask: *if this process crashed right now, would my filter emit anything?* If not, widen it.\n\n # Wrong \u2014 silent on crash, hang, or any non-success exit\n tail -f run.log | grep --line-buffered "elapsed_steps="\n\n # Right \u2014 one alternation covering progress + the failure signatures you\'d act on\n tail -f run.log | grep -E --line-buffered "elapsed_steps=|Traceback|Error|FAILED|assert|Killed|OOM"\n\nFor poll loops checking job state, emit on every terminal status (`succeeded|failed|cancelled|timeout`), not just success. If you cannot confidently enumerate the failure signatures, broaden the grep alternation rather than narrow it \u2014 some extra noise is better than missing a crashloop.\n\n**Output volume**: Every stdout line is a conversation message, so the filter should be selective \u2014 but selective means "the lines you\'d act on," not "only good news." Never pipe raw logs; use `grep --line-buffered`, `awk`, or a wrapper that emits exactly the success and failure signals you care about. Monitors that produce too many events are automatically stopped; restart with a tighter filter if this happens.\n\nStdout lines within 200ms are batched into a single notification, so multiline output from a single event groups naturally.\n\nThe script runs in the same shell environment as Bash. Exit ends the watch (exit code is reported). Timeout \u2192 killed. Set `persistent: true` for session-length watches (PR monitoring, log tails) \u2014 the monitor runs until you call TaskStop or the session ends. Use TaskStop to cancel early.',
783
+ description: 'Start a background monitor that streams events from a long-running script. Each stdout line is an event \u2014 you keep working and notifications arrive in the chat. Events arrive on their own schedule and are not replies from the user, even if one lands while you\'re waiting for the user to answer a question.\n\nPick by how many notifications you need:\n- **One** ("tell me when the server is ready / the build finishes") \u2192 use **Bash with `run_in_background`** and a command that exits when the condition is true, e.g. `until grep -q "Ready in" dev.log; do sleep 0.5; done`. You get a single completion notification when it exits.\n- **One per occurrence, indefinitely** ("tell me every time an ERROR line appears") \u2192 Monitor with an unbounded command (`tail -f`, `inotifywait -m`, `while true`).\n- **One per occurrence, until a known end** ("emit each CI step result, stop when the run completes") \u2192 Monitor with a command that emits lines and then exits.\n\nYour script\'s stdout is the event stream. Each line becomes a notification. Exit ends the watch.\n\n # Each matching log line is an event\n tail -f /var/log/app.log | grep --line-buffered "ERROR"\n\n # Each file change is an event\n inotifywait -m --format \'%e %f\' /watched/dir\n\n # Poll GitHub for new PR comments and emit one line per new comment\n last=$(date -u +%Y-%m-%dT%H:%M:%SZ)\n while true; do\n now=$(date -u +%Y-%m-%dT%H:%M:%SZ)\n gh api "repos/owner/repo/issues/123/comments?since=$last" --jq \'.[] | "\\(.user.login): \\(.body)"\'\n last=$now; sleep 30\n done\n\n # Node script that emits events as they arrive (e.g. WebSocket listener)\n node watch-for-events.js\n\n # Per-occurrence with a natural end: emit each CI check as it lands, exit when the run completes\n prev=""\n while true; do\n s=$(gh pr checks 123 --json name,bucket)\n cur=$(jq -r \'.[] | select(.bucket!="pending") | "\\(.name): \\(.bucket)"\' <<<"$s" | sort)\n comm -13 <(echo "$prev") <(echo "$cur")\n prev=$cur\n jq -e \'all(.bucket!="pending")\' <<<"$s" >/dev/null && break\n sleep 30\n done\n\n**Don\'t use an unbounded command for a single notification.** `tail -f`, `inotifywait -m`, and `while true` never exit on their own, so the monitor stays armed until timeout even after the event has fired. For "tell me when X is ready," use Bash `run_in_background` with an `until` loop instead (one notification, ends in seconds). Note that `tail -f log | grep -m 1 ...` does *not* fix this: if the log goes quiet after the match, `tail` never receives SIGPIPE and the pipeline hangs anyway.\n\n**Script quality:**\n- Always use `grep --line-buffered` in pipes \u2014 without it, pipe buffering delays events by minutes.\n- In poll loops, handle transient failures (`curl ... || true`) \u2014 one failed request shouldn\'t kill the monitor.\n- Poll intervals: 30s+ for remote APIs (rate limits), 0.5-1s for local checks.\n- Write a specific `description` \u2014 it appears in every notification ("errors in deploy.log" not "watching logs").\n- Only stdout is the event stream. Stderr goes to the output file (readable via Read) but does not trigger notifications \u2014 for a command you run directly (e.g. `python train.py 2>&1 | grep --line-buffered ...`), merge stderr with `2>&1` so its failures reach your filter. (No effect on `tail -f` of an existing log \u2014 that file only contains what its writer redirected.)\n\n**Coverage \u2014 silence is not success.** When watching a job or process for an outcome, your filter must match every terminal state, not just the happy path. A monitor that greps only for the success marker stays silent through a crashloop, a hung process, or an unexpected exit \u2014 and silence looks identical to "still running." Before arming, ask: *if this process crashed right now, would my filter emit anything?* If not, widen it.\n\n # Wrong \u2014 silent on crash, hang, or any non-success exit\n tail -f run.log | grep --line-buffered "elapsed_steps="\n\n # Right \u2014 one alternation covering progress + the failure signatures you\'d act on\n tail -f run.log | grep -E --line-buffered "elapsed_steps=|Traceback|Error|FAILED|assert|Killed|OOM"\n\nFor poll loops checking job state, emit on every terminal status (`succeeded|failed|cancelled|timeout`), not just success. If you cannot confidently enumerate the failure signatures, broaden the grep alternation rather than narrow it \u2014 some extra noise is better than missing a crashloop.\n\n**Output volume**: Every stdout line is a conversation message, so the filter should be selective \u2014 but selective means "the lines you\'d act on," not "only good news." Never pipe raw logs; use `grep --line-buffered`, `awk`, or a wrapper that emits exactly the success and failure signals you care about. Monitors that produce too many events are automatically stopped; restart with a tighter filter if this happens.\n\nStdout lines within 200ms are batched into a single notification, so multiline output from a single event groups naturally.\n\nThe script runs in the same shell environment as Bash. Exit ends the watch (exit code is reported). Timeout \u2192 killed. Set `persistent: true` for session-length watches (PR monitoring, log tails) \u2014 the monitor runs until you call TaskStop or the session ends. Use TaskStop to cancel early.',
783
784
  input_schema: {
784
785
  $schema: "https://json-schema.org/draft/2020-12/schema",
785
786
  type: "object",
@@ -889,7 +890,7 @@ If the result says the push wasn't sent, that's expected \u2014 no action needed
889
890
  },
890
891
  {
891
892
  name: "Read",
892
- description: 'Reads a file from the local filesystem. You can access any file directly by using this tool.\nAssume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.\n\nUsage:\n- The file_path parameter must be an absolute path, not a relative path\n- By default, it reads up to 2000 lines starting from the beginning of the file\n- When you already know which part of the file you need, only read that part. This can be important for larger files.\n- Results are returned using cat -n format, with line numbers starting at 1\n- This tool allows Claude Code to read images (eg PNG, JPG, etc). When reading an image file the contents are presented visually as Claude Code is a multimodal LLM.\n- This tool can read PDF files (.pdf). For large PDFs (more than 10 pages), you MUST provide the pages parameter to read specific page ranges (e.g., pages: "1-5"). Reading a large PDF without the pages parameter will fail. Maximum 20 pages per request.\n- This tool can read Jupyter notebooks (.ipynb files) and returns all cells with their outputs, combining code, text, and visualizations.\n- This tool can only read files, not directories. To read a directory, use an ls command via the Bash tool.\n- You will regularly be asked to read screenshots. If the user provides a path to a screenshot, ALWAYS use this tool to view the file at the path. This tool will work with all temporary file paths.\n- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.\n- Do NOT re-read a file you just edited to verify \u2014 Edit/Write would have errored if the change failed, and the harness tracks file state for you.',
893
+ description: 'Reads a file from the local filesystem. You can access any file directly by using this tool.\nAssume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.\n\nUsage:\n- The file_path parameter must be an absolute path, not a relative path\n- By default, it reads up to 2000 lines starting from the beginning of the file\n- When you already know which part of the file you need, only read that part. This can be important for larger files.\n- Results are returned using cat -n format, with line numbers starting at 1\n- This tool allows Claude Code to read images (eg PNG, JPG, etc). When reading an image file the contents are presented visually as Claude Code is a multimodal LLM.\n- This tool can read PDF files (.pdf). For large PDFs (more than 10 pages), you MUST provide the pages parameter to read specific page ranges (e.g., pages: "1-5"). Reading a large PDF without the pages parameter will fail. Maximum 20 pages per request.\n- This tool can read Jupyter notebooks (.ipynb files) and returns all cells with their outputs, combining code, text, and visualizations.\n- This tool can only read files, not directories. To list files in a directory, use the registered shell tool.\n- You will regularly be asked to read screenshots. If the user provides a path to a screenshot, ALWAYS use this tool to view the file at the path. This tool will work with all temporary file paths.\n- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.\n- Do NOT re-read a file you just edited to verify \u2014 Edit/Write would have errored if the change failed, and the harness tracks file state for you.',
893
894
  input_schema: {
894
895
  $schema: "https://json-schema.org/draft/2020-12/schema",
895
896
  type: "object",
@@ -1418,7 +1419,7 @@ IMPORTANT - Use the correct year in search queries:
1418
1419
  "Write"
1419
1420
  ],
1420
1421
  anthropic_beta: "claude-code-20250219,oauth-2025-04-20,context-1m-2025-08-07,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05,advisor-tool-2026-03-01,effort-2025-11-24",
1421
- cc_version: "2.1.114",
1422
+ cc_version: "2.1.121",
1422
1423
  header_order: [
1423
1424
  "Accept",
1424
1425
  "Authorization",
@@ -1448,7 +1449,7 @@ IMPORTANT - Use the correct year in search queries:
1448
1449
  "anthropic-dangerous-direct-browser-access": "true",
1449
1450
  "anthropic-version": "2023-06-01",
1450
1451
  "content-type": "application/json",
1451
- "user-agent": "claude-cli/2.1.114 (external, sdk-cli)",
1452
+ "user-agent": "claude-cli/2.1.121 (external, sdk-cli)",
1452
1453
  "x-app": "cli",
1453
1454
  "x-stainless-timeout": "600"
1454
1455
  },
@@ -1466,15 +1467,22 @@ IMPORTANT - Use the correct year in search queries:
1466
1467
  ]
1467
1468
  };
1468
1469
 
1469
- // src/cli-version.ts
1470
- import { execFileSync } from "child_process";
1471
- var DEFAULT_CLI_VERSION = "2.1.100";
1470
+ // src/claude-code/cli-version.ts
1471
+ import { execFileSync as defaultExecFileSync } from "child_process";
1472
+ var DEFAULT_CLI_VERSION = data_default.cc_version;
1472
1473
  var CLI_VERSION_PATTERN = /(\d+\.\d+\.\d+)/;
1473
1474
  var CLAUDE_VERSION_TIMEOUT_MS = 3e3;
1474
1475
  var detectedVersion = null;
1476
+ var cliVersionProbe = defaultExecFileSync;
1475
1477
  function parseCliVersion(output) {
1476
1478
  return output.match(CLI_VERSION_PATTERN)?.[1] ?? null;
1477
1479
  }
1480
+ function probeCliVersion() {
1481
+ return cliVersionProbe("claude", ["--version"], {
1482
+ encoding: "utf8",
1483
+ timeout: CLAUDE_VERSION_TIMEOUT_MS
1484
+ });
1485
+ }
1478
1486
  function detectCliVersion() {
1479
1487
  if (detectedVersion !== null) {
1480
1488
  return detectedVersion;
@@ -1485,10 +1493,7 @@ function detectCliVersion() {
1485
1493
  return detectedVersion;
1486
1494
  }
1487
1495
  try {
1488
- const output = execFileSync("claude", ["--version"], {
1489
- encoding: "utf8",
1490
- timeout: CLAUDE_VERSION_TIMEOUT_MS
1491
- });
1496
+ const output = probeCliVersion();
1492
1497
  detectedVersion = parseCliVersion(output) ?? DEFAULT_CLI_VERSION;
1493
1498
  } catch {
1494
1499
  detectedVersion = DEFAULT_CLI_VERSION;
@@ -1496,8 +1501,9 @@ function detectCliVersion() {
1496
1501
  return detectedVersion;
1497
1502
  }
1498
1503
 
1499
- // src/oauth-config-detect.ts
1504
+ // src/claude-code/oauth-config/detect.ts
1500
1505
  import { createHash } from "crypto";
1506
+ import { execFileSync as defaultExecFileSync2 } from "child_process";
1501
1507
  import { existsSync } from "fs";
1502
1508
  import { mkdir, readFile, writeFile } from "fs/promises";
1503
1509
  import { homedir, platform } from "os";
@@ -1513,14 +1519,14 @@ var cc_derived_defaults_default = {
1513
1519
  },
1514
1520
  oauth: {
1515
1521
  clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
1516
- authorizeUrl: "https://claude.com/cai/oauth/authorize",
1522
+ authorizeUrl: "https://claude.ai/oauth/authorize",
1517
1523
  tokenUrl: "https://platform.claude.com/v1/oauth/token",
1518
- scopes: "user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload",
1524
+ scopes: "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload",
1519
1525
  baseApiUrl: "https://api.anthropic.com"
1520
1526
  }
1521
1527
  };
1522
1528
 
1523
- // src/utils.ts
1529
+ // src/shared/utils.ts
1524
1530
  import {
1525
1531
  createMinimalClient,
1526
1532
  formatWaitTime,
@@ -1530,7 +1536,7 @@ import {
1530
1536
  sleep
1531
1537
  } from "opencode-multi-account-core";
1532
1538
 
1533
- // src/constants.ts
1539
+ // src/shared/constants.ts
1534
1540
  import { anthropicOAuthAdapter } from "opencode-multi-account-core";
1535
1541
  var ANTHROPIC_OAUTH_ADAPTER = anthropicOAuthAdapter;
1536
1542
  var ANTHROPIC_CLIENT_ID = ANTHROPIC_OAUTH_ADAPTER.oauthClientId;
@@ -1543,14 +1549,14 @@ var PLAN_LABELS = ANTHROPIC_OAUTH_ADAPTER.planLabels;
1543
1549
  var TOKEN_EXPIRY_BUFFER_MS = 6e4;
1544
1550
  var TOKEN_REFRESH_TIMEOUT_MS = 3e4;
1545
1551
 
1546
- // src/config.ts
1552
+ // src/shared/config.ts
1547
1553
  import {
1548
1554
  createConfigLoader
1549
1555
  } from "opencode-multi-account-core";
1550
1556
  var configLoader = createConfigLoader("claude-multiauth.json");
1551
1557
  var { getConfig, loadConfig, resetConfigCache, updateConfigField } = configLoader;
1552
1558
 
1553
- // src/utils.ts
1559
+ // src/shared/utils.ts
1554
1560
  async function showToast(client, message, variant) {
1555
1561
  if (getConfig().quiet_mode) return;
1556
1562
  try {
@@ -1566,28 +1572,57 @@ function debugLog(client, message, extra) {
1566
1572
  });
1567
1573
  }
1568
1574
 
1569
- // src/oauth-config-detect.ts
1575
+ // src/claude-code/oauth-config/detect.ts
1570
1576
  var CONFIG_SCAN_WINDOW_CHARS = 4096;
1571
1577
  var CONFIG_SCAN_LOOKBACK_CHARS = 512;
1572
- var REJECTED_SCOPE = ["org", "create_api_key"].join(":");
1573
- var SAFE_FALLBACK_SCOPES = "user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
1578
+ var KNOWN_PROD_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
1579
+ var POLLUTED_CACHED_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
1580
+ var SAFE_FALLBACK_SCOPES = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
1574
1581
  var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1575
1582
  var CACHE_FILE_NAME = "anthropic-oauth-config-cache.json";
1576
1583
  var DEFAULT_OVERRIDE_FILE_NAME = "oauth-config.override.json";
1577
1584
  var derivedDefaults = cc_derived_defaults_default;
1578
- var FALLBACK = {
1579
- clientId: derivedDefaults.oauth?.clientId || "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
1580
- authorizeUrl: derivedDefaults.oauth?.authorizeUrl || "https://claude.com/cai/oauth/authorize",
1585
+ var fallbackPayload = normalizeDetectedOAuthConfigPayload({
1586
+ clientId: derivedDefaults.oauth?.clientId || KNOWN_PROD_CLIENT_ID,
1587
+ authorizeUrl: derivedDefaults.oauth?.authorizeUrl || "https://claude.ai/oauth/authorize",
1581
1588
  tokenUrl: derivedDefaults.oauth?.tokenUrl || "https://platform.claude.com/v1/oauth/token",
1582
- scopes: sanitizeScopes(derivedDefaults.oauth?.scopes),
1583
- baseApiUrl: derivedDefaults.oauth?.baseApiUrl || "https://api.anthropic.com",
1589
+ scopes: derivedDefaults.oauth?.scopes || SAFE_FALLBACK_SCOPES,
1590
+ baseApiUrl: derivedDefaults.oauth?.baseApiUrl || "https://api.anthropic.com"
1591
+ });
1592
+ var FALLBACK = {
1593
+ ...fallbackPayload,
1584
1594
  source: "fallback"
1585
1595
  };
1586
- function sanitizeScopes(scopes) {
1587
- if (!scopes || scopes.includes(REJECTED_SCOPE)) {
1588
- return SAFE_FALLBACK_SCOPES;
1596
+ function hasPollutedCachedScope(scopes) {
1597
+ const parsedScopes = scopes.split(/\s+/).filter(Boolean);
1598
+ return parsedScopes.includes(POLLUTED_CACHED_SCOPE) || !parsedScopes.includes("org:create_api_key");
1599
+ }
1600
+ function filterScopesByBinaryPresence(buf, expected) {
1601
+ const verified = [];
1602
+ for (const scope of expected) {
1603
+ const needle = Buffer.from(`"${scope}"`);
1604
+ if (buf.includes(needle)) {
1605
+ verified.push(scope);
1606
+ }
1607
+ }
1608
+ return verified;
1609
+ }
1610
+ function getVerifiedCanonicalScopes(buf, fallbackScopes) {
1611
+ const expectedScopes = fallbackScopes.split(/\s+/).filter(Boolean);
1612
+ const verifiedScopes = filterScopesByBinaryPresence(buf, expectedScopes);
1613
+ return verifiedScopes.length === expectedScopes.length ? verifiedScopes.join(" ") : null;
1614
+ }
1615
+ function normalizeAuthorizeUrl(url) {
1616
+ if (url === "https://claude.com/cai/oauth/authorize") {
1617
+ return "https://claude.ai/oauth/authorize";
1589
1618
  }
1590
- return scopes;
1619
+ return url;
1620
+ }
1621
+ function normalizeDetectedOAuthConfigPayload(payload) {
1622
+ return {
1623
+ ...payload,
1624
+ authorizeUrl: normalizeAuthorizeUrl(payload.authorizeUrl)
1625
+ };
1591
1626
  }
1592
1627
  function pickNearestScopes(block, centerIndex) {
1593
1628
  return pickNearestValue(block, centerIndex, /SCOPES\s*:\s*"([^"]+)"/gi) || pickNearestValue(block, centerIndex, /scope[s]?\s*:\s*"([^"]+)"/gi) || null;
@@ -1611,10 +1646,12 @@ function extractCandidateBlocks(binaryText) {
1611
1646
  const currentIndex = currentMatch.index ?? 0;
1612
1647
  const previousClientIdIndex = clientIdMatches[index - 1]?.index;
1613
1648
  const nextClientIdIndex = clientIdMatches[index + 1]?.index;
1614
- const leftBoundary = previousClientIdIndex === void 0 ? Math.max(0, currentIndex - CONFIG_SCAN_LOOKBACK_CHARS) : Math.floor((previousClientIdIndex + currentIndex) / 2);
1615
- const rightBoundary = nextClientIdIndex === void 0 ? Math.min(binaryText.length, currentIndex + CONFIG_SCAN_WINDOW_CHARS) : Math.floor((currentIndex + nextClientIdIndex) / 2);
1616
- const start = Math.max(0, leftBoundary);
1617
- const end = Math.min(binaryText.length, Math.max(currentIndex + 1, rightBoundary));
1649
+ const { start, end } = getCandidateBlockRange(
1650
+ currentIndex,
1651
+ previousClientIdIndex,
1652
+ nextClientIdIndex,
1653
+ binaryText.length
1654
+ );
1618
1655
  const key = `${start}:${end}`;
1619
1656
  if (seenRanges.has(key)) {
1620
1657
  continue;
@@ -1627,6 +1664,19 @@ function extractCandidateBlocks(binaryText) {
1627
1664
  }
1628
1665
  return blocks;
1629
1666
  }
1667
+ function midpoint(left, right) {
1668
+ return Math.floor((left + right) / 2);
1669
+ }
1670
+ function getCandidateBlockRange(currentIndex, previousClientIdIndex, nextClientIdIndex, textLength) {
1671
+ const boundedLeftEdge = currentIndex - CONFIG_SCAN_LOOKBACK_CHARS;
1672
+ const boundedRightEdge = currentIndex + CONFIG_SCAN_WINDOW_CHARS;
1673
+ const leftBoundary = previousClientIdIndex === void 0 ? boundedLeftEdge : Math.max(boundedLeftEdge, midpoint(previousClientIdIndex, currentIndex));
1674
+ const rightBoundary = nextClientIdIndex === void 0 ? boundedRightEdge : Math.min(boundedRightEdge, midpoint(currentIndex, nextClientIdIndex));
1675
+ return {
1676
+ start: Math.max(0, leftBoundary),
1677
+ end: Math.min(textLength, Math.max(currentIndex + 1, rightBoundary))
1678
+ };
1679
+ }
1630
1680
  function pickNearestValue(block, centerIndex, pattern) {
1631
1681
  let nearestValue;
1632
1682
  let nearestDistance = Number.POSITIVE_INFINITY;
@@ -1665,7 +1715,7 @@ function extractCandidateFromBlock(block) {
1665
1715
  clientId: clientIdMatch[1],
1666
1716
  authorizeUrl: authorizeUrl || FALLBACK.authorizeUrl,
1667
1717
  tokenUrl: tokenUrl || FALLBACK.tokenUrl,
1668
- scopes: sanitizeScopes(extractedScopes),
1718
+ scopes: extractedScopes || FALLBACK.scopes,
1669
1719
  baseApiUrl: baseApiUrl || FALLBACK.baseApiUrl
1670
1720
  };
1671
1721
  if (!isDetectedOAuthConfigPayload(payload)) {
@@ -1678,9 +1728,15 @@ function extractCandidateFromBlock(block) {
1678
1728
  }
1679
1729
  var memoizedConfig = null;
1680
1730
  var detectorTestOverrides = {};
1731
+ function getPlatform() {
1732
+ return detectorTestOverrides.platform?.() ?? platform();
1733
+ }
1734
+ function fileExists(path) {
1735
+ return (detectorTestOverrides.existsSync ?? existsSync)(path);
1736
+ }
1681
1737
  function candidatePaths() {
1682
1738
  const home = homedir();
1683
- if (platform() === "win32") {
1739
+ if (getPlatform() === "win32") {
1684
1740
  return [
1685
1741
  join(home, ".local", "bin", "claude.exe"),
1686
1742
  join(home, "AppData", "Roaming", "npm", "node_modules", "@anthropic-ai", "claude-code", "cli.js"),
@@ -1700,6 +1756,49 @@ function candidatePaths() {
1700
1756
  join(home, ".claude", "local", "node_modules", "@anthropic-ai", "claude-code", "cli.mjs")
1701
1757
  ];
1702
1758
  }
1759
+ function enumerateCCCandidates() {
1760
+ const seen = /* @__PURE__ */ new Set();
1761
+ const candidates = [];
1762
+ const currentPlatform = getPlatform();
1763
+ const pathDelimiter = currentPlatform === "win32" ? ";" : ":";
1764
+ const pathDirs = (detectorTestOverrides.pathEnv ?? process.env.PATH ?? "").split(pathDelimiter).filter(Boolean);
1765
+ const pathCandidateNames = currentPlatform === "win32" ? ["claude.exe", "claude.cmd", "claude"] : ["claude"];
1766
+ const addCandidate = (candidatePath) => {
1767
+ const key = currentPlatform === "win32" ? candidatePath.toLowerCase() : candidatePath;
1768
+ if (seen.has(key) || !fileExists(candidatePath)) {
1769
+ return;
1770
+ }
1771
+ seen.add(key);
1772
+ candidates.push(candidatePath);
1773
+ };
1774
+ for (const dir of pathDirs) {
1775
+ for (const fileName of pathCandidateNames) {
1776
+ addCandidate(join(dir, fileName));
1777
+ }
1778
+ }
1779
+ for (const candidatePath of candidatePaths()) {
1780
+ addCandidate(candidatePath);
1781
+ }
1782
+ return candidates;
1783
+ }
1784
+ function probeOneVersion(binPath) {
1785
+ const currentPlatform = getPlatform();
1786
+ if (currentPlatform === "win32" && /\.(cmd|bat)$/i.test(binPath) && /[&|><^"'%\r\n`$;(){}\[\]]/.test(binPath)) {
1787
+ return null;
1788
+ }
1789
+ try {
1790
+ const output = (detectorTestOverrides.execFileSync ?? defaultExecFileSync2)(binPath, ["--version"], {
1791
+ timeout: 2e3,
1792
+ encoding: "utf-8",
1793
+ windowsHide: true,
1794
+ stdio: ["ignore", "pipe", "ignore"],
1795
+ shell: currentPlatform === "win32" && /\.(cmd|bat)$/i.test(binPath)
1796
+ });
1797
+ return output.match(/(\d+\.\d+\.\d+(?:[.\-][\w.\-]+)?)/)?.[1] ?? null;
1798
+ } catch {
1799
+ return null;
1800
+ }
1801
+ }
1703
1802
  function getCachePath() {
1704
1803
  return join(getConfigDir(), CACHE_FILE_NAME);
1705
1804
  }
@@ -1721,13 +1820,17 @@ function isDetectedOAuthConfigPayload(value) {
1721
1820
  const candidate = value;
1722
1821
  return typeof candidate.clientId === "string" && UUID_PATTERN.test(candidate.clientId) && typeof candidate.authorizeUrl === "string" && isValidUrl(candidate.authorizeUrl) && typeof candidate.tokenUrl === "string" && isValidUrl(candidate.tokenUrl) && typeof candidate.scopes === "string" && candidate.scopes.length > 0;
1723
1822
  }
1724
- function toFallbackConfig(ccPath, ccHash) {
1823
+ function buildResolvedConfig(payload, source, ccPath, ccHash) {
1725
1824
  return {
1726
- ...FALLBACK,
1825
+ ...payload,
1826
+ source,
1727
1827
  ...ccPath ? { ccPath } : {},
1728
1828
  ...ccHash ? { ccHash } : {}
1729
1829
  };
1730
1830
  }
1831
+ function toFallbackConfig(ccPath, ccHash) {
1832
+ return buildResolvedConfig(FALLBACK, "fallback", ccPath, ccHash);
1833
+ }
1731
1834
  function isOverrideDisabled() {
1732
1835
  return process.env.ANTHROPIC_MULTI_ACCOUNT_OAUTH_DISABLE_OVERRIDE === "1";
1733
1836
  }
@@ -1741,30 +1844,44 @@ function readOverrideString(value) {
1741
1844
  function getOverridePath() {
1742
1845
  return readOverrideString(process.env.ANTHROPIC_MULTI_ACCOUNT_OAUTH_OVERRIDE_PATH) ?? getDefaultOverridePath();
1743
1846
  }
1744
- function normalizeOverride(value) {
1847
+ function readOverrideRecord(value) {
1745
1848
  if (typeof value !== "object" || value === null) {
1849
+ return null;
1850
+ }
1851
+ return value;
1852
+ }
1853
+ function readOverrideField(candidate, key) {
1854
+ const value = candidate[key];
1855
+ return typeof value === "string" ? readOverrideString(value) : void 0;
1856
+ }
1857
+ function readOverrideUrl(candidate, key) {
1858
+ const value = readOverrideField(candidate, key);
1859
+ return value && isValidUrl(value) ? value : void 0;
1860
+ }
1861
+ function normalizeOverride(value) {
1862
+ const candidate = readOverrideRecord(value);
1863
+ if (!candidate) {
1746
1864
  return {};
1747
1865
  }
1748
- const candidate = value;
1749
1866
  const normalized = {};
1750
- const clientId = readOverrideString(typeof candidate.clientId === "string" ? candidate.clientId : void 0);
1867
+ const clientId = readOverrideField(candidate, "clientId");
1751
1868
  if (clientId && UUID_PATTERN.test(clientId)) {
1752
1869
  normalized.clientId = clientId;
1753
1870
  }
1754
- const authorizeUrl = readOverrideString(typeof candidate.authorizeUrl === "string" ? candidate.authorizeUrl : void 0);
1755
- if (authorizeUrl && isValidUrl(authorizeUrl)) {
1756
- normalized.authorizeUrl = authorizeUrl;
1871
+ const authorizeUrl = readOverrideUrl(candidate, "authorizeUrl");
1872
+ if (authorizeUrl) {
1873
+ normalized.authorizeUrl = normalizeAuthorizeUrl(authorizeUrl);
1757
1874
  }
1758
- const tokenUrl = readOverrideString(typeof candidate.tokenUrl === "string" ? candidate.tokenUrl : void 0);
1759
- if (tokenUrl && isValidUrl(tokenUrl)) {
1875
+ const tokenUrl = readOverrideUrl(candidate, "tokenUrl");
1876
+ if (tokenUrl) {
1760
1877
  normalized.tokenUrl = tokenUrl;
1761
1878
  }
1762
- const scopes = readOverrideString(typeof candidate.scopes === "string" ? candidate.scopes : void 0);
1879
+ const scopes = readOverrideField(candidate, "scopes");
1763
1880
  if (scopes) {
1764
- normalized.scopes = sanitizeScopes(scopes);
1881
+ normalized.scopes = scopes;
1765
1882
  }
1766
- const baseApiUrl = readOverrideString(typeof candidate.baseApiUrl === "string" ? candidate.baseApiUrl : void 0);
1767
- if (baseApiUrl && isValidUrl(baseApiUrl)) {
1883
+ const baseApiUrl = readOverrideUrl(candidate, "baseApiUrl");
1884
+ if (baseApiUrl) {
1768
1885
  normalized.baseApiUrl = baseApiUrl;
1769
1886
  }
1770
1887
  return normalized;
@@ -1801,16 +1918,22 @@ async function applyManualOverride(baseConfig) {
1801
1918
  };
1802
1919
  }
1803
1920
  function findCCBinary() {
1804
- const override = process.env.DARIO_CC_PATH;
1805
- if (override && existsSync(override)) {
1921
+ const override = process.env.ANTHROPIC_CC_PATH;
1922
+ if (override && fileExists(override)) {
1806
1923
  return override;
1807
1924
  }
1808
- for (const candidatePath of candidatePaths()) {
1809
- if (existsSync(candidatePath)) {
1810
- return candidatePath;
1811
- }
1925
+ const candidates = enumerateCCCandidates();
1926
+ if (candidates.length === 0) {
1927
+ return null;
1812
1928
  }
1813
- return null;
1929
+ if (candidates.length === 1) {
1930
+ return candidates[0] ?? null;
1931
+ }
1932
+ const probedCandidates = candidates.map((candidatePath) => {
1933
+ const version = probeOneVersion(candidatePath);
1934
+ return version ? { path: candidatePath, version } : null;
1935
+ }).filter((candidate) => candidate !== null).sort((left, right) => compareVersions(right.version, left.version) ?? 0);
1936
+ return probedCandidates[0]?.path ?? candidates[0] ?? null;
1814
1937
  }
1815
1938
  async function fingerprintBinary(path) {
1816
1939
  const binaryContents = await readFile(path);
@@ -1819,19 +1942,24 @@ async function fingerprintBinary(path) {
1819
1942
  function scanBinaryForOAuthConfig(buf) {
1820
1943
  const binaryText = buf.toString("latin1");
1821
1944
  const candidates = extractCandidateBlocks(binaryText).map(extractCandidateFromBlock).filter((candidate) => candidate !== null).sort((left, right) => right.score - left.score);
1822
- return candidates[0]?.payload ?? null;
1945
+ const preferredCandidate = candidates.find((candidate) => candidate.payload.clientId === KNOWN_PROD_CLIENT_ID);
1946
+ return preferredCandidate?.payload ?? candidates[0]?.payload ?? null;
1947
+ }
1948
+ async function readRawCacheEntries() {
1949
+ const raw = await readFile(getCachePath(), "utf-8");
1950
+ const parsed = JSON.parse(raw);
1951
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.entries !== "object" || parsed.entries === null) {
1952
+ return {};
1953
+ }
1954
+ return parsed.entries;
1823
1955
  }
1824
1956
  async function loadCache() {
1825
1957
  try {
1826
- const raw = await readFile(getCachePath(), "utf-8");
1827
- const parsed = JSON.parse(raw);
1828
- if (typeof parsed !== "object" || parsed === null || typeof parsed.entries !== "object" || parsed.entries === null) {
1829
- return {};
1830
- }
1958
+ const rawEntries = await readRawCacheEntries();
1831
1959
  const validEntries = {};
1832
- for (const [hash, value] of Object.entries(parsed.entries)) {
1833
- if (isDetectedOAuthConfigPayload(value)) {
1834
- validEntries[hash] = value;
1960
+ for (const [hash, value] of Object.entries(rawEntries)) {
1961
+ if (isDetectedOAuthConfigPayload(value) && !hasPollutedCachedScope(value.scopes)) {
1962
+ validEntries[hash] = normalizeDetectedOAuthConfigPayload(value);
1835
1963
  }
1836
1964
  }
1837
1965
  return validEntries;
@@ -1842,7 +1970,11 @@ async function loadCache() {
1842
1970
  async function saveCache(hash, config) {
1843
1971
  try {
1844
1972
  const cachePath = getCachePath();
1845
- const currentEntries = await loadCache();
1973
+ const currentEntries = {};
1974
+ try {
1975
+ Object.assign(currentEntries, await readRawCacheEntries());
1976
+ } catch {
1977
+ }
1846
1978
  currentEntries[hash] = config;
1847
1979
  await mkdir(dirname(cachePath), { recursive: true });
1848
1980
  await writeFile(
@@ -1867,27 +1999,23 @@ async function detectOAuthConfig() {
1867
1999
  const cachedEntries = await loadCache();
1868
2000
  const cachedConfig = cachedEntries[ccHash];
1869
2001
  if (cachedConfig) {
1870
- memoizedConfig = await applyManualOverride({
1871
- ...cachedConfig,
1872
- source: "cached",
1873
- ccPath,
1874
- ccHash
1875
- });
2002
+ memoizedConfig = await applyManualOverride(buildResolvedConfig(cachedConfig, "cached", ccPath, ccHash));
1876
2003
  return memoizedConfig;
1877
2004
  }
1878
2005
  const readBinaryFile = detectorTestOverrides.readBinaryFile || readFile;
1879
- const scannedConfig = scanBinaryForOAuthConfig(await readBinaryFile(ccPath));
2006
+ const binaryBuffer = await readBinaryFile(ccPath);
2007
+ const scannedConfig = scanBinaryForOAuthConfig(binaryBuffer);
1880
2008
  if (!scannedConfig) {
1881
2009
  memoizedConfig = await applyManualOverride(toFallbackConfig(ccPath, ccHash));
1882
2010
  return memoizedConfig;
1883
2011
  }
1884
- await saveCache(ccHash, scannedConfig);
1885
- memoizedConfig = await applyManualOverride({
1886
- ...scannedConfig,
1887
- source: "detected",
1888
- ccPath,
1889
- ccHash
1890
- });
2012
+ const verifiedCanonicalScopes = getVerifiedCanonicalScopes(binaryBuffer, FALLBACK.scopes);
2013
+ if (verifiedCanonicalScopes) {
2014
+ scannedConfig.scopes = verifiedCanonicalScopes;
2015
+ }
2016
+ const runtimeDetectedConfig = normalizeDetectedOAuthConfigPayload(scannedConfig);
2017
+ await saveCache(ccHash, runtimeDetectedConfig);
2018
+ memoizedConfig = await applyManualOverride(buildResolvedConfig(runtimeDetectedConfig, "detected", ccPath, ccHash));
1891
2019
  return memoizedConfig;
1892
2020
  } catch {
1893
2021
  memoizedConfig = await applyManualOverride(FALLBACK);
@@ -1895,7 +2023,7 @@ async function detectOAuthConfig() {
1895
2023
  }
1896
2024
  }
1897
2025
 
1898
- // src/fingerprint-capture.ts
2026
+ // src/claude-code/fingerprint/capture.ts
1899
2027
  var CURRENT_SCHEMA_VERSION = 1;
1900
2028
  var LIVE_TTL_MS = 24 * 60 * 60 * 1e3;
1901
2029
  var DEFAULT_CAPTURE_TIMEOUT_MS = 1e4;
@@ -1914,15 +2042,15 @@ var STATIC_HEADER_NAMES = [
1914
2042
  ];
1915
2043
  var SUPPORTED_CC_RANGE = {
1916
2044
  min: "1.0.0",
1917
- maxTested: "2.1.114"
2045
+ maxTested: "2.1.121"
1918
2046
  };
1919
- var bundledTemplate = fingerprint_data_default;
2047
+ var bundledTemplate = data_default;
1920
2048
  var fingerprintCaptureTestOverrides = {};
1921
2049
  function now() {
1922
2050
  return fingerprintCaptureTestOverrides.now?.() ?? Date.now();
1923
2051
  }
1924
2052
  function getCachePath2() {
1925
- return join2(getConfigDir(), CACHE_FILE_NAME2);
2053
+ return join2(fingerprintCaptureTestOverrides.getConfigDir?.() ?? getConfigDir(), CACHE_FILE_NAME2);
1926
2054
  }
1927
2055
  function isRecord(value) {
1928
2056
  return typeof value === "object" && value !== null;
@@ -2298,23 +2426,26 @@ function parseVersion(version) {
2298
2426
  if (!match) {
2299
2427
  return null;
2300
2428
  }
2301
- return [Number(match[1]), Number(match[2]), Number(match[3])];
2429
+ const [, major, minor, patch] = match;
2430
+ return [Number(major), Number(minor), Number(patch)];
2302
2431
  }
2303
2432
  function compareVersions(left, right) {
2304
- const leftVersion = parseVersion(left);
2305
- const rightVersion = parseVersion(right);
2306
- if (!leftVersion || !rightVersion) {
2433
+ const leftParts = parseVersion(left);
2434
+ const rightParts = parseVersion(right);
2435
+ if (!leftParts || !rightParts) {
2307
2436
  return null;
2308
2437
  }
2309
- for (let index = 0; index < leftVersion.length; index += 1) {
2310
- const leftPart = leftVersion[index] ?? 0;
2311
- const rightPart = rightVersion[index] ?? 0;
2312
- const diff = leftPart - rightPart;
2313
- if (diff !== 0) {
2314
- return diff;
2315
- }
2438
+ const [leftMajor, leftMinor, leftPatch] = leftParts;
2439
+ const [rightMajor, rightMinor, rightPatch] = rightParts;
2440
+ const majorDiff = leftMajor - rightMajor;
2441
+ if (majorDiff !== 0) {
2442
+ return majorDiff;
2316
2443
  }
2317
- return 0;
2444
+ const minorDiff = leftMinor - rightMinor;
2445
+ if (minorDiff !== 0) {
2446
+ return minorDiff;
2447
+ }
2448
+ return leftPatch - rightPatch;
2318
2449
  }
2319
2450
  function detectDrift(template, installedOverride) {
2320
2451
  const cachedVersion = template.cc_version ?? null;
@@ -2401,9 +2532,9 @@ function resetFingerprintCaptureForTest() {
2401
2532
  }
2402
2533
 
2403
2534
  export {
2404
- fingerprint_data_default,
2405
- detectCliVersion,
2406
- cc_derived_defaults_default,
2535
+ getConfig,
2536
+ loadConfig,
2537
+ updateConfigField,
2407
2538
  ANTHROPIC_OAUTH_ADAPTER,
2408
2539
  ANTHROPIC_USAGE_ENDPOINT,
2409
2540
  ANTHROPIC_PROFILE_ENDPOINT,
@@ -2412,9 +2543,9 @@ export {
2412
2543
  PLAN_LABELS,
2413
2544
  TOKEN_EXPIRY_BUFFER_MS,
2414
2545
  TOKEN_REFRESH_TIMEOUT_MS,
2415
- getConfig,
2416
- loadConfig,
2417
- updateConfigField,
2546
+ cc_derived_defaults_default,
2547
+ data_default,
2548
+ detectCliVersion,
2418
2549
  showToast,
2419
2550
  debugLog,
2420
2551
  createMinimalClient,
@@ -2431,9 +2562,10 @@ export {
2431
2562
  extractTemplate,
2432
2563
  captureLiveTemplateAsync,
2433
2564
  refreshLiveFingerprintAsync,
2565
+ compareVersions,
2434
2566
  detectDrift,
2435
2567
  checkCCCompat,
2436
2568
  setFingerprintCaptureTestOverridesForTest,
2437
2569
  resetFingerprintCaptureForTest
2438
2570
  };
2439
- //# sourceMappingURL=chunk-2SN3UVSM.js.map
2571
+ //# sourceMappingURL=chunk-5PQ3VP24.js.map