diff --git a/.gitignore b/.gitignore index 8ba6a45fe..8b5f95215 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,7 @@ cmd/telegram/ !web/backend/dist/ web/backend/dist/* !web/backend/dist/.gitkeep + +.claude/ + +docker/data diff --git a/README.fr.md b/README.fr.md index cbaffc2d1..301456262 100644 --- a/README.fr.md +++ b/README.fr.md @@ -3,7 +3,7 @@
@@ -24,147 +24,138 @@
---
-> **PicoClaw** est un projet open-source indépendant initié par [Sipeed](https://sipeed.com). Il est entièrement écrit en **Go** — ce n'est pas un fork d'OpenClaw, de NanoBot ou de tout autre projet.
+> **PicoClaw** est un projet open-source indépendant initié par [Sipeed](https://sipeed.com), entièrement écrit en **Go** à partir de zéro — ce n'est pas un fork d'OpenClaw, de NanoBot ou de tout autre projet.
-🦐 **PicoClaw** est un assistant personnel IA ultra-léger inspiré de [NanoBot](https://github.com/HKUDS/nanobot), entièrement réécrit en **Go** via un processus d'auto-amorçage (self-bootstrapping) — où l'agent IA lui-même a piloté l'intégralité de la migration architecturale et de l'optimisation du code.
+**PicoClaw** est un assistant personnel IA ultra-léger inspiré de [NanoBot](https://github.com/HKUDS/nanobot). Il a été entièrement reconstruit en **Go** via un processus d'auto-amorçage (self-bootstrapping) — l'Agent IA lui-même a piloté la migration architecturale et l'optimisation du code.
+
+**Fonctionne sur du matériel à $10 avec <10 Mo de RAM** — c'est 99% de mémoire en moins qu'OpenClaw et 98% moins cher qu'un Mac mini !
-⚡️ **Extrêmement léger :** Fonctionne sur du matériel à seulement **$10** avec **<10 Mo** de RAM. C'est 99% de mémoire en moins qu'OpenClaw et 98% moins cher qu'un Mac mini !
|
-
- |
-
-
- |
-
|
+
+ |
+
+
+ |
+
-> 📋 **[Liste de Compatibilité Matérielle](docs/hardware-compatibility.md)** — Voir toutes les cartes testées, du RISC-V à $5 au Raspberry Pi en passant par les téléphones Android. Votre carte n'est pas listée ? Soumettez une PR !
+
+
+
🧩 Ingénieur Full-Stack |
- 🗂️ Gestion des Logs & Planification |
- 🔎 Recherche Web & Apprentissage |
-
|---|---|---|
|
-
|
-
|
-
| Développer • Déployer • Mettre à l'échelle | -Planifier • Automatiser • Mémoriser | -Découvrir • Analyser • Tendances | -
Mode Ingénieur Full-Stack |
+Journalisation & Planification |
+Recherche Web & Apprentissage |
+
|
+
|
+
|
+
| Développer · Déployer · Mettre à l'échelle | +Planifier · Automatiser · Mémoriser | +Découvrir · Analyser · Tendances | +
-
-### 🐜 Déploiement Innovant à Faible Empreinte
+### 🐜 Déploiement innovant à faible empreinte
PicoClaw peut être déployé sur pratiquement n'importe quel appareil Linux !
-- 9,9$ [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) version E (Ethernet) ou W (WiFi6), pour un Assistant Domotique Minimaliste
-- 30~$50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), ou 100$ [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html) pour la Maintenance Automatisée de Serveurs
-- 50$ [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) ou 100$ [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera) pour la Surveillance Intelligente
+- $9,9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) édition E(Ethernet) ou W(WiFi6), pour un assistant domestique minimal
+- $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), ou $100 [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html), pour des opérations serveur automatisées
+- $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) ou $100 [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera), pour la surveillance intelligente
+
+
+
+
+
+**Option 2 : Installation APK (bientôt disponible)**
+
+Un APK Android autonome avec WebUI intégré est en développement. Restez à l'écoute !
+
+
+
+
+
-
diff --git a/README.id.md b/README.id.md
index 3f462981c..6b7025ffd 100644
--- a/README.id.md
+++ b/README.id.md
@@ -1,9 +1,9 @@
+
-
@@ -24,135 +24,125 @@
---
-> **PicoClaw** adalah proyek open-source independen yang diinisiasi oleh [Sipeed](https://sipeed.com). Ditulis sepenuhnya dalam **Go** — bukan fork dari OpenClaw, NanoBot, atau proyek lainnya.
+> **PicoClaw** adalah proyek open-source independen yang diinisiasi oleh [Sipeed](https://sipeed.com), ditulis sepenuhnya dalam **Go** — bukan fork dari OpenClaw, NanoBot, atau proyek lainnya.
-🦐 PicoClaw adalah asisten AI pribadi yang super ringan, terinspirasi dari [NanoBot](https://github.com/HKUDS/nanobot), ditulis ulang sepenuhnya dalam Go melalui proses "self-bootstrapping" — di mana AI Agent itu sendiri yang memandu seluruh migrasi arsitektur dan optimasi kode.
+**PicoClaw** adalah asisten AI pribadi yang super ringan, terinspirasi dari [NanoBot](https://github.com/HKUDS/nanobot). Dibangun ulang dari awal dalam **Go** melalui proses "self-bootstrapping" — AI Agent itu sendiri yang memandu migrasi arsitektur dan optimasi kode.
-⚡️ Berjalan di perangkat keras $10 dengan RAM <10MB: Hemat 99% memori dibanding OpenClaw dan 98% lebih murah dibanding Mac mini!
+**Berjalan di perangkat keras $10 dengan RAM <10MB** — hemat 99% memori dibanding OpenClaw dan 98% lebih murah dari Mac mini!
|
-
- |
-
-
- |
-
|
+
+ |
+
+
+ |
+
+
+
+
🧩 Full-Stack Engineer |
- 🗂️ Pencatatan & Manajemen Perencanaan |
- 🔎 Pencarian Web & Pembelajaran |
-
|---|---|---|
|
-
|
-
|
-
| Develop • Deploy • Scale | -Jadwal • Otomasi • Memori | -Penemuan • Wawasan • Tren | -
Mode Full-Stack Engineer |
+Pencatatan & Perencanaan |
+Pencarian Web & Pembelajaran |
+
|
+
|
+
|
+
| Develop · Deploy · Scale | +Jadwal · Otomasi · Ingat | +Temukan · Wawasan · Tren | +
-
### 🐜 Deploy Inovatif dengan Footprint Rendah
PicoClaw dapat di-deploy di hampir semua perangkat Linux!
-- $9,9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) versi E(Ethernet) atau W(WiFi6), untuk Home Assistant Minimal
-- $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), atau $100 [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html) untuk Pemeliharaan Server Otomatis
-- $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) atau $100 [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera) untuk Pemantauan Cerdas
+- $9,9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) versi E(Ethernet) atau W(WiFi6), untuk home assistant minimal
+- $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), atau $100 [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html), untuk operasi server otomatis
+- $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) atau $100 [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera), untuk pengawasan cerdas
+
+
+
+
+
+**Opsi 2: Instal APK (segera hadir)**
+
+APK Android mandiri dengan WebUI bawaan sedang dalam pengembangan. Pantau terus!
+
+
-
diff --git a/README.it.md b/README.it.md
index 27027d95f..dae541a17 100644
--- a/README.it.md
+++ b/README.it.md
@@ -1,9 +1,9 @@
+
-
@@ -24,135 +24,125 @@
---
-> **PicoClaw** è un progetto open-source indipendente avviato da [Sipeed](https://sipeed.com). È scritto interamente in **Go** — non è un fork di OpenClaw, NanoBot o di qualsiasi altro progetto.
+> **PicoClaw** è un progetto open-source indipendente avviato da [Sipeed](https://sipeed.com), scritto interamente in **Go** da zero — non è un fork di OpenClaw, NanoBot o di qualsiasi altro progetto.
-🦐 PicoClaw è un assistente IA personale ultra-leggero ispirato a [NanoBot](https://github.com/HKUDS/nanobot), riscritto da zero in Go attraverso un processo di auto-bootstrapping, in cui l'agente IA stesso ha guidato l'intera migrazione architetturale e l'ottimizzazione del codice.
+**PicoClaw** è un assistente IA personale ultra-leggero ispirato a [NanoBot](https://github.com/HKUDS/nanobot). È stato riscritto da zero in **Go** attraverso un processo di "auto-bootstrapping" — l'Agent IA stesso ha guidato la migrazione architetturale e l'ottimizzazione del codice.
-⚡️ Funziona su hardware da $10 con meno di 10MB di RAM: il 99% di memoria in meno rispetto a OpenClaw e il 98% più economico di un Mac mini!
+**Funziona su hardware da $10 con <10MB di RAM** — il 99% di memoria in meno rispetto a OpenClaw e il 98% più economico di un Mac mini!
|
-
- |
-
-
- |
-
|
+
+ |
+
+
+ |
+
+
+
+
🧩 Ingegnere Full-Stack |
- 🗂️ Gestione Log & Pianificazione |
- 🔎 Ricerca Web & Apprendimento |
-
|---|---|---|
|
-
|
-
|
-
| Sviluppa • Distribuisci • Scala | -Pianifica • Automatizza • Memorizza | -Scopri • Analizza • Tendenze | -
Modalità Ingegnere Full-Stack |
+Log & Pianificazione |
+Ricerca Web & Apprendimento |
+
|
+
|
+
|
+
| Sviluppa · Distribuisci · Scala | +Pianifica · Automatizza · Memorizza | +Scopri · Analizza · Tendenze | +
-
### 🐜 Deploy Innovativo a Bassa Impronta
PicoClaw può essere distribuito su quasi qualsiasi dispositivo Linux!
-- $9,9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) versione E (Ethernet) o W (WiFi6), per un Assistente Domotico Minimale
-- $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), o $100 [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html) per la Manutenzione Automatizzata dei Server
-- $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) o $100 [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera) per il Monitoraggio Intelligente
+- $9,9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) versione E (Ethernet) o W (WiFi6), per un assistente domotico minimale
+- $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), o $100 [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html), per la manutenzione automatizzata dei server
+- $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) o $100 [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera), per la sorveglianza intelligente
+
+
+
+
+
+**Opzione 2: APK Install (prossimamente)**
+
+Un APK Android standalone con WebUI integrato è in sviluppo. Resta sintonizzato!
+
+
+WeChat:
+
diff --git a/README.ja.md b/README.ja.md
index e5a927505..3096d4022 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -3,7 +3,7 @@
@@ -26,9 +26,9 @@
> **PicoClaw** は [Sipeed](https://sipeed.com) が立ち上げた独立したオープンソースプロジェクトです。完全に **Go 言語**で一から書かれており、OpenClaw、NanoBot、その他のプロジェクトのフォークではありません。
-🦐 PicoClaw は [NanoBot](https://github.com/HKUDS/nanobot) にインスパイアされた超軽量パーソナル AI アシスタントです。Go でゼロからリファクタリングされ、AI エージェント自身がアーキテクチャの移行とコード最適化を推進するセルフブートストラッピングプロセスで構築されました。
+**PicoClaw** は [NanoBot](https://github.com/HKUDS/nanobot) にインスパイアされた超軽量パーソナル AI アシスタントです。**Go** でゼロからリビルドされ、「セルフブートストラッピング」プロセスで構築されました — AI Agent 自身がアーキテクチャの移行とコード最適化を推進しました。
-⚡️ $10 のハードウェアで 10MB 未満の RAM で動作:OpenClaw より 99% 少ないメモリ、Mac mini より 98% 安い!
+**$10 のハードウェアで 10MB 未満の RAM で動作** — OpenClaw より 99% 少ないメモリ、Mac mini より 98% 安い!
-> 📋 **[ハードウェア互換性リスト](docs/hardware-compatibility.md)** — テスト済みの全ボード一覧($5 RISC-V から Raspberry Pi、Android スマートフォンまで)。お使いのボードが未掲載?PR を送ってください!
+
+
+
🧩 フルスタックエンジニア |
- 🗂️ ログ&計画管理 |
- 🔎 Web 検索&学習 |
-
|---|---|---|
|
-
|
-
|
-
| 開発 · デプロイ · スケール | -スケジュール · 自動化 · メモリ | -発見 · インサイト · トレンド | -
フルスタックエンジニアモード |
+ログ&計画管理 |
+Web 検索&学習 |
+
|
+
|
+
|
+
| 開発 · デプロイ · スケール | +スケジュール · 自動化 · メモリ | +発見 · インサイト · トレンド | +
-
### 🐜 革新的な省フットプリントデプロイ
PicoClaw はほぼすべての Linux デバイスにデプロイできます!
@@ -178,9 +166,12 @@ git clone https://github.com/sipeed/picoclaw.git
cd picoclaw
make deps
-# ビルド(インストール不要)
+# コアバイナリをビルド
make build
+# Web UI Launcher をビルド(WebUI モードに必要)
+make build-launcher
+
# 複数プラットフォーム向けビルド
make build-all
@@ -193,20 +184,330 @@ make install
**Raspberry Pi Zero 2 W:** OS に合ったバイナリを使用してください:32-bit Raspberry Pi OS → `make build-linux-arm`、64-bit → `make build-linux-arm64`。または `make build-pi-zero` で両方をビルド。
-## 📚 ドキュメント
+## 🚀 クイックスタートガイド
-詳細なガイドは以下のドキュメントを参照してください。この README はクイックスタートのみをカバーしています。
+### 🌐 WebUI Launcher(デスクトップ向け推奨)
-| トピック | 説明 |
-|---------|------|
-| 🐳 [Docker & クイックスタート](docs/ja/docker.md) | Docker Compose セットアップ、Launcher/Agent モード、クイックスタート設定 |
-| 💬 [チャットアプリ](docs/ja/chat-apps.md) | Telegram、Discord、WhatsApp、Matrix、QQ、Slack、IRC、DingTalk、LINE、Feishu、WeCom など |
-| ⚙️ [設定](docs/ja/configuration.md) | 環境変数、ワークスペース構成、スキルソース、セキュリティサンドボックス、ハートビート |
-| 🔌 [プロバイダー&モデル](docs/ja/providers.md) | 20 以上の LLM プロバイダー、モデルルーティング、model_list 設定、プロバイダーアーキテクチャ |
-| 🔄 [Spawn & 非同期タスク](docs/ja/spawn-tasks.md) | クイックタスク、spawn による長時間タスク、非同期サブエージェントオーケストレーション |
-| 🐛 [トラブルシューティング](docs/ja/troubleshooting.md) | よくある問題と解決策 |
-| 🔧 [ツール設定](docs/ja/tools_configuration.md) | ツールごとの有効/無効、exec ポリシー |
-| 📋 [ハードウェア互換性](docs/hardware-compatibility.md) | テスト済みボード、最小要件、ボードの追加方法 |
+WebUI Launcher はブラウザベースの設定・チャットインターフェースを提供します。コマンドラインの知識不要で、最も簡単に始められる方法です。
+
+**オプション 1: ダブルクリック(デスクトップ)**
+
+[picoclaw.io](https://picoclaw.io) からダウンロード後、`picoclaw-launcher`(Windows では `picoclaw-launcher.exe`)をダブルクリックしてください。ブラウザが自動的に `http://localhost:18800` を開きます。
+
+**オプション 2: コマンドライン**
+
+```bash
+picoclaw-launcher
+# ブラウザで http://localhost:18800 を開く
+```
+
+> [!TIP]
+> **リモートアクセス / Docker / VM:** すべてのインターフェースでリッスンするには `-public` フラグを追加してください:
+> ```bash
+> picoclaw-launcher -public
+> ```
+
+
+
+
+
+
+
+**オプション 2: APK インストール(近日公開)**
+
+内蔵 WebUI を備えたスタンドアロン Android APK を開発中です。お楽しみに!
+
+
+WeChat:
+
diff --git a/README.md b/README.md
index 652792d83..72d38103c 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
+
-
@@ -24,141 +24,129 @@
---
-> **PicoClaw** is an independent open-source project initiated by [Sipeed](https://sipeed.com). It is written entirely in **Go** — not a fork of OpenClaw, NanoBot, or any other project.
+> **PicoClaw** is an independent open-source project initiated by [Sipeed](https://sipeed.com), written entirely in **Go** from scratch — not a fork of OpenClaw, NanoBot, or any other project.
-🦐 PicoClaw is an ultra-lightweight personal AI Assistant inspired by [NanoBot](https://github.com/HKUDS/nanobot), refactored from the ground up in Go through a self-bootstrapping process, where the AI agent itself drove the entire architectural migration and code optimization.
+**PicoClaw** is an ultra-lightweight personal AI assistant inspired by [NanoBot](https://github.com/HKUDS/nanobot). It was rebuilt from the ground up in **Go** through a "self-bootstrapping" process — the AI Agent itself drove the architecture migration and code optimization.
-⚡️ Runs on $10 hardware with <10MB RAM: That's 99% less memory than OpenClaw and 98% cheaper than a Mac mini!
+**Runs on $10 hardware with <10MB RAM** — that's 99% less memory than OpenClaw and 98% cheaper than a Mac mini!
|
-
- |
-
-
- |
-
|
+
+ |
+
+
+ |
+
-> 📋 **[Hardware Compatibility List](docs/hardware-compatibility.md)** — See all tested boards, from $5 RISC-V to Raspberry Pi to Android phones. Your board not listed? Submit a PR!
+
+
+
🧩 Full-Stack Engineer |
- 🗂️ Logging & Planning Management |
- 🔎 Web Search & Learning |
-
|---|---|---|
|
-
|
-
|
-
| Develop • Deploy • Scale | -Schedule • Automate • Memory | -Discovery • Insights • Trends | -
Full-Stack Engineer Mode |
+Logging & Planning |
+Web Search & Learning |
+
|
+
|
+
|
+
| Develop · Deploy · Scale | +Schedule · Automate · Remember | +Discover · Insights · Trends | +
-
-### 🐜 Innovative Low-Footprint Deploy
-
-PicoClaw can be deployed on almost any Linux device!
-
-- $9.9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) E(Ethernet) or W(WiFi6) version, for Minimal Home Assistant
-- $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), or $100 [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html) for Automated Server Maintenance
-- $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) or $100 [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera) for Smart Monitoring
+- $9.9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) E(Ethernet) or W(WiFi6) edition, for a minimal home assistant
+- $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), or $100 [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html), for automated server operations
+- $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) or $100 [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera), for smart surveillance
+
+
+
+
center">
-
-
- |
-
- |
-
-
- |
-
-
-> 📋 **[Hardware Compatibility List](docs/hardware-compatibility.md)** — See all tested boards, from $5 RISC-V to Raspberry Pi to Android phones. Your board not listed? Submit a PR!
-
-## 🦾 Demonstration
-
-### 🛠️ Standard Assistant Workflows
-
-🧩 Full-Stack Engineer |
- 🗂️ Logging & Planning Management |
- 🔎 Web Search & Learning |
-
|---|---|---|
|
-
|
-
|
-
| Develop • Deploy • Scale | -Schedule • Automate • Memory | -Discovery • Insights • Trends | -
+
-### 🐜 Innovative Low-Footprint Deploy
+**Option 2: APK Install (coming soon)**
-PicoClaw can be deployed on almost any Linux device!
+A standalone Android APK with built-in WebUI is in development. Stay tuned!
-- $9.9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) E(Ethernet) or W(WiFi6) version, for Minimal Home Assistant
-- $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), or $100 [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html) for Automated Server Maintenance
-- $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) or $100 [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera) for Smart Monitoring
+
-
-##
+WeChat:
+
diff --git a/README.pt-br.md b/README.pt-br.md
index c1df570a5..3c039f190 100644
--- a/README.pt-br.md
+++ b/README.pt-br.md
@@ -1,9 +1,9 @@
+
-
@@ -24,149 +24,137 @@
---
-> **PicoClaw** é um projeto open-source independente iniciado pela [Sipeed](https://sipeed.com). É escrito inteiramente em **Go** — não é um fork do OpenClaw, NanoBot ou qualquer outro projeto.
+> **PicoClaw** é um projeto open-source independente iniciado pela [Sipeed](https://sipeed.com), escrito inteiramente em **Go** do zero — não é um fork do OpenClaw, NanoBot ou qualquer outro projeto.
-🦐 PicoClaw é um assistente pessoal de IA ultra-leve inspirado no [NanoBot](https://github.com/HKUDS/nanobot), reescrito do zero em Go por meio de um processo de auto-inicialização (self-bootstrapping), onde o próprio agente de IA conduziu toda a migração de arquitetura e otimização de código.
+**PicoClaw** é um assistente de IA pessoal ultra-leve inspirado no [NanoBot](https://github.com/HKUDS/nanobot). Foi reconstruído do zero em **Go** por meio de um processo de "auto-bootstrapping" — o próprio AI Agent conduziu a migração de arquitetura e a otimização do código.
-⚡️ Roda em hardware de $10 com <10MB de RAM: Isso é 99% menos memória que o OpenClaw e 98% mais barato que um Mac mini!
+**Roda em hardware de $10 com menos de 10MB de RAM** — isso é 99% menos memória que o OpenClaw e 98% mais barato que um Mac mini!
|
-
- |
-
-
- |
-
|
+
+ |
+
+
+ |
+
-> 📋 **[Lista de Compatibilidade de Hardware](docs/hardware-compatibility.md)** — Veja todas as placas testadas, de RISC-V de $5 a Raspberry Pi e telefones Android. Sua placa não está listada? Envie um PR!
+
+
+
🧩 Engenharia Full-Stack |
- 🗂️ Gerenciamento de Logs & Planejamento |
- 🔎 Busca Web & Aprendizado |
-
|---|---|---|
|
-
|
-
|
-
| Desenvolver • Implantar • Escalar | -Agendar • Automatizar • Memorizar | -Descobrir • Analisar • Tendências | -
Modo Engenheiro Full-Stack |
+Registro e Planejamento |
+Busca na Web e Aprendizado |
+
|
+
|
+
|
+
| Desenvolver · Implantar · Escalar | +Agendar · Automatizar · Lembrar | +Descobrir · Insights · Tendências | +
-
-### 🐜 Implantação Inovadora com Baixo Consumo
+### 🐜 Implantação Inovadora de Baixo Consumo
O PicoClaw pode ser implantado em praticamente qualquer dispositivo Linux!
-- $9.9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) versão E(Ethernet) ou W(WiFi6), para Assistente Doméstico Minimalista
-- $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), ou $100 [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html) para Manutenção Automatizada de Servidores
-- $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) ou $100 [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera) para Monitoramento Inteligente
+- $9,9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) edição E(Ethernet) ou W(WiFi6), para um assistente doméstico mínimo
+- $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), ou $100 [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html), para operações automatizadas de servidor
+- $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) ou $100 [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera), para vigilância inteligente
+
+
+
+
+
+**Opção 2: Instalação via APK (em breve)**
+
+Um APK Android independente com WebUI integrado está em desenvolvimento. Fique ligado!
+
+
+WeChat:
+
diff --git a/README.vi.md b/README.vi.md
index cd65ac526..b63fd4ef7 100644
--- a/README.vi.md
+++ b/README.vi.md
@@ -1,9 +1,9 @@
+
-
@@ -24,153 +24,141 @@
---
-> **PicoClaw** là dự án mã nguồn mở độc lập được khởi xướng bởi [Sipeed](https://sipeed.com). Được viết hoàn toàn bằng **Go** — không phải là bản fork của OpenClaw, NanoBot hay bất kỳ dự án nào khác.
+> **PicoClaw** là một dự án mã nguồn mở độc lập do [Sipeed](https://sipeed.com) khởi xướng, được viết hoàn toàn bằng **Go** từ đầu — không phải fork của OpenClaw, NanoBot hay bất kỳ dự án nào khác.
-🦐 PicoClaw là trợ lý AI cá nhân siêu nhẹ, lấy cảm hứng từ [NanoBot](https://github.com/HKUDS/nanobot), được viết lại hoàn toàn bằng Go thông qua quá trình "tự khởi tạo" (self-bootstrapping) — nơi chính AI Agent đã tự dẫn dắt toàn bộ quá trình chuyển đổi kiến trúc và tối ưu hóa mã nguồn.
+**PicoClaw** là trợ lý AI cá nhân siêu nhẹ lấy cảm hứng từ [NanoBot](https://github.com/HKUDS/nanobot). Nó được xây dựng lại từ đầu bằng **Go** thông qua quá trình "tự khởi động" — chính AI Agent đã dẫn dắt quá trình di chuyển kiến trúc và tối ưu hóa mã nguồn.
-⚡️ Chạy trên phần cứng chỉ $10 với RAM <10MB: Tiết kiệm 99% bộ nhớ so với OpenClaw và rẻ hơn 98% so với Mac mini!
+**Chạy trên phần cứng $10 với <10MB RAM** — ít hơn 99% bộ nhớ so với OpenClaw và rẻ hơn 98% so với Mac mini!
|
-
- |
-
-
- |
-
|
+
+ |
+
+
+ |
+
-> 📋 **[Danh Sách Tương Thích Phần Cứng](docs/hardware-compatibility.md)** — Xem tất cả các board đã được kiểm tra, từ RISC-V $5 đến Raspberry Pi và điện thoại Android. Board của bạn chưa có? Gửi PR!
+
+
+
🧩 Lập trình Full-Stack |
- 🗂️ Quản lý Nhật ký & Kế hoạch |
- 🔎 Tìm kiếm Web & Học hỏi |
-
|---|---|---|
|
-
|
-
|
-
| Phát triển • Triển khai • Mở rộng | -Lên lịch • Tự động hóa • Ghi nhớ | -Khám phá • Phân tích • Xu hướng | -
Chế độ Kỹ sư Full-Stack |
+Ghi nhật ký & Lập kế hoạch |
+Tìm kiếm Web & Học tập |
+
|
+
|
+
|
+
| Phát triển · Triển khai · Mở rộng | +Lên lịch · Tự động hóa · Ghi nhớ | +Khám phá · Thông tin · Xu hướng | +
-
-### 🐜 Triển khai sáng tạo trên phần cứng tối thiểu
-
-PicoClaw có thể triển khai trên hầu hết mọi thiết bị Linux!
-
-- $9.9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) phiên bản E(Ethernet) hoặc W(WiFi6), dùng làm Trợ lý Gia đình tối giản
-- $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), hoặc $100 [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html) dùng cho quản trị Server tự động
-- $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) hoặc $100 [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera) dùng cho Giám sát thông minh
+- $9.9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) phiên bản E(Ethernet) hoặc W(WiFi6), cho trợ lý gia đình tối giản
+- $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html), hoặc $100 [NanoKVM-Pro](https://www.aliexpress.com/item/1005010048471263.html), cho vận hành máy chủ tự động
+- $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) hoặc $100 [MaixCAM2](https://www.kickstarter.com/projects/zepan/maixcam2-build-your-next-gen-4k-ai-camera), cho giám sát thông minh
+
+
+
+
+
+**Tùy chọn 2: Cài đặt APK (sắp ra mắt)**
+
+Một APK Android độc lập với WebUI tích hợp đang được phát triển. Hãy đón chờ!
+
+
+WeChat:
+
diff --git a/README.zh.md b/README.zh.md
index db34f57da..de96e5164 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -3,7 +3,7 @@
@@ -95,6 +95,8 @@
_*近期版本因快速合并 PR 可能占用 10–20MB,资源优化已列入计划。启动速度对比基于 0.8GHz 单核实测(见下方对比表)。_
+
-> 📋 **[硬件兼容列表](docs/hardware-compatibility.md)** — 查看所有已测试的板卡,从 $5 RISC-V 到树莓派到安卓手机。你的板卡没在列表中?欢迎提交 PR!
+
+
+
-
### 🐜 创新的低占用部署
PicoClaw 几乎可以部署在任何 Linux 设备上!
@@ -177,9 +166,12 @@ git clone https://github.com/sipeed/picoclaw.git
cd picoclaw
make deps
-# 构建(无需安装)
+# 构建核心二进制文件
make build
+# 构建 Web UI Launcher(WebUI 模式必需)
+make build-launcher
+
# 为多平台构建
make build-all
@@ -192,20 +184,330 @@ make install
**Raspberry Pi Zero 2 W:** 请使用与系统匹配的二进制文件:32 位 Raspberry Pi OS → `make build-linux-arm`;64 位 → `make build-linux-arm64`。或运行 `make build-pi-zero` 同时构建两者。
-## 📚 文档
+## 🚀 快速开始
-详细指南请参阅以下文档,README 仅涵盖快速入门。
+### 🌐 WebUI Launcher(推荐桌面用户)
-| 主题 | 说明 |
-|------|------|
-| 🐳 [Docker 与快速开始](docs/zh/docker.md) | Docker Compose 配置、Launcher/Agent 模式、快速开始 |
-| 💬 [聊天应用配置](docs/zh/chat-apps.md) | Telegram、Discord、WhatsApp、Matrix、QQ、Slack、IRC、钉钉、LINE、飞书、企业微信等 |
-| ⚙️ [配置指南](docs/zh/configuration.md) | 环境变量、工作区布局、技能来源、安全沙箱、心跳任务 |
-| 🔌 [提供商与模型配置](docs/zh/providers.md) | 20+ LLM 提供商、模型路由、model_list 配置、Provider 架构 |
-| 🔄 [异步任务与 Spawn](docs/zh/spawn-tasks.md) | 快速任务、长任务与 Spawn、异步子 Agent 编排 |
-| 🐛 [疑难解答](docs/zh/troubleshooting.md) | 常见问题与解决方案 |
-| 🔧 [工具配置](docs/zh/tools_configuration.md) | 工具启用/禁用、执行策略 |
-| 📋 [硬件兼容列表](docs/hardware-compatibility.md) | 已测试板卡、最低要求、如何添加你的板卡 |
+WebUI Launcher 提供基于浏览器的配置与聊天界面,是最简单的上手方式——无需命令行知识。
+
+**方式一:双击启动(桌面)**
+
+从 [picoclaw.io](https://picoclaw.io) 下载后,双击 `picoclaw-launcher`(Windows 上为 `picoclaw-launcher.exe`),浏览器将自动打开 `http://localhost:18800`。
+
+**方式二:命令行**
+
+```bash
+picoclaw-launcher
+# 在浏览器中打开 http://localhost:18800
+```
+
+> [!TIP]
+> **远程访问 / Docker / 虚拟机:** 添加 `-public` 参数以监听所有网络接口:
+> ```bash
+> picoclaw-launcher -public
+> ```
+
+
+
+
+
+
+
+**方式二:APK 安装(即将推出)**
+
+内置 WebUI 的独立 Android APK 正在开发中,敬请期待!
+
+
+WeChat:
+
+
+
+
+
+
diff --git a/assets/hardware-banner.jpg b/assets/hardware-banner.jpg
new file mode 100644
index 000000000..f9a1190b1
Binary files /dev/null and b/assets/hardware-banner.jpg differ
diff --git a/assets/launcher-tui.jpg b/assets/launcher-tui.jpg
new file mode 100644
index 000000000..cf5e8ea4d
Binary files /dev/null and b/assets/launcher-tui.jpg differ
diff --git a/assets/launcher-webui.jpg b/assets/launcher-webui.jpg
new file mode 100644
index 000000000..9e7c699b2
Binary files /dev/null and b/assets/launcher-webui.jpg differ
diff --git a/assets/wechat.png b/assets/wechat.png
index 6512421ed..effb4dab9 100644
Binary files a/assets/wechat.png and b/assets/wechat.png differ
diff --git a/cmd/picoclaw/internal/agent/helpers.go b/cmd/picoclaw/internal/agent/helpers.go
index c3ddbb77f..0af743bb5 100644
--- a/cmd/picoclaw/internal/agent/helpers.go
+++ b/cmd/picoclaw/internal/agent/helpers.go
@@ -23,16 +23,16 @@ func agentCmd(message, sessionKey, model string, debug bool) error {
sessionKey = "cli:default"
}
- if debug {
- logger.SetLevel(logger.DEBUG)
- fmt.Println("🔍 Debug mode enabled")
- }
-
cfg, err := internal.LoadConfig()
if err != nil {
return fmt.Errorf("error loading config: %w", err)
}
+ if debug {
+ logger.SetLevel(logger.DEBUG)
+ fmt.Println("🔍 Debug mode enabled")
+ }
+
if model != "" {
cfg.Agents.Defaults.ModelName = model
}
diff --git a/cmd/picoclaw/internal/auth/helpers.go b/cmd/picoclaw/internal/auth/helpers.go
index 4bf132685..531cb76aa 100644
--- a/cmd/picoclaw/internal/auth/helpers.go
+++ b/cmd/picoclaw/internal/auth/helpers.go
@@ -56,9 +56,6 @@ func authLoginOpenAI(useDeviceCode bool) error {
appCfg, err := internal.LoadConfig()
if err == nil {
- // Update Providers (legacy format)
- appCfg.Providers.OpenAI.AuthMethod = "oauth"
-
// Update or add openai in ModelList
foundOpenAI := false
for i := range appCfg.ModelList {
@@ -71,7 +68,7 @@ func authLoginOpenAI(useDeviceCode bool) error {
// If no openai in ModelList, add it
if !foundOpenAI {
- appCfg.ModelList = append(appCfg.ModelList, config.ModelConfig{
+ appCfg.ModelList = append(appCfg.ModelList, &config.ModelConfig{
ModelName: "gpt-5.4",
Model: "openai/gpt-5.4",
AuthMethod: "oauth",
@@ -130,9 +127,6 @@ func authLoginGoogleAntigravity() error {
appCfg, err := internal.LoadConfig()
if err == nil {
- // Update Providers (legacy format, for backward compatibility)
- appCfg.Providers.Antigravity.AuthMethod = "oauth"
-
// Update or add antigravity in ModelList
foundAntigravity := false
for i := range appCfg.ModelList {
@@ -145,7 +139,7 @@ func authLoginGoogleAntigravity() error {
// If no antigravity in ModelList, add it
if !foundAntigravity {
- appCfg.ModelList = append(appCfg.ModelList, config.ModelConfig{
+ appCfg.ModelList = append(appCfg.ModelList, &config.ModelConfig{
ModelName: "gemini-flash",
Model: "antigravity/gemini-3-flash",
AuthMethod: "oauth",
@@ -210,8 +204,6 @@ func authLoginAnthropicSetupToken() error {
appCfg, err := internal.LoadConfig()
if err == nil {
- appCfg.Providers.Anthropic.AuthMethod = "oauth"
-
found := false
for i := range appCfg.ModelList {
if isAnthropicModel(appCfg.ModelList[i].Model) {
@@ -221,7 +213,7 @@ func authLoginAnthropicSetupToken() error {
}
}
if !found {
- appCfg.ModelList = append(appCfg.ModelList, config.ModelConfig{
+ appCfg.ModelList = append(appCfg.ModelList, &config.ModelConfig{
ModelName: defaultAnthropicModel,
Model: "anthropic/" + defaultAnthropicModel,
AuthMethod: "oauth",
@@ -287,7 +279,6 @@ func authLoginPasteToken(provider string) error {
if err == nil {
switch provider {
case "anthropic":
- appCfg.Providers.Anthropic.AuthMethod = "token"
// Update ModelList
found := false
for i := range appCfg.ModelList {
@@ -298,7 +289,7 @@ func authLoginPasteToken(provider string) error {
}
}
if !found {
- appCfg.ModelList = append(appCfg.ModelList, config.ModelConfig{
+ appCfg.ModelList = append(appCfg.ModelList, &config.ModelConfig{
ModelName: defaultAnthropicModel,
Model: "anthropic/" + defaultAnthropicModel,
AuthMethod: "token",
@@ -306,7 +297,6 @@ func authLoginPasteToken(provider string) error {
appCfg.Agents.Defaults.ModelName = defaultAnthropicModel
}
case "openai":
- appCfg.Providers.OpenAI.AuthMethod = "token"
// Update ModelList
found := false
for i := range appCfg.ModelList {
@@ -317,7 +307,7 @@ func authLoginPasteToken(provider string) error {
}
}
if !found {
- appCfg.ModelList = append(appCfg.ModelList, config.ModelConfig{
+ appCfg.ModelList = append(appCfg.ModelList, &config.ModelConfig{
ModelName: "gpt-5.4",
Model: "openai/gpt-5.4",
AuthMethod: "token",
@@ -365,15 +355,6 @@ func authLogoutCmd(provider string) error {
}
}
}
- // Clear AuthMethod in Providers (legacy)
- switch provider {
- case "openai":
- appCfg.Providers.OpenAI.AuthMethod = ""
- case "anthropic":
- appCfg.Providers.Anthropic.AuthMethod = ""
- case "google-antigravity", "antigravity":
- appCfg.Providers.Antigravity.AuthMethod = ""
- }
config.SaveConfig(internal.GetConfigPath(), appCfg)
}
@@ -392,10 +373,6 @@ func authLogoutCmd(provider string) error {
for i := range appCfg.ModelList {
appCfg.ModelList[i].AuthMethod = ""
}
- // Clear all AuthMethods in Providers (legacy)
- appCfg.Providers.OpenAI.AuthMethod = ""
- appCfg.Providers.Anthropic.AuthMethod = ""
- appCfg.Providers.Antigravity.AuthMethod = ""
config.SaveConfig(internal.GetConfigPath(), appCfg)
}
diff --git a/cmd/picoclaw/internal/gateway/command.go b/cmd/picoclaw/internal/gateway/command.go
index 4812f1bee..7fa588c5c 100644
--- a/cmd/picoclaw/internal/gateway/command.go
+++ b/cmd/picoclaw/internal/gateway/command.go
@@ -34,7 +34,7 @@ func NewGatewayCommand() *cobra.Command {
return nil
},
RunE: func(_ *cobra.Command, _ []string) error {
- return gateway.Run(debug, internal.GetConfigPath(), allowEmpty)
+ return gateway.Run(debug, internal.GetPicoclawHome(), internal.GetConfigPath(), allowEmpty)
},
}
diff --git a/cmd/picoclaw/internal/helpers.go b/cmd/picoclaw/internal/helpers.go
index 6b2d65c91..17de88ccb 100644
--- a/cmd/picoclaw/internal/helpers.go
+++ b/cmd/picoclaw/internal/helpers.go
@@ -4,10 +4,12 @@ import (
"os"
"path/filepath"
+ "github.com/sipeed/picoclaw/pkg"
"github.com/sipeed/picoclaw/pkg/config"
+ "github.com/sipeed/picoclaw/pkg/logger"
)
-const Logo = "🦞"
+const Logo = pkg.Logo
// GetPicoclawHome returns the picoclaw home directory.
// Priority: $PICOCLAW_HOME > ~/.picoclaw
@@ -16,7 +18,7 @@ func GetPicoclawHome() string {
return home
}
home, _ := os.UserHomeDir()
- return filepath.Join(home, ".picoclaw")
+ return filepath.Join(home, pkg.DefaultPicoClawHome)
}
func GetConfigPath() string {
@@ -27,7 +29,12 @@ func GetConfigPath() string {
}
func LoadConfig() (*config.Config, error) {
- return config.LoadConfig(GetConfigPath())
+ cfg, err := config.LoadConfig(GetConfigPath())
+ if err != nil {
+ return nil, err
+ }
+ logger.SetLevelFromString(cfg.Gateway.LogLevel)
+ return cfg, nil
}
// FormatVersion returns the version string with optional git commit
diff --git a/cmd/picoclaw/internal/helpers_test.go b/cmd/picoclaw/internal/helpers_test.go
index 583751781..953da8886 100644
--- a/cmd/picoclaw/internal/helpers_test.go
+++ b/cmd/picoclaw/internal/helpers_test.go
@@ -8,6 +8,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+
+ "github.com/sipeed/picoclaw/pkg/config"
)
func TestGetConfigPath(t *testing.T) {
@@ -20,7 +22,7 @@ func TestGetConfigPath(t *testing.T) {
}
func TestGetConfigPath_WithPICOCLAW_HOME(t *testing.T) {
- t.Setenv("PICOCLAW_HOME", "/custom/picoclaw")
+ t.Setenv(config.EnvHome, "/custom/picoclaw")
t.Setenv("HOME", "/tmp/home")
got := GetConfigPath()
@@ -31,7 +33,7 @@ func TestGetConfigPath_WithPICOCLAW_HOME(t *testing.T) {
func TestGetConfigPath_WithPICOCLAW_CONFIG(t *testing.T) {
t.Setenv("PICOCLAW_CONFIG", "/custom/config.json")
- t.Setenv("PICOCLAW_HOME", "/custom/picoclaw")
+ t.Setenv(config.EnvHome, "/custom/picoclaw")
t.Setenv("HOME", "/tmp/home")
got := GetConfigPath()
diff --git a/cmd/picoclaw/internal/model/command.go b/cmd/picoclaw/internal/model/command.go
index cad106fd5..314259d0f 100644
--- a/cmd/picoclaw/internal/model/command.go
+++ b/cmd/picoclaw/internal/model/command.go
@@ -56,9 +56,6 @@ Note: 'local-model' is a special value for using a local VLLM server
func showCurrentModel(cfg *config.Config) {
defaultModel := cfg.Agents.Defaults.ModelName
- if defaultModel == "" {
- defaultModel = cfg.Agents.Defaults.Model
- }
if defaultModel == "" {
fmt.Println("No default model is currently set.")
@@ -78,16 +75,13 @@ func listAvailableModels(cfg *config.Config) {
}
defaultModel := cfg.Agents.Defaults.ModelName
- if defaultModel == "" {
- defaultModel = cfg.Agents.Defaults.Model
- }
for _, model := range cfg.ModelList {
marker := " "
if model.ModelName == defaultModel {
marker = "> "
}
- if model.APIKey == "" {
+ if model.APIKey() == "" {
continue
}
fmt.Printf("%s- %s (%s)\n", marker, model.ModelName, model.Model)
@@ -98,7 +92,7 @@ func setDefaultModel(configPath string, cfg *config.Config, modelName string) er
// Validate that the model exists in model_list
modelFound := false
for _, model := range cfg.ModelList {
- if model.APIKey != "" && model.ModelName == modelName {
+ if model.APIKey() != "" && model.ModelName == modelName {
modelFound = true
break
}
@@ -111,12 +105,8 @@ func setDefaultModel(configPath string, cfg *config.Config, modelName string) er
// Update the default model
// Clear old model field and set new model_name
oldModel := cfg.Agents.Defaults.ModelName
- if oldModel == "" {
- oldModel = cfg.Agents.Defaults.Model
- }
cfg.Agents.Defaults.ModelName = modelName
- cfg.Agents.Defaults.Model = "" // Clear deprecated field
// Save config back to file
if err := config.SaveConfig(configPath, cfg); err != nil {
diff --git a/cmd/picoclaw/internal/model/command_test.go b/cmd/picoclaw/internal/model/command_test.go
index 82943e4a6..6cbbf0b55 100644
--- a/cmd/picoclaw/internal/model/command_test.go
+++ b/cmd/picoclaw/internal/model/command_test.go
@@ -58,17 +58,24 @@ func TestNewModelCommand(t *testing.T) {
}
func TestShowCurrentModel_WithDefaultModel(t *testing.T) {
- cfg := &config.Config{
+ cfg := (&config.Config{
Agents: config.AgentsConfig{
Defaults: config.AgentDefaults{
ModelName: "gpt-4",
},
},
- ModelList: []config.ModelConfig{
- {ModelName: "gpt-4", Model: "openai/gpt-4", APIKey: "test"},
- {ModelName: "claude-3", Model: "anthropic/claude-3", APIKey: "test"},
+ ModelList: []*config.ModelConfig{
+ {ModelName: "gpt-4", Model: "openai/gpt-4"},
+ {ModelName: "claude-3", Model: "anthropic/claude-3"},
},
- }
+ }).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
+ "gpt-4": {
+ APIKeys: []string{"test"},
+ },
+ "claude-3": {
+ APIKeys: []string{"test"},
+ },
+ }})
output := captureStdout(func() {
showCurrentModel(cfg)
@@ -81,17 +88,20 @@ func TestShowCurrentModel_WithDefaultModel(t *testing.T) {
}
func TestShowCurrentModel_NoDefaultModel(t *testing.T) {
- cfg := &config.Config{
+ cfg := (&config.Config{
Agents: config.AgentsConfig{
Defaults: config.AgentDefaults{
ModelName: "",
- Model: "",
},
},
- ModelList: []config.ModelConfig{
- {ModelName: "gpt-4", Model: "openai/gpt-4", APIKey: "test"},
+ ModelList: []*config.ModelConfig{
+ {ModelName: "gpt-4", Model: "openai/gpt-4"},
},
- }
+ }).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
+ "gpt-4": {
+ APIKeys: []string{"test"},
+ },
+ }})
output := captureStdout(func() {
showCurrentModel(cfg)
@@ -101,26 +111,9 @@ func TestShowCurrentModel_NoDefaultModel(t *testing.T) {
assert.Contains(t, output, "Available models in your config:")
}
-func TestShowCurrentModel_BackwardCompatibility(t *testing.T) {
- cfg := &config.Config{
- Agents: config.AgentsConfig{
- Defaults: config.AgentDefaults{
- Model: "legacy-model",
- },
- },
- ModelList: []config.ModelConfig{},
- }
-
- output := captureStdout(func() {
- showCurrentModel(cfg)
- })
-
- assert.Contains(t, output, "Current default model: legacy-model")
-}
-
func TestListAvailableModels_Empty(t *testing.T) {
cfg := &config.Config{
- ModelList: []config.ModelConfig{},
+ ModelList: []*config.ModelConfig{},
}
output := captureStdout(func() {
@@ -131,18 +124,25 @@ func TestListAvailableModels_Empty(t *testing.T) {
}
func TestListAvailableModels_WithModels(t *testing.T) {
- cfg := &config.Config{
+ cfg := (&config.Config{
Agents: config.AgentsConfig{
Defaults: config.AgentDefaults{
ModelName: "gpt-4",
},
},
- ModelList: []config.ModelConfig{
- {ModelName: "gpt-4", Model: "openai/gpt-4", APIKey: "test"},
- {ModelName: "claude-3", Model: "anthropic/claude-3", APIKey: "test"},
- {ModelName: "no-key-model", Model: "openai/test", APIKey: ""},
+ ModelList: []*config.ModelConfig{
+ {ModelName: "gpt-4", Model: "openai/gpt-4"},
+ {ModelName: "claude-3", Model: "anthropic/claude-3"},
+ {ModelName: "no-key-model", Model: "openai/test"},
},
- }
+ }).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
+ "gpt-4": {
+ APIKeys: []string{"test"},
+ },
+ "claude-3": {
+ APIKeys: []string{"test"},
+ },
+ }})
output := captureStdout(func() {
listAvailableModels(cfg)
@@ -157,17 +157,24 @@ func TestListAvailableModels_WithModels(t *testing.T) {
func TestSetDefaultModel_ValidModel(t *testing.T) {
initTest(t)
- cfg := &config.Config{
+ cfg := (&config.Config{
Agents: config.AgentsConfig{
Defaults: config.AgentDefaults{
ModelName: "old-model",
},
},
- ModelList: []config.ModelConfig{
- {ModelName: "new-model", Model: "openai/new-model", APIKey: "test"},
- {ModelName: "old-model", Model: "openai/old-model", APIKey: "test"},
+ ModelList: []*config.ModelConfig{
+ {ModelName: "new-model", Model: "openai/new-model"},
+ {ModelName: "old-model", Model: "openai/old-model"},
},
- }
+ }).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
+ "new-model": {
+ APIKeys: []string{"test"},
+ },
+ "old-model": {
+ APIKeys: []string{"test"},
+ },
+ }})
output := captureStdout(func() {
err := setDefaultModel(configPath, cfg, "new-model")
@@ -180,44 +187,25 @@ func TestSetDefaultModel_ValidModel(t *testing.T) {
updatedCfg, err := config.LoadConfig(configPath)
require.NoError(t, err)
assert.Equal(t, "new-model", updatedCfg.Agents.Defaults.ModelName)
- assert.Empty(t, updatedCfg.Agents.Defaults.Model)
-}
-
-func TestSetDefaultModel_LegacyModelField(t *testing.T) {
- initTest(t)
-
- cfg := &config.Config{
- Agents: config.AgentsConfig{
- Defaults: config.AgentDefaults{
- Model: "legacy-old",
- },
- },
- ModelList: []config.ModelConfig{
- {ModelName: "new-model", Model: "openai/new-model", APIKey: "test"},
- },
- }
-
- output := captureStdout(func() {
- err := setDefaultModel(configPath, cfg, "new-model")
- assert.NoError(t, err)
- })
-
- assert.Contains(t, output, "Default model changed from 'legacy-old' to 'new-model'")
}
func TestSetDefaultModel_InvalidModel(t *testing.T) {
initTest(t)
- cfg := &config.Config{
+ cfg := (&config.Config{
Agents: config.AgentsConfig{
Defaults: config.AgentDefaults{
ModelName: "existing-model",
},
},
- ModelList: []config.ModelConfig{
- {ModelName: "existing-model", Model: "openai/existing", APIKey: "test"},
+ ModelList: []*config.ModelConfig{
+ {ModelName: "existing-model", Model: "openai/existing"},
},
- }
+ }).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
+ "existing-model": {
+ APIKeys: []string{"test"},
+ },
+ }})
assert.Error(t, setDefaultModel(configPath, cfg, "nonexistent-model"))
}
@@ -225,17 +213,24 @@ func TestSetDefaultModel_InvalidModel(t *testing.T) {
func TestSetDefaultModel_ModelWithoutAPIKey(t *testing.T) {
initTest(t)
- cfg := &config.Config{
+ cfg := (&config.Config{
Agents: config.AgentsConfig{
Defaults: config.AgentDefaults{
ModelName: "existing-model",
},
},
- ModelList: []config.ModelConfig{
- {ModelName: "existing-model", Model: "openai/existing", APIKey: "test"},
- {ModelName: "no-key-model", Model: "openai/nokey", APIKey: ""},
+ ModelList: []*config.ModelConfig{
+ {ModelName: "existing-model", Model: "openai/existing"},
+ {ModelName: "no-key-model", Model: "openai/nokey"},
},
- }
+ }).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
+ "existing-model": {
+ APIKeys: []string{"test"},
+ },
+ "no-key-model": {
+ APIKeys: []string{""},
+ },
+ }})
assert.Error(t, setDefaultModel(configPath, cfg, "no-key-model"))
}
@@ -244,16 +239,20 @@ func TestSetDefaultModel_SaveConfigError(t *testing.T) {
// Use an invalid path to trigger save error
invalidPath := "/nonexistent/directory/config.json"
- cfg := &config.Config{
+ cfg := (&config.Config{
Agents: config.AgentsConfig{
Defaults: config.AgentDefaults{
ModelName: "old-model",
},
},
- ModelList: []config.ModelConfig{
- {ModelName: "new-model", Model: "openai/new-model", APIKey: "test"},
+ ModelList: []*config.ModelConfig{
+ {ModelName: "new-model", Model: "openai/new-model"},
},
- }
+ }).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
+ "new-model": {
+ APIKeys: []string{"test"},
+ },
+ }})
err := setDefaultModel(invalidPath, cfg, "new-model")
@@ -285,16 +284,20 @@ func TestModelCommandExecution_Show(t *testing.T) {
initTest(t)
// Create a test config
- cfg := &config.Config{
+ cfg := (&config.Config{
Agents: config.AgentsConfig{
Defaults: config.AgentDefaults{
ModelName: "test-model",
},
},
- ModelList: []config.ModelConfig{
- {ModelName: "test-model", Model: "openai/test", APIKey: "test"},
+ ModelList: []*config.ModelConfig{
+ {ModelName: "test-model", Model: "openai/test"},
},
- }
+ }).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
+ "test-model": {
+ APIKeys: []string{"test"},
+ },
+ }})
err := config.SaveConfig(configPath, cfg)
require.NoError(t, err)
@@ -312,17 +315,25 @@ func TestModelCommandExecution_Show(t *testing.T) {
func TestModelCommandExecution_Set(t *testing.T) {
initTest(t)
- cfg := &config.Config{
+ sec := &config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
+ "old-model": {
+ APIKeys: []string{"test"},
+ },
+ "new-model": {
+ APIKeys: []string{"test"},
+ },
+ }}
+ cfg := (&config.Config{
Agents: config.AgentsConfig{
Defaults: config.AgentDefaults{
ModelName: "old-model",
},
},
- ModelList: []config.ModelConfig{
- {ModelName: "old-model", Model: "openai/old", APIKey: "test"},
- {ModelName: "new-model", Model: "openai/new", APIKey: "test"},
+ ModelList: []*config.ModelConfig{
+ {ModelName: "old-model", Model: "openai/old"},
+ {ModelName: "new-model", Model: "openai/new"},
},
- }
+ }).WithSecurity(sec)
err := config.SaveConfig(configPath, cfg)
require.NoError(t, err)
@@ -346,18 +357,28 @@ func TestModelCommandExecution_TooManyArgs(t *testing.T) {
}
func TestListAvailableModels_MarkerLogic(t *testing.T) {
- cfg := &config.Config{
+ cfg := (&config.Config{
Agents: config.AgentsConfig{
Defaults: config.AgentDefaults{
ModelName: "middle-model",
},
},
- ModelList: []config.ModelConfig{
- {ModelName: "first-model", Model: "openai/first", APIKey: "test"},
- {ModelName: "middle-model", Model: "openai/middle", APIKey: "test"},
- {ModelName: "last-model", Model: "openai/last", APIKey: "test"},
+ ModelList: []*config.ModelConfig{
+ {ModelName: "first-model", Model: "openai/first"},
+ {ModelName: "middle-model", Model: "openai/middle"},
+ {ModelName: "last-model", Model: "openai/last"},
},
- }
+ }).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
+ "first-model": {
+ APIKeys: []string{"test"},
+ },
+ "middle-model": {
+ APIKeys: []string{"test"},
+ },
+ "last-model": {
+ APIKeys: []string{"test"},
+ },
+ }})
output := captureStdout(func() {
listAvailableModels(cfg)
diff --git a/cmd/picoclaw/internal/onboard/command.go b/cmd/picoclaw/internal/onboard/command.go
index 9f8b288c6..1f94c6718 100644
--- a/cmd/picoclaw/internal/onboard/command.go
+++ b/cmd/picoclaw/internal/onboard/command.go
@@ -16,14 +16,22 @@ func NewOnboardCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "onboard",
Aliases: []string{"o"},
- Short: "Initialize picoclaw configuration and workspace",
+ Short: "Initialize picoclaw configuration, workspace, and channel accounts",
+ // Run without subcommands → original onboard flow
Run: func(cmd *cobra.Command, args []string) {
- onboard(encrypt)
+ if len(args) == 0 {
+ onboard(encrypt)
+ } else {
+ _ = cmd.Help()
+ }
},
}
cmd.Flags().BoolVar(&encrypt, "enc", false,
"Enable credential encryption (generates SSH key and prompts for passphrase)")
+ // Channel onboarding subcommands
+ cmd.AddCommand(newWeixinCommand())
+
return cmd
}
diff --git a/cmd/picoclaw/internal/onboard/command_test.go b/cmd/picoclaw/internal/onboard/command_test.go
index 56936190b..6b9fb6e95 100644
--- a/cmd/picoclaw/internal/onboard/command_test.go
+++ b/cmd/picoclaw/internal/onboard/command_test.go
@@ -13,7 +13,7 @@ func TestNewOnboardCommand(t *testing.T) {
require.NotNil(t, cmd)
assert.Equal(t, "onboard", cmd.Use)
- assert.Equal(t, "Initialize picoclaw configuration and workspace", cmd.Short)
+ assert.Equal(t, "Initialize picoclaw configuration, workspace, and channel accounts", cmd.Short)
assert.Len(t, cmd.Aliases, 1)
assert.True(t, cmd.HasAlias("o"))
@@ -28,5 +28,6 @@ func TestNewOnboardCommand(t *testing.T) {
encFlag := cmd.Flags().Lookup("enc")
require.NotNil(t, encFlag, "expected --enc flag to be registered")
assert.Equal(t, "false", encFlag.DefValue, "--enc should default to false")
- assert.False(t, cmd.HasSubCommands())
+ assert.True(t, cmd.HasSubCommands())
+ assert.NotNil(t, cmd.Commands())
}
diff --git a/cmd/picoclaw/internal/onboard/helpers_test.go b/cmd/picoclaw/internal/onboard/helpers_test.go
index f3e0c92e0..23fc97c5a 100644
--- a/cmd/picoclaw/internal/onboard/helpers_test.go
+++ b/cmd/picoclaw/internal/onboard/helpers_test.go
@@ -6,20 +6,32 @@ import (
"testing"
)
-func TestCopyEmbeddedToTargetUsesAgentsMarkdown(t *testing.T) {
+func TestCopyEmbeddedToTargetUsesStructuredAgentFiles(t *testing.T) {
targetDir := t.TempDir()
if err := copyEmbeddedToTarget(targetDir); err != nil {
t.Fatalf("copyEmbeddedToTarget() error = %v", err)
}
- agentsPath := filepath.Join(targetDir, "AGENTS.md")
- if _, err := os.Stat(agentsPath); err != nil {
- t.Fatalf("expected %s to exist: %v", agentsPath, err)
+ agentPath := filepath.Join(targetDir, "AGENT.md")
+ if _, err := os.Stat(agentPath); err != nil {
+ t.Fatalf("expected %s to exist: %v", agentPath, err)
}
- legacyPath := filepath.Join(targetDir, "AGENT.md")
- if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
- t.Fatalf("expected legacy file %s to be absent, got err=%v", legacyPath, err)
+ soulPath := filepath.Join(targetDir, "SOUL.md")
+ if _, err := os.Stat(soulPath); err != nil {
+ t.Fatalf("expected %s to exist: %v", soulPath, err)
+ }
+
+ userPath := filepath.Join(targetDir, "USER.md")
+ if _, err := os.Stat(userPath); err != nil {
+ t.Fatalf("expected %s to exist: %v", userPath, err)
+ }
+
+ for _, legacyName := range []string{"AGENTS.md", "IDENTITY.md"} {
+ legacyPath := filepath.Join(targetDir, legacyName)
+ if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
+ t.Fatalf("expected legacy file %s to be absent, got err=%v", legacyPath, err)
+ }
}
}
diff --git a/cmd/picoclaw/internal/onboard/weixin.go b/cmd/picoclaw/internal/onboard/weixin.go
new file mode 100644
index 000000000..2e1c2ad75
--- /dev/null
+++ b/cmd/picoclaw/internal/onboard/weixin.go
@@ -0,0 +1,124 @@
+package onboard
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/spf13/cobra"
+
+ "github.com/sipeed/picoclaw/cmd/picoclaw/internal"
+ "github.com/sipeed/picoclaw/pkg/channels/weixin"
+ "github.com/sipeed/picoclaw/pkg/config"
+)
+
+func newWeixinCommand() *cobra.Command {
+ var baseURL string
+ var proxy string
+ var timeout int
+
+ cmd := &cobra.Command{
+ Use: "weixin",
+ Short: "Connect a WeChat personal account via QR code",
+ Long: `Start the interactive Weixin (WeChat personal) QR code login flow.
+
+A QR code is displayed in the terminal. Scan it with the WeChat mobile app
+to authorize your account. On success, the bot token is saved to the picoclaw
+config so you can start the gateway immediately.
+
+Example:
+ picoclaw onboard weixin`,
+ RunE: func(cmd *cobra.Command, _ []string) error {
+ return runWeixinOnboard(baseURL, proxy, time.Duration(timeout)*time.Second)
+ },
+ }
+
+ cmd.Flags().StringVar(&baseURL, "base-url", "https://ilinkai.weixin.qq.com/", "iLink API base URL")
+ cmd.Flags().StringVar(&proxy, "proxy", "", "HTTP proxy URL (e.g. http://localhost:7890)")
+ cmd.Flags().IntVar(&timeout, "timeout", 300, "Login timeout in seconds")
+
+ return cmd
+}
+
+func runWeixinOnboard(baseURL, proxy string, timeout time.Duration) error {
+ fmt.Println("Starting Weixin (WeChat personal) login...")
+ fmt.Println()
+
+ botToken, userID, accountID, returnedBaseURL, err := weixin.PerformLoginInteractive(
+ context.Background(),
+ weixin.AuthFlowOpts{
+ BaseURL: baseURL,
+ Timeout: timeout,
+ Proxy: proxy,
+ },
+ )
+ if err != nil {
+ return fmt.Errorf("login failed: %w", err)
+ }
+
+ fmt.Println()
+ fmt.Println("✅ Login successful!")
+ fmt.Printf(" Account ID : %s\n", accountID)
+ if userID != "" {
+ fmt.Printf(" User ID : %s\n", userID)
+ }
+ fmt.Println()
+
+ // Prefer the server-returned base URL (may be region-specific)
+ effectiveBaseURL := returnedBaseURL
+ if effectiveBaseURL == "" {
+ effectiveBaseURL = baseURL
+ }
+
+ if err := saveWeixinConfig(botToken, effectiveBaseURL, proxy); err != nil {
+ fmt.Printf("⚠️ Could not auto-save to config: %v\n", err)
+ printManualWeixinConfig(botToken, effectiveBaseURL)
+ return nil
+ }
+
+ fmt.Println("✓ Config updated. Start the gateway with:")
+ fmt.Println()
+ fmt.Println(" picoclaw gateway")
+ fmt.Println()
+ fmt.Println("To restrict which WeChat users can send messages, add their user IDs")
+ fmt.Println("to channels.weixin.allow_from in your config.")
+
+ return nil
+}
+
+// saveWeixinConfig patches channels.weixin in the config and saves it.
+func saveWeixinConfig(token, baseURL, proxy string) error {
+ cfgPath := internal.GetConfigPath()
+
+ cfg, err := config.LoadConfig(cfgPath)
+ if err != nil {
+ return fmt.Errorf("failed to load config: %w", err)
+ }
+
+ cfg.Channels.Weixin.Enabled = true
+ cfg.Channels.Weixin.SetToken(token)
+ const defaultBase = "https://ilinkai.weixin.qq.com/"
+ if baseURL != "" && baseURL != defaultBase {
+ cfg.Channels.Weixin.BaseURL = baseURL
+ }
+ if proxy != "" {
+ cfg.Channels.Weixin.Proxy = proxy
+ }
+
+ return config.SaveConfig(cfgPath, cfg)
+}
+
+func printManualWeixinConfig(token, baseURL string) {
+ fmt.Println()
+ fmt.Println("Add the following to the channels section of your picoclaw config:")
+ fmt.Println()
+ fmt.Println(` "weixin": {`)
+ fmt.Println(` "enabled": true,`)
+ fmt.Printf(" \"token\": %q,\n", token)
+ const defaultBase = "https://ilinkai.weixin.qq.com/"
+ if baseURL != "" && baseURL != defaultBase {
+ fmt.Printf(" \"base_url\": %q,\n", baseURL)
+ }
+ fmt.Println(` "allow_from": []`)
+ fmt.Println(` }`)
+}
diff --git a/cmd/picoclaw/internal/skills/command.go b/cmd/picoclaw/internal/skills/command.go
index 8c666b810..4f64ef3f9 100644
--- a/cmd/picoclaw/internal/skills/command.go
+++ b/cmd/picoclaw/internal/skills/command.go
@@ -31,7 +31,7 @@ func NewSkillsCommand() *cobra.Command {
d.workspace = cfg.WorkspacePath()
installer, err := skills.NewSkillInstaller(
d.workspace,
- cfg.Tools.Skills.Github.Token,
+ cfg.Tools.Skills.Github.Token(),
cfg.Tools.Skills.Github.Proxy,
)
if err != nil {
diff --git a/cmd/picoclaw/internal/skills/helpers.go b/cmd/picoclaw/internal/skills/helpers.go
index a59a2013a..a246f7da5 100644
--- a/cmd/picoclaw/internal/skills/helpers.go
+++ b/cmd/picoclaw/internal/skills/helpers.go
@@ -64,9 +64,20 @@ func skillsInstallFromRegistry(cfg *config.Config, registryName, slug string) er
fmt.Printf("Installing skill '%s' from %s registry...\n", slug, registryName)
+ clawHubConfig := cfg.Tools.Skills.Registries.ClawHub
registryMgr := skills.NewRegistryManagerFromConfig(skills.RegistryConfig{
MaxConcurrentSearches: cfg.Tools.Skills.MaxConcurrentSearches,
- ClawHub: skills.ClawHubConfig(cfg.Tools.Skills.Registries.ClawHub),
+ ClawHub: skills.ClawHubConfig{
+ Enabled: clawHubConfig.Enabled,
+ BaseURL: clawHubConfig.BaseURL,
+ AuthToken: clawHubConfig.AuthToken(),
+ SearchPath: clawHubConfig.SearchPath,
+ SkillsPath: clawHubConfig.SkillsPath,
+ DownloadPath: clawHubConfig.DownloadPath,
+ Timeout: clawHubConfig.Timeout,
+ MaxZipSize: clawHubConfig.MaxZipSize,
+ MaxResponseSize: clawHubConfig.MaxResponseSize,
+ },
})
registry := registryMgr.GetRegistry(registryName)
@@ -226,9 +237,20 @@ func skillsSearchCmd(query string) {
return
}
+ clawHubConfig := cfg.Tools.Skills.Registries.ClawHub
registryMgr := skills.NewRegistryManagerFromConfig(skills.RegistryConfig{
MaxConcurrentSearches: cfg.Tools.Skills.MaxConcurrentSearches,
- ClawHub: skills.ClawHubConfig(cfg.Tools.Skills.Registries.ClawHub),
+ ClawHub: skills.ClawHubConfig{
+ Enabled: clawHubConfig.Enabled,
+ BaseURL: clawHubConfig.BaseURL,
+ AuthToken: clawHubConfig.AuthToken(),
+ SearchPath: clawHubConfig.SearchPath,
+ SkillsPath: clawHubConfig.SkillsPath,
+ DownloadPath: clawHubConfig.DownloadPath,
+ Timeout: clawHubConfig.Timeout,
+ MaxZipSize: clawHubConfig.MaxZipSize,
+ MaxResponseSize: clawHubConfig.MaxResponseSize,
+ },
})
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
diff --git a/cmd/picoclaw/internal/status/helpers.go b/cmd/picoclaw/internal/status/helpers.go
index dd7063fe6..43c5786a8 100644
--- a/cmd/picoclaw/internal/status/helpers.go
+++ b/cmd/picoclaw/internal/status/helpers.go
@@ -42,48 +42,6 @@ func statusCmd() {
if _, err := os.Stat(configPath); err == nil {
fmt.Printf("Model: %s\n", cfg.Agents.Defaults.GetModelName())
- hasOpenRouter := cfg.Providers.OpenRouter.APIKey != ""
- hasAnthropic := cfg.Providers.Anthropic.APIKey != ""
- hasOpenAI := cfg.Providers.OpenAI.APIKey != ""
- hasGemini := cfg.Providers.Gemini.APIKey != ""
- hasZhipu := cfg.Providers.Zhipu.APIKey != ""
- hasQwen := cfg.Providers.Qwen.APIKey != ""
- hasGroq := cfg.Providers.Groq.APIKey != ""
- hasVLLM := cfg.Providers.VLLM.APIBase != ""
- hasMoonshot := cfg.Providers.Moonshot.APIKey != ""
- hasDeepSeek := cfg.Providers.DeepSeek.APIKey != ""
- hasVolcEngine := cfg.Providers.VolcEngine.APIKey != ""
- hasNvidia := cfg.Providers.Nvidia.APIKey != ""
- hasOllama := cfg.Providers.Ollama.APIBase != ""
-
- status := func(enabled bool) string {
- if enabled {
- return "✓"
- }
- return "not set"
- }
- fmt.Println("OpenRouter API:", status(hasOpenRouter))
- fmt.Println("Anthropic API:", status(hasAnthropic))
- fmt.Println("OpenAI API:", status(hasOpenAI))
- fmt.Println("Gemini API:", status(hasGemini))
- fmt.Println("Zhipu API:", status(hasZhipu))
- fmt.Println("Qwen API:", status(hasQwen))
- fmt.Println("Groq API:", status(hasGroq))
- fmt.Println("Moonshot API:", status(hasMoonshot))
- fmt.Println("DeepSeek API:", status(hasDeepSeek))
- fmt.Println("VolcEngine API:", status(hasVolcEngine))
- fmt.Println("Nvidia API:", status(hasNvidia))
- if hasVLLM {
- fmt.Printf("vLLM/Local: ✓ %s\n", cfg.Providers.VLLM.APIBase)
- } else {
- fmt.Println("vLLM/Local: not set")
- }
- if hasOllama {
- fmt.Printf("Ollama: ✓ %s\n", cfg.Providers.Ollama.APIBase)
- } else {
- fmt.Println("Ollama: not set")
- }
-
store, _ := auth.LoadStore()
if store != nil && len(store.Credentials) > 0 {
fmt.Println("\nOAuth/Token Auth:")
diff --git a/config/config.example.json b/config/config.example.json
index 81c9014ec..88578701a 100644
--- a/config/config.example.json
+++ b/config/config.example.json
@@ -5,6 +5,7 @@
"restrict_to_workspace": true,
"model_name": "gpt-5.4",
"max_tokens": 8192,
+ "context_window": 131072,
"temperature": 0.7,
"max_tool_iterations": 20,
"summarize_message_threshold": 20,
@@ -546,11 +547,22 @@
"monitor_usb": true
},
"voice": {
+ "model_name": "",
"echo_transcription": false
},
+ "hooks": {
+ "enabled": true,
+ "defaults": {
+ "observer_timeout_ms": 500,
+ "interceptor_timeout_ms": 5000,
+ "approval_timeout_ms": 60000
+ }
+ },
"gateway": {
+ "_comment": "Default log level is set to 'fatal'. Other available options are 'debug', 'info', 'warn' and 'error'.",
"host": "127.0.0.1",
"port": 18790,
- "hot_reload": false
+ "hot_reload": false,
+ "log_level": "fatal"
}
}
diff --git a/docker/Dockerfile.heavy b/docker/Dockerfile.heavy
new file mode 100644
index 000000000..cbc243e39
--- /dev/null
+++ b/docker/Dockerfile.heavy
@@ -0,0 +1,67 @@
+# ============================================================
+# Stage 1: Build the picoclaw binary
+# ============================================================
+FROM golang:1.26.0-alpine AS builder
+
+RUN apk add --no-cache git make
+
+WORKDIR /src
+
+# Cache dependencies
+COPY go.mod go.sum ./
+RUN go mod download
+
+# Copy source and build
+COPY . .
+RUN make build
+
+# ============================================================
+# Stage 2: Node.js runtime with Python + MCP support
+# ============================================================
+FROM node:24-alpine3.23
+
+RUN apk add --no-cache \
+ ca-certificates \
+ curl \
+ git \
+ python3 \
+ py3-pip \
+ chromium \
+ jq
+
+# Install Playwright browsers for agent-browser
+ENV PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers
+RUN npm install -g agent-browser && \
+ npx playwright install chromium && \
+ chmod -R o+rx $PLAYWRIGHT_BROWSERS_PATH
+
+# Install uv
+RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \
+ ln -s /root/.local/bin/uv /usr/local/bin/uv && \
+ ln -s /root/.local/bin/uvx /usr/local/bin/uvx && \
+ uv --version
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+ CMD wget -q --spider http://localhost:18790/health || exit 1
+
+# Copy binary
+COPY --from=builder /src/build/picoclaw /usr/local/bin/picoclaw
+
+# Reuse existing node user (UID/GID 1000) — rename to picoclaw
+RUN deluser node 2>/dev/null; delgroup node 2>/dev/null; \
+ addgroup -g 1000 picoclaw 2>/dev/null; \
+ adduser -D -u 1000 -G picoclaw -h /home/picoclaw picoclaw 2>/dev/null || true
+
+USER picoclaw
+
+# Run onboard to create initial directories and config
+RUN /usr/local/bin/picoclaw onboard
+
+# Copy default workspace
+COPY --chown=picoclaw:picoclaw workspace/ /home/picoclaw/.picoclaw/workspace/
+
+VOLUME /home/picoclaw/.picoclaw/workspace
+
+ENTRYPOINT ["picoclaw"]
+CMD ["gateway"]
diff --git a/docs/agent-refactor/context.md b/docs/agent-refactor/context.md
new file mode 100644
index 000000000..2269d9258
--- /dev/null
+++ b/docs/agent-refactor/context.md
@@ -0,0 +1,164 @@
+# Context
+
+## What this document covers
+
+This document makes explicit the boundaries of context management in the agent loop:
+
+- what fills the context window and how space is divided
+- what is stored in session history vs. built at request time
+- when and how context compression happens
+- how token budgets are estimated
+
+These are existing concepts. This document clarifies their boundaries rather than introducing new ones.
+
+---
+
+## Context window regions
+
+The context window is the model's total input capacity. Four regions fill it:
+
+| Region | Assembled by | Stored in session? |
+|---|---|---|
+| System prompt | `BuildMessages()` — static + dynamic parts | No |
+| Summary | `SetSummary()` stores it; `BuildMessages()` injects it | Separate from history |
+| Session history | User / assistant / tool messages | Yes |
+| Tool definitions | Provider adapter injects at call time | No |
+
+`MaxTokens` (the output generation limit) must also be reserved from the total budget.
+
+The available space for history is therefore:
+
+```
+history_budget = ContextWindow - system_prompt - summary - tool_definitions - MaxTokens
+```
+
+---
+
+## ContextWindow vs MaxTokens
+
+These serve different purposes:
+
+- **MaxTokens** — maximum tokens the LLM may generate in one response. Sent as the `max_tokens` request parameter.
+- **ContextWindow** — the model's total input context capacity.
+
+These were previously set to the same value, which caused the summarization threshold to fire either far too early (at the default 32K) or not at all (when a user raised `max_tokens`).
+
+Current default when not explicitly configured: `ContextWindow = MaxTokens * 4`.
+
+---
+
+## Session history
+
+Session history stores only conversation messages:
+
+- `user` — user input
+- `assistant` — LLM response (may include `ToolCalls`)
+- `tool` — tool execution results
+
+Session history does **not** contain:
+
+- System prompts — assembled at request time by `BuildMessages`
+- Summary content — stored separately via `SetSummary`, injected by `BuildMessages`
+
+This distinction matters: any code that operates on session history — compression, boundary detection, token estimation — must not assume a system message is present.
+
+---
+
+## Turn
+
+A **Turn** is one complete cycle:
+
+> user message -> LLM iterations (possibly including tool calls) -> final assistant response
+
+This definition comes from the agent loop design (#1316). In session history, Turn boundaries are identified by `user`-role messages.
+
+Turn is the atomic unit for compression. Cutting inside a Turn can orphan tool-call sequences — an assistant message with `ToolCalls` separated from its corresponding `tool` results. Compressing at Turn boundaries avoids this by construction.
+
+`parseTurnBoundaries(history)` returns the starting index of each Turn.
+`findSafeBoundary(history, targetIndex)` snaps a target cut point to the nearest Turn boundary.
+
+---
+
+## Compression paths
+
+Three compression paths exist, in order of preference:
+
+### 1. Async summarization
+
+`maybeSummarize` runs after each Turn completes.
+
+Triggers when message count exceeds a threshold, or when estimated history tokens exceed a percentage of `ContextWindow`. If triggered, a background goroutine calls the LLM to produce a summary of the oldest messages. The summary is stored via `SetSummary`; `BuildMessages` injects it into the system prompt on the next call.
+
+Cut point uses `findSafeBoundary` so no Turn is split.
+
+### 2. Proactive budget check
+
+`isOverContextBudget` runs before each LLM call.
+
+Uses the full budget formula: `message_tokens + tool_def_tokens + MaxTokens > ContextWindow`. If over budget, triggers `forceCompression` and rebuilds messages before calling the LLM.
+
+This prevents wasted (and billed) LLM calls that would otherwise fail with a context-window error.
+
+### 3. Emergency compression (reactive)
+
+`forceCompression` runs when the LLM returns a context-window error despite the proactive check.
+
+Drops the oldest ~50% of Turns. If the history is a single Turn with no safe split point (e.g. one user message followed by a massive tool response), falls back to keeping only the most recent user message — breaking Turn atomicity as a last resort to avoid a context-exceeded loop.
+
+Stores a compression note in the session summary (not in history messages) so `BuildMessages` can include it in the next system prompt.
+
+This is the fallback for when the token estimate undershoots reality.
+
+---
+
+## Token estimation
+
+Estimation uses a heuristic of ~2.5 characters per token (`chars * 2 / 5`).
+
+`estimateMessageTokens` counts:
+
+- `Content` (rune count, for multibyte correctness)
+- `ReasoningContent` (extended thinking / chain-of-thought)
+- `ToolCalls` — ID, type, function name, arguments
+- `ToolCallID` (tool result metadata)
+- Per-message overhead (role label, JSON structure)
+- `Media` items — flat per-item token estimate, added directly to the final count (not through the character heuristic, since actual cost depends on resolution and provider-specific image tokenization)
+
+`estimateToolDefsTokens` counts tool definition overhead: name, description, JSON schema of parameters.
+
+These are deliberately heuristic. The proactive check handles the common case; the reactive path catches estimation errors.
+
+---
+
+## Interface boundaries
+
+Context budget functions (`parseTurnBoundaries`, `findSafeBoundary`, `estimateMessageTokens`, `isOverContextBudget`) are **pure functions**. They take `[]providers.Message` and integer parameters. They have no dependency on `AgentLoop` or any other runtime struct.
+
+`BuildMessages` is the sole assembler of the final message array sent to the LLM. Budget functions inform compression decisions but do not construct messages.
+
+`forceCompression` and `summarizeSession` mutate session state (history and summary). `BuildMessages` reads that state to construct context. The flow is:
+
+```
+budget check --> compression decision --> mutate session --> BuildMessages reads session --> LLM call
+```
+
+---
+
+## Known gaps
+
+These are recognized limitations in the current implementation, documented here for visibility:
+
+- **Summarization trigger does not use the full budget formula.** `maybeSummarize` compares estimated history tokens against a percentage of `ContextWindow`. It does not account for system prompt size, tool definition overhead, or `MaxTokens` reserve. The proactive check covers the critical path (preventing 400 errors), but the summarization trigger could be aligned with the same budget model for more accurate early compression.
+
+- **Token estimation is heuristic.** It does not account for provider-specific tokenization, exact system prompt size (assembled separately), or variable image token costs. The two-path design (proactive + reactive) is intended to tolerate this imprecision.
+
+- **Reactive retry does not preserve media.** When the reactive path rebuilds context after compression, it currently passes empty values for media references. This is a pre-existing issue in the main loop, not introduced by the budget system.
+
+---
+
+## What this document does not cover
+
+- How `AGENT.md` frontmatter configures context parameters — that is part of the Agent definition work
+- How the context builder assembles context in the new architecture — that is upcoming work
+- How compression events surface through the event system — that is part of the event model (#1316)
+- Subagent context isolation — that is a separate track
diff --git a/docs/channels/matrix/README.fr.md b/docs/channels/matrix/README.fr.md
new file mode 100644
index 000000000..ec762a8b8
--- /dev/null
+++ b/docs/channels/matrix/README.fr.md
@@ -0,0 +1,64 @@
+> Retour au [README](../../../README.fr.md)
+
+# Guide de configuration du canal Matrix
+
+## 1. Exemple de configuration
+
+Ajoutez ceci à `config.json` :
+
+```json
+{
+ "channels": {
+ "matrix": {
+ "enabled": true,
+ "homeserver": "https://matrix.org",
+ "user_id": "@your-bot:matrix.org",
+ "access_token": "YOUR_MATRIX_ACCESS_TOKEN",
+ "device_id": "",
+ "join_on_invite": true,
+ "allow_from": [],
+ "group_trigger": {
+ "mention_only": true
+ },
+ "placeholder": {
+ "enabled": true,
+ "text": "Thinking..."
+ },
+ "reasoning_channel_id": "",
+ "message_format": "richtext"
+ }
+ }
+}
+```
+
+## 2. Référence des champs
+
+| Champ | Type | Requis | Description |
+|----------------------|----------|--------|-------------|
+| enabled | bool | Oui | Activer ou désactiver le canal Matrix |
+| homeserver | string | Oui | URL du homeserver Matrix (par exemple `https://matrix.org`) |
+| user_id | string | Oui | ID utilisateur Matrix du bot (par exemple `@bot:matrix.org`) |
+| access_token | string | Oui | Jeton d'accès du bot |
+| device_id | string | Non | ID d'appareil Matrix optionnel |
+| join_on_invite | bool | Non | Rejoindre automatiquement les salons invités |
+| allow_from | []string | Non | Liste blanche d'utilisateurs (IDs Matrix) |
+| group_trigger | object | Non | Stratégie de déclenchement de groupe (`mention_only` / `prefixes`) |
+| placeholder | object | Non | Configuration du message de remplacement |
+| reasoning_channel_id | string | Non | Canal cible pour la sortie de raisonnement |
+| message_format | string | Non | Format de sortie : `"richtext"` (défaut) rend le markdown en HTML ; `"plain"` envoie du texte brut uniquement |
+
+## 3. Fonctionnalités actuellement supportées
+
+- Envoi/réception de messages texte avec rendu markdown (gras, italique, titres, blocs de code, etc.)
+- Format de message configurable (`richtext` / `plain`)
+- Téléchargement d'images/audio/vidéo/fichiers entrants (MediaStore en priorité, chemin local en secours)
+- Normalisation de l'audio entrant dans le flux de transcription existant (`[audio: ...]`)
+- Upload et envoi d'images/audio/vidéo/fichiers sortants
+- Règles de déclenchement de groupe (y compris le mode mention uniquement)
+- État de frappe (`m.typing`)
+- Message de remplacement + remplacement de la réponse finale
+- Rejoindre automatiquement les salons invités (peut être désactivé)
+
+## 4. TODO
+
+- Améliorations des métadonnées des médias riches (par exemple taille et miniatures des images/vidéos)
diff --git a/docs/channels/matrix/README.ja.md b/docs/channels/matrix/README.ja.md
new file mode 100644
index 000000000..e5a773d4d
--- /dev/null
+++ b/docs/channels/matrix/README.ja.md
@@ -0,0 +1,64 @@
+> [README](../../../README.ja.md) に戻る
+
+# Matrix チャンネル設定ガイド
+
+## 1. 設定例
+
+`config.json` に以下を追加してください:
+
+```json
+{
+ "channels": {
+ "matrix": {
+ "enabled": true,
+ "homeserver": "https://matrix.org",
+ "user_id": "@your-bot:matrix.org",
+ "access_token": "YOUR_MATRIX_ACCESS_TOKEN",
+ "device_id": "",
+ "join_on_invite": true,
+ "allow_from": [],
+ "group_trigger": {
+ "mention_only": true
+ },
+ "placeholder": {
+ "enabled": true,
+ "text": "Thinking..."
+ },
+ "reasoning_channel_id": "",
+ "message_format": "richtext"
+ }
+ }
+}
+```
+
+## 2. フィールドリファレンス
+
+| フィールド | 型 | 必須 | 説明 |
+|----------------------|----------|------|------|
+| enabled | bool | はい | Matrix チャンネルの有効/無効 |
+| homeserver | string | はい | Matrix ホームサーバー URL(例:`https://matrix.org`) |
+| user_id | string | はい | ボットの Matrix ユーザー ID(例:`@bot:matrix.org`) |
+| access_token | string | はい | ボットのアクセストークン |
+| device_id | string | いいえ | オプションの Matrix デバイス ID |
+| join_on_invite | bool | いいえ | 招待されたルームに自動参加 |
+| allow_from | []string | いいえ | ユーザーホワイトリスト(Matrix ユーザー ID) |
+| group_trigger | object | いいえ | グループトリガー戦略(`mention_only` / `prefixes`) |
+| placeholder | object | いいえ | プレースホルダーメッセージ設定 |
+| reasoning_channel_id | string | いいえ | 推論出力のターゲットチャンネル |
+| message_format | string | いいえ | 出力形式:`"richtext"`(デフォルト)は markdown を HTML としてレンダリング;`"plain"` はプレーンテキストのみ送信 |
+
+## 3. 現在サポートされている機能
+
+- markdown レンダリング付きテキストメッセージ送受信(太字、斜体、見出し、コードブロックなど)
+- 設定可能なメッセージ形式(`richtext` / `plain`)
+- 受信画像/音声/動画/ファイルのダウンロード(MediaStore 優先、ローカルパスフォールバック)
+- 受信音声の既存文字起こしフローへの正規化(`[audio: ...]`)
+- 送信画像/音声/動画/ファイルのアップロードと送信
+- グループトリガールール(メンションのみモードを含む)
+- タイピング状態(`m.typing`)
+- プレースホルダーメッセージ + 最終返信の置き換え
+- 招待されたルームへの自動参加(無効化可能)
+
+## 4. TODO
+
+- リッチメディアメタデータの改善(例:画像/動画のサイズとサムネイル)
diff --git a/docs/channels/matrix/README.md b/docs/channels/matrix/README.md
index 233f5c0a3..2ed19245a 100644
--- a/docs/channels/matrix/README.md
+++ b/docs/channels/matrix/README.md
@@ -1,3 +1,5 @@
+> Back to [README](../../../README.md)
+
# Matrix Channel Configuration Guide
## 1. Example Configuration
diff --git a/docs/channels/matrix/README.pt-br.md b/docs/channels/matrix/README.pt-br.md
new file mode 100644
index 000000000..11a9aaa11
--- /dev/null
+++ b/docs/channels/matrix/README.pt-br.md
@@ -0,0 +1,64 @@
+> Voltar ao [README](../../../README.pt-br.md)
+
+# Guia de Configuração do Canal Matrix
+
+## 1. Exemplo de Configuração
+
+Adicione isto ao `config.json`:
+
+```json
+{
+ "channels": {
+ "matrix": {
+ "enabled": true,
+ "homeserver": "https://matrix.org",
+ "user_id": "@your-bot:matrix.org",
+ "access_token": "YOUR_MATRIX_ACCESS_TOKEN",
+ "device_id": "",
+ "join_on_invite": true,
+ "allow_from": [],
+ "group_trigger": {
+ "mention_only": true
+ },
+ "placeholder": {
+ "enabled": true,
+ "text": "Thinking..."
+ },
+ "reasoning_channel_id": "",
+ "message_format": "richtext"
+ }
+ }
+}
+```
+
+## 2. Referência de Campos
+
+| Campo | Tipo | Obrigatório | Descrição |
+|----------------------|----------|-------------|-----------|
+| enabled | bool | Sim | Habilitar ou desabilitar o canal Matrix |
+| homeserver | string | Sim | URL do homeserver Matrix (por exemplo `https://matrix.org`) |
+| user_id | string | Sim | ID de usuário Matrix do bot (por exemplo `@bot:matrix.org`) |
+| access_token | string | Sim | Token de acesso do bot |
+| device_id | string | Não | ID de dispositivo Matrix opcional |
+| join_on_invite | bool | Não | Entrar automaticamente em salas convidadas |
+| allow_from | []string | Não | Lista branca de usuários (IDs Matrix) |
+| group_trigger | object | Não | Estratégia de gatilho de grupo (`mention_only` / `prefixes`) |
+| placeholder | object | Não | Configuração de mensagem de espaço reservado |
+| reasoning_channel_id | string | Não | Canal alvo para saída de raciocínio |
+| message_format | string | Não | Formato de saída: `"richtext"` (padrão) renderiza markdown como HTML; `"plain"` envia apenas texto simples |
+
+## 3. Suporte Atual
+
+- Envio/recebimento de mensagens de texto com renderização markdown (negrito, itálico, cabeçalhos, blocos de código, etc.)
+- Formato de mensagem configurável (`richtext` / `plain`)
+- Download de imagens/áudio/vídeo/arquivos recebidos (MediaStore primeiro, fallback para caminho local)
+- Normalização de áudio recebido no fluxo de transcrição existente (`[audio: ...]`)
+- Upload e envio de imagens/áudio/vídeo/arquivos de saída
+- Regras de gatilho de grupo (incluindo modo somente menção)
+- Estado de digitação (`m.typing`)
+- Mensagem de espaço reservado + substituição de resposta final
+- Entrada automática em salas convidadas (pode ser desabilitado)
+
+## 4. TODO
+
+- Melhorias nos metadados de mídia rica (por exemplo tamanho e miniaturas de imagens/vídeos)
diff --git a/docs/channels/matrix/README.vi.md b/docs/channels/matrix/README.vi.md
new file mode 100644
index 000000000..f1272076f
--- /dev/null
+++ b/docs/channels/matrix/README.vi.md
@@ -0,0 +1,64 @@
+> Quay lại [README](../../../README.vi.md)
+
+# Hướng dẫn Cấu hình Kênh Matrix
+
+## 1. Cấu hình Mẫu
+
+Thêm vào `config.json`:
+
+```json
+{
+ "channels": {
+ "matrix": {
+ "enabled": true,
+ "homeserver": "https://matrix.org",
+ "user_id": "@your-bot:matrix.org",
+ "access_token": "YOUR_MATRIX_ACCESS_TOKEN",
+ "device_id": "",
+ "join_on_invite": true,
+ "allow_from": [],
+ "group_trigger": {
+ "mention_only": true
+ },
+ "placeholder": {
+ "enabled": true,
+ "text": "Thinking..."
+ },
+ "reasoning_channel_id": "",
+ "message_format": "richtext"
+ }
+ }
+}
+```
+
+## 2. Tham chiếu Trường
+
+| Trường | Kiểu | Bắt buộc | Mô tả |
+|----------------------|----------|----------|-------|
+| enabled | bool | Có | Bật hoặc tắt kênh Matrix |
+| homeserver | string | Có | URL homeserver Matrix (ví dụ `https://matrix.org`) |
+| user_id | string | Có | ID người dùng Matrix của bot (ví dụ `@bot:matrix.org`) |
+| access_token | string | Có | Token truy cập của bot |
+| device_id | string | Không | ID thiết bị Matrix tùy chọn |
+| join_on_invite | bool | Không | Tự động tham gia phòng được mời |
+| allow_from | []string | Không | Danh sách trắng người dùng (ID Matrix) |
+| group_trigger | object | Không | Chiến lược kích hoạt nhóm (`mention_only` / `prefixes`) |
+| placeholder | object | Không | Cấu hình tin nhắn giữ chỗ |
+| reasoning_channel_id | string | Không | Kênh đích cho đầu ra suy luận |
+| message_format | string | Không | Định dạng đầu ra: `"richtext"` (mặc định) render markdown thành HTML; `"plain"` chỉ gửi văn bản thuần |
+
+## 3. Tính năng Hiện tại
+
+- Gửi/nhận tin nhắn văn bản với render markdown (đậm, nghiêng, tiêu đề, khối code, v.v.)
+- Định dạng tin nhắn có thể cấu hình (`richtext` / `plain`)
+- Tải xuống hình ảnh/âm thanh/video/tệp đến (MediaStore trước, fallback đường dẫn cục bộ)
+- Chuẩn hóa âm thanh đến vào luồng phiên âm hiện có (`[audio: ...]`)
+- Tải lên và gửi hình ảnh/âm thanh/video/tệp đi
+- Quy tắc kích hoạt nhóm (bao gồm chế độ chỉ đề cập)
+- Trạng thái đang gõ (`m.typing`)
+- Tin nhắn giữ chỗ + thay thế phản hồi cuối cùng
+- Tự động tham gia phòng được mời (có thể tắt)
+
+## 4. TODO
+
+- Cải thiện metadata phương tiện phong phú (ví dụ kích thước và hình thu nhỏ hình ảnh/video)
diff --git a/docs/channels/matrix/README.zh.md b/docs/channels/matrix/README.zh.md
index 1f9e5bbe2..8db3e4383 100644
--- a/docs/channels/matrix/README.zh.md
+++ b/docs/channels/matrix/README.zh.md
@@ -1,3 +1,5 @@
+> 返回 [README](../../../README.zh.md)
+
# Matrix 通道配置指南
## 1. 配置示例
diff --git a/docs/channels/telegram/README.md b/docs/channels/telegram/README.md
index a3e057ba4..86c016a5d 100644
--- a/docs/channels/telegram/README.md
+++ b/docs/channels/telegram/README.md
@@ -2,7 +2,7 @@
# Telegram
-The Telegram channel uses long polling via the Telegram Bot API for bot-based communication. It supports text messages, media attachments (photos, voice, audio, documents), voice transcription via Groq Whisper, and built-in command handling.
+The Telegram channel uses long polling via the Telegram Bot API for bot-based communication. It supports text messages, media attachments (photos, voice, audio, documents), voice transcription ([setup](../../providers.md#voice-transcription)), and built-in command handling.
## Configuration
@@ -33,3 +33,23 @@ The Telegram channel uses long polling via the Telegram Bot API for bot-based co
3. Obtain the HTTP API Token
4. Fill in the Token in the configuration file
5. (Optional) Configure `allow_from` to restrict which user IDs can interact (you can get IDs via `@userinfobot`)
+
+## Built-in Commands
+
+Telegram auto-registers PicoClaw's top-level bot commands at startup, including `/start`, `/help`, `/show`, `/list`, and `/use`.
+
+Skill-related commands:
+
+- `/list skills` lists the installed skills visible to the current agent.
+- `/use