Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
9de15e1
docs: voice commands go to a resident session that speaks as the buddy
longzhi Oct 12, 2026
8dd9af1
Merge remote-tracking branch 'origin/main' into claude/web-body-voice…
longzhi Oct 13, 2026
1c61395
docs: ADR-0015 delivers voice commands through codex app-server
longzhi Oct 13, 2026
d46347a
docs: ADR-0015 no longer assumes an ES7210
longzhi Oct 13, 2026
d56a3db
Add a microphone bring-up diagnostic: device.mic.record
longzhi Oct 13, 2026
243af87
Add an experimental resident session driven over codex app-server
longzhi Oct 13, 2026
3567037
docs: describe the resident session
longzhi Oct 13, 2026
a3ba01c
Take the mic's DMA ring from the heap, not the main stack
longzhi Oct 13, 2026
ba9d298
Start the resident app-server with no tools but ours
longzhi Oct 13, 2026
967657e
Merge branch 'worktree-agent-ac87c7718258a240f' into claude/web-body-…
longzhi Oct 13, 2026
266fe18
Capture the mic linearly into PSRAM, and report why when it fails
longzhi Oct 13, 2026
d5fcabc
Count the mic capture by descriptor lengths, not buffer size
longzhi Oct 13, 2026
0526c0b
Size the mic buffer for an EOF every 4094 bytes
longzhi Oct 13, 2026
15f5a6f
Record the mic result, explain the 4094-byte EOF, judge the mic slot
longzhi Oct 13, 2026
f300232
Merge origin/main into the mic bring-up branch
longzhi Oct 13, 2026
7077be6
Merge remote-tracking branch 'origin/main' into claude/web-body-voice…
longzhi Oct 13, 2026
79d2d3e
docs: renumber the voice ADR to 0017
longzhi Oct 13, 2026
ca84fc2
docs: the box-to-computer messages for a voice command
longzhi Oct 13, 2026
0f3bed9
Merge remote-tracking branch 'origin/claude/mic-bringup' into worktre…
longzhi Oct 13, 2026
0c7308f
docs: ADR-0017 says where recognition runs and allows the user's own
longzhi Oct 13, 2026
b6a3f84
Take push-to-talk voice commands from the box
longzhi Oct 13, 2026
2daaadb
Remove test recordings committed by mistake
longzhi Oct 13, 2026
40f6ddd
Merge branch 'worktree-agent-a7c27af260985ab8c' into claude/web-body-…
longzhi Oct 13, 2026
2d0a994
Push-to-talk voice commands on the box
longzhi Oct 13, 2026
eeecac9
Merge branch 'worktree-agent-a94da637ae9c47c0b' into claude/web-body-…
longzhi Oct 13, 2026
53d59dc
Keep voice off the UART bridge and refuse a busy turn at its start
longzhi Oct 13, 2026
452e98b
docs: spoken reply audio as MP3 chunks over the link
longzhi Oct 13, 2026
13a34d5
docs: mark a cut-short spoken reply
longzhi Oct 13, 2026
1942ce7
Speak voice replies: stream synthesized audio to the box as voice.audio
longzhi Oct 13, 2026
5e2d1d1
docs: voice.done follows the last reply's audio
longzhi Oct 13, 2026
32e392b
Draw Chinese on the box with a 12 px pixel font
longzhi Oct 13, 2026
506534a
Merge branch 'worktree-agent-a872696b5cd821176' into claude/web-body-…
longzhi Oct 13, 2026
91b38cc
Play spoken replies' MP3 on the box
longzhi Oct 13, 2026
084e670
Merge branch 'worktree-agent-a77928687a3ec6c78' into claude/web-body-…
longzhi Oct 13, 2026
4ea17f4
Pace reply audio into the link, and keep THINKING moving
longzhi Oct 13, 2026
e8e5230
Keep a reply playing past a lost chunk, and UART bytes out of USB lines
longzhi Oct 13, 2026
8f7b9da
Never fork the resident thread when it is open elsewhere
longzhi Oct 13, 2026
855bea4
Give the resident session its own Codex home
longzhi Oct 13, 2026
b4ee5b8
Answer voice commands sooner
longzhi Oct 13, 2026
a776283
docs: the resident session's model is set, not Codex's default
longzhi Oct 13, 2026
7a640b6
Show the conversation with the buddy in both Apps
longzhi Oct 14, 2026
c38ca0f
Resident session: no made-up facts, live web search, recent Activities
longzhi Oct 14, 2026
4c17eab
Merge branch 'worktree-agent-a5fa6ea96c410d725' into claude/web-body-…
longzhi Oct 14, 2026
8bb8ce3
Hold an Agent's news back during a voice command
longzhi Oct 14, 2026
1b3dc9d
Speak replies in each Character's own ElevenLabs voice
longzhi Oct 14, 2026
59f489e
Fix a test still expecting an Edge voice
longzhi Oct 14, 2026
52d0a63
Give a Character without an ElevenLabs voice the stock one
longzhi Oct 14, 2026
d9c0d45
Pick a stock voice by gender for a Character ElevenLabs can't speak
longzhi Oct 14, 2026
088e2c7
Update the Conversation tab by itself and say when voice needs Codex
longzhi Oct 14, 2026
57868c4
Let the resident session run on Claude Code with the user's own API key
longzhi Oct 14, 2026
5ad219e
Pick who answers voice commands and add the Anthropic API key, in bot…
longzhi Oct 14, 2026
617c5e1
Tell the Claude resident its time zone, and end a turn after speak
longzhi Oct 14, 2026
66dfbb5
Reset the Conversation tab's scroll when the answering Agent changes
longzhi Oct 14, 2026
14bde46
Speak spoken replies as the Character the buddy wears
longzhi Oct 14, 2026
4ad558b
Remind the Claude resident of its Character's language on every command
longzhi Oct 14, 2026
dfea772
Report why the box last started, with a mark for a panic
longzhi Oct 14, 2026
04d9f03
Keep shell snapshots and skills out of the resident Codex session
longzhi Oct 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The product name, in use since 2026-09-15 and written as one word since 2026-10-
_Avoid_: Vibe Buddy (two words, the spelling until 2026-10-09), AgentBeacon, beacond, beacon-hook (old names, only when talking about history), 氛围助手 ("vibe assistant"; that is a description, not a name)

