Skip to content

v1.5.0

Choose a tag to compare

@cn0xroot cn0xroot released this 16 Sep 14:50
· 80 commits to master since this release

新增

  • AI 审批台接管 Claude Code 原生确认框:新注册 PermissionRequest hook(install.py
    重跑会自动补上这一条,已有的两条不动)。以前审批台只镜像规则表里 confirm 的操作,
    Claude Code 自己要弹的 "Do you want to proceed?"(没命中任何规则的那些)在网页上完全
    看不到——PreToolUse 阶段根本不知道它接下来会不会弹。现在这类询问以 kind='permission'
    出现在审批台,按钮跟 confirm 一样(允许/拒绝/10、30 分钟/一直允许,"一直允许"按
    session + 工具名 记),网页或终端给了答案就通过 hookSpecificOutput.decision.behavior
    替用户答掉;没人答(90s 超时)或终端里敲回车,hook 静默退出、原生确认框照常弹出。
    审批历史新增 "已转回原生确认框" 状态。

  • macOS 系统层探针(网络部分):新增 cc_monitor/probe_darwin.py,bin/CC-Monitor-probe
    在 macOS 上自动切过去。用系统自带的 nettop -d -L 0 每 2 秒采样 claude 进程树(claude

    • 子进程,每次采样重新算)每条连接的远端 IP:port 和上传/下载字节增量,写进跟 Linux
      探针同一张 network_traffic 表和同一种 os_net 事件,网络流量页/世界地图/AI 轨迹在
      Mac 上不再永远是空的。不需要 root。没有 execve 观测(verify 的绕过检测仍是 Linux
      独有),域名只能靠反向 DNS 兜底。SIGTERM 时会把 nettop 子进程一起收掉。
      Web UI 上网络页的几条提示文案改成会告诉你"去跑 bin/CC-Monitor-probe",并提到走本地
      代理时远端全是 127.0.0.1、地图上不会有点这种情况。
  • install.sh 自动安装并接线 ccstatusline:新增第 1 步,检测系统上有没有
    ccstatusline(command -v ccstatusline),
    没有就 npm install -g ccstatusline;装好后如果 ~/.claude/settings.json 里还没有
    statusLine 配置,install.py 的 configure_statusline() 会自动写入一份(command: "ccstatusline",padding: 0,refreshInterval: 10)。两个条件都要满足才会真的写——
    没装就不接(接了也是空跑),已经有 statusLine(不管是不是 ccstatusline、不管什么参数)
    就绝不覆盖,尊重用户已有的定制。--skip-ccstatusline / CC_MONITOR_SKIP_CCSTATUSLINE=1
    能把安装和接线两件事一起跳过(内部会转成 install.py --skip-statusline 透传下去)。
    GeoIP 数据库下载顺延成第 5 步。

  • install.sh 第 5 步自动下载 GeoIP 数据库:DB-IP Lite(CC BY 4.0,约 60MB)下到
    ~/.cc-monitor/dbip-city.mmdb(尊重 CC_MONITOR_HOME)。已有任何 .mmdb 就跳过;
    先下到 .part 再改名、小于 1MB 视为失败并删掉,不会留半截文件让 geoip.js 静默打不开;
    失败只提示不中断。--skip-geoip / CC_MONITOR_SKIP_GEOIP=1 跳过,CC_MONITOR_GEOIP_URL
    换镜像。

  • system_package_install 规则覆盖 macOS 的包管理器:brew install/reinstall/uninstall/ remove/rm/upgrade/tap/untap/bundle(含 brew cask …)和 MacPorts 的 port [-flags] install/uninstall/upgrade/activate/deactivate/selfupdate(port installed、lsof -i :port
    这类不会误命中)。首页"软件安装统计"的"系统包管理器"卡片标签同步。顺手修了 pacman -Syu 不命中的问题(原正则要求 S/R 是最后一个字母)。已有用户的 ~/.cc-monitor/rules.json
    是首次运行时拷的副本,不会自动更新——想要新规则的话删掉它让它重新生成,或者手动改。

