Files
AgentHub/client/src/i18n/locales/en/users.json
T
nntrivi2001 783b7f4101 fix(cost): bill a session outside every workspace to its Linux account's user
A script's claude -p in a temp folder (the SG Flow eval runner strips the
pane's env) had no workspace, so its spend reached nobody's Today, the Users
page or a spend quota — ~$57 of a $74 day was missing from the status bar.

sessions.account records the Linux account a session ran as (the hook's
peer uid, else its folder for poller-made and old rows); a session no
workspace owns is owned by that account's user — a member's to them, the
host's to the admin ticked 'Owns this server's host account' (or the only
admin). Workspace ownership still wins.
2026-10-30 08:50:34 +07:00

453 lines
22 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"title": "Users",
"description": "Every account on this server, with its workspaces, sessions and recent usage.",
"name": "User",
"role": "Role",
"workspaces": "Workspaces",
"sessions": "Sessions",
"today": "Today",
"last7d": "7 days",
"last30d": "30 days",
"storage": "Storage",
"storageHint": "{{files}} files · measured {{at}}",
"storagePartial": "at least — part of the folder could not be read in time",
"storageNone": "No home folder of their own",
"storageMeasuring": "Measuring…",
"disabled": "disabled",
"you": "you",
"tokens": "{{n}} tokens",
"actions": "Actions",
"roles": {
"admin": "Admin",
"analyst": "Analyst",
"developer": "Developer"
},
"roleHint": {
"admin": "Everything, including this server's configuration and every user's data",
"analyst": "Sees every user's data; works only in their own workspaces, changes no settings",
"developer": "Only their own workspaces; changes no settings"
},
"add": "Add user",
"username": "Username",
"usernameHint": "Letters, digits, '.', '_' or '-'",
"password": "Password",
"passwordHint": "Any starting password — they choose their own at first sign-in",
"newPassword": "New password",
"changeRole": "Change role",
"disable": "Disable",
"enable": "Enable",
"resetPassword": "Reset password",
"assignWorkspace": "Give a workspace",
"workspace": "Workspace",
"noOwner": "no owner",
"ownedBy": "owned by {{name}}",
"noWorkspaces": "There are no workspaces yet.",
"yourPassword": "Your password",
"yourPasswordHint": "Type your own password to confirm this change.",
"save": "Save",
"dialog": {
"add": "Add a user",
"role": "Change {{name}}'s role",
"disable": "Disable {{name}}?",
"disableBody": "{{name}} is signed out everywhere and cannot sign in until enabled again. Their workspaces and history stay.",
"enable": "Enable {{name}}?",
"password": "Reset {{name}}'s password",
"passwordBody": "{{name}} is signed out everywhere and signs in with the new password.",
"workspace": "Give {{name}} a workspace",
"workspaceBody": "The workspace's sessions and costs move to {{name}}. A developer sees only their own workspaces."
},
"done": {
"created": "{{name}} added",
"role": "{{name}} is now {{role}}",
"disabled": "{{name}} disabled",
"enabled": "{{name}} enabled",
"password": "{{name}}'s password reset",
"workspace": "Workspace given to {{name}}",
"updated": "{{name}} updated",
"deleted": "{{name}} was deleted."
},
"errors": {
"EBADIMAGE": "Only a PNG, JPEG, GIF or WebP picture",
"ETOOBIG": "The picture must be at most 1 MB",
"EBADURL": "Enter an https:// link to a picture.",
"EPRIVATEURL": "That link points to a private or local address.",
"EFETCH": "Could not download the picture from that link.",
"EREAUTH": "Your password is wrong.",
"ETOOMANY": "Too many wrong passwords. Try again in 15 minutes.",
"ESELF": "You cannot change your own role or disable yourself.",
"EEXISTS": "That username is taken.",
"EBADUSER": "Username: letters, digits, '.', '_' or '-' (up to 64).",
"EBADPASSWORD": "Password cannot be empty.",
"EBADEMAIL": "That email address does not look valid.",
"ENAMETAKEN": "Another account already uses that name. Pick one that is yours.",
"EBADFIELD": "One of the values is not valid.",
"ENOLANE": "That workspace no longer exists.",
"ENOTOWNED": "That workspace is not this user's any more.",
"EOWNERFOLDER": "That workspace's folder belongs to another user's account.",
"EBADROLE": "Unknown role.",
"ELOCALMODE": "Accounts are managed in server mode only.",
"ELASTADMIN": "The last admin cannot be deleted, demoted or disabled.",
"EWEAKPASSWORD": "The new password does not meet every rule (or is the current one).",
"EPWCHANGE": "Choose your own password first.",
"EBADQUOTA": "Enter a positive dollar limit and a period of a day or 5 hours."
},
"readOnly": "Analysts can view accounts but not change them.",
"auditTitle": "Audit log",
"auditDescription": "Account changes made here, and each time an admin or analyst opened a session a developer owns.",
"auditEmpty": "Nothing recorded yet.",
"activeCount": "{{n}} active",
"cost30d": "Cost · 30 days",
"displayName": "Display name",
"displayNameHint": "Shown beside the username. Optional.",
"email": "Email",
"edit": {
"button": "Edit user",
"title": "Edit {{name}}",
"created": "Created",
"lastLogin": "Last sign-in",
"never": "Never",
"logins": "Active sign-ins",
"profile": "Profile",
"access": "Access",
"status": "Status",
"active": "Active",
"statusHint": "A disabled user cannot sign in; their workspaces and history stay.",
"hostAccount": "Owns this server's host account",
"hostAccountHint": "Sessions run as the server's own Linux account outside every workspace (a script's claude -p, a CLI in a plain terminal) count toward this admin's spend. At most one admin; with none marked, the only admin.",
"selfRole": "You cannot change your own role.",
"selfDisable": "You cannot disable yourself.",
"password": "Password",
"passwordHint": "Leave blank to keep the current one. A new one signs them out everywhere, and they choose their own at their next sign-in.",
"show": "Show password",
"hide": "Hide password",
"copy": "Copy password",
"generate": "Generate",
"loading": "Loading…",
"noOwned": "Owns no workspace.",
"undo": "Undo",
"removeWorkspace": "Remove {{name}}",
"removeWorkspaceHint": "The workspace is left with no owner",
"addWorkspace": "Add workspace",
"chooseWorkspace": "Choose a workspace…",
"sessions": "Sign-ins",
"signOut": "Sign out everywhere",
"signOutHint": "Ends this user's {{n}} active sign-in(s); they sign in again with their password.",
"noChanges": "No changes yet.",
"ownPasswordHint": "Leave blank to keep the current one. Your other sign-ins end."
},
"pool": {
"title": "Account pool",
"description": "Each pool holds accounts of every AI tool (Claude Code, Codex, Cursor) and the members who run on them. A member's new session takes the least-used account of their pool, so nobody signs in per user.",
"refresh": "Re-read quotas",
"add": "Add account",
"account": "Account",
"livePanes": "Live",
"quota": "Quota",
"spend": "5 hours",
"spend7d": "{{cost}} · {{tokens}} tokens in 7 days",
"tokens": "{{tokens}} tokens",
"added": "added {{at}}",
"expired": "expired",
"full": "full",
"name": "Name",
"nameHint": "Any label you will recognise: letters, digits, '.', '_' or '-'",
"remove": "Remove {{name}}",
"removeHint": "New sessions stop using this account. Sessions already running on it keep running.",
"removeButton": "Remove",
"done": {
"added": "{{name}} added to the pool",
"removed": "{{name}} removed from the pool"
},
"errors": {
"EREAUTH": "Your password is wrong.",
"ETOOMANY": "Too many wrong passwords. Try again in 15 minutes.",
"EBADNAME": "Name: 1–64 letters, digits, '.', '_' or '-'.",
"EBADTOKEN": "That is not a valid token for this tool.",
"EEXISTS": "An account with that name is already in the pool.",
"ENOACCOUNT": "That account is no longer in the pool.",
"ELOCALMODE": "In local mode the pool is changed from the host's shell (agenthub accounts).",
"EACTIVE": "This server's Claude Code keeps that login in the macOS Keychain. Switch it to another profile first.",
"ENOTFOUND": "That saved login no longer exists.",
"ESTALE": "That login was already refreshed elsewhere. Log in again and save it anew.",
"EUPSTREAM": "Anthropic could not be reached. Try again.",
"ENOUSER": "That user no longer exists.",
"ENOGROUP": "That group no longer exists."
},
"source": "From",
"sources": {
"profile": "A saved profile (Settings → AI tools → Profiles)",
"credentials": "Paste .credentials.json / Keychain",
"token": "claude setup-token"
},
"sourceHint": {
"profile": "The login is moved into the pool: the profile leaves the switcher, and the server writes it into each user's home when their session starts.",
"credentials": "From ~/.claude/.credentials.json, or on a Mac `security find-generic-password -s \"Claude Code-credentials\" -w`. Do not keep using that login where you copied it from — two copies log each other out.",
"token": "A one-year token from `claude setup-token`, given to every session as an environment variable.",
"codex": "An OpenAI API key (sk-…), or the whole ~/.codex/auth.json after `codex login` with a ChatGPT account.",
"cursor": "A Cursor API key from the Cursor dashboard (Integrations → API keys)."
},
"profile": "Profile",
"profileHint": "Saved in Settings → AI tools → Profiles. The one this server's own CLI is logged into can be moved too: the server keeps running on it.",
"chooseProfile": "Choose a saved login…",
"noProfiles": "No saved profiles — save one in Settings → AI tools → Profiles",
"loadingProfiles": "Loading…",
"activeHere": "in use on this server — keeps working",
"nameFromProfile": "Leave blank to use the profile's name",
"secret": {
"credentials": "Credentials JSON",
"token": "Token",
"codex": "API key or auth.json",
"cursor": "Cursor API key"
},
"secretHint": {
"credentials": "The whole file: {\"claudeAiOauth\": {…}}",
"token": "The sk-ant-… that `claude setup-token` printed.",
"codex": "An OpenAI API key (sk-…), or the whole ~/.codex/auth.json after `codex login` with a ChatGPT account.",
"cursor": "Create one in the Cursor dashboard under Integrations → API keys."
},
"refreshFailed": "refresh failed",
"pinned": "Used by: {{users}}",
"loadingUsers": "Loading…",
"pinDone": {
"own-login": "{{user}} signed in with their own login, which was left alone",
"expired": "{{account}} has expired — {{user}}'s new sessions fall back to the least-used account"
},
"edit": "Edit {{name}}",
"editHint": "Tick the users whose sessions run on this account instead of the least-used one — even when it is full. A user on another account moves here. A Claude login is written into their home at once: sessions already running move to it at their next login check, with no restart. A setup-token or Codex account applies to new sessions. Untick to send a user back to the least-used account.",
"save": "Save",
"users": "Users on this account",
"noUsers": "No users yet.",
"onAccount": "now on {{account}}",
"editDone": "{{name}} saved",
"profilesLink": "Profiles in Settings",
"addFor": "Add {{tool}} account",
"type": "Type",
"about": {
"claude": "New Claude Code sessions run on these accounts. A setup-token goes in as an environment variable; a real login is written into each user's home by the server and refreshed by it.",
"codex": "New Codex sessions (not on an AI Proxy) run on these accounts. An OpenAI API key goes in as an environment variable; a ChatGPT login is written into each user's own Codex home.",
"cursor": "New Cursor CLI sessions run on these Cursor API keys, given to the session as CURSOR_API_KEY — it outranks the user's own `cursor-agent login` for that session only. Cursor reports no usage or quota here, so the account with the fewest live sessions is picked."
},
"notInstalled": "{{tool}} is not installed on this server yet.",
"emptyFor": "No {{tool}} accounts yet. Until you add one, each user's {{tool}} session needs that user's own sign-in.",
"kindShort": {
"token": "setup-token",
"login": "Login",
"apikey": "API key",
"chatgpt": "ChatGPT login"
},
"omp": {
"badgeOn": "shared",
"about": "Oh My Pi has no account pool: it shares this server's own omp login instead. Sign the host account in with `omp` (then /login), turn sharing on, and every user's omp session runs on it through omp's own gateway — with a key that works only while its pane runs.",
"off": "Sharing is off: each user's omp session uses that user's own login.",
"unavailable": "Oh My Pi has no account pool. Its login can be shared only on a server where every user has their own Linux account (server mode with per-user accounts); here each user's omp session uses that user's own login."
},
"none": {
"title": "{{tool}} has no account pool",
"reason": {
"antigravity": "Antigravity signs in with a Google account and keeps that login in the system keyring; it takes no key AgentHub could hand a session. Each user signs in to agy themselves.",
"default": "AgentHub cannot hand {{tool}} a shared login, so each user signs in to it themselves."
}
},
"liveCount_one": "{{count}} live",
"liveCount_other": "{{count}} live",
"savedProfiles": {
"title": "Saved profiles you can share",
"empty": "No saved profiles for this tool. Save one in Settings → AI tools → Profiles, then share it here.",
"activeHere": "in use on this server",
"share": "Share to pool",
"shareNamed": "Share {{name}} to the pool"
},
"groups": {
"title": "Pools",
"description": "Members in no pool, and accounts in no pool, make up the shared pool.",
"new": "New pool",
"empty": "No groups yet — every user shares every account.",
"members_one": "{{count}} member",
"members_other": "{{count}} members",
"edit": "Edit group {{name}}",
"delete": "Delete pool {{name}}",
"deleteHint": "Its members and accounts go back to the shared pool. No account is removed.",
"noAccounts": "none — members sign in themselves",
"saved": "Group {{name}} saved",
"deleted": "Pool {{name}} deleted",
"hint": "Tick the members and the accounts they run on. A user belongs to one group, so ticking one moves them here. An account can serve several groups.",
"name": "Pool name",
"memberList": "Members",
"inGroup": "now in {{group}}",
"accountList": "Accounts",
"noneInPool": "No accounts of this tool in the pool.",
"shared": "Shared pool"
},
"window": {
"5h": "5 hours",
"wk": "Week"
},
"resetsIn": "resets in {{time}} · {{at}}",
"members": {
"title": "Members",
"filter": "Show members",
"all": "All",
"noGroup": "No pool",
"search": "Search members",
"tickShown": "Tick all {{count}}",
"noneShown": "No member matches."
},
"board": {
"howTo": "Drag an account or a member between pools; drop one on New pool to split it off, or drag a pool's only account into another pool to merge them. Nothing is saved until Save.",
"sharedHint": "Members and accounts in no pool",
"toShared": "Send {{name}} back to the shared pool",
"removeAccount": "Send {{name}} back to the shared pool",
"moveHere_one": "Move {{count}} here",
"moveHere_other": "Move {{count}} here",
"noMembers": "No members",
"noSharedAccounts": "No account is left for the shared pool.",
"dropMembers": "Drop members here",
"untitled": "New pool",
"new": "not saved yet",
"pick": "Pick members of {{name}}",
"pickHint": "Filter by role or by \"No pool\", tick who belongs here. A member is in one pool, so ticking one moves them here. Applied on the board — Save sends it.",
"apply": "Apply",
"addAccount": "Move an account into {{name}}",
"addAccountOption": "+ Move an account or saved profile here…",
"profilesOf": "Saved {{tool}} profiles — moved into the pool on Save",
"profilePending": "A saved profile: it is moved into the pool when you save",
"pending_one": "{{count}} unsaved change",
"pending_other": "{{count}} unsaved changes",
"needsName": "Every pool needs a name.",
"discard": "Discard",
"saveTitle": "Save the pools",
"saveHint": "Type your own password once to apply every change below.",
"summaryProfile": "Move the saved profile {{name}} into the pool",
"summaryNew": "Create {{name}}: {{count}} member(s), {{accounts}} account(s)",
"summaryGroup": "Set {{name}}: {{count}} member(s), {{accounts}} account(s)",
"saved": "Pools saved",
"dropAccounts": "Drag accounts here",
"noAccounts": "No accounts",
"accountsCount_one": "{{count}} account",
"accountsCount_other": "{{count}} accounts",
"mergeHint": "Drop to merge {{from}} into {{into}}",
"merged": "{{from}} merged into {{into}} — Save to apply",
"summaryMerge": "Merge {{from}} into {{into}}: pool {{from}} is deleted",
"newDrop": "Drop here to split it into a pool of its own",
"newHint": "Or drag an account or a member here to split it off"
},
"addAccount": "Add account",
"otherTools": "Other AI tools"
},
"delete": {
"button": "Delete user",
"title": "Delete {{name}}?",
"body": "This deletes {{name}} for good: their login, {{count}} workspace(s), and their Linux account with the whole home folder (/srv/agenthub/users/{{user}}) — every file and all source code in it. It cannot be undone. Session history and the audit log stay.",
"confirm": "Delete for good",
"needsPassword": "Enter your own password below first."
},
"tmux": {
"title": "Members' tmux",
"description": "Each member's own tmux server keeps the version it started with until its last pane closes. Installed: tmux {{version}}.",
"refresh": "Refresh",
"notMatched": "Run `agenthub server install-system` once: until then an older member server is asked with the installed tmux, which can leave it stuck at 100% CPU.",
"empty": "No member has a tmux server running.",
"user": "User",
"version": "tmux",
"sessions": "Sessions",
"agents": "Agents",
"stuck": "not answering",
"outdated": "older than installed",
"since": "since {{at}}",
"working_one": "{{count}} working",
"working_other": "{{count}} working",
"idle": "idle",
"restart": "Restart {{name}}'s tmux",
"restartIdle_one": "Restart {{count}} idle member",
"restartIdle_other": "Restart {{count}} idle members",
"restartIdleTitle": "Restart idle members' tmux",
"restartHint": "Every pane of {{name}} closes ({{count}} sessions). Their Claude sessions resume from the workspace, and the next start runs the installed tmux.",
"restartHintBusy": "An agent of {{name}} is working right now and will be stopped. Every pane of theirs closes ({{count}} sessions); their Claude sessions resume from the workspace.",
"restartIdleHint": "Restarts the tmux of every member on an older version with no agent working: {{names}}. Their panes close; their Claude sessions resume from the workspace.",
"restartButton": "Restart",
"done": "{{name}}'s tmux restarted",
"doneIdle": "Restarted: {{names}}",
"errors": {
"ETMUXSTUCK": "The server did not stop. Run the `sudo kill -9` command shown, as root, on the server."
},
"sessionsCount_one": "{{count}} session",
"sessionsCount_other": "{{count}} sessions"
},
"rowMenu": {
"openHome": "Open home in Files",
"copyUsername": "Copy username",
"copyEmail": "Copy email"
},
"mustChange": "New password due",
"mustChangeHint": "Signed in with a password an admin gave; must choose their own before using AgentHub",
"passwordRules": {
"length": "At least {{min}} characters",
"lower": "A lower-case letter",
"upper": "An upper-case letter",
"digit": "A digit",
"symbol": "A symbol (e.g. ! - _ @)",
"username": "Does not contain the username"
},
"detail": {
"open": "Open {{name}}'s details",
"lastLogin": "Last sign-in",
"lastSession": "Last activity",
"never": "never",
"spendDaily": "Spend · last 30 days",
"noUsage": "No spend in the last 30 days.",
"pool": "Account pool & quota",
"poolPinned": "pinned to {{account}}",
"poolChosen": "chose {{account}}",
"poolGroup": "group {{name}}",
"poolShared": "shared pool",
"poolNone": "No account in this tool's pool.",
"poolNotAllowed": "no longer allowed",
"poolTotal7d": "Account total 7 days: {{cost}}",
"noPanes": "Nothing running.",
"recentTitle": "Recent sessions",
"failed": "Could not load this user",
"activeLabel": "Running now",
"activeValue": "{{sessions}} sessions · {{panes}} panes",
"nowTitle": "Running now",
"poolTheirs7d": "theirs 7 d {{cost}}"
},
"activity": "Activity",
"spend": "Spend today",
"spendSub": "7 d {{week}} · 30 d {{month}}",
"sessionsN": "{{n}} sessions",
"workspacesN": "{{n}} workspaces",
"tmuxCol": "tmux",
"quota": {
"title": "Spend quota",
"limit": "Limit in dollars",
"period": "Period",
"periods": {
"day": "per day (from local midnight)",
"5h": "per 5 hours (sliding)"
},
"per": {
"day": "{{limit}} / day",
"5h": "{{limit}} / 5 h"
},
"used": "{{spend}} of {{limit}}",
"defaultTag": "(default)",
"none": "No quota",
"blockedUntil": "Used up — no new session and no prompt until {{at}}",
"cardTitle": "Default spend quota",
"cardDescription": "Every user without a quota of their own gets this one. Once it is used up they can start no session and their prompts are refused until it resets; a pane no hook can stop (Antigravity, Cursor, Codex without trusted hooks, omp on its own login) is ended.",
"noDefault": "No default — no limit",
"editDefault": "Edit",
"defaultDialog": "Default spend quota",
"defaultOn": "Limit what each user may spend",
"defaultSaved": "Default spend quota saved",
"enforceHint": "Over the quota: no new session, Claude Code and Codex prompts refused, other AI tools' panes ended — until it resets.",
"modes": {
"defaultOf": "Default ({{quota}})",
"defaultNone": "Default (no limit)",
"unlimited": "No limit",
"custom": "Their own limit"
},
"now": "Now: {{spend}} of {{limit}}"
}
}