**The buddy (氛围小助手)**:
The only anthropomorphic presence on the device: the one who watches the Agents and speaks up for you. It is a role, not a look; at any moment it wears exactly one Character. Before 2026-09-15 it was called 小灯灵 ("little lamp sprite"); in code it is `buddy`.
The only anthropomorphic presence on the device: the one who watches the Agents and speaks up for you. It is a role, not a look; at any moment it wears exactly one Character. It speaks in two ways: lines, written in advance, when an announcement fires; and spoken replies, written on the spot by the resident session in its persona, when the user talks to it. Before 2026-09-15 it was called 小灯灵 ("little lamp sprite"); in code it is `buddy`.
_Avoid_: 小灯灵 (old name), pet (fine as a generic word, not when referring to this role), Pet, Codex pet, assistant (on its own it means an Agent, see below)

**Character (角色)**:
Expand All @@ -23,7 +23,7 @@ A local AI assistant observed by VibeBuddy, such as Codex or Claude Code. An Age
_Avoid_: client, AI, assistant; also never means a subagent inside Claude Code

**Adapter**:
The layer that translates one Agent's events into VibeBuddy domain concepts, consisting of the local privacy-filtering script and the event mapping inside the daemon. There is one Adapter per Agent; aggregation logic does not belong to the Adapter.
The layer that translates between one Agent and VibeBuddy domain concepts, in both directions: the Agent's events into VibeBuddy concepts (the local privacy-filtering script and the event mapping inside the daemon), and voice commands and spoken replies into and out of that Agent's Sessions. There is one Adapter per Agent; aggregation logic does not belong to the Adapter.
_Avoid_: integration, plugin, connector

