8fdda66893a9c43279d4a809dadcfae8f857a9d9
7 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8fdda66893 |
fix: v2.5.9 — Vietnamese in the terminal on macOS, every slash command, the right Claude account
Issue #62: a tmux client whose LC_ALL/LC_CTYPE/LANG names no UTF-8 prints `_` for every non-ASCII character, and a macOS LaunchAgent starts the dashboard with no locale at all — Vietnamese text and Claude Code's logo came out as underscores in every browser terminal. - shell-path.js: ensureUtf8Locale() defaults LC_CTYPE (en_US.UTF-8 on macOS, C.UTF-8 elsewhere) at start, before any tmux call; an explicit LC_ALL and Windows are left alone - RunSetup.tsx: the `/` list now offers Claude Code's bundled skills (/code-review, /security-review, /simplify, /loop…) and newer built-ins, and every enabled plugin's commands and skills as /<plugin>:<name> - Claude login decided by the TOKEN's owner (GET /api/oauth/profile, cached per token), not by .claude.json: a running session can leave the credentials store holding one account's token under another account's name. The Profiles modal, its Check quota and the status-bar quota now show the account requests are really billed to and warn on the mismatch; a switch saves the outgoing token into its OWNER's profile (credentials only) instead of overwriting the profile .claude.json names; saving a profile while they disagree is refused (EMISMATCH); a saved profile holding another account's token reports EWRONGACCOUNT instead of that account's usage - Running Claude Code sessions follow a switch on their own (they re-read the credentials store when it changes), so the "close your sessions first" warning is gone |
||
|
|
daf5b7be92 |
feat: edit in the Changes panel, git graph history, plan quota (v2.3.0)
- Changes panel: a working file opens in a CodeMirror editor with its diff inline (DiffEditor.tsx, @codemirror/merge, lazy-loaded); Changes folds the unchanged lines, Whole file shows them; side by side at 50/50 and full width; a Revert per block; original from HEAD or the branch's merge base (`mergeBase` on GET /git/changes?range=branch) - Every diff: green = line added whole, red = removed whole, blue = edited in place (--diff-mod) - History drawn as a commit graph (#58: `parents` on GET /git/log, --date-order; lib/commitGraph.ts) - Per-workspace never-stage list, drawn orange in the Files tree (--kept); closed Terminal rows past three deleted - Status bar plan quota for Claude (per account) and Codex (GET /api/pricing/quota, server/lib/quota.js); compact hover panel that stays open, Refresh button with a spinner - Settings → Appearance holds language, theme and pet; Ctrl+Shift+C copies; compact sidebar navigation and short session times |
||
|
|
64017c6ca5 |
feat(run,notifications,profiles): stop sending prompts nobody typed, and fix notification + profile delivery
Run console:
- Picking a pipeline no longer pre-writes anything into the prompt box
(`promptPrefill` in `LaneConsolePane.tsx` is gone — it wrote the driving
slash command on a claude lane and a stage checklist on a codex one).
Whatever sits in that box is sent as a real prompt the moment the run
starts, so the box is the user's alone now.
- Resuming a session sends NO prompt at all (`handleStart` in `RunSetup.tsx`):
the resumed session carries its own transcript, and leftover text in the box
fired an extra turn into it.
Notifications:
- `client/src/lib/notify.ts` is now the ONE sender, used by both
`useNotifications` and Settings' test button, and it reports where the
notification landed: `POST /api/push/send` answering `{native:false,pushed:0}`
means it went NOWHERE, so the client falls back to a local notification
instead of counting the 200 as delivered.
- A notification carries the lane that raised it (`/workspace?lane=<id>`), and
Workspace selects that lane on arrival.
Profiles:
- `server/lib/cc-profiles.js` reads and writes the macOS login Keychain item
`Claude Code-credentials`, where the `claude` CLI actually keeps
`claudeAiOauth` on darwin — a switch used to rewrite a file nothing reads.
The file stays the fallback, is kept in step only when it already exists, and
`AGENTHUB_CC_KEYCHAIN=0` pins it (tests set it).
Tests: npm run test:server (1820 pass), client vitest (553 pass).
|
||
|
|
1aee080fca |
fix(cc-config): read the live account's quota from the CLI, and ask for every account at once
The quota check failed with `invalid_grant` for the account the user was logged into - the one profile they would try first. An OAuth refresh token is single use, so every refresh Claude Code performs on its own account retires the copy sitting in that account's saved profile: the stored credentials of the LIVE account are the one set guaranteed to be rejected, however recently the profile was saved. Confirmed on a real install - the saved profile held a refresh token three rotations behind `.credentials.json`. `cc-profiles.liveCredentialsFor` now matches a profile's accountUuid against the live credentials and, on a hit, the usage call uses what the CLI is holding. That token is never refreshed and that file is never written: rotating it would log the user out of their own terminal. An expired live token answers ELIVEEXPIRED - using Claude Code once makes the CLI refresh it - and a genuinely spent refresh token is now reported as ESTALE with what to do about it, instead of echoing Anthropic's raw 400. Opening the Profiles modal also asks for every saved account at once, rather than one button per row: comparing accounts is the only reason the panel exists. The guard against rewriting credentials over and over is no longer "one row at a time" but the rules above - the live account costs a plain read, and any other profile refreshes only once its token has actually expired, which then leaves it valid for hours of further opens. Each row resolves on its own, so a superseded account still lets its neighbours report. |
||
|
|
5ba9e07d78 |
feat(cc-config,lanes): per-profile quota, and three fixes that made a write look lost
Closes #19, #20, #21, #27. #19 — a saved auth profile now reports how much of its account's quota is already spent, which is the only number that decides which one is worth switching to. Nothing under CLAUDE_HOME records it, so it comes from the same endpoint the CLI's own /usage reads, answered for whichever account's OAuth token is presented. It is a POST and a button, never a poll: every profile that is not the active one is normally expired, so answering means refreshing it first, and a refresh ROTATES the stored refresh token. The rotated pair is written straight back — dropping it would leave the profile holding a credential Anthropic has already retired, i.e. an account needing a fresh login. The token is read, put in an Authorization header, and never returned, logged or echoed; what leaves the server is two percentages and two reset times. #20 — a settings patch that changes nothing wrote nothing but still took a timestamped backup, so opening the config panel quietly grew a backup directory inside every lane. patchSettings now compares the rendered output with what is already on disk and returns early, fixed once where all callers route through. A lane's git exclude also picks up .claude/.cc-config-backups/ beside settings.local.json. #21 — the router apply route was the only mutating lane route that never broadcast its lane. The write reached disk and the column, but the client's cached row kept the old router_profile forever: the picker looked right until the user switched lanes and back, at which point the effect re-read the stale row and flipped the selection to the previous profile. One broadcastLane, plus a lane-switch guard so an apply that resolves after the user has moved on no longer paints its result onto whichever lane is now on screen. #27 — Add-lane in worktree mode left the dialog up after a clean setup with a dead confirm button and no obvious way out. It closes itself when nothing failed, and when something did, the summary stays and the confirm becomes a real Close. |
||
|
|
11dcc04ac4 |
fix(cc-config): one account is one profile, and rename it in place
Four defects, all from one design mistake: a profile's display name and its filename were the same string. - Saving the logged-in account made a SECOND profile of an account another profile already held. Both then reported themselves active, so there was nothing to switch between. `POST /profiles` now refuses `EDUPLICATE` and names the profile that already holds the account. Re-saving into the account's own profile stays allowed - that is the token-refresh path. - Worse, an accidental duplicate could not be cleaned up: two profiles of one account are both active, and the delete guard refused any active profile, so neither was ever deletable. The guard now blocks only the LAST saved copy of the live account, which is the actual credential-loss condition it exists for. The trash button had to follow, or the server would allow what the UI hid. - A name could not contain spaces or diacritics, because it had to be filename-safe. A profile now carries `name` (internal identifier, still the filename, still what every route takes) and `label` (what the user reads and edits: 1-60 free-form characters, control characters rejected, stored trimmed, unique case-insensitively). A label never touches a path, which is what makes "Tài khoản chính" a legal name. - Naming happened up front in a "save current account as" field. That field is gone. The server derives the identifier, the save button lives on the live account's card and appears only while it is unsaved, and the label is edited by clicking it (Enter saves, Escape cancels). Renaming rewrites the label in place and deliberately does NOT move `name`, so it cannot half-fail and leave a profile unreachable. Profiles written before labels existed report `label` equal to their `name`, so nothing needs migrating. Adds `PATCH /api/cc-config/profiles`; `POST` now takes an optional `name`. Verified: server 1431/1431 (11 new), client 459/459, tsc clean. Also exercised end to end against a COPY of the real profile store on this machine: the duplicate deleted, the last copy still refused, a Vietnamese label accepted, and the stored access/refresh tokens and 0600 mode intact after a rename. |
||
|
|
4bc77ded17 |
feat(cc-config): switch between Claude Code accounts, keeping the workspace
Adds a "Switch profile" button left of Backups on /settings?tool=claude-code. A *profile* is one Anthropic account - one quota - saved under a name, so the local `claude` CLI can be pointed at a different account without disturbing anything else. A switch swaps exactly two identity artifacts and nothing more: `claudeAiOauth` inside `<CLAUDE_HOME>/.credentials.json`, and `oauthAccount` + `userID` inside the `.claude.json` config file(s). Those files are key-merged in place, and only where they already exist, so `projects`/`numStartups`/everything else survives. Skills, subagents, commands, memory, plugins, marketplaces, MCP servers and settings are never read or written here - that is what makes a switch keep the whole workspace and change only the account. `mcpOAuth` stays put too: those are the machine's MCP server tokens, not account identity. Profiles are one JSON file each under `<CLAUDE_HOME>/agenthub-profiles/` (dir 0700, files 0600, atomic temp+rename) and hold live OAuth tokens, so no route ever returns token material - `GET /profiles` answers identity metadata only. The dashboard cannot log an account in; only `claude auth login` mints tokens. Adding a profile is therefore always "log in from a terminal, then snapshot what the CLI wrote". `activateProfile` re-snapshots the outgoing account first so tokens Claude Code refreshed since its last save are not lost, and refuses (`EUNSAVED`) to switch away from an account that was never saved - that would discard the only copy of its credentials. A switch applies to new sessions only, since a running session rewrites the config on exit. Routes: GET/POST/DELETE /api/cc-config/profiles, POST /profiles/activate. Verified: server 1420/1420 (14 new, incl. token-free responses and 0600/0700 permissions), client 459/459. |