修复

  • 桌面版(Electron)在 macOS 上 AI 审批台完全没有提醒:页面里走的是浏览器 Notification
    API,Electron 渲染进程里 Notification.permission 永远是 "granted"、new Notification()
    也不报错,但底层 UNUserNotificationCenter 对未正式签名的 app(npm run electron 用的
    node_modules 里那个 Electron.app 只有 ad-hoc 签名)直接拒绝,实测主进程侧是 failed 事件
    UNErrorDomain error 1(NotificationsNotAllowed),渲染进程侧静默失败。改为主进程自己
    每 2 秒轮询 pending_approvals:新请求 → shell.beep() + Dock 图标跳动 + 角标数字(这
    三样不需要任何授权),能弹系统通知就一并弹(点击跳到审批台),弹失败只警告一次不再重试。
    页面按 UA 识别 Electron 后关掉浏览器那条路,按钮改成说明文字,避免两边重复。
  • Web 终端"新建会话"直接 500(posix_spawnp failed.):node-pty 靠
    prebuilds/<平台>/spawn-helper 这个小程序起 pty,npm 解包时(实测 macOS + npm 11)
    会丢掉它的可执行位。新增 webui/scripts/fix-node-pty-perms.js,作为 postinstall
    跑一次,lib/sessions.js 加载时再幂等地跑一次(打包后没有 postinstall)。
  • macOS 上额度/套餐永远显示"没找到 Claude Code 的登录凭证":Claude Code 在 macOS 上
    不写 ~/.claude/.credentials.json,OAuth 凭证存在登录钥匙串里(service
    Claude Code-credentials,内容是同一份 JSON)。全新的 Mac 必现。新增
    webui/lib/credentials.js:先读文件,没有再 security find-generic-password -s "Claude Code-credentials" -w 读钥匙串,usage.js/account.js 共用。跟有没有装
    ccstatusline 无关——我们不调用它,只是复刻它的读取逻辑(它在 macOS 上走的正是钥匙串)。
  • macOS 上终端里永远看不到 CC-Monitor 的 [y/N] 确认提示:notify.py 用文本模式
    open("/dev/tty", "r+"),这会套一层要求可 seek 的 BufferedRandom;Linux 上对 tty
    lseek 返回 0 所以没事,macOS 返回 ESPIPE,open() 直接抛 io.UnsupportedOperation
    (OSError 子类)被静默吞掉,tty 一直是 None,只剩网页一条路。改成 "r+b", buffering=0
    的裸 FileIO。
  • 终端处于 raw 模式时敲 y/N 可能把 hook 卡死:Claude Code 的 TUI 让终端处于 raw 模式,
    回车发过来的是 \r 不是 \n,原来的 readline() 会一直等那个不来的换行,连网页那条
    路的轮询也一起卡住。改为直接读当前可读的字节、看首字符。

English

