@yoooclaw/cli 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,24 +1,55 @@
1
1
  # @yoooclaw/cli
2
2
 
3
- yoooclaw 独立 CLI 工具 —— 自带后台守护进程(daemon),不依赖 openclaw 客户端在线。
4
- 设计对齐飞书 [lark-cli](https://github.com/larksuite/cli):Service-oriented 命令树、三层命令体系(Shortcuts / Service Commands / Raw API)、统一 `--format`、Agent-Native。
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![npm version](https://img.shields.io/npm/v/@yoooclaw/cli.svg)](https://www.npmjs.com/package/@yoooclaw/cli)
5
+ [![Node](https://img.shields.io/badge/node-%3E%3D22.12-blue.svg)](https://nodejs.org/)
5
6
 
6
- 完整文档见 [packages/docs/src/cli](../docs/src/cli)。
7
+ yoooclaw 独立 CLI 工具 —— **自带后台守护进程(daemon),不依赖 openclaw 客户端在线**,为人类与 AI Agent 而生。本地接收手机通知、Relay 隧道、录音 ASR、灯效规则评估,统一 `--format` 输出,随包发布 Agent Skill 开箱即用。
7
8
 
8
- > **状态:可用。** 配置、认证、daemon、通知、录音、图片、灯效规则、监控、Relay、日志、gateway 自检和 raw API 等 service 命令已落地;本地查询类可纯读磁盘,控制类通过本地 daemon 协作。
9
+ Service-oriented 命令树、三层命令体系、Agent-Native。
9
10
 
10
- ## 安装与命令
11
+ [安装](#安装与快速开始) · [Agent 技能](#agent-技能) · [鉴权](#鉴权) · [命令体系](#三层命令体系) · [进阶](#进阶用法) · [安全](#安全与风险提示) · [贡献](#开发与贡献)
12
+
13
+ ## 为什么用 yoooclaw?
14
+
15
+ - **自带守护进程** —— 本地 daemon 收通知、跑规则、连 Relay,不依赖 openclaw 客户端在线
16
+ - **Agent-Native** —— 随包 [Skill](skills/) 开箱即用,Agent 零额外配置直接调 `yoooclaw` 命令
17
+ - **三层命令体系** —— Shortcuts(人/AI 友好)→ Service Commands(结构化)→ Raw API(全覆盖),按粒度选择
18
+ - **纯读磁盘的查询** —— 通知 / 录音 / 图片查询直接读 `~/.yoooclaw`,不需要 daemon 在跑
19
+ - **统一输出契约** —— `--format json|pretty|table|ndjson`,成功失败同通道、结构可预测、非零退出码表达失败
20
+ - **凭据安全** —— OS keychain 优先存储,多 api-key 管理,gateway token 鉴权本地 ingest
21
+ - **零依赖二进制** —— 原生单文件(内嵌 Bun runtime)或 npm 渠道,macOS / Linux / Windows 全平台
22
+
23
+ ## 能力一览
24
+
25
+ | 领域 | 能力 | daemon |
26
+ | --------------------- | ------------------------------------------------------------ | ------ |
27
+ | 📱 通知 Notification | 按时间/应用/发送人/关键词查询,今日/最近摘要,多维聚合统计 | 🟢 |
28
+ | 🔄 同步 Sync | 扫描未处理通知、按日期取详情、提交批次,供记忆系统消费 | 🟢 |
29
+ | 🎙️ 录音 Recording | 列举与查询录音、ASR 转写配置(api / local)、状态事件流跟随 | 🟢 |
30
+ | 🖼️ 图片 Image | 列举与查询图片、本地路径 / 缩略图解析 | 🟢 |
31
+ | 💡 灯效 Light | 下发灯效指令到硬件(段 / 预设 / 规则三选一),连通性自检 | 🟡 |
32
+ | 📐 灯效规则 Lightrule | 「通知 → 灯效」持久规则的增删改查、启用 / 停用 | 🟡 |
33
+ | ⏰ 监控 Monitor | cron 驱动的定时通知监控任务 | 🟡 |
34
+ | 🔌 隧道 Tunnel | Relay 隧道状态、强制重连、本地 ingest 回环自检 | 🟡 |
35
+ | 🛡️ 网关 Gateway | 模拟手机端调 daemon,校验本地连通与鉴权 | 🟢/🟡 |
36
+ | 📋 日志 Log | daemon 日志检索与 error 级筛选 | 🟢 |
37
+ | ⚙️ 基础设施 | config / profile / auth / daemon / migrate / update / doctor | 🟢/🔵 |
38
+ | 🧩 技能 Skills | 把随包 SKILL.md 安装到 Agent 可发现目录 | 🟢 |
39
+
40
+ > daemon 标记:🟢 不需要 daemon · 🟡 需要 daemon 在跑 · 🔵 管理 daemon 自身。
41
+
42
+ ## 安装与快速开始
11
43
 
12
44
  两种分发渠道,二者**功能一致**,按需选择。
13
45
 
14
46
  **平台支持**:macOS / Linux 两种渠道都支持;**Windows 走 npm 渠道**(需 Node ≥ 22.12.0),原生二进制暂未提供 Windows 目标。Windows 上凭据以明文落 `~/.yoooclaw/credentials.json`(无系统 keychain 加固,`yoooclaw doctor` 会提示),daemon 停止经 HTTP 优雅退出。
15
47
 
16
- ### A. npm(需要 Node ≥ 22.12.0,Windows 用此渠道)
48
+ ### 渠道 A npm(需要 Node ≥ 22.12.0,Windows 用此渠道)
17
49
 
18
50
  ```bash
19
51
  # 免安装(每次拉最新版)
20
52
  npx @yoooclaw/cli --help
21
- npx @yoooclaw/cli notification +today
22
53
 
23
54
  # 全局安装(提供 yoooclaw / yc 两个命令)
24
55
  npm i -g @yoooclaw/cli
@@ -26,92 +57,233 @@ yoooclaw --help
26
57
  yc --help
27
58
  ```
28
59
 
29
- ### B. 原生二进制(无需 Node)
60
+ ### 渠道 B 原生二进制(无需 Node)
30
61
 
31
62
  单文件可执行(内嵌 Bun runtime),首次安装 ~60–90 MB,冷启动与 Node 相当。
32
63
 
33
64
  ```bash
34
65
  # 自动检测平台、下载、校验 sha256、写到 ~/.local/bin
35
- curl -fsSL https://raw.githubusercontent.com/Yoooclaw/openclaw-plugin/master/packages/cli/scripts/install.sh | sh
66
+ curl -fsSL https://raw.githubusercontent.com/YoooClaw/cli/master/scripts/install.sh | sh
36
67
 
37
68
  # 指定版本 / 安装目录 / 覆盖
38
- curl -fsSL https://raw.githubusercontent.com/Yoooclaw/openclaw-plugin/master/packages/cli/scripts/install.sh \
69
+ curl -fsSL https://raw.githubusercontent.com/YoooClaw/cli/master/scripts/install.sh \
39
70
  | sh -s -- --version 0.0.5 --dir ~/bin --force
40
71
  ```
41
72
 
42
- 支持平台:`darwin-arm64` / `darwin-x64` / `linux-x64` / `linux-arm64`。**Windows 暂无原生二进制,请用上面的 npm 渠道。**
43
-
44
- 二进制也可以从 [GitHub Releases](https://github.com/Yoooclaw/openclaw-plugin/releases?q=cli-v) 手动下载(同 release 内 `checksums.txt` 校验)。
73
+ 支持平台:`darwin-arm64` / `darwin-x64` / `linux-x64` / `linux-arm64`。也可从 [GitHub Releases](https://github.com/YoooClaw/cli/releases?q=cli-v) 手动下载(同 release 内 `checksums.txt` 校验)。
45
74
 
46
75
  > `yoooclaw update self` 会按当前安装来源给出对应升级命令(npm 走 `npm update -g`,二进制走 install.sh)。
47
76
 
48
- ## 命令体系
77
+ ### 快速开始(人类用户)
49
78
 
50
- - **Shortcuts**(`+` 前缀):`yoooclaw notification +today`、`yoooclaw light +blink` …
51
- - **Service commands**:`yoooclaw <service> <subcommand>`,service `yoooclaw --help`。
52
- - **Raw API**:`yoooclaw api <METHOD> <PATH> [--data ...]` 直达 daemon HTTP 端点。
79
+ ```bash
80
+ # 1. 交互式首次向导:生成 config + gateway token,并自动拉起 daemon
81
+ yoooclaw config init
53
82
 
54
- ### 全局 flags
83
+ # 2. 查鉴权与环境是否就绪
84
+ yoooclaw auth status
85
+ yoooclaw doctor
55
86
 
56
- | flag | 说明 |
57
- | ------------------ | ------------------------------------------------- |
58
- | `--profile <name>` | 切换 profile(默认 `default`) |
59
- | `--format <fmt>` | `json\|pretty\|table\|ndjson`(TTY 默认 pretty,管道默认 json) |
60
- | `--quiet` | 抑制进度日志,只输出最终结果 |
61
- | `--no-color` | 关闭终端颜色 |
87
+ # 3. 开始用:纯读磁盘查通知,不需要 daemon
88
+ yoooclaw notification +today
89
+ ```
62
90
 
63
- ### 输出契约
91
+ ### 快速开始(AI Agent)
64
92
 
65
- 成功与失败共用同一通道(stdout)与可预测结构;失败额外以非零退出码表达:
93
+ > 以下步骤面向 AI Agent,部分需用户在终端确认。查询类命令纯读磁盘即可,控制类命令需 daemon 在跑。
66
94
 
67
- ```json
68
- { "ok": false, "error": { "code": "YOOOCLAW_DAEMON_NOT_RUNNING", "message": "...", "hint": "..." } }
69
- ```
95
+ ```bash
96
+ # 1. stdin 注入配置完成初始化(避免交互),随后自动起 daemon
97
+ yoooclaw config init --non-interactive --from-file -
70
98
 
71
- 错误码统一前缀 `YOOOCLAW_*`(见 [src/errors.ts](src/errors.ts))。
99
+ # 2. 确认 daemon 在跑、隧道已连
100
+ yoooclaw daemon status
101
+ yoooclaw tunnel status
72
102
 
73
- ## 录音与 Relay
103
+ # 3. 流式查询通知(基于磁盘最新数据,勿依赖记忆)
104
+ yoooclaw notification summary --app 微信 --from 2026-06-01T00:00:00+08:00 --format json
74
105
 
75
- 独立 daemon 会复用 `@yoooclaw/phone-notifications` 的录音存储、状态机和 ASR 调度,并通过 `RelayClient + RelayDispatcher` 接收手机端 `recordings.sync` / `POST /recordings`。
106
+ # 4. 把随包 Skill 装到当前 Agent 的发现目录
107
+ yoooclaw skills install
108
+ ```
109
+
110
+ ## Agent 技能
111
+
112
+ > **推荐搭配 [YoooClaw/skills](https://github.com/YoooClaw/skills) 一起用。** 该仓库分发的 `yoooclaw-cli` Skill 把命令路由、`--format` 输出契约、daemon 依赖与错误处理打包成一份给 Agent 的「使用说明书」,让 Codex / Claude Code 等开箱即懂怎么调 `yoooclaw`。先装 CLI,再装 Skill:
76
113
 
77
114
  ```bash
78
- # api 模式:未写 --api-key 时回退 account 级 ock- key
79
- yoooclaw recording setup-asr --mode api --language auto --non-interactive
115
+ # 1. 安装 CLI(前置)
116
+ npm install -g @yoooclaw/cli
80
117
 
81
- # 查询和跟随录音状态事件
82
- yoooclaw recording list
83
- yoooclaw recording events --since 1h --limit 50
84
- yoooclaw recording events --id <recording-id> --watch
118
+ # 2. 安装 yoooclaw-cli Skill —— Codex + Claude Code
119
+ npx skills@latest add YoooClaw/skills --skill yoooclaw-cli --global --agent codex --agent claude-code --copy --yes
120
+
121
+ # 只装其中一个 agent:把 --agent 改成单个即可
122
+ npx skills@latest add YoooClaw/skills --skill yoooclaw-cli --global --agent claude-code --copy --yes
85
123
  ```
86
124
 
87
- 录音配置与事件分别落在当前 profile 的:
125
+ > Hermes Agent 用 `hermes skills install https://raw.githubusercontent.com/YoooClaw/skills/main/yoooclaw-cli/SKILL.md`。装好后重启 Agent 会话即可被发现。
88
126
 
89
- ```text
90
- ~/.yoooclaw/profiles/<profile>/recordings/asr-config.json
91
- ~/.yoooclaw/profiles/<profile>/recordings/state/events.jsonl
92
- ```
127
+ ### 本仓库随包内置 Skill
93
128
 
94
- ## Agent Skill
129
+ 随包发布 [skills/](skills/) 下的 SKILL.md,教 Agent 直接调 `yoooclaw` 命令。在 openclaw 插件里由 `openclaw.plugin.json` 自动注册;独立 CLI 形态下用 `yoooclaw skills install` 软链到 Agent 的 skills 发现目录。
95
130
 
96
- 随包发布 [skills/](skills/) 下的 SKILL.md(流式查通知、从 stdin 建灯效规则、隧道排查),教 Agent 直接调 `yoooclaw` 命令。在 openclaw 插件里这些 Skill 由 `openclaw.plugin.json` 自动注册;独立 CLI 形态下需手动安装到 Agent 的 skills 发现目录:
131
+ | Skill | 说明 |
132
+ | ----------------------------- | --------------------------------------------------------------------------------------------------------------- |
133
+ | `yoooclaw-notification-query` | 流式查询手机通知原始数据:「看看最近的通知」「谁找过我」「总结今天的消息」。纯读磁盘、不需要 daemon |
134
+ | `yoooclaw-lightrule-create` | 从自然语言或 stdin 创建/管理「通知 → 灯效」持久规则,由 daemon 在 ingest 后评估命中并触发灯效(🟡) |
135
+ | `yoooclaw-tunnel-debug` | 排查手机端推送链路:组合 auth / daemon / tunnel / gateway 状态定位本地配置、ingest 鉴权与 Relay WebSocket(🟡) |
97
136
 
98
137
  ```bash
99
138
  yoooclaw skills list # 列出随包发布的内置 Skill
100
139
  yoooclaw skills targets # 查看支持的 Agent 目标和探测结果
101
140
  yoooclaw skills install # 自动探测唯一 Agent 后软链安装
102
- yoooclaw skills install --agent codex
103
141
  yoooclaw skills install --agent claude
104
142
  yoooclaw skills install --copy # 复制而非软链(Windows 无管理员权限时用)
105
- yoooclaw skills install --target <dir> --force
106
143
  ```
107
144
 
108
- 默认软链而非复制:`yoooclaw update self` 升级 CLI 后,Skill 内容自动跟随新版本。裸 `skills install` 只会在检测到唯一 Agent 时自动安装;否则显式传 `--agent claude` / `--agent codex` 或 `--target <dir>`。安装后重启 Agent 会话即可被发现。
145
+ 默认软链而非复制:`yoooclaw update self` 升级 CLI Skill 内容自动跟随新版本。安装后重启 Agent 会话即可被发现。
146
+
147
+ ## 鉴权
148
+
149
+ yoooclaw 的鉴权围绕两类凭据:**api-key**(account 级,签名手机端上行 ingest)与 **gateway token**(本地 daemon HTTP 鉴权)。多数命令为本地检查(🟢),`auth check` 会端到端调 daemon(🟡)。
150
+
151
+ | 命令 | 说明 |
152
+ | ---------------------------------- | -------------------------------------------------------- |
153
+ | `auth set-api-key <key>` | 设置/轮换 account 级 default api-key(`-` 从 stdin 读) |
154
+ | `auth add-api-key <key>` | 新增一条 multi-key api-key,可带 `--label` / `--default` |
155
+ | `auth list-api-keys` | 列出 api-key 条目(key 自动遮罩) |
156
+ | `auth set-default-api-key <label>` | 切换 default api-key |
157
+ | `auth remove-api-key <label>` | 删除指定 label 的 api-key |
158
+ | `auth token-rotate` | 生成新 gateway token;daemon 在跑时随后 restart 生效 |
159
+ | `auth status` | 显示鉴权状态(本地检查,不调 daemon) |
160
+ | `auth check` | 端到端鉴权体检(调 daemon `/daemon/status`) |
161
+
162
+ ```bash
163
+ # 从 stdin 安全写入 default api-key,并存入 OS keychain
164
+ echo "ock-xxxx" | yoooclaw auth set-api-key - --keychain
165
+
166
+ # 多设备/多客户端:按 label 管理多条 api-key
167
+ yoooclaw auth add-api-key - --label phone-a --default
168
+ yoooclaw auth list-api-keys
169
+
170
+ # 轮换 gateway token 后让 daemon 生效
171
+ yoooclaw auth token-rotate
172
+ yoooclaw daemon restart
173
+ ```
174
+
175
+ ## 三层命令体系
176
+
177
+ 按粒度从快捷到完全自定义,覆盖日常操作到任意 daemon 端点:
178
+
179
+ ### 1. Shortcuts
180
+
181
+ 以 `+` 前缀,对人类与 AI 都友好,自带智能默认值与表格输出。
182
+
183
+ ```bash
184
+ yoooclaw notification +today # 今日通知摘要
185
+ yoooclaw notification +recent # 最近 1 小时通知
186
+ yoooclaw recording +latest # 最新一条录音详情
187
+ yoooclaw light +blink # 灯效连通性测试(red-strobe-3)
188
+ yoooclaw lightrule +on # 启用所有灯效规则
189
+ yoooclaw tunnel +test # daemon 本地 ingest + 鉴权自检
190
+ yoooclaw log +errors # 昨天起的 error 级日志
191
+ ```
192
+
193
+ 运行 `yoooclaw <service> --help` 查看某 service 的全部快捷命令。
194
+
195
+ ### 2. Service Commands
196
+
197
+ `yoooclaw <service> <subcommand> [...flags]`,结构化访问各领域能力,service 列表见 `yoooclaw --help`。
198
+
199
+ ```bash
200
+ yoooclaw notification search --app 微信 --keyword 会议 --limit 50
201
+ yoooclaw notification stats --dim app --from 2026-05-26
202
+ yoooclaw recording list --status synced
203
+ yoooclaw recording setup-asr --mode api --language auto --non-interactive
204
+ yoooclaw lightrule create --from-file - # 从 stdin 提交规则定义
205
+ yoooclaw monitor create daily-standup --schedule "0 9 * * 1-5" --match-rules @rules.json
206
+ ```
207
+
208
+ ### 3. Raw API
209
+
210
+ `yoooclaw api <METHOD> <PATH> [--data ...]` 直达 daemon HTTP 端点,覆盖未被 service 命令封装的部分。
211
+
212
+ ```bash
213
+ yoooclaw api GET /daemon/status
214
+ yoooclaw api POST /notifications --data @payload.json --header "x-client-label:phone-a"
215
+ echo '{"...":"..."}' | yoooclaw api POST /recordings --data -
216
+ ```
217
+
218
+ ## 进阶用法
219
+
220
+ ### 全局 flags
221
+
222
+ | flag | 说明 |
223
+ | ------------------ | --------------------------------------------------------------- |
224
+ | `--profile <name>` | 切换 profile(默认 `default`) |
225
+ | `--format <fmt>` | `json\|pretty\|table\|ndjson`(TTY 默认 pretty,管道默认 json) |
226
+ | `--quiet` | 抑制进度日志,只输出最终结果 |
227
+ | `--no-color` | 关闭终端颜色 |
109
228
 
110
- ## 数据目录
229
+ ### 输出格式
230
+
231
+ ```bash
232
+ --format json # 完整 JSON(管道默认)
233
+ --format pretty # 人类友好的格式化输出(TTY 默认)
234
+ --format table # 可读表格
235
+ --format ndjson # 行分隔 JSON,便于逐条管道处理
236
+ ```
237
+
238
+ ### 输出契约
239
+
240
+ 成功与失败共用同一通道(stdout)与可预测结构;失败额外以非零退出码表达:
241
+
242
+ ```json
243
+ {
244
+ "ok": false,
245
+ "error": {
246
+ "code": "YOOOCLAW_DAEMON_NOT_RUNNING",
247
+ "message": "...",
248
+ "hint": "..."
249
+ }
250
+ }
251
+ ```
252
+
253
+ 错误码统一前缀 `YOOOCLAW_*`(见 [src/errors.ts](src/errors.ts))。
254
+
255
+ ### 多 profile
256
+
257
+ `--profile <name>` 在不同账号/设备间切换,数据隔离在 `~/.yoooclaw/profiles/<profile>/`。
258
+
259
+ ```bash
260
+ yoooclaw profile list
261
+ yoooclaw profile create work
262
+ yoooclaw --profile work notification +today
263
+ ```
264
+
265
+ ### 录音与 Relay
266
+
267
+ 独立 daemon 复用 `@yoooclaw/phone-notifications` 的录音存储、状态机与 ASR 调度,并通过 `RelayClient + RelayDispatcher` 接收手机端 `recordings.sync` / `POST /recordings`。
268
+
269
+ ```bash
270
+ yoooclaw recording events --since 1h --limit 50
271
+ yoooclaw recording events --id <recording-id> --watch
272
+ ```
273
+
274
+ 录音配置与事件分别落在当前 profile 的 `recordings/asr-config.json` 与 `recordings/state/events.jsonl`。
275
+
276
+ ### 数据目录
111
277
 
112
278
  `~/.yoooclaw/`(可用 `YOOOCLAW_HOME` 覆盖,便于测试 / 多实例)。布局见 [src/paths.ts](src/paths.ts) 与 PRD「数据模型」。
113
279
 
114
- ## 开发
280
+ ## 安全与风险提示
281
+
282
+ 本工具会被 AI Agent 调用来自动化操作本地 daemon 与手机端链路,存在模型幻觉、不可预期执行与提示注入等固有风险。授权后 Agent 将在你的身份与授权范围内行事,可能导致敏感数据泄露或非预期操作,请谨慎使用。
283
+
284
+ 为降低风险,工具默认开启多层防护:daemon 仅监听本地端口、本地 ingest 经 gateway token 鉴权、凭据优先存入 OS keychain、终端输出对敏感字段遮罩。**强烈建议不要主动放宽这些默认安全设置**;一旦放宽,风险将显著上升,后果需自行承担。请充分理解全部使用风险——使用本工具即视为自愿承担相关责任。
285
+
286
+ ## 开发与贡献
115
287
 
116
288
  ```bash
117
289
  bun run build # bun 打包 dist/bin.cjs + dist/index.cjs,tsc 出类型
@@ -120,19 +292,24 @@ bun run test
120
292
  node dist/bin.cjs --help
121
293
  ```
122
294
 
123
- ## 源码结构
124
-
125
- | 文件 | 职责 |
126
- | ---------------------------------------- | ------------------------------------------ |
127
- | [src/bin.ts](src/bin.ts) | 可执行入口(package.json#bin) |
128
- | [src/index.ts](src/index.ts) | 程序化入口 `run(argv)` + 核心模块导出 |
129
- | [src/command-tree.ts](src/command-tree.ts) | 命令树声明(单一事实来源) |
130
- | [src/program.ts](src/program.ts) | 据命令树构建 commander 程序 + action 包装 |
131
- | [src/context.ts](src/context.ts) | 全局 flags → CliContext,profile 解析 |
132
- | [src/output/format.ts](src/output/format.ts) | `--format` 统一序列化 + 错误 schema |
133
- | [src/errors.ts](src/errors.ts) | `YOOOCLAW_*` 错误码 + `YoooclawError` |
134
- | [src/paths.ts](src/paths.ts) | `~/.yoooclaw/` 目录布局解析 |
295
+ 完整文档见 [packages/docs/src/cli](../docs/src/cli)。欢迎提 Issue / PR;较大改动建议先开 Issue 讨论。
296
+
297
+ ### 源码结构
298
+
299
+ | 文件 | 职责 |
300
+ | ---------------------------------------------------------------- | -------------------------------------------------------------------- |
301
+ | [src/bin.ts](src/bin.ts) | 可执行入口(package.json#bin) |
302
+ | [src/index.ts](src/index.ts) | 程序化入口 `run(argv)` + 核心模块导出 |
303
+ | [src/command-tree.ts](src/command-tree.ts) | 命令树声明(单一事实来源) |
304
+ | [src/program.ts](src/program.ts) | 据命令树构建 commander 程序 + action 包装 |
305
+ | [src/context.ts](src/context.ts) | 全局 flags CliContext,profile 解析 |
306
+ | [src/output/format.ts](src/output/format.ts) | `--format` 统一序列化 + 错误 schema |
307
+ | [src/errors.ts](src/errors.ts) | `YOOOCLAW_*` 错误码 + `YoooclawError` |
308
+ | [src/paths.ts](src/paths.ts) | `~/.yoooclaw/` 目录布局解析 |
135
309
  | [src/daemon/recording-bridge.ts](src/daemon/recording-bridge.ts) | daemon 形态的 `recordings.sync` 覆盖、ASR fallback 与 in-flight 去重 |
136
- | [src/daemon/recording-events.ts](src/daemon/recording-events.ts) | 录音状态事件 JSONL 追加日志 |
137
- | [src/daemon/relay-dispatcher.ts](src/daemon/relay-dispatcher.ts) | Relay 入站帧到 `StandaloneRuntime` 的进程内分发 |
138
- | [src/commands/skills.ts](src/commands/skills.ts) | 内置 Skill 列举 / 安装到 Agent skills 目录 |
310
+ | [src/daemon/relay-dispatcher.ts](src/daemon/relay-dispatcher.ts) | Relay 入站帧到 `StandaloneRuntime` 的进程内分发 |
311
+ | [src/commands/skills.ts](src/commands/skills.ts) | 内置 Skill 列举 / 安装到 Agent skills 目录 |
312
+
313
+ ## License
314
+
315
+ MIT —— 见 [LICENSE](LICENSE)。
package/dist/bin.cjs CHANGED
@@ -3756,9 +3756,10 @@ var COMMAND_TREE = [
3756
3756
  name: "stats",
3757
3757
  summary: "按维度聚合统计",
3758
3758
  options: [
3759
- { flags: "--from <date>", summary: "YYYY-MM-DD,默认 7 天前" },
3760
- { flags: "--to <date>", summary: "YYYY-MM-DD,默认今天" },
3759
+ { flags: "--from <time>", summary: "YYYY-MM-DD 或 ISO 8601,默认 7 天前" },
3760
+ { flags: "--to <time>", summary: "YYYY-MM-DD 或 ISO 8601,默认今天" },
3761
3761
  { flags: "--app <name>", summary: "仅统计指定应用" },
3762
+ { flags: "--sender <name>", summary: "仅统计指定发送人/标题" },
3762
3763
  { flags: "--client <label>", summary: "按 clientLabel 过滤;all 为全部" },
3763
3764
  { flags: "--dim <dim>", summary: "date|app|sender|hour|client|all", default: "all" }
3764
3765
  ]
@@ -4272,7 +4273,7 @@ function buildContext(flags) {
4272
4273
  var import_node_fs3 = require("node:fs");
4273
4274
  function readBuildInjectedVersion() {
4274
4275
  if (false) {}
4275
- const version = "0.1.5".trim();
4276
+ const version = "0.1.7".trim();
4276
4277
  return version || undefined;
4277
4278
  }
4278
4279
  function readVersionFromPackageJson() {
@@ -9546,14 +9547,14 @@ function quantizeWindow(value) {
9546
9547
  // src/vendor/light/sender.ts
9547
9548
  async function sendLightEffect(apiKey, segments, logger, repeatInput, reason, title) {
9548
9549
  const apiUrl = getEnvUrls().lightApiUrl;
9549
- const appKey = "7Q617S1G5WD274JI";
9550
+ const appKey = "phone-notifications";
9550
9551
  const templateId = "1990771146010017800";
9551
9552
  const resolvedTitle = resolveLightTitle(title, reason, segments);
9552
9553
  logger?.info(`Light sender: apiUrl=${apiUrl ?? "UNSET"}, appKey=${appKey ? appKey.substring(0, 8) + "…" : "UNSET"}, templateId=${templateId ?? "UNSET"}, apiKey=${apiKey ? apiKey.substring(0, 20) + "…" : "EMPTY"}, title=${resolvedTitle}, reason=${reason ?? ""}, segments=${JSON.stringify(segments)}`);
9553
- if (!apiUrl || !appKey || !templateId) {
9554
+ if (!apiUrl) {
9554
9555
  return {
9555
9556
  ok: false,
9556
- error: "灯效 API 未配置,请确认构建时已封装 OPENCLAW_HOST_* / LIGHT_APP_KEY / LIGHT_TEMPLATE_ID"
9557
+ error: "灯效 API 未配置,请确认构建时已封装 OPENCLAW_HOST_*"
9557
9558
  };
9558
9559
  }
9559
9560
  let bizContent;
@@ -13226,22 +13227,98 @@ async function notificationSummary(ctx, _args, opts) {
13226
13227
  };
13227
13228
  }
13228
13229
  var HOUR_KEY = (n) => String(new Date(n.timestamp).getHours()).padStart(2, "0");
13230
+ var DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
13231
+ var ISO_TIME_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(Z|[+-]\d{2}:\d{2})$/;
13232
+ function parseDateParts(value) {
13233
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
13234
+ if (!m)
13235
+ return null;
13236
+ const year = Number(m[1]);
13237
+ const month = Number(m[2]);
13238
+ const day = Number(m[3]);
13239
+ const d = new Date(year, month - 1, day);
13240
+ if (d.getFullYear() !== year || d.getMonth() !== month - 1 || d.getDate() !== day) {
13241
+ return null;
13242
+ }
13243
+ return { year, month, day };
13244
+ }
13245
+ function localDateTimestamp(parts, endOfDay) {
13246
+ return new Date(parts.year, parts.month - 1, parts.day, endOfDay ? 23 : 0, endOfDay ? 59 : 0, endOfDay ? 59 : 0, endOfDay ? 999 : 0).getTime();
13247
+ }
13248
+ function minDateKey(...keys) {
13249
+ return keys.sort()[0];
13250
+ }
13251
+ function maxDateKey(...keys) {
13252
+ return keys.sort().at(-1);
13253
+ }
13254
+ function parseStatsBoundary(value, optionName) {
13255
+ if (DATE_ONLY_RE.test(value)) {
13256
+ const parts = parseDateParts(value);
13257
+ if (!parts) {
13258
+ throw new YoooclawError("YOOOCLAW_INVALID_ARGUMENT", `${optionName} 必须是合法日期 YYYY-MM-DD`);
13259
+ }
13260
+ const startTs = localDateTimestamp(parts, false);
13261
+ const endTs = localDateTimestamp(parts, true);
13262
+ return {
13263
+ raw: value,
13264
+ exactTs: null,
13265
+ startTs,
13266
+ endTs,
13267
+ minDateKey: value,
13268
+ maxDateKey: value
13269
+ };
13270
+ }
13271
+ if (!ISO_TIME_RE.test(value)) {
13272
+ throw new YoooclawError("YOOOCLAW_INVALID_ARGUMENT", `${optionName} 必须是 YYYY-MM-DD 或 ISO 8601 时间,例如 2026-06-02 或 2026-06-02T09:00:00+08:00`);
13273
+ }
13274
+ const exactTs = Date.parse(value);
13275
+ if (Number.isNaN(exactTs)) {
13276
+ throw new YoooclawError("YOOOCLAW_INVALID_ARGUMENT", `${optionName} 不是合法时间`);
13277
+ }
13278
+ const declaredDateKey = value.slice(0, 10);
13279
+ const localDateKey = formatDate(new Date(exactTs));
13280
+ return {
13281
+ raw: value,
13282
+ exactTs,
13283
+ startTs: exactTs,
13284
+ endTs: exactTs,
13285
+ minDateKey: minDateKey(declaredDateKey, localDateKey),
13286
+ maxDateKey: maxDateKey(declaredDateKey, localDateKey)
13287
+ };
13288
+ }
13289
+ function buildStatsRange(fromRaw, toRaw) {
13290
+ const from = parseStatsBoundary(fromRaw ?? daysAgo(7), "--from");
13291
+ const to = parseStatsBoundary(toRaw ?? today(), "--to");
13292
+ if (from.startTs > to.endTs) {
13293
+ throw new YoooclawError("YOOOCLAW_INVALID_ARGUMENT", "--from 不能晚于 --to");
13294
+ }
13295
+ return {
13296
+ from: from.raw,
13297
+ to: to.raw,
13298
+ fromTs: from.exactTs,
13299
+ toTs: to.exactTs,
13300
+ fromDateKey: from.minDateKey,
13301
+ toDateKey: to.maxDateKey
13302
+ };
13303
+ }
13229
13304
  async function notificationStats(ctx, _args, opts) {
13230
- const from = opts.from ?? daysAgo(7);
13231
- const to = opts.to ?? today();
13305
+ const range = buildStatsRange(opts.from, opts.to);
13232
13306
  const dim = opts.dim ?? "all";
13233
13307
  const allowed = ["date", "app", "sender", "hour", "client", "all"];
13234
13308
  if (!allowed.includes(dim)) {
13235
13309
  throw new YoooclawError("YOOOCLAW_INVALID_ARGUMENT", `--dim 只能是 ${allowed.join("|")}`);
13236
13310
  }
13237
13311
  const options = {
13312
+ from: range.from,
13313
+ to: range.to,
13238
13314
  app: opts.app,
13315
+ sender: opts.sender,
13239
13316
  client: opts.client,
13240
13317
  limit: MAX_LIMIT,
13241
- fromTs: null,
13242
- toTs: null,
13243
- fromDateKey: from,
13244
- toDateKey: to
13318
+ fromTs: range.fromTs,
13319
+ toTs: range.toTs,
13320
+ fromDateKey: range.fromDateKey,
13321
+ toDateKey: range.toDateKey
13245
13322
  };
13246
13323
  const items = await queryNotifications(ctx.paths, options);
13247
13324
  const byDate = topCounts(items, (n) => formatDate(new Date(n.timestamp)), MAX_LIMIT);
@@ -13253,7 +13330,7 @@ async function notificationStats(ctx, _args, opts) {
13253
13330
  return {
13254
13331
  ok: true,
13255
13332
  total: items.length,
13256
- range: { from, to },
13333
+ range: { from: range.from, to: range.to },
13257
13334
  dim,
13258
13335
  ...dim === "all" ? dims : { [dim]: dims[dim] }
13259
13336
  };
@@ -14706,5 +14783,5 @@ async function run(argv = process.argv) {
14706
14783
  // src/bin.ts
14707
14784
  run(process.argv);
14708
14785
 
14709
- //# debugId=FB3319ED48E41BED64756E2164756E21
14786
+ //# debugId=C955DB0119FFC93B64756E2164756E21
14710
14787
  //# sourceMappingURL=bin.cjs.map