feat: v2.7.6 — edit users in one form, Settings → Account, display names, copy over plain HTTP

- Users page: one Edit user form (display name, email, role, active, a new or
  generated password, workspaces given or taken back, sign out everywhere),
  validated as a whole before anything is written (PATCH /api/users/:id,
  GET /api/users/:id); users.display_name/email added migration-safe.
- Settings → Account for every signed-in user: profile, own password (the
  current one required, this login kept), sign out other devices
  (/api/me/account, /api/me/password, /api/me/sign-out-others; login cookie
  only, never a pane token).
- Display names wherever a person is named (sidebar, workspace owner groups,
  Users page and audit log); owner_display_name / reader_display additive.
- Users page: admins add and remove Claude/Codex pool accounts.
- Folder browser: an admin with their own Linux account opens in their home;
  the "Server folder" tick opens the host's (never remembered).
- AI Proxy: "Config 9router" beside Add profile when 9router is installed,
  opened on the dashboard's own host (GET /api/ai-proxies/9router).
- Terminal: a tmux selection reaches the clipboard over plain HTTP too
  (writeClipboard's textarea fallback, focus handed back).
- Chat: a file the session touched outside the workspace previews
  (GET /api/sessions/:id/file).
- install-system: `!requiretty` only when visudo accepts it (sudo-rs on
  Ubuntu 25.10+ rejects the setting).
- openapi.yaml regenerated; docs (API, SERVER-MODE, LANES) updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 17:10:29 +07:00
parent 9c573f1e3c
commit 0c2f49876d
74 changed files with 5347 additions and 392 deletions
+388 -6
View File
@@ -3,7 +3,7 @@
openapi: 3.0.3
info:
title: Agent Dashboard for Claude Code API
version: 2.7.5
version: 2.7.6
description: HTTP API for real-time Claude Code session monitoring, agent lifecycle tracking, analytics, pricing, hooks ingestion, and workflow intelligence.
contact:
name: Nguyễn Ngọc Trí Vĩ
@@ -4803,6 +4803,37 @@ components:
- developer
disabled:
type: boolean
displayName:
type: string
nullable: true
email:
type: string
nullable: true
createdAt:
type: string
nullable: true
MyAccount:
type: object
required:
- mode
properties:
mode:
type: string
enum:
- local
- server
user:
$ref: '#/components/schemas/AuthUser'
workspaces:
type: integer
sessions:
type: integer
logins:
type: integer
description: Unexpired logins of the account
lastLoginAt:
type: string
nullable: true
AuthMe:
type: object
required:
@@ -11182,6 +11213,43 @@ paths:
description: EDIALOG — the dialog program failed.
'501':
description: ENODIALOG — no desktop session or no dialog program.
/api/lanes/browse/mkdir:
post:
tags:
- Lanes
summary: New folder, from the in-app folder browser
description: Makes ONE folder `name` (no `/`, `\`, NUL, `.` or `..`; ≤ 255 bytes) inside the existing folder `parent` — never `-p`, never over an existing entry. A non-admin makes one only inside their own folder, by real path (`EFOLDER`), and in server mode a non-admin with no folder of their own makes none. Under S3 a folder in a member's home is made as that member.
operationId: makeBrowseFolder
requestBody:
content:
application/json:
schema:
type: object
required:
- parent
- name
properties:
parent:
type: string
description: Absolute path of an existing folder.
name:
type: string
responses:
'201':
description: '`{path}` — the folder made.'
content:
application/json:
schema:
type: object
properties:
path:
type: string
'400':
description: EBADNAME, EBADPATH, or ENOTFOUND (no such parent folder).
'403':
description: EBADORIGIN, or EFOLDER (outside the caller's own folder).
'409':
description: EEXISTS (the name is taken) or EMEMBERGONE.
/api/lanes/ensure:
post:
tags:
@@ -13927,6 +13995,139 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/me/account:
get:
tags:
- Auth
operationId: getMyAccount
summary: 'Settings → Account: the signed-in account (local mode: {mode: "local"})'
responses:
'200':
description: Account
content:
application/json:
schema:
$ref: '#/components/schemas/MyAccount'
'401':
description: EAUTH
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
patch:
tags:
- Auth
operationId: updateMyAccount
summary: The account's own display name and email (""/null clears). Login cookie only, audited
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
displayName:
type: string
nullable: true
maxLength: 100
email:
type: string
nullable: true
maxLength: 254
responses:
'200':
description: Updated
content:
application/json:
schema:
$ref: '#/components/schemas/MyAccount'
'400':
description: EBADFIELD / EBADEMAIL
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: EAUTH — no login cookie (a pane token is refused)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: ELOCALMODE
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/me/password:
post:
tags:
- Auth
operationId: changeMyPassword
summary: 'Change the account''s own password: the current one required (throttled); this login stays, every other ends'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- currentPassword
- newPassword
properties:
currentPassword:
type: string
newPassword:
type: string
minLength: 1
responses:
'200':
description: 'Changed ({ok: true})'
'400':
description: EBADPASSWORD
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: EAUTH — no login cookie (a pane token is refused)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: EREAUTH (current password wrong) / ELOCALMODE
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: ETOOMANY — 10 wrong passwords in 15 minutes
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/me/sign-out-others:
post:
tags:
- Auth
operationId: signOutMyOtherLogins
summary: End every login of the account but this one; answers {ended}
responses:
'200':
description: Ended
'401':
description: EAUTH — no login cookie (a pane token is refused)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: ELOCALMODE
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/users:
get:
tags:
@@ -13992,7 +14193,7 @@ paths:
type: string
password:
type: string
minLength: 8
minLength: 1
role:
type: string
enum:
@@ -14037,11 +14238,37 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/users/{id}:
get:
tags:
- Auth
operationId: getServerUser
summary: One account with its logins and the workspaces it owns (admin/analyst)
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: Account
'403':
description: EADMIN — a developer
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: ENOUSER
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
patch:
tags:
- Auth
operationId: updateServerUser
summary: 'Admin: change a role and/or disable (ending every login) or enable. Never your own (ESELF)'
summary: 'Admin (Edit user): display name, email, role, disabled, a new password, workspaces given or taken back, sign out everywhere — every field validated before any is written. Never your own role or disable (ESELF)'
parameters:
- name: id
in: path
@@ -14057,6 +14284,14 @@ paths:
required:
- currentPassword
properties:
displayName:
type: string
nullable: true
maxLength: 100
email:
type: string
nullable: true
maxLength: 254
role:
type: string
enum:
@@ -14065,6 +14300,21 @@ paths:
- developer
disabled:
type: boolean
password:
type: string
minLength: 1
addWorkspaces:
type: array
items:
type: integer
removeWorkspaces:
type: array
items:
type: integer
signOut:
type: boolean
enum:
- true
currentPassword:
type: string
responses:
@@ -14078,7 +14328,7 @@ paths:
user:
$ref: '#/components/schemas/AuthUser'
'400':
description: EBADPASSWORD / EBADROLE / EBADFIELD
description: EBADPASSWORD / EBADROLE / EBADFIELD / EBADEMAIL
content:
application/json:
schema:
@@ -14096,7 +14346,7 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
'409':
description: ESELF — your own role or disable
description: ESELF / ENOTOWNED / EOWNERFOLDER
content:
application/json:
schema:
@@ -14131,7 +14381,7 @@ paths:
properties:
password:
type: string
minLength: 8
minLength: 1
currentPassword:
type: string
responses:
@@ -14282,6 +14532,12 @@ paths:
owner:
type: string
nullable: true
reader_display:
type: string
nullable: true
owner_display:
type: string
nullable: true
'403':
description: EADMIN — a developer
content:
@@ -14368,6 +14624,132 @@ paths:
nullable: true
full:
type: boolean
post:
tags:
- Auth
operationId: addPoolAccount
summary: 'Admin: add a Claude (saved login, pasted credentials or setup-token) or Codex (API key / auth.json text) account to the pool, with their own password again. The secret is never answered back'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- tool
- currentPassword
properties:
tool:
type: string
enum:
- claude
- codex
name:
type: string
description: Defaults to the profile's name
profile:
type: string
description: 'Claude: a saved switch-user profile, moved into the pool'
credentials:
type: string
description: 'Claude: the text of a .credentials.json'
token:
type: string
description: 'Claude: a setup-token; Codex: an API key or auth.json text'
currentPassword:
type: string
responses:
'201':
description: '`{account: {name, addedAt, …}}` — no token'
'400':
description: EBADTOOL, EBADNAME, EBADTOKEN
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: EREAUTH — wrong password; EADMIN — not an admin; ELOCALMODE
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'409':
description: EEXISTS — the name or account is taken; EACTIVE — the host's CLI uses that profile
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: ETOOMANY — too many wrong passwords
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'502':
description: EUPSTREAM — Anthropic could not be reached
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/accounts/profiles:
get:
tags:
- Auth
operationId: listPoolProfiles
summary: Saved switch-user profiles an admin may move into the Claude pool (no token)
responses:
'200':
description: '`{profiles: [{name, label, email, subscriptionType, active}]}`'
/api/accounts/{name}:
delete:
tags:
- Auth
operationId: removePoolAccount
summary: 'Admin: remove an account from the pool, with their own password again'
parameters:
- name: name
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- tool
- currentPassword
properties:
tool:
type: string
enum:
- claude
- codex
currentPassword:
type: string
responses:
'200':
description: '`{ok: true}`'
'403':
description: EREAUTH — wrong password; EADMIN — not an admin; ELOCALMODE
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: ENOACCOUNT
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: ETOOMANY — too many wrong passwords
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/auth/me:
get:
tags: