本页目录
  1. 界面布局
  2. 按键
  3. 表单
  4. grant / revoke
  5. cancel / clean / destroy 确认
  6. 预览、暂停与授权撤回
  7. 原生 attach
  8. 品牌、颜色与窄窗口
  9. 错误与终端恢复
  10. 身份与数据边界

交互管理界面

kuruwa cli 是本地人工管理界面,用 Ratatui + Crossterm 在真实 TTY 中查看和操作实例、终端与授权。它不是交互式 shell,不解释用户输入的命令,也不集成任何 agent 框架;键盘输入只用于界面操作,绝不会转发给终端。

kuruwa cli
kuruwa --socket /path/to/admin.sock cli

入口解析与普通客户端完全一致:--socket > KURUWA_SOCKET > <KURUWA_ROOT>/admin.sock(KURUWA_ROOT 未设置时为 $PWD/.kuruwa)。界面不会创建项目根、不会读取本地配置冒充授权,也不会自动启动 serve 或 RMUX。退出界面后终端仍可继续使用。

要求 stdin 与 stdout 都是交互式终端。非 TTY 直接报错;--json cli 明确拒绝,因为交互界面不是 JSON 管道的一部分,也不会向 stdout 混入 JSON。

界面布局

kuruwa  identity=ADMIN 或 TENANT <name>  project=<目录名>
root=<项目根>
┌──────────────────────────────┬──────────────────────────────────────────────┐
│ Instances                    │ Preview: <terminal> [PAUSED]                 │
│ > 实例名 [定义]              │ terminal: <完整终端名>                       │
│                              │ instance: <完整实例名>                       │
│ Terminals                    │ state: <状态> | 80x24                        │
│ > 终端名 实例/状态           │ 当前终端的纯文本视口                         │
├──────────────────────────────┴──────────────────────────────────────────────┤
│ 最近的消息 / 错误 / busy 状态                                               │
│ ? Help | q Quit | Tab focus | j/k move | Space pause                        │
│ i instance | n terminal | g/r grant/revoke | c/x/d lifecycle | Enter attach │
└─────────────────────────────────────────────────────────────────────────────┘
  • 顶栏分两行:第一行以紧凑 kuruwa 字标开头,然后显示 identity 和 project;第二行显示 root。project、root 来自本地解析路径;identity 只来自服务器根据接受连接的入口返回的上下文,不能由请求正文伪造。管理入口显示 ADMIN,租户入口显示 TENANT <name>。窄屏下即使 project 或 root 被截断,身份仍然优先可见。
  • 左栏上半是可控实例,下半是可访问终端。列表窄时名称/状态可能被截断,因此右栏详情固定三行:terminal: 完整终端名、instance: 完整实例名、state: 状态与尺寸;长 instance 名不会把 state 挤出。至少选中对象的名称与状态可直接读取。列表总是滚动到当前选择,选中项不会被窗口底部裁掉。Tab / Shift-Tab 切换焦点,Up/Down 或 j/k 在焦点列表内移动。
  • 右栏只显示服务器返回的纯文本视口,不直接回放 ANSI 控制序列,不调整后端终端尺寸。渲染时先裁掉末尾全空行,再显示最后可见内容;若确实没有可见输出,会明确显示“no visible output yet”等空状态,而不是无提示的空白。
  • 日常界面底栏固定三行:消息行,以及两行分组快捷键。第一组始终以 ? Help | q Quit 开头,因此在 80x24 和更窄窗口中仍能发现帮助与退出;第二组包含创建、授权和生命周期键。请求进行中带 [busy]。任何错误都不会关闭界面,可以继续操作。空状态欢迎页在初始连接成功后显示 connected; ready 并保留 i create instance | ? Help | q Quit 提示。

按键