**App**:
Expand Down Expand Up @@ -65,11 +65,11 @@ _Avoid_: environment, entry point, entrypoint, surface, terminal (a terminal is
### Sound

**Announcement voice (播报音色)**:
The single voice the buddy speaks with, shared by all its lines. It belongs to the Character and is never picked on its own: changing voice means changing Character.
The single voice the buddy speaks with, shared by all its lines and spoken replies. It belongs to the Character and is never picked on its own: changing voice means changing Character.
_Avoid_: sound, speech (speech means the lines themselves), TTS

**Persona (人设)**:
A Character's personality and way of talking, written down so that its lines can be written in it. A persona never sees what the Agents are working on.
A Character's personality and way of talking, written down so that its lines can be written in it. Lines written in a persona never see what the Agents are working on; only the resident session, writing spoken replies in it, does.
_Avoid_: system prompt (that is one way to use a persona), personality setting, style

**Line (台词)**:
Expand All @@ -92,6 +92,24 @@ _Avoid_: do not disturb, DND, sound off
The loudness level of the device speaker, from 20 to 100, stored on the device and kept across reboots; announcements and the Pomodoro chime share it. The App's slider is only a remote control, and it always shows the value the device reports back; the device menu changes it too. The minimum is not zero: the only way to silence the speaker is mute.
_Avoid_: loudness, volume settings (that is a place in the App's UI, not this concept)

### Voice commands

**Voice command (语音指令)**:
One sentence the user speaks to the box, turned into text and handed to the resident session as the user's own input. An Agent is the one who understands and acts on it; VibeBuddy only carries it, and never answers or executes a voice command itself.
_Avoid_: voice assistant (an assistant means an Agent), prompt (that is the Agent's own word), dictation, voice input

**Resident session (常驻会话)**:
The one long-running Agent Session every voice command goes to, so the user never has to say which work they mean. It answers from what it can see of the Activities and hands work on to other Sessions, but does no work itself: it runs no commands and changes no files. There is only one, and it is never restarted to make room: when its context grows long, it is compacted. It is an Agent that speaks as the buddy, in the persona of the Character the user picked, and changes persona when the Character changes; it is never an Activity: it makes no task cards, announcements or stats.
_Avoid_: assistant, concierge, dispatcher, main session

**Brain (大脑)**:
The Agent the resident session runs on: the user's own Codex, or the user's own Claude Code with their own Anthropic API key (never a Claude subscription). The user picks one, or leaves it automatic: Codex when it is ready, else Claude Code. Each brain has its own resident session; the UI calls the setting "Answered by".
_Avoid_: model (that is what the brain runs on), backend, engine, provider

**Spoken reply (口头回复)**:
The short sentence the resident session writes for the user to hear in answer to a voice command, synthesized when it is spoken, in the announcement voice of the Character the user picked. Unlike a line, it is written at the moment it is spoken and may talk about what the Agents are doing.
_Avoid_: line (lines are written in advance), announcement, TTS, response

### Modes

**Mode (模式)**:
Expand Down Expand Up @@ -151,6 +169,12 @@ Fixed wording for the App's English UI and English logs. Use these words when wr
| 固件 / 刷入 | firmware / flash | |
| 配对 / 已配对的电脑 | pair / paired computers | On the box's menu the row is COMPUTERS; "this computer" marks the one the App runs on |
| UART 桥 / 原生 USB | UART bridge / native USB | |
| 语音指令 | voice command | |
| 常驻会话 | resident session | Not shown to users so far; in the UI the buddy is the one you talk to |
| 口头回复 | spoken reply | |
| 对话 | conversation | The settings tab (Conversation) listing voice commands and the buddy's spoken replies; shown only while the resident session is on |
| 由谁回答 / 自动 | Answered by / Automatic | The brain setting on the Conversation tab: Automatic, Codex or Claude Code; "brain" stays a code word |
| Anthropic API 密钥 | Anthropic API key | The user's own key for Claude Code; never "Claude key" or "token", and never a Claude subscription |

### Activity lifecycle

Expand Down
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[workspace]
members = ["daemon", "desktop", "firmware-rs/core", "hook", "manifest", "protocol"]
# The device firmware builds separately with the Xtensa toolchain; see firmware-rs/device.
exclude = ["firmware-rs/device"]
exclude = ["firmware-rs/device", "firmware-rs/vendor/nanomp3-core"]
resolver = "3"

[workspace.package]
Expand All @@ -25,3 +25,6 @@ tokio-serial = "5"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

# The MP3 decoder for spoken replies, patched for the Xtensa backend (see its Cargo.toml).
[patch.crates-io]
nanomp3-core = { path = "firmware-rs/vendor/nanomp3-core" }
12 changes: 12 additions & 0 deletions LESSONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,15 @@ The box's mDNS responder built, passed every host test, and answered nothing on
Before waiting on shared state from a second task, check how many waiters it holds; embassy's `Signal` and the stack's state waits hold one. Let one task own the wait and hand the result on through a channel or a `Signal` only the other task waits on. Diagnostic lines sent with `try_send` on a 16-slot channel also got dropped in the burst at boot, so "no line" didn't mean "never got there": for a diagnosis, send with `.await`.

The same day, the fix and this lesson covered only the mDNS task. The Relay task had waited on `wait_config_up` alongside `address_task` since it was written, so on a box with no Wi-Fi set the two spun the whole time: a poll counter on the box read about 3 million polls every 5 s, and 3 once the Relay read the address from a `Watch` instead. Nothing looked wrong, because the spin ran only while there was no network to notice it on. When a lesson names a pattern, search the code for every other place it already occurs (`grep wait_config`), not just the one that broke. And a `Signal` with one reader is the same trap the next time someone adds a reader: where more than one task needs a value, use a `Watch`, which keeps a waker per reader.

## On the firmware, every new static in internal RAM comes out of the main stack

On 2026-10-13 the first microphone build boot-looped: `Detected a write to the stack guard value on ProCpu`, inside text drawing while the boot loaded the Character's look. With esp-hal the main task's stack is whatever internal DRAM is left between the end of `.bss` and the fixed start of the reclaimed heap (`_stack_end_cpu0` to `_stack_start_cpu0`). The build had added a 24 KB static DMA ring (`dma_rx_stream_buffer!`) and a 2.6 KB task (an async task's locals live in its static pool), and the stack went from 47,784 bytes to 21,032 with nothing in the build saying so. Host tests and the device build both passed.

Before a firmware change that adds statics or `dma_*_buffer!` macros goes to a box, compare `_stack_start_cpu0 - _stack_end_cpu0` (`xtensa-esp32s3-elf-nm` on the release ELF) with main's. A buffer only one feature uses for a few seconds belongs on the heap for those seconds (internal capabilities if DMA touches it), not in `.bss` for the life of the box.

## A full DMA buffer is not a full recording

The microphone bring-up on 2026-10-13 took four flashes after the stack fix. A four-descriptor ring stalled for good: the task that relinked used descriptors only ran when the main loop yielded, and once the DMA reached the end of the chain it stopped. A linear PSRAM buffer then filled every descriptor and still came back short: the I2S receiver signals an EOF every RXEOF_NUM bytes, each EOF closes the descriptor it lands in, and esp-hal leaves RXEOF_NUM at 0xFFE, so the descriptors came back 4080, 14, 4080, 14. I had read the TRM's formula as counting samples and predicted 4080, 4080, 30; the box showed bytes, as ESP-IDF's driver uses the register.

Count received data by the descriptors' lengths, never by the buffer's size, and on the ESP32-S3 expect a receive descriptor to be cut short by a peripheral EOF. What made each flash count was the diagnostic reporting its own state (descriptors handed back, the first lengths, the I2S and DMA registers) instead of only pass or fail: the second and third flashes each named the next cause outright. Put that in the first build of any bring-up, since each flash of a shared box costs a turn.
7 changes: 7 additions & 0 deletions LICENSE-ASSETS
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ terms of the service that produced it:
- characters/*/pack.bin, the Characters' synthesized lines (Volcano Engine
Doubao TTS)

Not covered, because it is a third-party font under its own license:

- firmware-rs/core/fonts/cjk-12px.bin, the firmware's Chinese glyphs: a
subset of Fusion Pixel Font 12 px by TakWolf, built from Ark Pixel Font
and Cubic 11, under the SIL Open Font License 1.1. Its license files are
next to it (OFL.txt, LICENSES/) and ship with the firmware as FONTS.txt.

Neither license grants rights to the name "VibeBuddy" or to the buddy
character as a trademark.

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ cargo run -p vibebuddyd

By default it only listens on `127.0.0.1:7331` and finds the box by its Espressif USB Serial/JTAG ID, `VID:PID 303A:1001`. `VIBEBUDDY_BIND` changes the listen address, `VIBEBUDDY_SERIAL_PORT` names a serial port explicitly, and `VIBEBUDDY_USB_SERIAL` picks one box among several identical ones; without it, the daemon connects to the box that has reported VibeBuddy firmware to it before, and when none of several devices is that box, the app asks which one is. Either variable shows up in the app's Device tab, so a pin left set can't pass for a missing box. A box running other firmware, such as Muse, is written nothing. HTTP `202 Accepted` means the event entered the bounded send queue; whether the box actually received it is what the daemon logs from the box's reply.

Voice commands are experimental and off by default: `VIBEBUDDY_RESIDENT_SESSION=1` turns on the resident session, and with a speech recognizer configured the box gets push-to-talk on a long K0. Your own Codex answers, or, if you'd rather, your own Claude Code with your own Anthropic API key (a Claude subscription can't be used). While it is on, Settings has a Conversation tab, on the Mac and on Linux, with your last voice commands and the buddy's spoken replies, which Agent answers, and the API key. See [`docs/resident-session.md`](docs/resident-session.md).

The app normally supervises the daemon. On a development machine without the app you can install it as a LaunchAgent with [`packaging/com.vibebuddy.vibebuddyd.plist`](packaging/com.vibebuddy.vibebuddyd.plist), but don't run both: they'd fight over the serial port. For the app, the daemon also serves `GET /v1/status`, SSE `/v1/status/stream`, `/v1/config`, `/v1/device/{identify,volume,choice,screenshot,voice-pack,firmware}` and `/v1/daemon/restart`.

When sending through the box's CH343 UART bridge, the daemon writes in line-rate chunks: the bridge can't take more than about two hundred bytes of continuous data and garbles the content without changing its length. The native USB port doesn't have this problem. See [`LESSONS.md`](LESSONS.md).
Expand Down
2 changes: 2 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ cargo run -p vibebuddyd

默认只监听 `127.0.0.1:7331`,并按 Espressif USB Serial/JTAG 的 `VID:PID 303A:1001` 自动发现设备。可用 `VIBEBUDDY_BIND` 修改监听地址、`VIBEBUDDY_SERIAL_PORT` 显式指定串口,或用 `VIBEBUDDY_USB_SERIAL` 在多块相同设备中选择目标;不指定时,daemon 连之前报出过 VibeBuddy 固件的那块;插着好几块、又都不是那块时,App 会问哪一块是盒子。这两个变量设了都会显示在 App 的设备页上,免得忘了撤的固定被当成找不到盒子。运行其他固件(比如 Muse)的盒子,daemon 不会往里写任何东西。HTTP `202 Accepted` 表示事件进入有界发送队列;设备实际接收结果以 daemon 记录的设备响应为准。

语音指令还在实验阶段,默认关闭:`VIBEBUDDY_RESIDENT_SESSION=1` 打开常驻会话,再配好语音识别,盒子长按 K0 就成了按住说话。由你自己的 Codex 来回答;也可以换成你自己的 Claude Code,配你自己的 Anthropic API 密钥(不能用 Claude 订阅)。打开期间,Mac 和 Linux 的设置里都会多出“对话”页,列出你最近的语音指令和氛围小助手的口头回复,也在这里选由谁回答、填 API 密钥。详见 [`docs/resident-session.md`](docs/resident-session.md)。

daemon 平时由 App 看管。没有 App 的开发机可以用 [`packaging/com.vibebuddy.vibebuddyd.plist`](packaging/com.vibebuddy.vibebuddyd.plist) 装成 LaunchAgent,但两者不能同时跑,会抢串口。daemon 还为 App 提供 `GET /v1/status`、SSE `/v1/status/stream`、`/v1/config`、`/v1/device/{identify,volume,choice,screenshot,voice-pack,firmware}` 与 `/v1/daemon/restart`。

经盒子的 CH343 UART 桥发送时 daemon 按线速分段写:这条桥一次吞不下超过两百字节的连续数据,会把内容错位而长度不变;原生 USB 口不受影响。教训见 [`LESSONS.md`](LESSONS.md)。
Expand Down
27 changes: 27 additions & 0 deletions app/Localization/zh-Hans.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,30 @@
"The network name is too long: 32 bytes at most." = "网络名太长了,最多 32 字节。";
"Status" = "状态";
"Change network" = "更换网络";

/* Conversation (the resident session, experimental) */
"Conversation" = "对话";
"Your last voice commands and the buddy's spoken replies, newest at the bottom." = "你最近的语音指令和氛围小助手的口头回复,最新的在最下面。";
"Couldn't read the conversation" = "读不到对话";
"The daemon isn't running. Restart it on the Advanced tab." = "daemon 没在运行,到“高级”页重启它。";
"The buddy's session isn't available: %@" = "氛围小助手的会话现在用不了:%@";
"Nothing said yet" = "还没说过话";
"Hold K0 on the box and talk to the buddy. What you say and what it says back show up here." = "按住盒子上的 K0,对氛围小助手说话。你说的和它回的都会显示在这里。";
"The daemon answered %lld" = "daemon 返回了 %lld";
"Voice needs Codex" = "语音需要 Codex";
"Voice commands are answered by Codex on this computer, and it isn't installed. Install the Codex CLI, then sign in with `codex login`. This tab notices within half a minute." = "语音指令由这台电脑上的 Codex 来回答,但它还没安装。先装好 Codex CLI,再用 `codex login` 登录。半分钟内这一页会自己更新。";
"Voice commands are answered by Codex on this computer, and it isn't signed in. Run `codex login` in a terminal and sign in. This tab notices within half a minute." = "语音指令由这台电脑上的 Codex 来回答,但它还没登录。在终端里运行 `codex login` 登录即可。半分钟内这一页会自己更新。";
"How to install the Codex CLI" = "如何安装 Codex CLI";
"Answered by" = "由谁回答";
"Automatic" = "自动";
"Automatic: Codex when it's ready, else Claude Code with your API key." = "自动:Codex 能用时用 Codex,否则用 Claude Code 和你的 API 密钥。";
"Anthropic API key" = "Anthropic API 密钥";
"Anthropic API key saved" = "已保存 Anthropic API 密钥";
"Save" = "保存";
"Get an API key" = "获取 API 密钥";
"Claude Code answers only with your own Anthropic API key, billed to your Anthropic account; a Claude subscription can't be used here. What you say and your Agents' task titles go to Anthropic." = "Claude Code 只用你自己的 Anthropic API 密钥回答,费用记在你的 Anthropic 账户上;这里不能用 Claude 订阅。你说的话和各个 Agent 的任务标题会发给 Anthropic。";
"Voice needs Claude Code" = "语音需要 Claude Code";
"Voice commands are answered by Claude Code on this computer, and it isn't installed. Install Claude Code; you don't need to sign in to it. This tab notices within half a minute." = "语音指令由这台电脑上的 Claude Code 来回答,但它还没安装。装好 Claude Code 即可,不用登录。半分钟内这一页会自己更新。";
"Claude Code answers voice commands only with your own Anthropic API key. Add your key above." = "Claude Code 只用你自己的 Anthropic API 密钥回答语音指令。请在上面填入你的密钥。";
"Or let Claude Code answer, with your own Anthropic API key: add it above." = "也可以让 Claude Code 来回答,用你自己的 Anthropic API 密钥:在上面填入即可。";
"How to install Claude Code" = "如何安装 Claude Code";
Loading
Loading