miki-moni 0.3.1
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/LICENSE +21 -0
- package/README.md +283 -0
- package/README.zh-CN.md +275 -0
- package/README.zh-TW.md +275 -0
- package/bin/miki.mjs +49 -0
- package/dist/web/assets/favicon-DFpLtP36.svg +13 -0
- package/dist/web/assets/index--89DkyV1.css +1 -0
- package/dist/web/assets/index-CyPlxvOn.js +64 -0
- package/dist/web/index.html +20 -0
- package/dist/web/pair-info.html +138 -0
- package/dist/web-phone/assets/app-CyQWCdKZ.js +64 -0
- package/dist/web-phone/assets/index-D5BUh7Uf.js +1 -0
- package/dist/web-phone/assets/index-D8vY_9ld.css +1 -0
- package/dist/web-phone/index.html +20 -0
- package/hooks/miki-emit.ps1 +56 -0
- package/package.json +89 -0
- package/shared/i18n.ts +915 -0
- package/src/cli/i18n-cli.ts +149 -0
- package/src/cli/miki.ts +168 -0
- package/src/cli/pair.ts +534 -0
- package/src/cli/prompt.ts +6 -0
- package/src/cli/pushable-iter.ts +45 -0
- package/src/cli/setup-self-host.ts +292 -0
- package/src/cli/setup-wizard.ts +130 -0
- package/src/cli/wrap.ts +742 -0
- package/src/config.ts +121 -0
- package/src/crypto.ts +66 -0
- package/src/data-dir.ts +31 -0
- package/src/ext-registry.ts +47 -0
- package/src/hook-handler.ts +86 -0
- package/src/index.ts +279 -0
- package/src/install-hooks.ts +107 -0
- package/src/notifier.ts +21 -0
- package/src/pairing.ts +100 -0
- package/src/protocol-ext.ts +46 -0
- package/src/relay-client.ts +468 -0
- package/src/relay-protocol.ts +57 -0
- package/src/server.ts +1134 -0
- package/src/session-resolver.ts +437 -0
- package/src/session-store.ts +131 -0
- package/src/types.ts +33 -0
- package/src/vscode-bridge.ts +407 -0
- package/src/wrap-process.ts +183 -0
- package/tools/tray.ps1 +286 -0
- package/worker/package.json +24 -0
- package/worker/src/daemon-relay.ts +348 -0
- package/worker/src/env.ts +11 -0
- package/worker/src/handshake.ts +63 -0
- package/worker/src/index.ts +81 -0
- package/worker/src/pairing-code.ts +39 -0
- package/worker/src/pairing-coordinator.ts +145 -0
- package/worker/wrangler-selfhost.toml +36 -0
- package/worker/wrangler.toml +29 -0
package/README.zh-TW.md
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Miki-Moni
|
|
2
|
+
|
|
3
|
+
**[English](README.md) · [繁體中文](README.zh-TW.md) · [简体中文](README.zh-CN.md)**
|
|
4
|
+
|
|
5
|
+
> 巫女 (Miki the Monitor) — 看著你所有 Claude Code session,要回應的時候喊你。
|
|
6
|
+
|
|
7
|
+
把散落在各個 VSCode 視窗的 Claude Code panel 收進一張本機儀表板,手機或第二台筆電可以透過端對端加密 relay 連進來。
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<img src="docs/images/dashboard-desktop.png" width="800" alt="桌面 dashboard — session 卡片含即時 transcript">
|
|
11
|
+
<br />
|
|
12
|
+
<em>本機 dashboard:<code>http://127.0.0.1:8765</code></em>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<table>
|
|
16
|
+
<tr>
|
|
17
|
+
<td align="center" width="33%">
|
|
18
|
+
<img src="docs/images/dashboard-phone.png" width="280" alt="手機 dashboard — 同樣內容、單欄行動版">
|
|
19
|
+
<br /><em>手機 dashboard(行動裝置)</em>
|
|
20
|
+
</td>
|
|
21
|
+
<td align="center" width="33%">
|
|
22
|
+
<img src="docs/images/phone-pair-screen.png" width="280" alt="手機配對畫面 — 掃 QR + 16 碼輸入">
|
|
23
|
+
<br /><em>手機配對畫面 — 掃 QR 或打 16 碼</em>
|
|
24
|
+
</td>
|
|
25
|
+
<td align="center" width="33%">
|
|
26
|
+
<img src="docs/images/cli-banner.png" width="280" alt="CLI banner — miki start 啟動時印 QR + URL + 16 碼">
|
|
27
|
+
<br /><em><code>miki start</code> 每次啟動印 QR + URL + 16 碼</em>
|
|
28
|
+
</td>
|
|
29
|
+
</tr>
|
|
30
|
+
</table>
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 為什麼
|
|
35
|
+
|
|
36
|
+
- 兩個 VSCode 視窗開了三個 Claude Code panel,其中一個跑完了,你 20 分鐘後才發現
|
|
37
|
+
- 走離桌前想瞥一眼「跑完沒?」但不想 VPN 進來
|
|
38
|
+
- 同事機器有專案,你想唯讀看一下
|
|
39
|
+
|
|
40
|
+
Miki-Moni 給你**一張 dashboard** 收齊所有 Claude session(跨視窗、跨專案、跨機器),任何地方都能回應。
|
|
41
|
+
|
|
42
|
+
**任何 session 都能從任何 terminal 接管繼續做。** VSCode 起的、CLI 起的都一樣,editor 崩了、視窗誤關、想換個 terminal 繼續做 — 一句 `miki claude -r <session-uuid>` 把**完整上下文**接回來。dashboard 每張 session 卡都有一鍵「Open CLI」按鈕;手機端就直接透過 relay 對同一個 session 繼續打字。再也不會「Claude 上下文掉了」 — session UUID 是耐用的把手,不是起它的那個視窗。
|
|
43
|
+
|
|
44
|
+
## 安裝
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install -g miki-moni
|
|
48
|
+
miki start
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
或從 source 裝(要貢獻 / 用未 release 的改動):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
git clone https://github.com/WarmBed/Miki-Moni
|
|
55
|
+
cd Miki-Moni
|
|
56
|
+
pnpm install
|
|
57
|
+
pnpm build:all
|
|
58
|
+
pnpm link --global # 把 `miki` 加到 PATH
|
|
59
|
+
miki start
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
首次啟動會跳設定 wizard:
|
|
63
|
+
|
|
64
|
+
1. **語言** — English / 繁體中文 / 简体中文
|
|
65
|
+
2. **Relay 模式** — 三選一:
|
|
66
|
+
- **Hosted**(預設)— 用作者免費 `relay.f1telemetrystationpro.org`,零設定
|
|
67
|
+
- **Self-host** — 自動部署 Cloudflare Worker + Pages 到你 CF 帳號(需要 `wrangler`)
|
|
68
|
+
- **Local-only** — 不配手機,只用本機 `127.0.0.1:8765`
|
|
69
|
+
|
|
70
|
+
接著印永久配對 QR + 16 碼:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
📱 Phone pairing — scan QR, open URL, or type the 16-char code:
|
|
74
|
+
|
|
75
|
+
[QR]
|
|
76
|
+
|
|
77
|
+
URL: https://miki-moni.pages.dev/#t=XXXX...&r=wss://...
|
|
78
|
+
Code: XXXX-XXXX-XXXX-XXXX
|
|
79
|
+
Local: http://127.0.0.1:8765
|
|
80
|
+
(QR / URL / Code 永久有效 — `miki pair --rotate` 可換)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
那個 QR 永久有效,每支要配對的裝置掃一次就好。洩漏時 rotate 即可。
|
|
84
|
+
|
|
85
|
+
## 三種部署模式
|
|
86
|
+
|
|
87
|
+
| | Hosted | Self-host | Local-only |
|
|
88
|
+
|---|---|---|---|
|
|
89
|
+
| **設定時間** | 0 秒 | 約 5 分鐘 wizard | 0 秒 |
|
|
90
|
+
| **需要 CF 帳號** | 否 | 是 | 否 |
|
|
91
|
+
| **手機可用** | 是 | 是 | 否 |
|
|
92
|
+
| **信任作者基礎設施** | 是([§ 資安](#資安)) | 否 | N/A |
|
|
93
|
+
| **流量限制** | 作者 CF 免費層(10 萬 req/天) | 你自己 CF 免費層 | N/A |
|
|
94
|
+
| **隨時切換** | `miki setup` | `miki setup` | `miki setup` |
|
|
95
|
+
|
|
96
|
+
## 架構
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
100
|
+
│ 你的電腦 │
|
|
101
|
+
│ ╭─────────────────────────────────────────────────────────────╮ │
|
|
102
|
+
│ │ miki-moni daemon (Node, 127.0.0.1:8765) │ │
|
|
103
|
+
│ │ POST /event GET /sessions POST /focus /send WS /ws │ │
|
|
104
|
+
│ │ ▲ ▲ ▲ │ │
|
|
105
|
+
│ │ PS hooks 瀏覽器 dashboard RelayClient │ │
|
|
106
|
+
│ ╰────────────────────────────────────────────┬────────────────╯ │
|
|
107
|
+
│ │ E2E 加密 envelope │
|
|
108
|
+
│ ╭─────────────▼────────────╮ │
|
|
109
|
+
│ │ Cloudflare Worker relay │ │
|
|
110
|
+
│ │ (零知識:只路由密文) │ │
|
|
111
|
+
│ ╰─────────────┬────────────╯ │
|
|
112
|
+
│ ▼ │
|
|
113
|
+
│ ╭──────────────────────────╮ │
|
|
114
|
+
│ │ 手機 / 第二台筆電 / 平板 │ │
|
|
115
|
+
│ │ · 掃 QR → 自動配對 │ │
|
|
116
|
+
│ │ · 看到一樣的 dashboard │ │
|
|
117
|
+
│ ╰──────────────────────────╯ │
|
|
118
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**加密**:配對時 X25519 ECDH → per-peer shared secret → 每個 envelope 用 NaCl `secretbox`。Relay 沒有 key;只有 daemon 跟配對好的手機能解內容。
|
|
122
|
+
|
|
123
|
+
**認證**:每支手機握一把 Ed25519 簽章 keypair(IndexedDB)。重連時簽 `daemon_id || utc_minute`,relay 驗章才放行。`miki pair --revoke <peer_id>` 砍單一裝置。
|
|
124
|
+
|
|
125
|
+
## Dashboard 功能
|
|
126
|
+
|
|
127
|
+
上方工具列:
|
|
128
|
+
|
|
129
|
+
| | 作用 |
|
|
130
|
+
|---|---|
|
|
131
|
+
| **計數器**(`5 進行中 · 0 閒置 · 56 全覽`) | 點一下篩選只看那個狀態,再點取消 |
|
|
132
|
+
| **➕ 新增 CLI** | 在指定資料夾開新 wrapped session(`miki claude --fresh`) |
|
|
133
|
+
| **⚙️ 設定** | 送出鍵(Enter vs Ctrl/⌘+Enter)、主題(淺/深)、語言 |
|
|
134
|
+
| **WS 燈號** | 綠=即時更新中 · 黃=重連中 |
|
|
135
|
+
|
|
136
|
+
Session 卡片:
|
|
137
|
+
|
|
138
|
+
| 元素 | 作用 |
|
|
139
|
+
|---|---|
|
|
140
|
+
| **專案名 + cwd** | 卡片標題 — 點開檢視完整 transcript |
|
|
141
|
+
| **🖥️ VSCode / 📟 CLI 切換** | 決定 *送出 / focus* 走哪邊。**VSCode**:用 `vscode://anthropic.claude-code/open?session=…` 把 prompt 預填 VSCode panel。**CLI**:直接打 wrap CLI 的 WebSocket。 |
|
|
142
|
+
| **權限 badge**(`✏️ auto edit` / `🚀 bypass`) | 只有跑 `miki claude --permission-mode acceptEdits` / `--bypass-permissions` 的 wrap CLI session 才會顯示,整個 session lifetime 鎖定不能改 |
|
|
143
|
+
| **Transcript view** | 即時渲染 Claude 對話。可開關 tool call。捲動門檻 10 / 50 / 200 / 全部。 |
|
|
144
|
+
| **送出輸入框** | 多行 prompt。Enter 或 Ctrl/⌘+Enter 送出(依你的設定)。支援貼/拖圖片。 |
|
|
145
|
+
| **開 CLI 按鈕** ⭐ | **從 CLI 接管這個 session,完整上下文都在。** 開 `wt.exe`(Windows Terminal)跑 `miki claude -r <session-uuid>` — Claude 從 VSCode panel 停的那回合接著做。原本從哪裡起的都不重要;panel 可以已關、已 crash、在另一個視窗。配上手機 dashboard,同一個 session 你在哪都能繼續打 |
|
|
146
|
+
| **Focus 按鈕** | `POST /focus` — 把對應 VSCode 視窗(或 CLI tab)拉到最前面 |
|
|
147
|
+
|
|
148
|
+
## CLI 指令
|
|
149
|
+
|
|
150
|
+
| 指令 | 用途 |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `miki start` | 跑 daemon + 印配對 banner。第一次跑會跳 wizard |
|
|
153
|
+
| `miki setup` | 重跑 wizard(換語言、切 relay 模式等) |
|
|
154
|
+
| `miki pair` | 印當前永久 QR + 已配對手機清單 |
|
|
155
|
+
| `miki pair --rotate` | 換新 token(舊 QR 失效;已配對手機照常工作) |
|
|
156
|
+
| `miki pair --list` | 列已配對手機 |
|
|
157
|
+
| `miki pair --revoke <peer_id>` | 砍掉某支手機(本機 config + relay 都清) |
|
|
158
|
+
| `miki pair --new` | 一次性 token(10 分鐘 TTL)— 舊機制 / debug 用 |
|
|
159
|
+
| `miki claude [...args]` | 包一個 Claude session,daemon 沒跑會自動起 |
|
|
160
|
+
| `miki install-hooks` | 把 Claude Code hooks 灌進 `~/.claude/settings.json`,沒 wrap 的 panel 也會出現在 dashboard |
|
|
161
|
+
|
|
162
|
+
啟動時看詳細 log:`MIKI_LOG_LEVEL=info miki start`。完整 trace 永遠在 `~/.miki-moni/miki-moni.log`。
|
|
163
|
+
|
|
164
|
+
## 資安
|
|
165
|
+
|
|
166
|
+
### 手機能做什麼、不能做什麼
|
|
167
|
+
|
|
168
|
+
刻意把手機端能力壓到最小,威脅模型才好顧:
|
|
169
|
+
|
|
170
|
+
| 手機**可以** | 手機**不可以** |
|
|
171
|
+
|---|---|
|
|
172
|
+
| 看即時 session 狀態 + transcript | 在你電腦上跑任意 shell 指令 |
|
|
173
|
+
| Pre-fill prompt 進 VSCode panel(`/focus`) | 不經你 VSCode 按鍵自動送出 prompt(Anthropic 設計) |
|
|
174
|
+
| 對 `miki claude` 起的 session 從 wrap CLI WebSocket 推 prompt | 開新 process 或讀 session 外的檔案 |
|
|
175
|
+
| Focus 已存在的 panel | 繞過 Claude Code 工具權限提示(每個工具呼叫一樣會問你) |
|
|
176
|
+
|
|
177
|
+
### 信賴邊界
|
|
178
|
+
|
|
179
|
+
daemon **只綁 `127.0.0.1`** — 公網永遠戳不到。遠端走加密 relay,不走本機 HTTP port。
|
|
180
|
+
|
|
181
|
+
daemon 信任**所有跟你同帳號**的本機程序去打 `/event`、`/send`、`/focus`、`/ws_ext`。這是故意的(Claude Code hooks 跟 VSCode helper extension 才不用帶 token),但代價是:**任何以你身分跑的程序都能跟 daemon 講話**。完整本機信賴分析跟硬化選項見 [`docs/security/hooks-trust-model.md`](docs/security/hooks-trust-model.md) 跟 [`docs/security/extension-ws-trust-model.md`](docs/security/extension-ws-trust-model.md)。
|
|
182
|
+
|
|
183
|
+
### 風險表
|
|
184
|
+
|
|
185
|
+
按可能性排序:
|
|
186
|
+
|
|
187
|
+
| 風險 | 緩解 |
|
|
188
|
+
|---|---|
|
|
189
|
+
| 🔴 **配對 QR 洩漏**(截圖、貼到聊天室、被路人拍) | 永久 QR = 任何人掃到都能 pair。把 QR 當 SSH key 看待。洩漏立刻 rotate:`miki pair --rotate` |
|
|
190
|
+
| 🟡 **配對手機被偷** | 手機握 Ed25519 簽章 key 才能連 relay。從 daemon 砍:`miki pair --revoke <peer_id>` |
|
|
191
|
+
| 🟡 **本機被入侵** | daemon 信任 loopback。任何以你身分跑的惡意程序可讀 session、可從 `/ws_ext` 攔 prompt。`~/.miki-moni/`(私鑰、配對紀錄)請當 `~/.ssh/` 那樣保護 |
|
|
192
|
+
| 🟢 暴力猜 token | 16 字元 Crockford base32 ≈ 80 bits entropy,宇宙熱寂前猜不到 |
|
|
193
|
+
| 🟢 Relay 看到內容 | 零知識架構 — relay 只路由密文,不持有 shared secret |
|
|
194
|
+
| 🟡 信任 hosted relay 維運者 | Self-host 完全擺脫這層信任。作者看得到 metadata(peer ID、時間、大小),理論上可改 PWA bundle。原始碼公開,可自行 audit 或 self-host。 |
|
|
195
|
+
| 🟢 Hosted relay 被 DDoS | Cloudflare rate limit 限 30 req/60 秒/IP。最壞:你的當日額度燒光 |
|
|
196
|
+
|
|
197
|
+
## Self-host(手動)
|
|
198
|
+
|
|
199
|
+
`miki setup` wizard 自動做完,但要手動的話:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# 在 clone 好的 cc-hub source 樹:
|
|
203
|
+
cd worker
|
|
204
|
+
wrangler login
|
|
205
|
+
wrangler deploy --config wrangler-selfhost.toml --name my-relay
|
|
206
|
+
wrangler pages project create my-phone --production-branch=main
|
|
207
|
+
wrangler pages deploy ../dist/web-phone --project-name my-phone --branch=main
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
接著編 `~/.miki-moni/config.json`:
|
|
211
|
+
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"remote": {
|
|
215
|
+
"worker_url": "wss://my-relay.<你 CF 帳號>.workers.dev",
|
|
216
|
+
"phone_pwa_url": "https://my-phone.pages.dev/"
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
下次 `miki start` 就會用新 endpoints。
|
|
222
|
+
|
|
223
|
+
## 開發
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
git clone https://github.com/WarmBed/Miki-Moni
|
|
227
|
+
cd Miki-Moni
|
|
228
|
+
pnpm install
|
|
229
|
+
pnpm typecheck
|
|
230
|
+
pnpm test # daemon + worker tests
|
|
231
|
+
pnpm dev # tsx watch src/index.ts
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Source 結構:
|
|
235
|
+
|
|
236
|
+
| 路徑 | 用途 |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `src/` | Node daemon(express + ws + better-sqlite3)— hooks、配對、RelayClient |
|
|
239
|
+
| `web/` | 桌面 / 手機完整 dashboard(Preact + Tailwind + Vite) |
|
|
240
|
+
| `web-phone/` | 手機 bootstrap(QR 掃描器 + tunnel 設定)— mount web/ |
|
|
241
|
+
| `worker/` | Cloudflare Worker relay(DaemonRelay + PairingCoordinator DOs) |
|
|
242
|
+
| `extension/` | VSCode helper extension — handle `claude-vscode.send` |
|
|
243
|
+
| `hooks/` | Claude Code hook 腳本(PowerShell)— POST event 到 daemon |
|
|
244
|
+
| `bin/miki.mjs` | npm 發佈的 CLI 入口 |
|
|
245
|
+
|
|
246
|
+
## Branch
|
|
247
|
+
|
|
248
|
+
- `main` — 版本化 release(目前:v0.0.0)
|
|
249
|
+
- `dev` — 開發中;每改動 bump `package.json` version
|
|
250
|
+
|
|
251
|
+
## 相關專案
|
|
252
|
+
|
|
253
|
+
**[Happy](https://happy.engineering)**(`slopus/happy-cli`, MIT)切的痛點有重疊 — 從手機操控 Claude Code — 但角度不同。兩者可以同一台機器並存。
|
|
254
|
+
|
|
255
|
+
| | Miki-Moni | Happy |
|
|
256
|
+
|---|---|---|
|
|
257
|
+
| 主入口 | VSCode panel — hooks 把每個 panel 拉進 dashboard | 取代 `claude` 的 terminal wrapper |
|
|
258
|
+
| Relay | Cloudflare Worker;可以 5 分鐘 self-host 到自己 CF 帳號 | 作者自架 socket.io server(`api.cluster-fluster.com`) |
|
|
259
|
+
| 手機端 | Web PWA — 掃 QR 就能用,免裝 app | 原生 iOS / Android app |
|
|
260
|
+
| 支援 agent | Claude Code | Claude Code、Codex、Gemini、通用 ACP |
|
|
261
|
+
| 語音輸入 | — | 有 |
|
|
262
|
+
| 多 session 視覺化 dashboard | 有 — 跨視窗聚合 | 各 session 獨立管 |
|
|
263
|
+
| 取代 `claude` 嗎 | 不取代 — hooks 並存 | 取代,自己 spawn `claude` |
|
|
264
|
+
| 遠端 spawn(人不在桌前也能起新 session) | — | 有(`happy daemon`) |
|
|
265
|
+
| 加密 relay | 有(X25519 + NaCl secretbox) | 有(X25519 + NaCl secretbox + AES-GCM) |
|
|
266
|
+
|
|
267
|
+
想要打磨好的手機原生體驗、跨多個 AI agent、不介意 SaaS relay → 用 Happy。住在 VSCode 裡、想要一張 dashboard 收齊多個並行 panel、想 self-host 到自己 CF → 用 Miki-Moni。
|
|
268
|
+
|
|
269
|
+
## 授權
|
|
270
|
+
|
|
271
|
+
MIT — 見 [LICENSE](LICENSE)。
|
|
272
|
+
|
|
273
|
+
## Credits
|
|
274
|
+
|
|
275
|
+
用 [Anthropic Claude](https://claude.ai/code) 透過 [Claude Code](https://github.com/anthropics/claude-code) 寫出來的。
|
package/bin/miki.mjs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `miki` global CLI shim.
|
|
3
|
+
//
|
|
4
|
+
// After `npm link` (run once from the cc-hub repo), this file is reachable
|
|
5
|
+
// on PATH as `miki`. We invoke `node` directly with tsx as an ESM loader,
|
|
6
|
+
// avoiding the `.cmd` shim — Node 24+ forbids spawning .cmd/.bat for
|
|
7
|
+
// security, so we point at tsx's own JS entry instead.
|
|
8
|
+
//
|
|
9
|
+
// process.cwd() is preserved by inherit: subcommand logic (e.g. miki claude →
|
|
10
|
+
// findLatestSessionInCwd) reads process.cwd() from the spawned child, which
|
|
11
|
+
// inherits the caller's directory. That's the whole point — `miki claude`
|
|
12
|
+
// from anywhere acts on that anywhere's cwd.
|
|
13
|
+
|
|
14
|
+
import { spawn } from "node:child_process";
|
|
15
|
+
import { createRequire } from "node:module";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
|
|
19
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const projectRoot = path.join(here, "..");
|
|
21
|
+
const target = path.join(projectRoot, "src", "cli", "miki.ts");
|
|
22
|
+
|
|
23
|
+
// Resolve tsx's JS entry via its package.json — works regardless of pnpm's
|
|
24
|
+
// .pnpm-store layout or Windows path quirks. tsx's `bin` is a plain string
|
|
25
|
+
// pointing at its real .mjs CLI entry; older packages use `bin: { name: path }`
|
|
26
|
+
// so we handle both shapes. Spawning that with node skips the .cmd wrapper
|
|
27
|
+
// entirely (Node 24 forbids .cmd spawn).
|
|
28
|
+
const require = createRequire(import.meta.url);
|
|
29
|
+
const tsxPkgPath = require.resolve("tsx/package.json", { paths: [projectRoot] });
|
|
30
|
+
const tsxPkg = require(tsxPkgPath);
|
|
31
|
+
const tsxBinRel = typeof tsxPkg.bin === "string" ? tsxPkg.bin : tsxPkg.bin?.tsx;
|
|
32
|
+
if (!tsxBinRel) {
|
|
33
|
+
console.error(`miki: could not locate tsx CLI entry in ${tsxPkgPath}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
const tsxBin = path.join(path.dirname(tsxPkgPath), tsxBinRel);
|
|
37
|
+
|
|
38
|
+
const child = spawn(process.execPath, [tsxBin, target, ...process.argv.slice(2)], {
|
|
39
|
+
stdio: "inherit",
|
|
40
|
+
windowsHide: true,
|
|
41
|
+
});
|
|
42
|
+
child.on("exit", (code, signal) => {
|
|
43
|
+
if (signal) process.kill(process.pid, signal);
|
|
44
|
+
else process.exit(code ?? 0);
|
|
45
|
+
});
|
|
46
|
+
child.on("error", (err) => {
|
|
47
|
+
console.error(`miki: failed to spawn node\n${err.message}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
miki-moni favicon — same sleeping cat as web/assets/miki-cat.svg, but with
|
|
3
|
+
slightly heavier strokes so the line art survives 16×16 rendering in the
|
|
4
|
+
browser tab. Single Z (drop the smaller second Z — invisible at favicon
|
|
5
|
+
size and just adds noise).
|
|
6
|
+
-->
|
|
7
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
|
8
|
+
fill="none" stroke="#1c2024" stroke-width="2"
|
|
9
|
+
stroke-linecap="round" stroke-linejoin="round">
|
|
10
|
+
<path d="M4.5 13.2 L6 10 L7.6 13 L9.6 11 L11.4 13 C14.5 13 18.5 13.6 20 15.8 C20.8 17 20.4 18.3 18.6 18.7 C16 19.2 7 19.2 5.4 18.4 C3.8 17.5 3.6 14.5 4.5 13.2 Z"/>
|
|
11
|
+
<path d="M6.8 15.2 q0.9 0.7 1.8 0" stroke-width="1.6"/>
|
|
12
|
+
<path d="M15 7 h2.4 l-2.4 2.6 h2.4" stroke-width="1.6"/>
|
|
13
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.shrink{flex-shrink:1}.grow{flex-grow:1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.resize{resize:both}.break-all{word-break:break-all}.border{border-width:1px}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.outline{outline-style:solid}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}:root{--sl1: #fcfcfd;--sl2: #f9f9fb;--sl3: #f0f0f3;--sl4: #e8e8ec;--sl5: #e0e1e6;--sl6: #d9d9e0;--sl7: #cdced6;--sl8: #b9bbc6;--sl9: #8b8d98;--sl10: #80828d;--sl11: #60646c;--sl12: #1c2024;--bg: #ffffff;--bg-elev: var(--sl1);--card: var(--sl2);--border: var(--sl4);--border-hi: var(--sl7);--fg: var(--sl12);--fg-muted: var(--sl11);--fg-subtle: var(--sl9);--btn-bg: var(--sl3);--cell-bg: rgba(255, 255, 255, .7);--cell-bg-hover: rgba(255, 255, 255, .95);--cell-bg-stale: rgba(252, 220, 230, .85);--cell-bg-stale-hover: rgba(248, 205, 220, .95);--pass: #1f7a4d;--warn: #a3661f;--neutral: #4b556a;--accent: #b3261e}[data-theme=dark]{--sl1: #000000;--sl2: #161618;--sl3: #1f2024;--sl4: #292a2f;--sl5: #3a3b40;--sl6: #4a4a4f;--sl7: #585860;--sl8: #6f6f76;--sl9: #858589;--sl10: #9f9fa3;--sl11: #c8c8c8;--sl12: #e6e6e6;--bg: var(--sl1);--bg-elev: var(--sl2);--card: var(--sl2);--border: var(--sl5);--border-hi: var(--sl6);--fg: var(--sl12);--fg-muted: var(--sl11);--fg-subtle: var(--sl9);--btn-bg: var(--sl3);--cell-bg: var(--sl2);--cell-bg-hover: var(--sl3);--cell-bg-stale: rgba(244, 71, 71, .1);--cell-bg-stale-hover: rgba(244, 71, 71, .16);--pass: #4ec9b0;--warn: #ce9178;--neutral: #569cd6;--accent: #f44747}*{box-sizing:border-box}html,body{margin:0;padding:0;background:var(--bg);color:var(--fg);font-family:Inter,Noto Sans TC,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;line-height:1.5;font-feature-settings:"tnum" 1,"cv11" 1;-webkit-font-smoothing:antialiased}a{color:inherit;text-decoration:none}a:hover{color:var(--fg)}::-webkit-scrollbar{width:4px;height:4px}::-webkit-scrollbar-thumb{background:var(--sl7);border-radius:2px}::-webkit-scrollbar-thumb:hover{background:var(--sl9)}button{font-family:inherit;cursor:pointer}button:disabled{cursor:not-allowed;opacity:.5}input,textarea,select{font-family:inherit;font-size:13px;color:var(--fg);background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:6px 10px;outline:none;transition:border-color .12s ease}input:hover,textarea:hover,select:hover{border-color:var(--border-hi)}input:focus,textarea:focus,select:focus{border-color:var(--sl8);box-shadow:0 0 0 2px var(--sl3)}textarea{resize:vertical}@media (pointer: coarse){input,textarea,select{font-size:16px!important}}.btn-ghost{display:inline-flex;align-items:center;gap:6px;background:transparent;color:var(--fg-muted);border:none;border-radius:6px;padding:5px 10px;font-size:13px;transition:background .12s ease,color .12s ease}.btn-ghost:hover{background:var(--sl3);color:var(--fg)}.btn-outline{display:inline-flex;align-items:center;gap:6px;height:34px;padding:0 12px;background:var(--bg);color:var(--fg);border:1px solid var(--border);border-radius:7px;font-size:13px;transition:border-color .12s ease,background .12s ease}.btn-outline:hover{border-color:var(--border-hi);background:var(--sl2)}.btn-primary{display:inline-flex;align-items:center;gap:6px;height:34px;padding:0 14px;background:var(--fg);color:var(--bg);border:none;border-radius:7px;font-size:13px;font-weight:500;transition:opacity .12s ease}.btn-primary:hover{opacity:.88}.btn-warn{display:inline-flex;align-items:center;gap:6px;height:34px;padding:0 14px;background:var(--accent);color:#fff;border:none;border-radius:7px;font-size:13px;font-weight:500;transition:opacity .12s ease}.btn-warn:hover{opacity:.9}.header-stat{display:inline-flex;align-items:center;gap:5px;padding:4px 9px;font-size:12px;color:var(--fg-muted);background:transparent;border:1px solid transparent;border-radius:6px;font-family:inherit;cursor:pointer;transition:background .12s ease,color .12s ease}.header-stat:hover,.header-stat.is-active{background:var(--sl3);color:var(--fg)}.header-stat strong{font-weight:600;font-variant-numeric:tabular-nums;color:var(--fg)}.header-stat .dot{width:6px;height:6px}.section-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--fg-subtle)}.card{background:var(--bg);border:1px solid var(--border);border-radius:8px;transition:border-color .12s ease}.card:hover{border-color:var(--border-hi)}.dot{display:inline-block;width:8px;height:8px;border-radius:50%;flex-shrink:0}.dot-active{background:var(--pass)}.dot-waiting{background:var(--warn)}.dot-idle{background:var(--neutral)}.dot-stale{background:var(--accent)}.toolbox{border:1px solid rgba(180,130,40,.22);border-radius:6px;overflow:hidden;background:transparent;font-size:12px}.toolbox-head{display:flex;align-items:center;gap:8px;padding:6px 10px;background:transparent;border-bottom:1px solid rgba(180,130,40,.18);font-weight:500;color:var(--fg-muted)}.toolbox-row{display:grid;grid-template-columns:36px 1fr;align-items:start}.toolbox-row+.toolbox-row{border-top:1px solid rgba(180,130,40,.18)}.toolbox-label{padding:6px 8px;background:transparent;border-right:1px solid rgba(180,130,40,.18);font-size:10px;font-weight:600;color:var(--fg-subtle);text-transform:uppercase;letter-spacing:.08em;text-align:center}.toolbox-content{padding:6px 10px;font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;font-size:12px;white-space:pre-wrap;word-break:break-word;color:var(--fg);max-height:260px;overflow-y:auto}.toolbox-content.is-error{color:var(--accent)}.md{color:var(--fg);font-size:13px}.md>*{margin:0 0 6px}.md>*:last-child{margin-bottom:0}.md h1,.md h2,.md h3,.md h4{margin:10px 0 4px;font-weight:600;color:var(--fg);line-height:1.3}.md h1{font-size:17px}.md h2{font-size:15px}.md h3{font-size:14px}.md h4{font-size:13px;color:var(--fg-muted)}.md code{font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;font-size:.875em;padding:1px 5px;background:var(--sl3);border-radius:3px}.md pre{padding:10px 12px;background:var(--sl2);border:1px solid var(--border);border-radius:6px;overflow-x:auto}.md pre code{background:none;padding:0;font-size:12px;color:var(--fg)}.md ul,.md ol{padding-left:20px}.md li{margin:2px 0}.md blockquote{padding:4px 0 4px 12px;border-left:3px solid var(--border-hi);color:var(--fg-muted)}.md a{color:var(--neutral);text-decoration:underline;text-decoration-color:var(--border-hi)}.md a:hover{text-decoration-color:var(--neutral)}.md table{width:100%;border-collapse:collapse;font-size:12px}.md th,.md td{padding:6px 10px;border:1px solid var(--border);text-align:left}.md th{background:var(--sl2);font-weight:600}.md hr{border:0;border-top:1px solid var(--border)}.tabbar{display:flex;align-items:stretch;gap:2px;border-bottom:1px solid var(--border);overflow-x:auto;scrollbar-width:thin}.tab{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;font-size:13px;color:var(--fg-muted);border:1px solid transparent;border-bottom:none;border-top-left-radius:6px;border-top-right-radius:6px;margin-bottom:-1px;white-space:nowrap;transition:background .12s ease,color .12s ease;cursor:pointer;background:transparent}.tab:hover{background:var(--sl2);color:var(--fg)}.tab.active{background:var(--bg);color:var(--fg);font-weight:500;border-color:var(--border);border-bottom-color:var(--bg)}.tab .tab-close{margin-left:4px;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;border-radius:3px;color:var(--fg-subtle);font-size:14px;line-height:1}.tab .tab-close:hover{background:var(--sl4);color:var(--accent)}.cwd-group{border-radius:10px;padding:12px;margin-bottom:16px;border:1px solid var(--border)}.cwd-group-header{display:flex;align-items:center;gap:8px;margin-bottom:10px;font-size:12px;color:var(--fg-muted);font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace}.cwd-group-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;align-items:stretch}.cwd-group-grid>.cell{height:100%}@media (max-width: 720px){.cwd-group-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (max-width: 480px){.cwd-group-grid{grid-template-columns:1fr}.cwd-group-grid>.cell{height:auto}.cell-turn-assistant .cell-turn-body{-webkit-line-clamp:3}.cell-preview{overflow:visible}}html,body{overflow-x:hidden;max-width:100vw}.app-shell{max-width:1280px;margin:0 auto;padding:16px 24px 64px}@media (max-width: 480px){.app-shell{padding:10px 10px 48px}}.cell{min-width:0;overflow:hidden}.cell-preview,.cell-convo,.cell-head,.cell-cwd,.cell-foot,.cell-turn{min-width:0}.cell-turn-body{min-width:0;overflow-wrap:anywhere}.cell-title{min-width:0}.md,.md *{overflow-wrap:anywhere;max-width:100%}.md pre,.md code{white-space:pre-wrap;word-break:break-all}.cell{background:var(--cell-bg);border:1px solid var(--border);border-radius:0;padding:10px 12px;cursor:default;transition:border-color .12s ease,background .12s ease,transform .06s ease;position:relative;display:flex;flex-direction:column;min-height:130px}.cell:hover{border-color:var(--border-hi);background:var(--cell-bg-hover)}.cell:active{transform:scale(.995)}.cell-head{display:flex;align-items:center;gap:6px;margin-bottom:4px}.cell-title{font-weight:500;font-size:13px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cell-status{font-size:10px;color:var(--fg-subtle);text-transform:uppercase;letter-spacing:.06em}.cell-cwd{font-size:10px;font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cell-preview{flex:1;margin-top:4px;color:var(--fg-muted);font-size:12px;line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical}.cell-empty{color:var(--fg-subtle);font-style:italic}.cell-convo{display:flex;flex-direction:column;gap:6px;-webkit-line-clamp:unset;-webkit-box-orient:unset;overflow:hidden}.cell-turn{display:grid;grid-template-columns:56px 1fr;gap:6px;align-items:start;padding:4px 6px;border-radius:4px;font-size:11.5px;line-height:1.45}.cell-turn-role{font-size:9.5px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;padding-top:1px;display:flex;flex-direction:column;gap:2px}.cell-turn-ts{font-size:9px;font-weight:400;text-transform:none;letter-spacing:0;color:var(--fg-subtle);font-family:ui-monospace,monospace}.streaming-cursor{display:inline-block;margin-left:2px;color:var(--pass);font-weight:600;animation:stream-blink 1s steps(2,start) infinite}@keyframes stream-blink{to{visibility:hidden}}.cell-turn-body{color:var(--fg);overflow:hidden;display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;word-break:break-word}.cell-turn-user{background:var(--turn-bg-user)}.cell-turn-user .cell-turn-role{color:var(--neutral, #607080)}.cell-turn-assistant{background:var(--turn-bg-assistant)}.cell-turn-assistant .cell-turn-role{color:var(--pass, #1f8a5a)}.cell-turn-assistant,.cell-turn-user{min-height:76px}.cell-turn-tool{background:#b4822812;padding:2px 6px;font-size:11px}.cell-turn-tool .cell-turn-role{color:#a8740d}.cell-tool-inline{display:flex;align-items:baseline;gap:3px;font-size:10px;color:var(--fg-muted);min-width:0;white-space:nowrap;flex:1 1 auto}.cell-tool-inline .cell-tool-icon{color:#a8740d;flex-shrink:0}.cell-tool-inline strong{font-weight:600;color:var(--fg);flex-shrink:0}.cell-tool-inline .cell-tool-desc{color:var(--fg-subtle);flex:1 1 0;min-width:0;overflow:hidden;text-overflow:ellipsis}.cell-tool-inline .cell-tool-ts{color:var(--fg-subtle);font-family:ui-monospace,monospace;flex-shrink:0;margin-left:2px}.cell-turn-tool .cell-turn-body{-webkit-line-clamp:1;display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}@keyframes cell-flash-yellow{0%,to{background:transparent;box-shadow:none}50%{background:#ffe16e52;box-shadow:0 0 0 2px #dcaa1e59}}.cell-flash{animation:cell-flash-yellow 1s ease-in-out infinite}.cell-activity{display:inline-flex;align-items:center;gap:4px;margin-left:6px;padding:0;font-size:10px;font-weight:500;color:#3b82f6;background:transparent;border:none;white-space:nowrap;animation:cell-activity-text-blink 1.4s ease-in-out infinite}.cell-activity-dot{width:6px;height:6px;border-radius:50%;background:#3b82f6}@keyframes cell-activity-text-blink{0%,to{opacity:.45}50%{opacity:1}}@keyframes cell-flash-update{0%,to{background:transparent;box-shadow:none}20%{background:#5096ff38;box-shadow:0 0 0 2px #3c82eb52}60%{background:#5096ff1a;box-shadow:0 0 0 2px #3c82eb29}}.cell-flash-update{animation:cell-flash-update 1.2s ease-in-out 1}.client-badge{display:inline-flex;align-items:center;font-size:10px;font-weight:600;line-height:1;padding:2px 6px;border-radius:10px;border:1px solid transparent;cursor:pointer;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;user-select:none;font-family:inherit;transition:filter .15s}.client-badge:hover{filter:brightness(.95)}.client-badge-md{font-size:11px;padding:3px 8px}.client-badge-vscode{background:#286ec81f;color:#2c5fa8;border-color:#286ec840}.client-badge-cli{background:#b4822829;color:#8a5d0a;border-color:#b4822859}.client-badge-wrap{background:#6e3cc824;color:#5a2db8;border-color:#6e3cc852;cursor:default}.ask-modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;background:#0000002e}.ask-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:min(560px,90vw);max-height:80vh;overflow-y:auto;background:var(--bg);border:1px solid var(--border-hi);border-radius:10px;box-shadow:0 12px 40px #00000047;padding:16px;z-index:1001}.ask-tab{font-size:11px;padding:4px 10px;border-radius:999px;border:1px solid var(--border);background:transparent;color:var(--fg-muted);cursor:pointer}.ask-tab:hover{background:var(--sl3)}.ask-tab-active{background:var(--sl3);color:var(--fg);border-color:var(--border-hi);font-weight:500}.ask-tab-done{color:var(--pass)}.ask-option{display:grid;grid-template-columns:22px 1fr;gap:8px;align-items:start;padding:8px 10px;border:1px solid var(--border);border-radius:6px;cursor:pointer;background:var(--bg);transition:background .12s,border-color .12s}.ask-option:hover{background:var(--sl2)}.ask-option-checked{background:#286ec80f;border-color:#286ec866}.ask-option input{margin-top:3px}@keyframes cell-ask-pulse{0%,to{background:transparent;box-shadow:0 0 #dc323200}50%{background:#dc323214;box-shadow:0 0 0 2px #dc323273}}.cell-ask-pending{animation:cell-ask-pulse 1.4s ease-in-out infinite}.cell-waiting-stale{background:var(--cell-bg-stale)}.cell-waiting-stale:hover{background:var(--cell-bg-stale-hover)}.cell-ask-bell{font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;border:1px solid rgba(220,50,50,.45);background:#dc32321a;color:#b3261e;cursor:pointer;white-space:nowrap;margin-left:4px;animation:cell-ask-bell-pulse 1.2s ease-in-out infinite}.cell-ask-bell:hover{background:#dc32322e}@keyframes cell-ask-bell-pulse{0%,to{transform:scale(1)}50%{transform:scale(1.06)}}.cell-modal-ask-banner{display:flex;align-items:center;gap:12px;padding:10px 16px;background:#dc32321a;border-bottom:1px solid rgba(220,50,50,.35);font-size:12px;color:#b3261e;flex-shrink:0}.cell-modal-ask-banner>span{flex:1;font-weight:500}.pmode-badge-auto{background:#288c6424;color:#1f7a4d;border-color:#288c6459;cursor:default}.pmode-badge-bypass{background:#c8322824;color:#b3261e;border-color:#c8322861;cursor:default}.pmode-badge-plan{background:#286ec824;color:#2c5fa8;border-color:#286ec852;cursor:default}.pmode-chip-wrap{position:relative;display:inline-flex;align-items:center}.pmode-chip{display:inline-flex;align-items:center;gap:4px;padding:1px 6px;font-size:9.5px;font-weight:600;letter-spacing:.02em;border-radius:3px;border:1px solid transparent;font-family:ui-monospace,monospace;white-space:nowrap;cursor:pointer;transition:filter .15s}.pmode-chip svg{flex-shrink:0}.pmode-chip:hover:not(:disabled){filter:brightness(.94)}.pmode-chip:disabled{cursor:default;opacity:.6}.pmode-menu{position:absolute;bottom:calc(100% + 4px);right:0;z-index:30;min-width:260px;max-width:calc(100vw - 16px);background:var(--bg);border:1px solid var(--border);border-radius:6px;box-shadow:0 8px 24px #00000029;padding:6px;display:flex;flex-direction:column;gap:1px;font-family:inherit}.pmode-menu.pmode-menu-left{right:auto;left:0;min-width:220px}.pmode-menu-head{font-size:11px;font-weight:600;color:var(--fg-subtle);letter-spacing:.04em;padding:4px 8px 6px;border-bottom:1px solid var(--border);margin-bottom:2px}.pmode-menu-item{display:flex;align-items:center;gap:10px;padding:7px 8px;color:var(--fg);background:transparent;border:1px solid transparent;border-radius:4px;text-align:left;cursor:pointer;font-family:inherit;width:100%}.pmode-menu-item:hover:not(:disabled){background:var(--sl3)}.pmode-menu-item:disabled{cursor:default;opacity:.5}.pmode-menu-item.is-current{background:var(--sl3)}.pmode-menu-icon{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px}.pmode-menu-item.pmode-chip-default .pmode-menu-icon{color:#60646c}.pmode-menu-item.pmode-chip-auto .pmode-menu-icon{color:#1f7a4d}.pmode-menu-item.pmode-chip-plan .pmode-menu-icon{color:#2c5fa8}.pmode-menu-item.pmode-chip-autopick .pmode-menu-icon{color:#7b3fb8}.pmode-menu-item.pmode-chip-bypass .pmode-menu-icon{color:#b3261e}[data-theme=dark] .pmode-menu-item.pmode-chip-default .pmode-menu-icon{color:#c8c8c8}[data-theme=dark] .pmode-menu-item.pmode-chip-auto .pmode-menu-icon{color:#4ec9b0}[data-theme=dark] .pmode-menu-item.pmode-chip-plan .pmode-menu-icon{color:#569cd6}[data-theme=dark] .pmode-menu-item.pmode-chip-autopick .pmode-menu-icon{color:#c586c0}[data-theme=dark] .pmode-menu-item.pmode-chip-bypass .pmode-menu-icon{color:#f44747}.pmode-menu-text{flex:1;min-width:0}.pmode-menu-label{font-size:12px;font-weight:600;color:var(--fg);line-height:1.2}.pmode-menu-desc{font-size:10.5px;color:var(--fg-subtle);margin-top:1px;line-height:1.3}.pmode-check{margin-left:6px;color:var(--pass);font-weight:700;flex-shrink:0}.pmode-err{font-size:10px;color:var(--accent);padding:4px 8px;border-top:1px solid var(--border);margin-top:4px}.pmode-chip-default{background:#7882961a;color:#60646c;border-color:#78829640}.pmode-chip-auto{background:#288c641f;color:#1f7a4d;border-color:#288c6452}.pmode-chip-bypass{background:#c832281f;color:#b3261e;border-color:#c832285c}.pmode-chip-plan{background:#286ec81f;color:#2c5fa8;border-color:#286ec84d}.pmode-chip-autopick{background:#aa6edc1f;color:#7b3fb8;border-color:#aa6edc4d}.pmode-chip-neutral{background:var(--sl2);color:var(--fg-muted);border-color:var(--border)}.pmode-chip-neutral:hover:not(:disabled){background:var(--sl3);border-color:var(--border-hi);color:var(--fg);filter:none}:root{--turn-bg-user: rgba(120, 130, 150, .08);--turn-bg-assistant: rgba(40, 140, 100, .06);--turn-bg-tool: rgba(200, 160, 50, .1)}[data-theme=dark]{--turn-bg-user: transparent;--turn-bg-assistant: transparent;--turn-bg-tool: transparent}.turn-bg-user{background:var(--turn-bg-user)}.turn-bg-assistant{background:var(--turn-bg-assistant)}.turn-bg-tool{background:var(--turn-bg-tool)}.turn-row{display:flex;padding:5px 12px}.turn-row.turn-row-user{justify-content:flex-end}.turn-row.turn-row-other{justify-content:flex-start}.turn-bubble{max-width:85%;min-width:0;padding:8px 12px;border:1px solid var(--border);border-radius:14px}.turn-row-user .turn-bubble{border-top-right-radius:4px}.turn-row-other .turn-bubble{border-top-left-radius:4px}.turn-bubble.turn-bubble-tool{padding:4px 8px}.turn-bubble.turn-bg-user{background:var(--turn-bg-user)}.turn-bubble.turn-bg-assistant{background:var(--turn-bg-assistant)}.turn-bubble.turn-bg-tool{background:var(--turn-bg-tool)}[data-theme=dark] .turn-bubble.turn-bg-user{background:#1f2937}[data-theme=dark] .turn-bubble.turn-bg-assistant{background:#0f1f1a}[data-theme=dark] .turn-bubble.turn-bg-tool{background:#1f1b12}@media (max-width: 640px){.turn-row{padding:4px 8px}.turn-bubble{max-width:92%;padding:7px 11px}}.cell-foot{display:flex;align-items:center;gap:6px;font-size:10px;color:var(--fg-subtle);font-family:ui-monospace,monospace;margin:6px -12px -10px;padding:6px 12px 10px}@keyframes cell-status-pulse{0%,to{opacity:1}50%{opacity:.4}}.cell-foot-active .cell-status{color:var(--pass);animation:cell-status-pulse 1.4s ease-in-out infinite}.cell-foot-waiting .cell-status{color:var(--accent);font-weight:600}.cell-open-tab{margin-left:auto;padding:2px 6px;font-size:10px;color:var(--fg-subtle);border:1px solid var(--border);border-radius:4px;background:var(--bg)}.cell-open-tab:hover{color:var(--fg);border-color:var(--border-hi)}.icon-btn{display:inline-flex;align-items:center;justify-content:center;line-height:1}.icon-btn svg{display:block}.cell-send-btn{margin-left:auto;padding:2px 8px;font-size:10px;color:var(--fg);border:1px solid var(--border);border-radius:4px;background:var(--bg);cursor:pointer;font-family:inherit}.cell-send-btn:hover{border-color:var(--pass, #1f8a5a);color:var(--pass, #1f8a5a);background:var(--bg)}.cell-wrap-btn-icon{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;font-size:12px;line-height:1;color:var(--fg);border:1px solid var(--border);border-radius:4px;background:var(--bg);cursor:pointer;font-family:inherit;transition:border-color .12s ease,color .12s ease,transform .08s ease}.cell-wrap-btn-icon:hover:not(:disabled){border-color:var(--pass, #1f7a4d);transform:scale(1.06)}.cell-wrap-btn-icon:disabled{opacity:.6;cursor:progress}.cell-clickable{cursor:pointer}@keyframes miki-toast-in{0%{opacity:0;transform:translate(-50%,-8px)}to{opacity:1;transform:translate(-50%)}}.spawn-pending-spinner{display:inline-block;font-size:14px;margin-top:1px;color:var(--pass);animation:miki-spin 1.1s linear infinite}@keyframes miki-spin{to{transform:rotate(360deg)}}.settings-popover::-webkit-scrollbar{width:8px}.settings-popover::-webkit-scrollbar-thumb{background:var(--sl8);border-radius:4px}.settings-popover::-webkit-scrollbar-thumb:hover{background:var(--sl10)}.settings-popover{scrollbar-width:thin}.cell-modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:60;background:#00000052;display:flex;align-items:center;justify-content:center;padding:24px;animation:cell-modal-fade-in .12s ease-out}@keyframes cell-modal-fade-in{0%{opacity:0}to{opacity:1}}.cell-modal{position:relative;width:min(760px,100%);height:min(85vh,900px);background:var(--bg);border:1px solid var(--border);border-radius:8px;box-shadow:0 20px 60px #00000047;display:flex;flex-direction:column;overflow:hidden;overscroll-behavior:contain}.cell-modal-backdrop{overscroll-behavior:contain}@media (max-width: 640px){.cell-modal-backdrop{padding:0}.cell-modal{width:100%;height:100vh;height:100dvh;max-width:100vw;max-height:100vh;max-height:100dvh;border:none;border-radius:0;box-shadow:none}}.cell-modal-close{position:absolute;top:8px;right:8px;z-index:1;width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:18px;line-height:1;color:var(--fg-subtle);background:transparent;border:1px solid transparent;border-radius:4px;cursor:pointer}@media (max-width: 640px){.cell-modal-close{top:4px;right:4px;width:24px;height:24px;font-size:16px}}.cell-modal-close:hover{color:var(--fg);background:var(--bg-subtle);border-color:var(--border)}.popover-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:50;background:#0000000a}.popover{position:fixed;z-index:51;background:var(--bg);border:1px solid var(--border-hi);border-radius:8px;box-shadow:0 8px 24px #0000001a,0 2px 6px #0000000d;padding:12px;width:360px;max-width:calc(100vw - 16px)}@media (max-width: 560px){.popover-backdrop{background:#00000052}.popover{width:calc(100vw - 16px)}}.log-entry{padding:4px 12px;font-size:12px;font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace}.log-entry-info{color:var(--fg-muted)}.log-entry-warn{color:var(--warn)}.log-entry-error{color:var(--accent)}.log-time,.log-ctx{color:var(--fg-subtle)}@keyframes mm-spin{to{transform:rotate(360deg)}}
|