按键 行为
Tab / Shift-Tab 在 Instances 与 Terminals 之间切换焦点;在表单中前后移动字段。
Up / Down / j / k 在焦点列表内移动;在表单的定义/实例选择字段中改变选择。
Space 暂停/恢复当前选中终端的内容刷新。暂停只停止预览内容轮询;授权检查继续运行。恢复后立即重新获取当前视口。
i 创建实例:选择本入口获准的 definition,再输入实例名。
n 创建终端:选择可控实例,输入终端名、argv JSON 和可选 cwd。
g 对选中终端打开 grant 租户选择框(仅管理入口)。
r 对选中终端打开 revoke 租户选择框(仅管理入口)。
c 取消选中终端,先弹出确认框。仅终端 owner 或管理入口可用。
x 清理选中终端,先弹出确认框。仅终端 owner 或管理入口可用。
d 销毁选中实例,先弹出确认框。默认非 force。
Enter 对选中终端进入原生 attach(管理入口或目标实例 owner)。
? 打开帮助。帮助中 ? 或 Esc 关闭,q 退出,Tab/方向键在 Keys 与 About 页间切换。
q 退出界面。表单/确认框/租户选择打开时,q 是普通文字或不会退出;只有主界面、帮助和文本字段之外才作为退出键。

Ctrl-C 在主界面也可退出,用于终端习惯;普通表单中请用 Esc 取消。

表单

  • i 的字段:definition 选择、实例名。definition 列表只包含当前入口允许创建的定义(管理入口为全部配置定义,租户为配置中该租户获准的定义)。Up/Down 只在 definition 字段改变定义,进入名称字段后不会误改定义。
  • n 的字段:可控实例选择、终端名、argv、cwd(可选)。cwd 留空表示使用实例后端的工作目录。打开表单时会快照当时的实例名称与定义;外部新建或删除实例不会把表单改到另一个目标。目标被删除时仍按表单显示的名称提交,由服务器返回 not_found,不会静默换实例。
  • argv 必须是 JSON 字符串数组,例如 ["bash"] 或 ["sh","-c","printf hello"]。界面不会把普通字符串按 shell 拼接或 eval;要 shell 语义必须显式写 "sh"/"bash" 和 -c。
  • Tab / Shift-Tab 移动字段,Ctrl-U 清空当前文本字段,Backspace 删除末尾字符;只有尾部编辑,没有光标,因此 Delete 在尾部不改变文本。
  • 多选项铺满弹窗时只滚动选项区,名称/argv/cwd、校验错误和 Enter/Esc 提示始终保留;Tab 不会把焦点移入不可见的输入字段。
  • 在最后一个字段按 Enter 提交;在中间字段按 Enter 前进到下一字段。Esc 取消整个表单。
  • 名称、definition、实例选择或 argv 校验失败时表单保持打开并显示错误,可直接修改后重试。
  • 创建实例/终端是异步请求;提交后表单保持打开并标记 Submitting...,期间忽略重复提交,Esc 可以离开。失败时在同一表单恢复提交时的草稿和服务端错误,成功后才关闭;若用户已离开或打开了新弹窗,迟到的结果不会覆盖新弹窗。

grant / revoke

g/r 仅管理入口可用。选择框列出 tenant list 的配置租户;Up/Down 或 j/k 选择,Enter 应用,Esc 取消。目标始终是按下 g/r 时的终端,同时打开选择框时也会快照租户名称;之后外部切换选中终端、撤销授权或增删租户都不会改变已打开选择框的目标。终端被外部清理时提交会得到服务器 not_found,不会 grant/revoke 到另一个终端。首次 g/r 时若租户列表尚未加载,界面显示加载状态并在完成后自动打开选择框;等待期间 Esc 或发起其他操作会取消待打开的选择框,迟到的租户响应不会覆盖新弹窗。确实为零租户时显示 no configured tenants,不会要求再次按键或循环加载。租户列表仍只通过管理专用请求获取;租户入口不能列出或授予其他租户。

cancel / clean / destroy 确认

c、x、d 都先打开确认框:

  • Enter 确认当前操作,Esc 取消。取消确认不产生任何后端副作用。
  • c(cancel)发送 C-c,宽限后必要时终止终端;终端记录保留。
  • x(clean)删除终端记录及 session,相关 grant 失效。
  • d(destroy)默认 非 force;若实例仍有任何终端记录,服务端返回冲突且不销毁任何东西。
  • 销毁确认框中按 f 显式切换 force。force: YES - active terminals are terminated and remain as orphaned records 明确写出影响;force 还会结束实例 daemon、调用一次 cleanup,并把销毁时仍运行的终端标为 orphaned。再按 f 可切回非 force。