Added

  • AI Approvals now covers Claude Code's native permission dialog: a new
    PermissionRequest hook is registered (re-running install.py adds it; the existing two
    entries are left alone). Previously the approvals page only mirrored operations our rule
    table flags as confirm; Claude Code's own "Do you want to proceed?" prompts (calls that
    matched no rule) never showed up — at PreToolUse time there is no way to know whether a
    prompt is coming. These now appear as kind='permission' with the same buttons as confirm
    (allow / deny / 10 or 30 min / always — "always" is remembered per session + tool name).
    An answer from the web page or the terminal is returned through
    hookSpecificOutput.decision.behavior; if nobody answers (90s timeout) or Enter is
    pressed at the terminal, the hook exits silently and the native dialog appears as usual.
    Approval history gains a "Handed back to native dialog" status.

  • macOS system-layer probe (network part): new cc_monitor/probe_darwin.py;
    bin/CC-Monitor-probe switches to it automatically on macOS. It samples the claude process
    tree (claude + descendants, recomputed each sample) every 2s with the built-in
    nettop -d -L 0, recording each connection's remote IP:port and upload/download byte
    deltas into the same network_traffic table and os_net events the Linux probe uses, so
    the traffic page / world map / AI trajectory are no longer permanently empty on a Mac. No
    root required. No execve observation (the bypass detection behind verify stays
    Linux-only); hostnames fall back to reverse DNS. SIGTERM also tears down the nettop child.
    The network page hints now tell you to run bin/CC-Monitor-probe, and mention that with a
    local proxy every remote is 127.0.0.1 so the map has nothing to plot.

  • install.sh now installs and wires up ccstatusline: new step 1 checks whether
    ccstatusline is already on PATH
    (command -v ccstatusline) and runs npm install -g ccstatusline if not; once it's
    available, install.py's configure_statusline() writes a statusLine entry into
    ~/.claude/settings.json if one isn't already there (command: "ccstatusline",
    padding: 0, refreshInterval: 10). Both conditions have to hold before anything is
    written — not installed means nothing to wire up, and an existing statusLine (whether
    it's ccstatusline or something else, with any settings) is never overwritten, respecting
    whatever the user already configured. --skip-ccstatusline /
    CC_MONITOR_SKIP_CCSTATUSLINE=1 skips both the install and the wiring (internally passed
    through as install.py --skip-statusline). The GeoIP download step is renumbered to 5.

  • install.sh step 5 downloads the GeoIP database: DB-IP Lite (CC BY 4.0, ~60MB) to
    ~/.cc-monitor/dbip-city.mmdb (honours CC_MONITOR_HOME). Skipped if any .mmdb is
    already present; downloads to a .part file first and treats anything under 1MB as a
    failure (deleted), so a truncated file can never make geoip.js fail silently; a failed
    download warns without aborting. --skip-geoip / CC_MONITOR_SKIP_GEOIP=1 skips it,
    CC_MONITOR_GEOIP_URL points at a mirror.

  • system_package_install rule now covers macOS package managers: brew install/reinstall/uninstall/remove/rm/upgrade/tap/untap/bundle (incl. brew cask …) and
    MacPorts port [-flags] install/uninstall/upgrade/activate/deactivate/selfupdate (things
    like port installed or lsof -i :port don't match). The "system package manager" card on
    the home page is relabelled accordingly. Also fixed pacman -Syu not matching (the old
    regex required S/R to be the last flag letter). Note that an existing
    ~/.cc-monitor/rules.json is a copy made on first run and is not updated automatically —
    delete it to regenerate, or edit it by hand.

Fixed

  • Desktop app (Electron) on macOS gave no alert at all for AI Approvals: the page used
    the browser Notification API; in an Electron renderer Notification.permission is always
    "granted" and new Notification() doesn't throw, but the underlying
    UNUserNotificationCenter refuses apps that aren't properly signed (npm run electron runs
    the ad-hoc-signed Electron.app from node_modules) — the main-process side gets a failed
    event with UNErrorDomain error 1 (NotificationsNotAllowed), the renderer side fails
    silently. The main process now polls pending_approvals every 2s itself: a new request →
    shell.beep() + Dock bounce + badge count (none of which need permission), plus a system
    notification when possible (click → approvals tab); a failed notification warns once and
    isn't retried. The page detects Electron via UA, disables the browser path and turns the
    button into an explanatory label so the two never double-notify.
  • Web terminal "new session" returned 500 (posix_spawnp failed.): node-pty spawns
    the pty through its bundled prebuilds/<platform>/spawn-helper binary, and npm dropped
    its executable bit when unpacking (seen on macOS + npm 11). New
    webui/scripts/fix-node-pty-perms.js runs as postinstall and again, idempotently, when
    lib/sessions.js loads (packaged builds have no postinstall step).
  • macOS always showed "Claude Code credentials not found" for usage/plan: on macOS
    Claude Code does not write ~/.claude/.credentials.json; the OAuth credential lives in
    the login Keychain (service Claude Code-credentials, same JSON payload). Every fresh Mac
    hit this. New webui/lib/credentials.js reads the file first and falls back to
    security find-generic-password -s "Claude Code-credentials" -w; shared by usage.js
    and account.js. Unrelated to whether ccstatusline is installed — we never invoke it, we
    only replicate its lookup (which uses the Keychain on macOS too).
  • macOS never showed CC-Monitor's [y/N] prompt in the terminal: notify.py opened
    /dev/tty in text mode "r+", which wraps a BufferedRandom that requires a seekable
    stream; lseek on a tty returns 0 on Linux but ESPIPE on macOS, so open() raised
    io.UnsupportedOperation (an OSError subclass), was silently swallowed, and the tty
    path was permanently disabled — only the web path worked. Now opens as a raw FileIO
    ("r+b", buffering=0).
  • Typing y/N while the terminal is in raw mode could hang the hook: Claude Code's TUI
    keeps the terminal in raw mode, so Enter arrives as \r rather than \n; the old
    readline() waited forever for a newline and stalled the web-side polling too. Now reads
    whatever bytes are available and inspects the first character.

完整更新日志见 CHANGELOG.md (English)。