nonoka-cli 0.2.3__tar.gz → 0.2.5__tar.gz
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.
- nonoka_cli-0.2.5/.agents/skills/nonoka-tui-testing/SKILL.md +413 -0
- nonoka_cli-0.2.5/.agents/skills/nonoka-tui-testing/references/checklist.md +48 -0
- nonoka_cli-0.2.5/.agents/skills/nonoka-tui-testing/scripts/analyze-timeline.py +100 -0
- nonoka_cli-0.2.5/.agents/skills/nonoka-tui-testing/scripts/capture-screenshots.py +191 -0
- nonoka_cli-0.2.5/.agents/skills/nonoka-tui-testing/scripts/setup-sandbox.sh +237 -0
- nonoka_cli-0.2.5/.agents/skills/tui-testing/SKILL.md +60 -0
- nonoka_cli-0.2.5/.agents/skills/tui-testing/scripts/opencode_tui.py +288 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/.gitignore +5 -0
- nonoka_cli-0.2.5/.nonoka/plugin.json.example +37 -0
- nonoka_cli-0.2.5/.nonoka/repo_map.jsonl +85 -0
- nonoka_cli-0.2.5/PKG-INFO +559 -0
- nonoka_cli-0.2.5/README.md +530 -0
- nonoka_cli-0.2.5/TODO.md +43 -0
- nonoka_cli-0.2.5/docs/external-mcp-skill-design.md +542 -0
- nonoka_cli-0.2.5/docs/nonoka-cli-complex-task-evaluation.md +254 -0
- nonoka_cli-0.2.5/docs/nonoka-cli-refactor-design.md +735 -0
- nonoka_cli-0.2.5/docs/subagent-refactor-design.md +162 -0
- nonoka_cli-0.2.5/docs/subagent-refactor-plan.md +134 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/install.sh +73 -6
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/nonoka.yaml.example +1 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/packages/nonoka-opencode-provider/README.md +12 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/packages/nonoka-opencode-provider/package.json +1 -1
- nonoka_cli-0.2.5/packages/nonoka-opencode-provider/src/nonoka-language-model.ts +654 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/packages/nonoka-opencode-provider/src/protocol.ts +41 -0
- nonoka_cli-0.2.5/packages/nonoka-opencode-provider/src/stream.ts +275 -0
- nonoka_cli-0.2.5/packages/nonoka-opencode-provider/tests/language-model.test.ts +251 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/packages/nonoka-opencode-provider/tests/protocol.test.ts +40 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/packages/nonoka-opencode-provider/tests/stream.test.ts +15 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/pyproject.toml +6 -2
- nonoka_cli-0.2.5/scripts/quick-install.sh +23 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/bridge/events.py +58 -7
- nonoka_cli-0.2.5/src/nonoka_cli/bridge/handler.py +507 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/bridge/protocol.py +72 -13
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/bridge/server.py +4 -23
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/cli.py +14 -5
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/commands/config_cmd.py +6 -1
- nonoka_cli-0.2.5/src/nonoka_cli/commands/opencode_cmd.py +350 -0
- nonoka_cli-0.2.5/src/nonoka_cli/commands/plugin_cmd.py +86 -0
- nonoka_cli-0.2.5/src/nonoka_cli/commands/run_cmd.py +110 -0
- nonoka_cli-0.2.5/src/nonoka_cli/config/models.py +172 -0
- nonoka_cli-0.2.5/src/nonoka_cli/core/agent_factory.py +638 -0
- nonoka_cli-0.2.5/src/nonoka_cli/core/context.py +28 -0
- nonoka_cli-0.2.5/src/nonoka_cli/core/context_trimmer.py +91 -0
- nonoka_cli-0.2.5/src/nonoka_cli/core/git_service.py +278 -0
- nonoka_cli-0.2.5/src/nonoka_cli/core/namespaces.py +78 -0
- nonoka_cli-0.2.5/src/nonoka_cli/core/orchestrator.py +776 -0
- nonoka_cli-0.2.5/src/nonoka_cli/core/planning_service.py +192 -0
- nonoka_cli-0.2.5/src/nonoka_cli/core/plugin_manifest.py +181 -0
- nonoka_cli-0.2.5/src/nonoka_cli/core/plugin_manifest_converter.py +71 -0
- nonoka_cli-0.2.5/src/nonoka_cli/core/prompt_builder.py +286 -0
- nonoka_cli-0.2.5/src/nonoka_cli/core/repo_map_service.py +111 -0
- nonoka_cli-0.2.5/src/nonoka_cli/core/review_service.py +293 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/core/runner_service.py +28 -0
- nonoka_cli-0.2.5/src/nonoka_cli/core/subagent_service.py +184 -0
- nonoka_cli-0.2.5/src/nonoka_cli/core/task_state.py +206 -0
- nonoka_cli-0.2.5/src/nonoka_cli/core/tool_output_policy.py +233 -0
- nonoka_cli-0.2.5/src/nonoka_cli/mcp/manager.py +116 -0
- nonoka_cli-0.2.5/src/nonoka_cli/skills/__init__.py +9 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/tools/builtins/__init__.py +2 -2
- nonoka_cli-0.2.5/src/nonoka_cli/tools/builtins/agent_tools.py +31 -0
- nonoka_cli-0.2.5/src/nonoka_cli/utils/trace_logger.py +81 -0
- nonoka_cli-0.2.5/tests/integration/test_server_external_capabilities.py +247 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/tests/unit/bridge/test_events.py +34 -0
- nonoka_cli-0.2.5/tests/unit/bridge/test_handler.py +303 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/tests/unit/bridge/test_protocol.py +40 -0
- nonoka_cli-0.2.5/tests/unit/commands/test_opencode_cmd.py +178 -0
- nonoka_cli-0.2.5/tests/unit/commands/test_run_cmd.py +113 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/tests/unit/config/test_models.py +27 -0
- nonoka_cli-0.2.5/tests/unit/core/test_agent_factory.py +463 -0
- nonoka_cli-0.2.5/tests/unit/core/test_context_trimmer.py +93 -0
- nonoka_cli-0.2.5/tests/unit/core/test_git_service.py +262 -0
- nonoka_cli-0.2.5/tests/unit/core/test_orchestrator.py +205 -0
- nonoka_cli-0.2.5/tests/unit/core/test_planning_service.py +174 -0
- nonoka_cli-0.2.5/tests/unit/core/test_plugin_manifest.py +66 -0
- nonoka_cli-0.2.5/tests/unit/core/test_plugin_manifest_converter.py +63 -0
- nonoka_cli-0.2.5/tests/unit/core/test_repo_map_service.py +41 -0
- nonoka_cli-0.2.5/tests/unit/core/test_review_service.py +251 -0
- nonoka_cli-0.2.5/tests/unit/core/test_task_state.py +83 -0
- nonoka_cli-0.2.5/tests/unit/core/test_tool_output_policy.py +123 -0
- nonoka_cli-0.2.5/tests/unit/utils/test_trace_logger.py +49 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/uv.lock +218 -9
- nonoka_cli-0.2.3/PKG-INFO +0 -274
- nonoka_cli-0.2.3/README.md +0 -247
- nonoka_cli-0.2.3/TODO.md +0 -77
- nonoka_cli-0.2.3/packages/nonoka-opencode-provider/nonoka-opencode-provider-0.2.0.tgz +0 -0
- nonoka_cli-0.2.3/packages/nonoka-opencode-provider/src/nonoka-language-model.ts +0 -378
- nonoka_cli-0.2.3/packages/nonoka-opencode-provider/src/stream.ts +0 -164
- nonoka_cli-0.2.3/packages/nonoka-opencode-provider/tests/language-model.test.ts +0 -38
- nonoka_cli-0.2.3/src/nonoka_cli/bridge/handler.py +0 -252
- nonoka_cli-0.2.3/src/nonoka_cli/commands/opencode_cmd.py +0 -149
- nonoka_cli-0.2.3/src/nonoka_cli/config/models.py +0 -53
- nonoka_cli-0.2.3/src/nonoka_cli/core/agent_factory.py +0 -220
- nonoka_cli-0.2.3/src/nonoka_cli/core/context.py +0 -17
- nonoka_cli-0.2.3/src/nonoka_cli/core/orchestrator.py +0 -399
- nonoka_cli-0.2.3/src/nonoka_cli/mcp/manager.py +0 -373
- nonoka_cli-0.2.3/src/nonoka_cli/skills/__init__.py +0 -7
- nonoka_cli-0.2.3/src/nonoka_cli/skills/manager.py +0 -204
- nonoka_cli-0.2.3/tests/unit/bridge/test_handler.py +0 -79
- nonoka_cli-0.2.3/tests/unit/commands/test_opencode_cmd.py +0 -51
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/LICENSE +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/NOTICE +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/nonoka.yaml +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/packages/nonoka-opencode-provider/.gitignore +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/packages/nonoka-opencode-provider/LICENSE +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/packages/nonoka-opencode-provider/bun.lock +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/packages/nonoka-opencode-provider/src/index.ts +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/packages/nonoka-opencode-provider/tsconfig.json +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/__init__.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/__main__.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/bridge/__init__.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/commands/__init__.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/commands/doctor_cmd.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/config/__init__.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/config/loader.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/config/manager.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/core/__init__.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/core/mcp_service.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/core/session_service.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/core/tool_service.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/mcp/__init__.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/mcp/models.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/sessions/__init__.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/sessions/manager.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/sessions/models.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/tools/__init__.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/tools/builtins/file_tools.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/tools/loader.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/utils/__init__.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/utils/errors.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/src/nonoka_cli/utils/logging.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/tests/conftest.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/tests/unit/commands/test_config_cmd.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/tests/unit/commands/test_doctor_cmd.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/tests/unit/config/test_loader.py +0 -0
- {nonoka_cli-0.2.3 → nonoka_cli-0.2.5}/tests/unit/sessions/test_manager.py +0 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nonoka-tui-testing
|
|
3
|
+
description: |
|
|
4
|
+
nonoka-cli 的端到端 TUI 与功能测试流程。用于在隔离沙箱中搭建最新
|
|
5
|
+
nonoka-cli + nonoka-agent + nonoka-opencode-provider,通过 OpenCode
|
|
6
|
+
实际运行对话与开发任务,截图检查 TUI 渲染,并按分层策略定位根因。
|
|
7
|
+
Use when:
|
|
8
|
+
(1) 验证 nonoka-cli 基础对话是否卡住;
|
|
9
|
+
(2) 验证复杂任务(Next.js 应用等)的工具调用链与 HITL;
|
|
10
|
+
(3) 检查 OpenCode TUI 文字分段、工具卡片、HITL 弹窗等渲染细节;
|
|
11
|
+
(4) 排查 nonoka-cli / adapter / nonoka 框架边界问题。
|
|
12
|
+
license: MIT
|
|
13
|
+
metadata:
|
|
14
|
+
author: Kimi Code CLI
|
|
15
|
+
version: "1.1.0"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# nonoka-cli TUI 与功能测试 Skill
|
|
19
|
+
|
|
20
|
+
## 1. 快速开始
|
|
21
|
+
|
|
22
|
+
运行项目内的 setup 脚本,一键准备 `/tmp/nonoka-test` 沙箱:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd /home/fyerfyer/fyerfyer/Projects/nonoka-cli
|
|
26
|
+
bash .agents/skills/nonoka-tui-testing/scripts/setup-sandbox.sh
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
如果项目根目录存在 `.env`(例如 `OPENAI_API_KEY=...`),脚本会自动 source 并生成
|
|
30
|
+
`nonoka.yaml` / `opencode.json`。完成后启动 OpenCode:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd /tmp/nonoka-test
|
|
34
|
+
./run-opencode.sh # 前台运行
|
|
35
|
+
# 或
|
|
36
|
+
./run-opencode-tmux.sh # 后台 tmux,然后 tmux attach -t nonoka-test
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 2. 测试目标
|
|
40
|
+
|
|
41
|
+
- 基础对话不卡住(`hello`、`what is 2+2?`)。
|
|
42
|
+
- 复杂开发任务能完成工具调用链(生成 Next.js 应用)。
|
|
43
|
+
- HITL 审批正常弹出、批准/拒绝后流程正确。
|
|
44
|
+
- TUI 渲染细节:文字分段、工具卡片、卡片后文字续接、HITL 弹窗布局。
|
|
45
|
+
- Session 管理:新建、切换、重命名、删除、持久化。
|
|
46
|
+
- 工具实际执行效果:文件读写、命令执行。
|
|
47
|
+
|
|
48
|
+
## 3. 测试分层
|
|
49
|
+
|
|
50
|
+
| 层级 | 名称 | 验证方式 | 问题归属 |
|
|
51
|
+
| ---- | ------------ | ----------------------------------------- | ------------------------------------- |
|
|
52
|
+
| L1 | 协议层 | 直接对 `nonoka-cli --server` 喂 NDJSON | nonoka-cli bridge / nonoka 框架 |
|
|
53
|
+
| L2 | Provider 层 | 检查 `/tmp/nonoka-provider.log`、timeline | nonoka-opencode-provider |
|
|
54
|
+
| L3 | TUI E2E | Playwright/tmux 截屏 + UI 检查清单 | OpenCode / Ink / provider stream 转换 |
|
|
55
|
+
| L4 | Session 管理 | `nonoka-cli` 子命令 + SQLite 检查 | nonoka-cli sessions |
|
|
56
|
+
| L5 | 产物评估 | 验证生成项目能否 build | nonoka 框架 / tool 实现 |
|
|
57
|
+
|
|
58
|
+
## 4. 环境搭建(手动版)
|
|
59
|
+
|
|
60
|
+
setup 脚本已经把这些步骤自动化;下面是等价的手动流程,便于排错。
|
|
61
|
+
|
|
62
|
+
### 4.1 沙箱目录
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
export NONOKA_TEST_ROOT=/tmp/nonoka-test
|
|
66
|
+
rm -rf "$NONOKA_TEST_ROOT"
|
|
67
|
+
mkdir -p "$NONOKA_TEST_ROOT"
|
|
68
|
+
cd "$NONOKA_TEST_ROOT"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 4.2 复制/链接源码
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
cd "$NONOKA_TEST_ROOT"
|
|
75
|
+
cp -r /home/fyerfyer/fyerfyer/Projects/nonoka-cli ./nonoka-cli
|
|
76
|
+
ln -s /home/fyerfyer/fyerfyer/Projects/nonoka-agent ./nonoka-agent
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 4.3 创建 Python 虚拟环境并 editable 安装
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
cd "$NONOKA_TEST_ROOT/nonoka-cli"
|
|
83
|
+
uv venv .venv
|
|
84
|
+
source .venv/bin/activate
|
|
85
|
+
uv pip install -e ./../nonoka-agent -e .
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
> 测试阶段使用 editable 安装 `nonoka-agent`,修复完成后再 `uv run twine publish` 发布新版到 PyPI。
|
|
89
|
+
|
|
90
|
+
### 4.4 启动本地 verdaccio 并发布 provider
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
cd "$NONOKA_TEST_ROOT"
|
|
94
|
+
cat > verdaccio.yaml <<'EOF'
|
|
95
|
+
storage: ./verdaccio-storage
|
|
96
|
+
packages:
|
|
97
|
+
'nonoka-opencode-provider':
|
|
98
|
+
access: $all
|
|
99
|
+
publish: $all
|
|
100
|
+
'**':
|
|
101
|
+
access: $all
|
|
102
|
+
publish: $all
|
|
103
|
+
proxy: npmjs
|
|
104
|
+
EOF
|
|
105
|
+
|
|
106
|
+
nohup npx --yes verdaccio --config verdaccio.yaml > verdaccio.log 2>&1 &
|
|
107
|
+
for i in {1..30}; do
|
|
108
|
+
if curl -fsSL http://localhost:4873/ >/dev/null 2>&1; then break; fi
|
|
109
|
+
sleep 1
|
|
110
|
+
done
|
|
111
|
+
|
|
112
|
+
cd "$NONOKA_TEST_ROOT/nonoka-cli/packages/nonoka-opencode-provider"
|
|
113
|
+
# 只让当前目录使用本地 registry,不污染全局 npm 配置
|
|
114
|
+
cat > .npmrc <<EOF
|
|
115
|
+
registry=http://localhost:4873
|
|
116
|
+
EOF
|
|
117
|
+
npm install
|
|
118
|
+
npm run build
|
|
119
|
+
npm publish --access public
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 4.5 清理 OpenCode provider 缓存
|
|
123
|
+
|
|
124
|
+
OpenCode 会把 provider 缓存到 `~/.cache/opencode/packages/nonoka-opencode-provider`,
|
|
125
|
+
如果里面是从 npmjs 下载的旧版本,就会覆盖 verdaccio 的新版本。每次重新 setup 时都要清理:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
rm -rf "$HOME/.cache/opencode/packages/nonoka-opencode-provider"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 4.6 准备 OpenCode
|
|
132
|
+
|
|
133
|
+
确保 `opencode` 已在 PATH 中:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
which opencode
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 4.7 生成配置
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
cd "$NONOKA_TEST_ROOT"
|
|
143
|
+
export DEEPSEEK_API_KEY="your-key"
|
|
144
|
+
nonoka-cli config init --yes --model deepseek-chat --config "$NONOKA_TEST_ROOT/nonoka.yaml"
|
|
145
|
+
nonoka-cli opencode init --config "$NONOKA_TEST_ROOT/nonoka.yaml"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`opencode init` 写入的 `serverCommand` 会通过 PATH 找到沙箱内的 `nonoka-cli`,并把 stderr
|
|
149
|
+
重定向到 `/tmp/nonoka-server.log`。
|
|
150
|
+
|
|
151
|
+
## 5. 日志策略
|
|
152
|
+
|
|
153
|
+
所有日志都写文件,不再混进 OpenCode TUI:
|
|
154
|
+
|
|
155
|
+
| 日志文件 | 来源 | 用途 |
|
|
156
|
+
| -------- | ---- | ---- |
|
|
157
|
+
| `/tmp/nonoka-server-{cwdhash}.log` | `nonoka-cli --server` 的 stderr | 框架/LLM 调试、消息顺序 |
|
|
158
|
+
| `/tmp/nonoka-provider.log` | `nonoka-opencode-provider` | adapter 输入/输出摘要 |
|
|
159
|
+
| `/tmp/nonoka-trace/trace-YYYYMMDD.jsonl` | `nonoka-cli` bridge | 结构化请求/事件追踪 |
|
|
160
|
+
| `/tmp/nonoka-tui-timeline.ndjson` | provider + bridge | 渲染时间线对齐 |
|
|
161
|
+
|
|
162
|
+
启动 OpenCode 前设置:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
export NONOKA_LOG_LEVEL=DEBUG
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 6. TUI 渲染时间线日志
|
|
169
|
+
|
|
170
|
+
TUI 问题不能仅靠 stdout/stderr 日志定位,需要**事件时间线 + 截图**对齐。
|
|
171
|
+
|
|
172
|
+
provider 和 bridge 共同写 `/tmp/nonoka-tui-timeline.ndjson`,每行一个事件:
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{"ts":"2026-07-04T09:00:01.123Z","source":"bridge","type":"text_delta","len":42,"has_newline":true,"preview":"Hello! How can I..."}
|
|
176
|
+
{"ts":"2026-07-04T09:00:01.145Z","source":"provider","type":"text-delta","len":42,"preview":"Hello! How can I..."}
|
|
177
|
+
{"ts":"2026-07-04T09:00:01.200Z","source":"provider","type":"tool-call","toolName":"write_file"}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
字段说明:
|
|
181
|
+
|
|
182
|
+
- `ts`: ISO 8601 时间戳,精确到毫秒。
|
|
183
|
+
- `source`: `bridge`(nonoka-cli 发出)、`provider`(adapter 转换后)、`test`(测试脚本标记)。
|
|
184
|
+
- `type`: 事件类型。
|
|
185
|
+
- `len`/`has_newline`/`preview`: text 类事件的关键字段,用于判断文字堆叠是否因为后端没发换行。
|
|
186
|
+
|
|
187
|
+
截屏脚本在每次截图前写入一行 `test` 事件:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"ts": "2026-07-04T09:00:02.500Z",
|
|
192
|
+
"source": "test",
|
|
193
|
+
"type": "screenshot",
|
|
194
|
+
"file": "01_hello.png"
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
事后可用 `scripts/analyze-timeline.py` 把截图与前后 500ms 的事件对齐:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
python .agents/skills/nonoka-tui-testing/scripts/analyze-timeline.py --root /tmp/nonoka-test
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
快速判断:
|
|
205
|
+
|
|
206
|
+
- 如果 bridge 发了 `\n` 但 UI 没分段 → OpenCode / Ink 渲染问题。
|
|
207
|
+
- 如果 bridge 没发 `\n` → nonoka-cli / nonoka 框架问题。
|
|
208
|
+
- 如果 provider 没把 `text_delta` 传给 OpenCode → adapter 问题。
|
|
209
|
+
|
|
210
|
+
## 7. 文本拼接快照
|
|
211
|
+
|
|
212
|
+
在 bridge 端维护一个当前 turn 的文本缓冲区,每次 `text_delta` 后把完整文本写入 `/tmp/nonoka-rendered-text.txt`。截图后对比:
|
|
213
|
+
|
|
214
|
+
- 文件里的文本应该是 UI 上应该显示的内容。
|
|
215
|
+
- 如果文件有分段但 UI 没有 → 渲染问题。
|
|
216
|
+
- 如果文件本身就没分段 → 后端问题。
|
|
217
|
+
|
|
218
|
+
## 8. 辅助脚本
|
|
219
|
+
|
|
220
|
+
| 脚本 | 用途 |
|
|
221
|
+
| ---- | ---- |
|
|
222
|
+
| `scripts/setup-sandbox.sh` | 搭建 `/tmp/nonoka-test` |
|
|
223
|
+
| `scripts/capture-screenshots.py` | 驱动 tmux 输入并触发截图 |
|
|
224
|
+
| `scripts/analyze-timeline.py` | 对齐截图与 timeline |
|
|
225
|
+
|
|
226
|
+
setup 完成后,沙箱内还会生成:
|
|
227
|
+
|
|
228
|
+
- `start-verdaccio.sh`:重新启动 verdaccio。
|
|
229
|
+
- `run-opencode.sh`:前台启动 OpenCode。
|
|
230
|
+
- `run-opencode-tmux.sh`:后台 tmux 启动 OpenCode。
|
|
231
|
+
- `stop-sandbox.sh`:停止 verdaccio 与 tmux session。
|
|
232
|
+
|
|
233
|
+
## 9. 测试用例
|
|
234
|
+
|
|
235
|
+
### 9.1 基础对话
|
|
236
|
+
|
|
237
|
+
| ID | 输入 | 通过标准 |
|
|
238
|
+
| ----- | ---------------------- | ----------------------------------------- |
|
|
239
|
+
| TC-01 | `hello` | 3 秒内出现 assistant 回复,无 error 日志 |
|
|
240
|
+
| TC-02 | `what is 2+2?` | 截图包含答案(如 4) |
|
|
241
|
+
| TC-03 | 连续 3 轮简单问答 | provider 日志 session_id 一致,上下文不丢 |
|
|
242
|
+
| TC-04 | 发送一段多段落文本请求 | assistant 回复段落分明,无堆叠 |
|
|
243
|
+
|
|
244
|
+
### 9.2 复杂任务
|
|
245
|
+
|
|
246
|
+
| ID | 输入 | 通过标准 |
|
|
247
|
+
| ----- | ---------------------------------- | ---------------------------------------------------- |
|
|
248
|
+
| TC-10 | `create a Next.js app in ./my-app` | 出现 write_file / execute_command 工具卡片 |
|
|
249
|
+
| TC-11 | 全部批准 | `my-app/package.json` 存在,且 `npm run build` 成功 |
|
|
250
|
+
| TC-12 | 拒绝一个危险工具 | provider 日志出现 `approved: false`,agent 正确处理 |
|
|
251
|
+
| TC-13 | 多步工具链任务 | server 日志显示 tool_call / tool_result 交替,无死锁 |
|
|
252
|
+
| TC-14 | 让 agent 自己读取它刚写的文件 | 返回内容与文件实际内容一致 |
|
|
253
|
+
|
|
254
|
+
### 9.3 TUI 专项
|
|
255
|
+
|
|
256
|
+
| ID | 场景 | 通过标准 |
|
|
257
|
+
| ----- | ----------------------------- | -------------------------------------------- |
|
|
258
|
+
| TC-20 | 单轮短回复 | 文字分段,不是连续一团 |
|
|
259
|
+
| TC-21 | 工具调用后 assistant 继续输出 | 文字接在卡片后,不堆叠 |
|
|
260
|
+
| TC-22 | HITL 弹窗 | 工具名、参数、Approve/Decline 完整可见 |
|
|
261
|
+
| TC-23 | 长回复滚动 | 无重影、无截断 |
|
|
262
|
+
| TC-24 | 中文/多语言输出 | 字符宽度正确,不散落/重叠 |
|
|
263
|
+
| TC-25 | 代码块输出 | 渲染为代码块,不是原始 markdown(如 ```) |
|
|
264
|
+
| TC-26 | ANSI / 终端序列 | 无 `[14;4H` 这类裸 ANSI 字符出现在文字中 |
|
|
265
|
+
| TC-27 | 工具卡片展开/折叠 | 点击展开后显示完整参数,折叠后不影响后续文字 |
|
|
266
|
+
|
|
267
|
+
### 9.4 Session 管理
|
|
268
|
+
|
|
269
|
+
| ID | 操作 | 通过标准 |
|
|
270
|
+
| ----- | ----------------- | -------------------------------------- |
|
|
271
|
+
| TC-30 | `/new` 后发送消息 | 新 session_id,旧会话保留在 SQLite |
|
|
272
|
+
| TC-31 | 切换历史会话 | 能恢复上下文并继续对话 |
|
|
273
|
+
| TC-32 | 重命名会话 | `cli_sessions` 表 `name` 字段更新 |
|
|
274
|
+
| TC-33 | 删除非当前会话 | 删除后列表中不再出现,当前会话不受影响 |
|
|
275
|
+
|
|
276
|
+
### 9.5 工具实际执行
|
|
277
|
+
|
|
278
|
+
| ID | 操作 | 通过标准 |
|
|
279
|
+
| ----- | --------------------------- | --------------------------------------------------- |
|
|
280
|
+
| TC-40 | `write_file` 创建文件 | 文件存在且内容与参数一致 |
|
|
281
|
+
| TC-41 | `edit_file` 修改文件 | 文件内容被精确替换 |
|
|
282
|
+
| TC-42 | `execute_command echo test` | 返回 `test` |
|
|
283
|
+
| TC-43 | `delete_file` 删除文件 | 文件不存在 |
|
|
284
|
+
| TC-44 | 路径解析 | 相对路径基于 OpenCode cwd,不是 nonoka-cli 安装目录 |
|
|
285
|
+
|
|
286
|
+
## 10. TUI 测试方法
|
|
287
|
+
|
|
288
|
+
### 10.1 用 tmux + Playwright 截屏
|
|
289
|
+
|
|
290
|
+
启动后台 tmux session:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
cd /tmp/nonoka-test
|
|
294
|
+
./run-opencode-tmux.sh
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
获取 pane id:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
tmux list-panes -t nonoka-test -F '#{pane_id}'
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
输入消息并截图:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
tmux read -t %0 -l 20
|
|
307
|
+
tmux type -t %0 'hello'
|
|
308
|
+
tmux keys -t %0 Enter
|
|
309
|
+
tmux read -t %0 -l 50
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
用 Playwright 截取终端窗口,保存到 `/tmp/nonoka-test/screenshots/`。
|
|
313
|
+
|
|
314
|
+
### 10.2 UI 检查清单
|
|
315
|
+
|
|
316
|
+
每次截图后检查:
|
|
317
|
+
|
|
318
|
+
- [ ] 用户输入完整显示。
|
|
319
|
+
- [ ] assistant 回复分段清晰,段落间有空行或缩进。
|
|
320
|
+
- [ ] 工具调用卡片独立渲染,不与文字混排。
|
|
321
|
+
- [ ] 工具调用后文字接在卡片下方,不紧跟卡片右侧。
|
|
322
|
+
- [ ] HITL 弹窗完整,工具名、参数、Approve/Decline 按钮可见。
|
|
323
|
+
- [ ] 滚动后无重影、无截断。
|
|
324
|
+
- [ ] 输入框光标位置正确,无明显延迟。
|
|
325
|
+
- [ ] 中文/日文/韩文等宽字符宽度正确,无散落。
|
|
326
|
+
- [ ] 代码块有语法高亮或独立背景,不是裸 markdown。
|
|
327
|
+
- [ ] 无裸 ANSI 转义序列(如 `[14;4H`)出现在文字中。
|
|
328
|
+
|
|
329
|
+
## 11. 问题定位决策树
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
发现问题
|
|
333
|
+
│
|
|
334
|
+
├─ 是否卡住?
|
|
335
|
+
│ ├─ 是 → 查看 /tmp/nonoka-provider.log
|
|
336
|
+
│ │ ├─ 无 doStream start → OpenCode 未调用 provider
|
|
337
|
+
│ │ ├─ spawned child 后无响应 → server 启动失败,看 /tmp/nonoka-server.log
|
|
338
|
+
│ │ ├─ 收到 finish/error 但未 close → provider bug
|
|
339
|
+
│ │ └─ 无 finish 且 server 也无 final → nonoka 框架 / runner 层
|
|
340
|
+
│ └─ 否 → 看 TUI / 结果
|
|
341
|
+
│
|
|
342
|
+
├─ 是否结果错误?
|
|
343
|
+
│ ├─ 先隔离 nonoka-cli --server:直接喂 NDJSON,看 stdout 是否正确
|
|
344
|
+
│ ├─ stdout 正确 → adapter 或 OpenCode 问题
|
|
345
|
+
│ └─ stdout 错误 → nonoka-cli / nonoka 框架问题
|
|
346
|
+
│
|
|
347
|
+
├─ 是否 UI 异常?
|
|
348
|
+
│ ├─ 查看 /tmp/nonoka-tui-timeline.ndjson
|
|
349
|
+
│ │ ├─ bridge text_delta 含 \n 但 UI 未分段 → OpenCode / Ink 渲染问题
|
|
350
|
+
│ │ ├─ bridge text_delta 不含 \n → nonoka-cli / nonoka 框架问题
|
|
351
|
+
│ │ └─ provider 未转发 text_delta → adapter 问题
|
|
352
|
+
│ ├─ 对比 /tmp/nonoka-rendered-text.txt 与截图
|
|
353
|
+
│ └─ 检查 CJK/emoji 宽度、ANSI 泄漏、代码块 markdown
|
|
354
|
+
│
|
|
355
|
+
└─ 是否 Session / 工具执行异常?
|
|
356
|
+
├─ 检查 SQLite `cli_sessions` 表
|
|
357
|
+
├─ 检查 tool 实际副作用(文件是否存在、命令是否执行)
|
|
358
|
+
└─ 检查 tool 的路径解析是否基于 cwd
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## 12. 回归与发布
|
|
362
|
+
|
|
363
|
+
修复后:
|
|
364
|
+
|
|
365
|
+
1. 重新跑 TC-01 ~ TC-44,全部通过。
|
|
366
|
+
2. 如果修改了 `nonoka-agent`:
|
|
367
|
+
- 更新 `nonoka-agent/pyproject.toml` 版本号。
|
|
368
|
+
- `uv run twine publish` 发布到 PyPI。
|
|
369
|
+
- 更新 `nonoka-cli/pyproject.toml` 的 `nonoka` 依赖版本。
|
|
370
|
+
3. 如果修改了 `nonoka-opencode-provider`:
|
|
371
|
+
- 更新 `package.json` 版本号。
|
|
372
|
+
- `npm run build && npm publish`。
|
|
373
|
+
4. 如果修改了 `nonoka-cli`:
|
|
374
|
+
- 更新 `pyproject.toml` 版本号。
|
|
375
|
+
- 跑 `uv run pytest tests/unit`。
|
|
376
|
+
- 必要时发布新版到 PyPI。
|
|
377
|
+
|
|
378
|
+
## 13. 常见问题
|
|
379
|
+
|
|
380
|
+
### 13.1 OpenCode 仍然加载旧 provider
|
|
381
|
+
|
|
382
|
+
症状:timeline 里没有 `session_init`,或行为不像最新代码。
|
|
383
|
+
|
|
384
|
+
解决:
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
rm -rf ~/.cache/opencode/packages/nonoka-opencode-provider
|
|
388
|
+
# 然后重启 OpenCode
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### 13.2 verdaccio 启动后 npm publish 报 ECONNREFUSED
|
|
392
|
+
|
|
393
|
+
症状:`npm publish --registry http://localhost:4873` 失败。
|
|
394
|
+
|
|
395
|
+
原因:脚本里的 `npx verdaccio &` 在 `bash -c` 包装下可能拿到错误的 PID,导致 verdaccio 没真正启动。
|
|
396
|
+
|
|
397
|
+
解决:使用沙箱内生成的 `start-verdaccio.sh`(它用 `nohup` 启动并等待端口可用)。
|
|
398
|
+
|
|
399
|
+
### 13.3 沙箱内的 nonoka-cli 找不到
|
|
400
|
+
|
|
401
|
+
症状:OpenCode 启动后 provider 子进程报 `nonoka-cli: command not found`。
|
|
402
|
+
|
|
403
|
+
原因:`opencode.json` 的 `serverCommand` 依赖 PATH,而 OpenCode 的环境没有继承 `env.sh`。
|
|
404
|
+
|
|
405
|
+
解决:始终通过 `run-opencode.sh` 或 `run-opencode-tmux.sh` 启动,它们会先 `source env.sh`。
|
|
406
|
+
|
|
407
|
+
### 13.4 API key 未生效
|
|
408
|
+
|
|
409
|
+
症状:`nonoka-cli config init` 提示 API key 来自环境变量,但值为空。
|
|
410
|
+
|
|
411
|
+
原因:`.env` 里用的是 `OPENAI_API_KEY`,而 deepseek-chat 需要 `DEEPSEEK_API_KEY`。
|
|
412
|
+
|
|
413
|
+
解决:`env.sh` 会自动把 `OPENAI_API_KEY` 同步到 `DEEPSEEK_API_KEY`。手动运行时执行 `source env.sh` 即可。
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# nonoka-cli TUI 检查清单速查表
|
|
2
|
+
|
|
3
|
+
每次截屏后逐项检查,勾选通过项,对未通过项记录截图文件名与现象。
|
|
4
|
+
|
|
5
|
+
## 基础布局
|
|
6
|
+
|
|
7
|
+
- [ ] 终端整体布局未崩溃,边界对齐。
|
|
8
|
+
- [ ] 顶部/底部状态栏信息完整,不被内容覆盖。
|
|
9
|
+
- [ ] 输入框始终可见,光标位置正确。
|
|
10
|
+
- [ ] 滚动后无重影、无残影、无内容覆盖。
|
|
11
|
+
|
|
12
|
+
## 文字渲染
|
|
13
|
+
|
|
14
|
+
- [ ] 用户输入的消息完整显示。
|
|
15
|
+
- [ ] Assistant 回复按段落分段,段间有视觉分隔。
|
|
16
|
+
- [ ] 长文本自动换行,不超出容器边界。
|
|
17
|
+
- [ ] 代码块渲染为代码块(独立背景/语法高亮),不是裸 markdown(如 ```)。
|
|
18
|
+
- [ ] 内联代码、粗体、斜体等 markdown 样式正确。
|
|
19
|
+
- [ ] 工具调用后,后续文字出现在卡片下方,不紧跟卡片右侧。
|
|
20
|
+
|
|
21
|
+
## 多语言与特殊字符
|
|
22
|
+
|
|
23
|
+
- [ ] 中文、日文、韩文等 CJK 字符宽度正确,不散落或重叠。
|
|
24
|
+
- [ ] Emoji 宽度正确,不导致后续文字错位。
|
|
25
|
+
- [ ] 无裸 ANSI 转义序列(如 `[14;4H`)出现在可见文字中。
|
|
26
|
+
|
|
27
|
+
## 工具调用与 HITL
|
|
28
|
+
|
|
29
|
+
- [ ] 工具调用卡片独立渲染,有图标/工具名/参数。
|
|
30
|
+
- [ ] 工具调用卡片与文字有明显分隔。
|
|
31
|
+
- [ ] 工具执行结果(成功/失败)状态可见。
|
|
32
|
+
- [ ] HITL 弹窗完整显示工具名、参数、Approve/Decline/Modify 按钮。
|
|
33
|
+
- [ ] HITL 弹窗不遮挡输入框或历史消息。
|
|
34
|
+
- [ ] 批准/拒绝后 UI 及时更新,无残留弹窗。
|
|
35
|
+
|
|
36
|
+
## 交互响应
|
|
37
|
+
|
|
38
|
+
- [ ] 输入时无明显延迟。
|
|
39
|
+
- [ ] Enter 发送后输入框清空。
|
|
40
|
+
- [ ] 等待 AI 回复时有 loading / thinking 指示。
|
|
41
|
+
- [ ] 多轮对话上下文连续,无会话重置错觉。
|
|
42
|
+
|
|
43
|
+
## 长任务与复杂输出
|
|
44
|
+
|
|
45
|
+
- [ ] 长回复滚动流畅,无跳变。
|
|
46
|
+
- [ ] 多个工具调用按时间顺序排列。
|
|
47
|
+
- [ ] 工具调用失败时错误信息可读。
|
|
48
|
+
- [ ] 任务完成后有清晰的总结输出。
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Align screenshots with the TUI event timeline.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python analyze-timeline.py --root /tmp/nonoka-test --window 0.5
|
|
6
|
+
|
|
7
|
+
Output:
|
|
8
|
+
For each screenshot, prints the bridge/provider events within +/- window
|
|
9
|
+
seconds of the screenshot timestamp. Helps determine whether a rendering
|
|
10
|
+
issue originates in the backend or the frontend.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
from datetime import datetime, timedelta, timezone
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_ts(ts: str) -> datetime:
|
|
23
|
+
"""Parse an ISO 8601 timestamp with naive or explicit timezone."""
|
|
24
|
+
# Replace trailing Z with +00:00 for fromisoformat compatibility.
|
|
25
|
+
if ts.endswith("Z"):
|
|
26
|
+
ts = ts[:-1] + "+00:00"
|
|
27
|
+
dt = datetime.fromisoformat(ts)
|
|
28
|
+
if dt.tzinfo is None:
|
|
29
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
30
|
+
return dt
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def load_timeline(root: Path) -> list[dict[str, Any]]:
|
|
34
|
+
timeline = root / "nonoka-tui-timeline.ndjson"
|
|
35
|
+
events: list[dict[str, Any]] = []
|
|
36
|
+
if not timeline.exists():
|
|
37
|
+
return events
|
|
38
|
+
for line in timeline.read_text(encoding="utf-8").splitlines():
|
|
39
|
+
line = line.strip()
|
|
40
|
+
if not line:
|
|
41
|
+
continue
|
|
42
|
+
try:
|
|
43
|
+
events.append(json.loads(line))
|
|
44
|
+
except json.JSONDecodeError:
|
|
45
|
+
continue
|
|
46
|
+
return events
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def describe_event(event: dict[str, Any]) -> str:
|
|
50
|
+
parts = [event.get("source", "?"), event.get("type", "?")]
|
|
51
|
+
for key in ("toolName", "tool_name", "step", "file", "len", "has_newline"):
|
|
52
|
+
if key in event:
|
|
53
|
+
parts.append(f"{key}={event[key]}")
|
|
54
|
+
preview = event.get("preview")
|
|
55
|
+
if preview is not None:
|
|
56
|
+
parts.append(f"preview={preview[:60]!r}")
|
|
57
|
+
return " ".join(parts)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def analyze(root: Path, window_seconds: float) -> None:
|
|
61
|
+
events = load_timeline(root)
|
|
62
|
+
if not events:
|
|
63
|
+
print("No timeline events found.")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
screenshots = [e for e in events if e.get("type") == "screenshot"]
|
|
67
|
+
if not screenshots:
|
|
68
|
+
print("No screenshot events found.")
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
for shot in screenshots:
|
|
72
|
+
shot_ts = parse_ts(shot["ts"])
|
|
73
|
+
shot_file = shot.get("file", "unknown")
|
|
74
|
+
print(f"\n=== {Path(shot_file).name} @ {shot['ts']} ===")
|
|
75
|
+
for event in events:
|
|
76
|
+
if event.get("type") == "screenshot":
|
|
77
|
+
continue
|
|
78
|
+
event_ts = parse_ts(event["ts"])
|
|
79
|
+
delta = abs((event_ts - shot_ts).total_seconds())
|
|
80
|
+
if delta <= window_seconds:
|
|
81
|
+
marker = " "
|
|
82
|
+
if event_ts < shot_ts:
|
|
83
|
+
marker = "<-"
|
|
84
|
+
elif event_ts > shot_ts:
|
|
85
|
+
marker = "->"
|
|
86
|
+
print(f" {marker} {delta:.3f}s {describe_event(event)}")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main() -> int:
|
|
90
|
+
parser = argparse.ArgumentParser(description="Align screenshots with TUI timeline")
|
|
91
|
+
parser.add_argument("--root", default="/tmp/nonoka-test", help="Sandbox root")
|
|
92
|
+
parser.add_argument("--window", type=float, default=0.5, help="Time window in seconds around each screenshot")
|
|
93
|
+
args = parser.parse_args()
|
|
94
|
+
|
|
95
|
+
analyze(Path(args.root), args.window)
|
|
96
|
+
return 0
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if __name__ == "__main__":
|
|
100
|
+
raise SystemExit(main())
|