确认框会显示目标名称和后果。状态变化以服务端结果为准;界面禁用生命周期键只是提示,真正授权仍由每个 handler 检查。

预览、暂停与授权撤回

  • 选中终端后,界面约每 400 ms 请求一次该终端的 screen 视口;实例与终端列表约每 1 s 刷新;身份/权限上下文约每 500 ms 刷新。这些都是独立 Tokio 任务,慢请求不会阻塞键盘、退出或重绘。实例和终端列表作为一批请求,两者都结束后才启动下一批,避免迟到的旧结果覆盖新列表。
  • 后端 screen 是完整可见视口,末尾通常带有大量空行。界面按行渲染时会先去掉末尾空白行,再从剩余内容中取最后可显示的部分;这不会修改后端内容、不 resize pane,也不伪造输出。窄的 80x24 终端仍能看到顶部 shell 输出和新的 literal printf 结果。
  • 列表请求失败时不会被当成“对象消失”或成功的空列表:Instances/Terminals 保留上次成功读取的对象名称供定位,并在标题标记 [stale],底栏显示读取错误;只有请求成功且结果确实为空时才显示空列表。终端列表失败会立即清空预览并暂停预览请求,因为对象可见性暂时无法确认;已有名称本身仍可用于定位,所有操作仍由服务端重新授权。列表恢复成功后清除 [stale] 并重新获取预览。
  • 上下文请求失败时身份与授权状态未知:界面清除身份字段对应的旧授权上下文、清空敏感预览、关闭基于旧授权的确认/授权弹窗,并停止预览刷新;恢复成功后才重新启用相应动作。
  • 切换选中终端时立即清空旧画面,再获取新终端内容;不会保留上一终端的画面。
  • Space 暂停后不再发起新的预览内容请求,但身份/权限上下文仍刷新。若 grant 被撤回、终端被清理或服务器返回 not_found/denied,旧预览会立即清空,暂停状态下也一样。
  • 预览请求不改变后端尺寸、不发送按键、不建立流式订阅。它读取的 screen 是当前视口纯文本;不是持续输出记录,也不替代 transcript。
  • 终端 grant 提供只读能力时,Instances 仍为空(租户只看到自己创建的实例),Terminals 只显示被授予的终端;c/x/d 等生命周期键会提示需要 owner/管理权限,最终仍由服务端拒绝。

原生 attach

主界面选中一个 running 终端后按 Enter:

  1. 界面异步向当前入口请求 attach 所需的真实 endpoint/session/rmux 路径。

  2. 取得结果后退出 alternate screen、关闭 raw mode、显示光标并停止界面输入线程。

  3. 直接 spawn 原生客户端(不是 shell 字符串):

    rmux -N -S <endpoint> attach-session -t <session>
  4. 原生 attach 退出(默认 C-b d detach)后重新初始化 ratatui、恢复 raw/alternate screen、重启输入线程并刷新上下文、列表和预览。

仅终端 grant 的租户入口不能使用 attach;目标实例 owner 可以。界面在发送请求前按可控终端提示,服务器 handler 仍会再次拒绝 grant-only 入口。原 CLI 的 kuruwa attach NAME 仍取得 endpoint 后 exec 原生客户端。

品牌、颜色与窄窗口

  • 尚无实例和终端时,界面显示空状态欢迎页:现有 32 列小号 logo、i create instance、? Help 与 q Quit。日常界面只保留紧凑 kuruwa 字标、身份、项目和项目根,不重复大 logo。
  • 品牌红、橙、黄只用于 logo、短装饰符和边框;关键正文、root、底栏快捷键、身份、确认对象与后果一律使用终端默认前景。身份、当前选择和确认提示用粗体或反显强调,暗色和浅色终端都沿用默认前景;不再用浅灰或 DIM 作为可读文字。状态始终有文字(如 running、exited、cancelled、orphaned、unknown),不依赖颜色表达。unknown 表示创建或回滚无法确认但管理映射仍需保留,界面不会把它显示成 running,也不能据此进入原生 attach。
  • 管理身份 ADMIN 在连接建立后始终显示在第一行;租户入口显示 TENANT <name>。两者都来自接受连接的入口,不能由请求正文覆盖。
  • NO_COLOR 只要存在就关闭 RGB/ANSI 颜色(空值也算)。单色资产继续保留可辨认的 logo 形状;粗体和反显仍可用于焦点与选中行,但不会发出颜色 SGR。
  • 帮助页先为快捷键保留空间,再按剩余宽高选择中号 logo、小号 logo 或文字字标。Keys 页列出操作和表单规则,About 页说明身份、预览和颜色边界;Tab 或左右键切换。80x24 使用文字字标以保证快捷键完整可见;更高的终端才会显示 logo。
  • 小于约 60x18 时界面显示明确的放大提示和当前尺寸,不再绘制主布局;? 仍可打开精简帮助,q 仍可退出,不 panic。帮助、表单和确认框在极小窗口也不会越过缓冲区。
  • 资产中的 ANSI 只被解析为 Ratatui styled spans,不会把资产原字节写入 alternate screen。解析器只接受资产实际使用的 truecolor/256 色、reset、反显和默认前景/背景等 SGR;其它 CSI/OSC 或控制字节被丢弃,未知视觉 SGR 报错。CLI 的终端 banner 只从这些 span 受控重编码。
  • 确认框把具体对象和后果写在正文中;确认标题和操作提示用默认前景加粗,对象问句与 force: YES 的后果用默认前景反显强调。品牌色不作为必读文字的唯一前景;拒绝确认仍不产生后端副作用。

错误与终端恢复

  • 启动时先通过入口 socket 做一次有界的 ui_context 读取;失败直接报错并 exit 1,不进入 raw mode/alternate screen,不自动启动 daemon,也不回退到其他 socket。成功时立即使用该上下文显示身份和 connected; ready,不会长期停留在 connecting。
  • 运行中请求失败在底栏显示可读消息,界面继续可用;上下文重连成功后只清除连接类消息(connecting... / context:),不会覆盖创建、授权等真正的操作错误。列表请求失败会保留上次对象名称并标为 [stale],不会渲染成成功空列表;终端列表过期时预览内容立即清空,恢复后才重新读取。上下文失败会撤销旧授权上下文并清空预览,不会用旧缓存继续授权或展示内容。
  • 输入线程的 poll/read 失败不会静默退出后留下一个只能刷新、不能接收键盘的界面。界面通过现有消息通道收到输入故障,结束事件循环、恢复终端,再由普通客户端错误路径在 stderr 输出安全的 I/O 错误并以 1 退出;不会在 alternate screen 中打印日志。正常 q 退出仍是 0,测试可区分两者。
  • 现有刷新 tick 同时检查输入线程是否已结束;panic 或无故退出也会结束界面并报告错误,即使主线程仍持有输入通道 sender。
  • 正常退出、attach 前后、绘制错误和 panic 都恢复 raw mode、alternate screen 与光标。Ratatui 初始化函数安装的 panic hook 负责 panic 路径。
  • 退出码:正常 q 为 0;入口连接、终端初始化/恢复或输入线程 I/O 失败为 1。--json cli 与非 TTY 也是 1。

身份与数据边界

界面使用一个只读的 ui_context 请求,服务器从接受连接的 Principal 返回:

  • 是否管理主体及租户名;
  • 该主体可创建的 definition 名(管理为全部配置定义,租户仅其获准定义);
  • 该主体当前可访问的终端名;
  • 其中可执行生命周期操作(cancel/clean/destroy)的终端名。owner 即使实例已失效、终端已 orphaned,仍保留该能力;grant 只允许读取/输入,不包含生命周期操作。

该请求不接受任何调用者提供的身份、租户、定义、endpoint、session 或权限字段。ui_context 只读内存状态,不探测后端,因此可以频繁刷新。实例列表仍使用既有 instance list:管理入口为全部可控实例,租户仅自己创建的实例;终端列表仍使用既有 ls。所有集合都按同一授权关系过滤,界面不会显示他人对象、endpoint、session 或配置命令。